Skip to content

Dialogs

FileDialog and MessageBox handle the most common Windows system dialogs.


FileDialog

FileDialog

Helper for interacting with Windows Open/Save file dialogs.

Source code in src\dolphin_desktop\_dialogs.py
class FileDialog:
    """Helper for interacting with Windows Open/Save file dialogs."""

    def __init__(self, _window) -> None:
        self._win = _window

    @staticmethod
    def wait_for(timeout: float = 10.0) -> FileDialog:
        """Wait for any open/save dialog to appear and return a FileDialog."""
        return FileDialog(_find_dialog_window(timeout))

    def set_path(self, path: str) -> FileDialog:
        """Type a file path into the filename field and press Enter."""
        from pywinauto.keyboard import send_keys  # type: ignore[import-untyped]

        # Try to find an Edit control named 'File name' or similar
        edit = None
        for ctrl in self._win.children():
            try:
                if ctrl.class_name() in ("Edit", "RichEdit20W", "RICHEDIT50W"):
                    edit = ctrl
                    break
            except Exception:
                continue

        if edit is not None:
            try:
                edit.set_focus()
                edit.set_edit_text(path)
                # Do NOT press Enter here — that would already submit/close the
                # dialog, leaving confirm() with nothing to click (and the closed
                # dialog could be picked up as a stale #32770 by later waits).
                # Submission is the caller's job via confirm().
                return self
            except Exception:
                pass

        # Fallback: Ctrl+L to open address bar then type the path. The address
        # bar requires Enter to commit the typed location.
        self._win.set_focus()
        send_keys("^l")
        time.sleep(0.1)
        send_keys(path, with_spaces=True)
        send_keys("{ENTER}")
        return self

    def _dialog_gone(self) -> bool:
        """True if the underlying dialog window no longer exists/visible."""
        try:
            return not self._win.exists() or not self._win.is_visible()
        except Exception:
            return True

    def _click_by_auto_id(self, auto_id: str) -> bool:
        """Click the dialog control with the given AutomationId; True if clicked.

        Standard #32770 dialogs expose their default buttons by Win32 control id
        (IDOK="1", IDCANCEL="2"). Targeting the id avoids the ambiguity of the
        title-based lookup, where the Win11 file picker has several Buttons named
        "Otwórz" (the DropDown arrows) and the real accept control is a
        SplitButton — so control_type="Button" + title matched the wrong ones.

        The id alone can still be ambiguous (the picker exposes two elements with
        auto_id "1"/"2"), so we pin it to the control type of the actual button —
        a SplitButton (Open with its dropdown) or a plain Button (Save/Cancel).
        """
        for control_type in ("SplitButton", "Button"):
            try:
                self._win.child_window(auto_id=auto_id, control_type=control_type).click_input()
                return True
            except Exception:
                continue
        return False

    def confirm(self) -> None:
        """Click the Open / Save button."""
        # IDOK == "1": the dialog's default accept button (Open/Save/Otwórz/…).
        if self._click_by_auto_id("1"):
            return
        confirm_titles = ("Open", "Save", "Otwórz", "Zapisz", "OK")
        for title in confirm_titles:
            try:
                btn = self._win.child_window(title=title, control_type="Button")
                btn.click_input()
                return
            except Exception:
                continue
        # If the dialog already closed (e.g. submitted via Enter in the address
        # bar fallback), there is nothing left to confirm — treat as success.
        if self._dialog_gone():
            return
        raise RuntimeError("Could not find a confirm button in the file dialog")

    def cancel(self) -> None:
        """Click the Cancel button."""
        # IDCANCEL == "2".
        if self._click_by_auto_id("2"):
            return
        for title in ("Cancel", "Anuluj"):
            try:
                btn = self._win.child_window(title=title, control_type="Button")
                btn.click_input()
                return
            except Exception:
                continue
        if self._dialog_gone():
            return
        raise RuntimeError("Could not find a Cancel button in the file dialog")

wait_for staticmethod

wait_for(timeout: float = 10.0) -> FileDialog

