diff --git a/kotonebot/interop/win/message_box.py b/kotonebot/interop/win/message_box.py new file mode 100644 index 0000000..1ba1d03 --- /dev/null +++ b/kotonebot/interop/win/message_box.py @@ -0,0 +1,314 @@ +import ctypes +from typing import Optional, Literal, List, overload +from typing_extensions import assert_never + + +# 按钮常量 +MB_OK = 0x00000000 +MB_OKCANCEL = 0x00000001 +MB_ABORTRETRYIGNORE = 0x00000002 +MB_YESNOCANCEL = 0x00000003 +MB_YESNO = 0x00000004 +MB_RETRYCANCEL = 0x00000005 +MB_CANCELTRYCONTINUE = 0x00000006 + +# 图标常量 +MB_ICONSTOP = 0x00000010 +MB_ICONERROR = 0x00000010 +MB_ICONQUESTION = 0x00000020 +MB_ICONWARNING = 0x00000030 +MB_ICONINFORMATION = 0x00000040 + +# 默认按钮常量 +MB_DEFBUTTON1 = 0x00000000 +MB_DEFBUTTON2 = 0x00000100 +MB_DEFBUTTON3 = 0x00000200 +MB_DEFBUTTON4 = 0x00000300 + +# 模态常量 +MB_APPLMODAL = 0x00000000 +MB_SYSTEMMODAL = 0x00001000 +MB_TASKMODAL = 0x00002000 + +# 其他选项 +MB_HELP = 0x00004000 +MB_NOFOCUS = 0x00008000 +MB_SETFOREGROUND = 0x00010000 +MB_DEFAULT_DESKTOP_ONLY = 0x00020000 +MB_TOPMOST = 0x00040000 +MB_RIGHT = 0x00080000 +MB_RTLREADING = 0x00100000 +MB_SERVICE_NOTIFICATION = 0x00200000 + +# 返回值常量 +IDOK = 1 +IDCANCEL = 2 +IDABORT = 3 +IDRETRY = 4 +IDIGNORE = 5 +IDYES = 6 +IDNO = 7 +IDCLOSE = 8 +IDHELP = 9 +IDTRYAGAIN = 10 +IDCONTINUE = 11 + +# 为清晰起见,定义类型别名 +ButtonsType = Literal['ok', 'ok_cancel', 'abort_retry_ignore', 'yes_no_cancel', 'yes_no', 'retry_cancel', 'cancel_try_continue'] +IconType = Optional[Literal['stop', 'error', 'question', 'warning', 'information']] +DefaultButtonType = Literal['button1', 'button2', 'button3', 'button4'] +ModalType = Literal['application', 'system', 'task'] +OptionsType = Optional[List[Literal['help', 'no_focus', 'set_foreground', 'default_desktop_only', 'topmost', 'right', 'rtl_reading', 'service_notification']]] +ReturnType = Literal['ok', 'cancel', 'abort', 'retry', 'ignore', 'yes', 'no', 'close', 'help', 'try_again', 'continue'] + +user32 = ctypes.windll.user32 + + +@overload +def message_box( + hWnd: Optional[int], + text: str, + caption: str, + buttons: Literal['ok'] = 'ok', + icon: IconType = None, + default_button: DefaultButtonType = 'button1', + modal: ModalType = 'application', + options: OptionsType = None +) -> Literal['ok']: ... + + +@overload +def message_box( + hWnd: Optional[int], + text: str, + caption: str, + buttons: Literal['ok_cancel'], + icon: IconType = None, + default_button: DefaultButtonType = 'button1', + modal: ModalType = 'application', + options: OptionsType = None +) -> Literal['ok', 'cancel']: ... + + +@overload +def message_box( + hWnd: Optional[int], + text: str, + caption: str, + buttons: Literal['abort_retry_ignore'], + icon: IconType = None, + default_button: DefaultButtonType = 'button1', + modal: ModalType = 'application', + options: OptionsType = None +) -> Literal['abort', 'retry', 'ignore']: ... + + +@overload +def message_box( + hWnd: Optional[int], + text: str, + caption: str, + buttons: Literal['yes_no_cancel'], + icon: IconType = None, + default_button: DefaultButtonType = 'button1', + modal: ModalType = 'application', + options: OptionsType = None +) -> Literal['yes', 'no', 'cancel']: ... + + +@overload +def message_box( + hWnd: Optional[int], + text: str, + caption: str, + buttons: Literal['yes_no'], + icon: IconType = None, + default_button: DefaultButtonType = 'button1', + modal: ModalType = 'application', + options: OptionsType = None +) -> Literal['yes', 'no']: ... + + +@overload +def message_box( + hWnd: Optional[int], + text: str, + caption: str, + buttons: Literal['retry_cancel'], + icon: IconType = None, + default_button: DefaultButtonType = 'button1', + modal: ModalType = 'application', + options: OptionsType = None +) -> Literal['retry', 'cancel']: ... + + +@overload +def message_box( + hWnd: Optional[int], + text: str, + caption: str, + buttons: Literal['cancel_try_continue'], + icon: IconType = None, + default_button: DefaultButtonType = 'button1', + modal: ModalType = 'application', + options: OptionsType = None +) -> Literal['cancel', 'try_again', 'continue']: ... + + +def message_box( + hWnd: Optional[int], + text: str, + caption: str, + buttons: ButtonsType = 'ok', + icon: IconType = None, + default_button: DefaultButtonType = 'button1', + modal: ModalType = 'application', + options: OptionsType = None +) -> ReturnType: + """ + 显示消息框。 + + :param hWnd: 所属窗口的句柄。可以为 None。 + :param text: 要显示的消息。 + :param caption: 消息框的标题。 + :param buttons: 要显示的按钮。 + :param icon: 要显示的图标。 + :param default_button: 默认按钮。 + :param modal: 消息框的模态。 + :param options: 其他杂项选项列表。 + :return: 表示用户点击的按钮的字符串。 + """ + uType = 0 + + # --- 按钮类型 --- + match buttons: + case 'ok': + uType |= MB_OK + case 'ok_cancel': + uType |= MB_OKCANCEL + case 'abort_retry_ignore': + uType |= MB_ABORTRETRYIGNORE + case 'yes_no_cancel': + uType |= MB_YESNOCANCEL + case 'yes_no': + uType |= MB_YESNO + case 'retry_cancel': + uType |= MB_RETRYCANCEL + case 'cancel_try_continue': + uType |= MB_CANCELTRYCONTINUE + case _: + assert_never(buttons) + + # --- 图标类型 --- + if icon: + match icon: + case 'stop' | 'error': + uType |= MB_ICONSTOP + case 'question': + uType |= MB_ICONQUESTION + case 'warning': + uType |= MB_ICONWARNING + case 'information': + uType |= MB_ICONINFORMATION + case _: + assert_never(icon) + + # --- 默认按钮 --- + match default_button: + case 'button1': + uType |= MB_DEFBUTTON1 + case 'button2': + uType |= MB_DEFBUTTON2 + case 'button3': + uType |= MB_DEFBUTTON3 + case 'button4': + uType |= MB_DEFBUTTON4 + case _: + assert_never(default_button) + + # --- 模态 --- + match modal: + case 'application': + uType |= MB_APPLMODAL + case 'system': + uType |= MB_SYSTEMMODAL + case 'task': + uType |= MB_TASKMODAL + case _: + assert_never(modal) + + # --- 其他选项 --- + if options: + for option in options: + match option: + case 'help': + uType |= MB_HELP + case 'no_focus': + uType |= MB_NOFOCUS + case 'set_foreground': + uType |= MB_SETFOREGROUND + case 'default_desktop_only': + uType |= MB_DEFAULT_DESKTOP_ONLY + case 'topmost': + uType |= MB_TOPMOST + case 'right': + uType |= MB_RIGHT + case 'rtl_reading': + uType |= MB_RTLREADING + case 'service_notification': + uType |= MB_SERVICE_NOTIFICATION + case _: + assert_never(option) + + result = user32.MessageBoxW(hWnd, text, caption, uType) + + match result: + case 1: # IDOK + return 'ok' + case 2: # IDCANCEL + return 'cancel' + case 3: # IDABORT + return 'abort' + case 4: # IDRETRY + return 'retry' + case 5: # IDIGNORE + return 'ignore' + case 6: # IDYES + return 'yes' + case 7: # IDNO + return 'no' + case 8: # IDCLOSE + return 'close' + case 9: # IDHELP + return 'help' + case 10: # IDTRYAGAIN + return 'try_again' + case 11: # IDCONTINUE + return 'continue' + case _: + # 对于标准消息框,不应发生这种情况 + raise RuntimeError(f"Unknown MessageBox return code: {result}") + + +if __name__ == '__main__': + # 示例用法 + response = message_box( + None, + "是否要退出程序?", + "确认", + buttons='yes_no', + icon='question' + ) + + if response == 'yes': + print("程序退出。") + else: + print("程序继续运行。") + + message_box( + None, + "操作已完成。", + "通知", + buttons='ok', + icon='information' + ) \ No newline at end of file diff --git a/kotonebot/interop/win/task_dialog.py b/kotonebot/interop/win/task_dialog.py new file mode 100644 index 0000000..782c9e8 --- /dev/null +++ b/kotonebot/interop/win/task_dialog.py @@ -0,0 +1,469 @@ +import ctypes +from ctypes import wintypes +import time +from typing import List, Tuple, Optional +from typing import Literal + +__all__ = [ + "TaskDialog", + "TDCBF_OK_BUTTON", "TDCBF_YES_BUTTON", "TDCBF_NO_BUTTON", "TDCBF_CANCEL_BUTTON", + "TDCBF_RETRY_BUTTON", "TDCBF_CLOSE_BUTTON", + "IDOK", "IDCANCEL", "IDABORT", "IDRETRY", "IDIGNORE", "IDYES", "IDNO", "IDCLOSE", + "TD_WARNING_ICON", "TD_ERROR_ICON", "TD_INFORMATION_ICON", "TD_SHIELD_ICON" +] + +# --- Windows API 常量定义 --- + +# 常用按钮 +TDCBF_OK_BUTTON = 0x0001 +TDCBF_YES_BUTTON = 0x0002 +TDCBF_NO_BUTTON = 0x0004 +TDCBF_CANCEL_BUTTON = 0x0008 +TDCBF_RETRY_BUTTON = 0x0010 +TDCBF_CLOSE_BUTTON = 0x0020 + +# 对话框返回值 +IDOK = 1 +IDCANCEL = 2 +IDABORT = 3 +IDRETRY = 4 +IDIGNORE = 5 +IDYES = 6 +IDNO = 7 +IDCLOSE = 8 + + +# 标准图标 (使用 MAKEINTRESOURCE 宏) +def MAKEINTRESOURCE(i: int) -> wintypes.LPWSTR: + return wintypes.LPWSTR(i) + + +TD_WARNING_ICON = MAKEINTRESOURCE(65535) +TD_ERROR_ICON = MAKEINTRESOURCE(65534) +TD_INFORMATION_ICON = MAKEINTRESOURCE(65533) +TD_SHIELD_ICON = MAKEINTRESOURCE(65532) + +# Task Dialog 标志 +TDF_ENABLE_HYPERLINKS = 0x0001 +TDF_USE_HICON_MAIN = 0x0002 +TDF_USE_HICON_FOOTER = 0x0004 +TDF_ALLOW_DIALOG_CANCELLATION = 0x0008 +TDF_USE_COMMAND_LINKS = 0x0010 +TDF_USE_COMMAND_LINKS_NO_ICON = 0x0020 +TDF_EXPAND_FOOTER_AREA = 0x0040 +TDF_EXPANDED_BY_DEFAULT = 0x0080 +TDF_VERIFICATION_FLAG_CHECKED = 0x0100 +TDF_SHOW_PROGRESS_BAR = 0x0200 +TDF_SHOW_MARQUEE_PROGRESS_BAR = 0x0400 +TDF_CALLBACK_TIMER = 0x0800 +TDF_POSITION_RELATIVE_TO_WINDOW = 0x1000 +TDF_RTL_LAYOUT = 0x2000 +TDF_NO_DEFAULT_RADIO_BUTTON = 0x4000 +TDF_CAN_BE_MINIMIZED = 0x8000 + +# Task Dialog 通知 +TDN_CREATED = 0 +TDN_NAVIGATED = 1 +TDN_BUTTON_CLICKED = 2 +TDN_HYPERLINK_CLICKED = 3 +TDN_TIMER = 4 +TDN_DESTROYED = 5 +TDN_RADIO_BUTTON_CLICKED = 6 +TDN_DIALOG_CONSTRUCTED = 7 +TDN_VERIFICATION_CLICKED = 8 +TDN_HELP = 9 +TDN_EXPANDO_BUTTON_CLICKED = 10 + +# Windows 消息 +WM_USER = 0x0400 +TDM_SET_PROGRESS_BAR_POS = WM_USER + 114 + +CommonButtonLiteral = Literal["ok", "yes", "no", "cancel", "retry", "close"] +IconLiteral = Literal["warning", "error", "information", "shield"] + + +# --- C 结构体定义 (使用 ctypes) --- + +class TASKDIALOG_BUTTON(ctypes.Structure): + _pack_ = 1 + _fields_ = [("nButtonID", ctypes.c_int), + ("pszButtonText", wintypes.LPCWSTR)] + + +# 定义回调函数指针原型 +PFTASKDIALOGCALLBACK = ctypes.WINFUNCTYPE( + ctypes.HRESULT, # 返回值 + wintypes.HWND, # hwnd + ctypes.c_uint, # msg + ctypes.c_size_t, # wParam + ctypes.c_size_t, # lParam + ctypes.c_ssize_t # lpRefData +) + + +class TASKDIALOGCONFIG(ctypes.Structure): + _pack_ = 1 + _fields_ = [ + ("cbSize", ctypes.c_uint), + ("hwndParent", wintypes.HWND), + ("hInstance", wintypes.HINSTANCE), + ("dwFlags", ctypes.c_uint), + ("dwCommonButtons", ctypes.c_uint), + ("pszWindowTitle", wintypes.LPCWSTR), + ("pszMainIcon", wintypes.LPCWSTR), + ("pszMainInstruction", wintypes.LPCWSTR), + ("pszContent", wintypes.LPCWSTR), + ("cButtons", ctypes.c_uint), + ("pButtons", ctypes.POINTER(TASKDIALOG_BUTTON)), + ("nDefaultButton", ctypes.c_int), + ("cRadioButtons", ctypes.c_uint), + ("pRadioButtons", ctypes.POINTER(TASKDIALOG_BUTTON)), + ("nDefaultRadioButton", ctypes.c_int), + ("pszVerificationText", wintypes.LPCWSTR), + ("pszExpandedInformation", wintypes.LPCWSTR), + ("pszExpandedControlText", wintypes.LPCWSTR), + ("pszCollapsedControlText", wintypes.LPCWSTR), + ("pszFooterIcon", wintypes.LPCWSTR), + ("pszFooter", wintypes.LPCWSTR), + ("pfCallback", PFTASKDIALOGCALLBACK), # 使用定义好的原型 + ("lpCallbackData", ctypes.c_ssize_t), + ("cxWidth", ctypes.c_uint) + ] + + +# --- 加载 comctl32.dll 并定义函数原型 --- + +comctl32 = ctypes.WinDLL('comctl32') +user32 = ctypes.WinDLL('user32') + +TaskDialogIndirect = comctl32.TaskDialogIndirect +TaskDialogIndirect.restype = ctypes.HRESULT +TaskDialogIndirect.argtypes = [ + ctypes.POINTER(TASKDIALOGCONFIG), + ctypes.POINTER(ctypes.c_int), + ctypes.POINTER(ctypes.c_int), + ctypes.POINTER(wintypes.BOOL) +] + + +# --- Python 封装类 --- + +class TaskDialog: + """ + 一个用于显示 Windows TaskDialog 的 Python 封装类。 + 支持自定义按钮、单选按钮、进度条、验证框等。 + """ + + def __init__(self, + parent_hwnd: Optional[int] = None, + title: str = "Task Dialog", + main_instruction: str = "", + content: str = "", + common_buttons: int | List[CommonButtonLiteral] = TDCBF_OK_BUTTON, + main_icon: Optional[wintypes.LPWSTR | int | IconLiteral] = None, + footer: str = "", + custom_buttons: Optional[List[Tuple[int, str]]] = None, + default_button: int = 0, + radio_buttons: Optional[List[Tuple[int, str]]] = None, + default_radio_button: int = 0, + verification_text: Optional[str] = None, + verification_checked_by_default: bool = False, + show_progress_bar: bool = False, + show_marquee_progress_bar: bool = False + ): + """初始化 TaskDialog 实例。 + + :param parent_hwnd: 父窗口的句柄。 + :param title: 对话框窗口的标题。 + :param main_instruction: 对话框的主要指令文本。 + :param content: 对话框的详细内容文本。 + :param common_buttons: 要显示的通用按钮。可以是以下两种形式之一: + 1. TDCBF_* 常量的按位或组合 (例如 TDCBF_OK_BUTTON | TDCBF_CANCEL_BUTTON) + 2. 字符串列表,支持 "ok", "yes", "no", "cancel", "retry", "close" + :param main_icon: 主图标。可以是以下几种形式之一: + 1. TD_*_ICON 常量之一 + 2. HICON 句柄 + 3. 字符串:"warning", "error", "information", "shield" + :param footer: 页脚区域显示的文本。 + :param custom_buttons: 自定义按钮列表。每个元组包含 (按钮ID, 按钮文本)。 + :param default_button: 默认按钮的ID。可以是通用按钮ID (例如 IDOK) 或自定义按钮ID。 + :param radio_buttons: 单选按钮列表。每个元组包含 (按钮ID, 按钮文本)。 + :param default_radio_button: 默认选中的单选按钮的ID。 + :param verification_text: 验证复选框的文本。如果为 None,则不显示复选框。 + :param verification_checked_by_default: 验证复选框是否默认勾选。 + :param show_progress_bar: 是否显示标准进度条。 + :param show_marquee_progress_bar: 是否显示跑马灯式进度条。 + """ + self.config = TASKDIALOGCONFIG() + self.config.cbSize = ctypes.sizeof(TASKDIALOGCONFIG) + self.config.hwndParent = parent_hwnd + self.config.dwFlags = TDF_ALLOW_DIALOG_CANCELLATION | TDF_POSITION_RELATIVE_TO_WINDOW + self.config.dwCommonButtons = self._process_common_buttons(common_buttons) + self.config.pszWindowTitle = title + self.config.pszMainInstruction = main_instruction + self.config.pszContent = content + self.config.pszFooter = footer + + self.progress: int = 0 + if show_progress_bar or show_marquee_progress_bar: + # 进度条暂时还没实现 + raise NotImplementedError("Progress bar is not implemented yet.") + self.config.dwFlags |= TDF_CALLBACK_TIMER + if show_progress_bar: + self.config.dwFlags |= TDF_SHOW_PROGRESS_BAR + else: + self.config.dwFlags |= TDF_SHOW_MARQUEE_PROGRESS_BAR + + # 将实例方法转为 C 回调函数指针。 + # 必须将其保存为实例成员,否则会被垃圾回收! + self._callback_func_ptr = PFTASKDIALOGCALLBACK(self._callback) + self.config.pfCallback = self._callback_func_ptr + # 将本实例的id作为lpCallbackData传递,以便在回调中识别 + self.config.lpCallbackData = id(self) + + # --- 图标设置 --- + processed_icon = self._process_main_icon(main_icon) + if processed_icon is not None: + if isinstance(processed_icon, wintypes.LPWSTR): + self.config.pszMainIcon = processed_icon + else: + self.config.dwFlags |= TDF_USE_HICON_MAIN + self.config.hMainIcon = processed_icon + + # --- 自定义按钮设置 --- + self.custom_buttons_list = [] + if custom_buttons: + self.config.cButtons = len(custom_buttons) + button_array_type = TASKDIALOG_BUTTON * len(custom_buttons) + self.custom_buttons_list = button_array_type() + for i, (btn_id, btn_text) in enumerate(custom_buttons): + self.custom_buttons_list[i].nButtonID = btn_id + self.custom_buttons_list[i].pszButtonText = btn_text + self.config.pButtons = self.custom_buttons_list + + if default_button: + self.config.nDefaultButton = default_button + + # --- 单选按钮设置 --- + self.radio_buttons_list = [] + if radio_buttons: + self.config.cRadioButtons = len(radio_buttons) + radio_array_type = TASKDIALOG_BUTTON * len(radio_buttons) + self.radio_buttons_list = radio_array_type() + for i, (btn_id, btn_text) in enumerate(radio_buttons): + self.radio_buttons_list[i].nButtonID = btn_id + self.radio_buttons_list[i].pszButtonText = btn_text + self.config.pRadioButtons = self.radio_buttons_list + + if default_radio_button: + self.config.nDefaultRadioButton = default_radio_button + + # --- 验证复选框设置 --- + if verification_text: + self.config.pszVerificationText = verification_text + if verification_checked_by_default: + self.config.dwFlags |= TDF_VERIFICATION_FLAG_CHECKED + + def _process_common_buttons(self, common_buttons: int | List[CommonButtonLiteral]) -> int: + """处理 common_buttons 参数,支持常量和字符串列表两种形式""" + if isinstance(common_buttons, int): + # 直接使用 Win32 常量 + return common_buttons + elif isinstance(common_buttons, list): + # 处理字符串列表 + result = 0 + for button in common_buttons: + # 使用 match 和 assert_never 进行类型检查 + match button: + case "ok": + result |= TDCBF_OK_BUTTON + case "yes": + result |= TDCBF_YES_BUTTON + case "no": + result |= TDCBF_NO_BUTTON + case "cancel": + result |= TDCBF_CANCEL_BUTTON + case "retry": + result |= TDCBF_RETRY_BUTTON + case "close": + result |= TDCBF_CLOSE_BUTTON + case _: + # 这在实际中不会发生,因为类型检查会阻止它 + from typing import assert_never + assert_never(button) + return result + else: + raise TypeError("common_buttons must be either an int or a list of strings") + + def _process_main_icon(self, main_icon: Optional[wintypes.LPWSTR | int | IconLiteral]) -> Optional[wintypes.LPWSTR | int]: + """处理 main_icon 参数,支持常量和字符串两种形式""" + if main_icon is None: + return None + elif isinstance(main_icon, (wintypes.LPWSTR, int)): + # 直接使用 Win32 常量或 HICON 句柄 + return main_icon + elif isinstance(main_icon, str): + # 处理字符串 + match main_icon: + case "warning": + return TD_WARNING_ICON + case "error": + return TD_ERROR_ICON + case "information": + return TD_INFORMATION_ICON + case "shield": + return TD_SHIELD_ICON + case _: + # 这在实际中不会发生,因为类型检查会阻止它 + from typing import assert_never + assert_never(main_icon) + else: + raise TypeError("main_icon must be None, a Windows constant, or a string") + + def _callback(self, hwnd: wintypes.HWND, msg: int, wParam: int, lParam: int, lpRefData: int) -> int: + # 仅当 lpRefData 指向的是当前这个对象实例时才处理 + if lpRefData != id(self): + return 0 # S_OK + + if msg == TDN_TIMER: + # 更新进度条 + if self.progress < 100: + self.progress += 5 + # 发送消息给对话框来更新进度条位置 + user32.SendMessageW(hwnd, TDM_SET_PROGRESS_BAR_POS, self.progress, 0) + else: + # 示例:进度达到100%后,可以模拟点击OK按钮关闭对话框 + # from ctypes import wintypes + # user32.PostMessageW(hwnd, wintypes.UINT(1125), IDOK, 0) # TDM_CLICK_BUTTON + pass + + elif msg == TDN_DESTROYED: + # 对话框已销毁 + pass + + return 0 # S_OK + + def show(self) -> Tuple[int, int, bool]: + """ + 显示对话框并返回用户交互的结果。 + + :return: 一个元组 (button_id, radio_button_id, verification_checked) + - button_id: 用户点击的按钮ID (例如 IDOK, IDCANCEL)。 + - radio_button_id: 用户选择的单选按钮的ID。 + - verification_checked: 验证复选框是否被勾选 (True/False)。 + """ + pnButton = ctypes.c_int(0) + pnRadioButton = ctypes.c_int(0) + pfVerificationFlagChecked = wintypes.BOOL(False) + + hr = TaskDialogIndirect( + ctypes.byref(self.config), + ctypes.byref(pnButton), + ctypes.byref(pnRadioButton), + ctypes.byref(pfVerificationFlagChecked) + ) + + if hr == 0: # S_OK + return pnButton.value, pnRadioButton.value, bool(pfVerificationFlagChecked.value) + else: + raise ctypes.WinError(hr) + + +# --- 示例用法 --- +if __name__ == '__main__': + + print("--- 示例 1: 简单信息框 ---") + dlg_simple = TaskDialog( + title="操作成功", + main_instruction="您的操作已成功完成。", + content="文件已保存到您的文档目录。", + common_buttons=["ok"], + main_icon="information" + ) + result_simple, _, _ = dlg_simple.show() + print(f"用户点击了按钮: {result_simple} (1=OK)\n") + + print("--- 示例 2: 确认框 ---") + dlg_confirm = TaskDialog( + title="确认删除", + main_instruction="您确定要永久删除这个文件吗?", + content="这个操作无法撤销。文件将被立即删除。", + common_buttons=["yes", "no", "cancel"], + main_icon="warning", + default_button=IDNO + ) + result_confirm, _, _ = dlg_confirm.show() + if result_confirm == IDYES: + print("用户选择了“是”。") + elif result_confirm == IDNO: + print("用户选择了“否”。") + elif result_confirm == IDCANCEL: + print("用户选择了“取消”。") + print(f"返回的按钮ID: {result_confirm}\n") + + # 示例 3 + print("--- 示例 3: 自定义按钮 ---") + CUSTOM_BUTTON_SAVE_ID = 101 + CUSTOM_BUTTON_DONT_SAVE_ID = 102 + my_buttons = [ + (CUSTOM_BUTTON_SAVE_ID, "保存并退出"), + (CUSTOM_BUTTON_DONT_SAVE_ID, "不保存直接退出") + ] + dlg_custom = TaskDialog( + title="未保存的更改", + main_instruction="文档中有未保存的更改,您想如何处理?", + custom_buttons=my_buttons, + common_buttons=["cancel"], + main_icon="warning", + footer="这是一个重要的提醒!" + ) + result_custom, _, _ = dlg_custom.show() + if result_custom == CUSTOM_BUTTON_SAVE_ID: + print("用户选择了“保存并退出”。") + elif result_custom == CUSTOM_BUTTON_DONT_SAVE_ID: + print("用户选择了“不保存直接退出”。") + elif result_custom == IDCANCEL: + print("用户选择了“取消”。") + print(f"返回的按钮ID: {result_custom}\n") + + # 示例 4: 带单选按钮和验证框的对话框 + print("--- 示例 4: 单选按钮和验证框 ---") + RADIO_BTN_WORD_ID = 201 + RADIO_BTN_EXCEL_ID = 202 + RADIO_BTN_PDF_ID = 203 + + radio_buttons = [ + (RADIO_BTN_WORD_ID, "保存为 Word 文档 (.docx)"), + (RADIO_BTN_EXCEL_ID, "保存为 Excel 表格 (.xlsx)"), + (RADIO_BTN_PDF_ID, "导出为 PDF 文档 (.pdf)") + ] + + dlg_radio = TaskDialog( + title="选择导出格式", + main_instruction="请选择您想要导出的文件格式。", + content="选择一个格式后,点击“确定”继续。", + common_buttons=["ok", "cancel"], + main_icon="information", + radio_buttons=radio_buttons, + default_radio_button=RADIO_BTN_PDF_ID, # 默认选中PDF + verification_text="设为我的默认导出选项", + verification_checked_by_default=True + ) + btn_id, radio_id, checked = dlg_radio.show() + + if btn_id == IDOK: + print(f"用户点击了“确定”。") + if radio_id == RADIO_BTN_WORD_ID: + print("选择了导出为 Word。") + elif radio_id == RADIO_BTN_EXCEL_ID: + print("选择了导出为 Excel。") + elif radio_id == RADIO_BTN_PDF_ID: + print("选择了导出为 PDF。") + + if checked: + print("用户勾选了“设为我的默认导出选项”。") + else: + print("用户未勾选“设为我的默认导出选项”。") + else: + print("用户点击了“取消”。") + print(f"返回的按钮ID: {btn_id}, 单选按钮ID: {radio_id}, 验证框状态: {checked}\n")