Wait for any open/save dialog to appear and return a FileDialog.

Source code in src\dolphin_desktop\_dialogs.py
@staticmethod
def wait_for(timeout: float = 10.0) -> FileDialog:
    """Wait for any open/save dialog to appear and return a FileDialog."""
    return FileDialog(_find_dialog_window(timeout))

set_path

set_path(path: str) -> FileDialog

Type a file path into the filename field and press Enter.

Source code in src\dolphin_desktop\_dialogs.py
def set_path(self, path: str) -> FileDialog:
    """Type a file path into the filename field and press Enter."""
    from pywinauto.keyboard import send_keys  # type: ignore[import-untyped]

    # Try to find an Edit control named 'File name' or similar
    edit = None
    for ctrl in self._win.children():
        try:
            if ctrl.class_name() in ("Edit", "RichEdit20W", "RICHEDIT50W"):
                edit = ctrl
                break
        except Exception:
            continue

    if edit is not None:
        try:
            edit.set_focus()
            edit.set_edit_text(path)
            # Do NOT press Enter here — that would already submit/close the
            # dialog, leaving confirm() with nothing to click (and the closed
            # dialog could be picked up as a stale #32770 by later waits).
            # Submission is the caller's job via confirm().
            return self
        except Exception:
            pass

    # Fallback: Ctrl+L to open address bar then type the path. The address
    # bar requires Enter to commit the typed location.
    self._win.set_focus()
    send_keys("^l")
    time.sleep(0.1)
    send_keys(path, with_spaces=True)
    send_keys("{ENTER}")
    return self

confirm

confirm() -> None

Click the Open / Save button.

Source code in src\dolphin_desktop\_dialogs.py
def confirm(self) -> None:
    """Click the Open / Save button."""
    # IDOK == "1": the dialog's default accept button (Open/Save/Otwórz/…).
    if self._click_by_auto_id("1"):
        return
    confirm_titles = ("Open", "Save", "Otwórz", "Zapisz", "OK")
    for title in confirm_titles:
        try:
            btn = self._win.child_window(title=title, control_type="Button")
            btn.click_input()
            return
        except Exception:
            continue
    # If the dialog already closed (e.g. submitted via Enter in the address
    # bar fallback), there is nothing left to confirm — treat as success.
    if self._dialog_gone():
        return
    raise RuntimeError("Could not find a confirm button in the file dialog")

cancel

cancel() -> None

Click the Cancel button.

Source code in src\dolphin_desktop\_dialogs.py
def cancel(self) -> None:
    """Click the Cancel button."""
    # IDCANCEL == "2".
    if self._click_by_auto_id("2"):
        return
    for title in ("Cancel", "Anuluj"):
        try:
            btn = self._win.child_window(title=title, control_type="Button")
            btn.click_input()
            return
        except Exception:
            continue
    if self._dialog_gone():
        return
    raise RuntimeError("Could not find a Cancel button in the file dialog")

Usage

from dolphin_desktop import FileDialog

# Wait for a dialog to appear (Open or Save As)
dlg = FileDialog.wait_for(timeout=10)

# Set the file path and confirm
dlg.set_path(r"C:\Users\me\Documents\report.xlsx")
dlg.confirm()

# Or cancel
dlg.cancel()

Notes

  • FileDialog.wait_for() detects dialogs by Win32 class #32770 or title pattern (handles both English and Polish Windows).
  • Uses win32gui.EnumWindows internally to find visible dialog windows.
  • set_path() first tries the filename Edit control; falls back to Ctrl+L (address bar).

MessageBox

MessageBox

Helper for interacting with Windows MessageBox dialogs.

Source code in src\dolphin_desktop\_dialogs.py
class MessageBox:
    """Helper for interacting with Windows MessageBox dialogs."""

    def __init__(self, _window) -> None:
        self._win = _window

    @staticmethod
    def wait_for(timeout: float = 10.0) -> MessageBox:
        """Wait for a MessageBox (#32770) dialog and return a MessageBox."""
        return MessageBox(_find_window("#32770", None, timeout))

    def text(self) -> str:
        """Return the message text from the Static control."""
        for ctrl in self._win.children():
            try:
                if ctrl.class_name() == "Static":
                    t = ctrl.window_text()
                    if t:
                        return t
            except Exception:
                continue
        return ""

    def click_ok(self) -> None:
        """Click the OK button."""
        self.click("OK")

    def click_cancel(self) -> None:
        """Click the Cancel button."""
        self.click("Cancel", "Anuluj")

    def click_yes(self) -> None:
        """Click the Yes button."""
        self.click("Yes", "Tak")

    def click_no(self) -> None:
        """Click the No button."""
        self.click("No", "Nie")

    def click(self, *button_texts: str) -> None:
        """Click a button by its text label.

        Accepts several candidate labels and clicks the first one present, so
        the same call works regardless of the Windows display language
        (e.g. ``"Yes"`` / ``"Tak"``).
        """
        for title in button_texts:
            try:
                btn = self._win.child_window(title=title, control_type="Button")
                btn.click_input()
                return
            except Exception:
                continue
        labels = ", ".join(repr(t) for t in button_texts)
        raise RuntimeError(f"No button matching {labels} found in MessageBox")

wait_for staticmethod

wait_for(timeout: float = 10.0) -> MessageBox

Wait for a MessageBox (#32770) dialog and return a MessageBox.

Source code in src\dolphin_desktop\_dialogs.py
@staticmethod
def wait_for(timeout: float = 10.0) -> MessageBox:
    """Wait for a MessageBox (#32770) dialog and return a MessageBox."""
    return MessageBox(_find_window("#32770", None, timeout))

text

text() -> str

Return the message text from the Static control.

Source code in src\dolphin_desktop\_dialogs.py
def text(self) -> str:
    """Return the message text from the Static control."""
    for ctrl in self._win.children():
        try:
            if ctrl.class_name() == "Static":
                t = ctrl.window_text()
                if t:
                    return t
        except Exception:
            continue
    return ""

click_ok

click_ok() -> None

Click the OK button.

Source code in src\dolphin_desktop\_dialogs.py
def click_ok(self) -> None:
    """Click the OK button."""
    self.click("OK")

click_cancel

click_cancel() -> None

Click the Cancel button.

Source code in src\dolphin_desktop\_dialogs.py
def click_cancel(self) -> None:
    """Click the Cancel button."""
    self.click("Cancel", "Anuluj")

click_yes

click_yes() -> None

Click the Yes button.

Source code in src\dolphin_desktop\_dialogs.py
def click_yes(self) -> None:
    """Click the Yes button."""
    self.click("Yes", "Tak")

click_no

click_no() -> None

Click the No button.

Source code in src\dolphin_desktop\_dialogs.py
def click_no(self) -> None:
    """Click the No button."""
    self.click("No", "Nie")

click

click(*button_texts: str) -> None

Click a button by its text label.

Accepts several candidate labels and clicks the first one present, so the same call works regardless of the Windows display language (e.g. "Yes" / "Tak").

Source code in src\dolphin_desktop\_dialogs.py
def click(self, *button_texts: str) -> None:
    """Click a button by its text label.

    Accepts several candidate labels and clicks the first one present, so
    the same call works regardless of the Windows display language
    (e.g. ``"Yes"`` / ``"Tak"``).
    """
    for title in button_texts:
        try:
            btn = self._win.child_window(title=title, control_type="Button")
            btn.click_input()
            return
        except Exception:
            continue
    labels = ", ".join(repr(t) for t in button_texts)
    raise RuntimeError(f"No button matching {labels} found in MessageBox")

Usage

from dolphin_desktop import MessageBox

# Wait for a MessageBox
mb = MessageBox.wait_for()

# Read the message text
print(mb.text())

# Click buttons
mb.click_ok()
mb.click_cancel()
mb.click_yes()
mb.click_no()
mb.click("Retry")   # any button by title