kotonebot 0.4.0__py3-none-any.whl → 0.5.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. kotonebot/__init__.py +39 -39
  2. kotonebot/backend/bot.py +312 -312
  3. kotonebot/backend/color.py +525 -525
  4. kotonebot/backend/context/__init__.py +3 -3
  5. kotonebot/backend/context/task_action.py +183 -183
  6. kotonebot/backend/core.py +129 -129
  7. kotonebot/backend/debug/entry.py +89 -89
  8. kotonebot/backend/debug/mock.py +78 -78
  9. kotonebot/backend/debug/server.py +222 -222
  10. kotonebot/backend/debug/vars.py +351 -351
  11. kotonebot/backend/dispatch.py +227 -227
  12. kotonebot/backend/flow_controller.py +196 -196
  13. kotonebot/backend/ocr.py +535 -529
  14. kotonebot/backend/preprocessor.py +103 -103
  15. kotonebot/client/__init__.py +9 -9
  16. kotonebot/client/device.py +528 -503
  17. kotonebot/client/fast_screenshot.py +377 -377
  18. kotonebot/client/host/__init__.py +43 -12
  19. kotonebot/client/host/adb_common.py +107 -103
  20. kotonebot/client/host/custom.py +118 -114
  21. kotonebot/client/host/leidian_host.py +196 -201
  22. kotonebot/client/host/mumu12_host.py +353 -358
  23. kotonebot/client/host/protocol.py +214 -213
  24. kotonebot/client/host/windows_common.py +58 -58
  25. kotonebot/client/implements/__init__.py +71 -15
  26. kotonebot/client/implements/adb.py +89 -85
  27. kotonebot/client/implements/adb_raw.py +162 -158
  28. kotonebot/client/implements/nemu_ipc/__init__.py +11 -7
  29. kotonebot/client/implements/nemu_ipc/external_renderer_ipc.py +284 -284
  30. kotonebot/client/implements/nemu_ipc/nemu_ipc.py +327 -327
  31. kotonebot/client/implements/remote_windows.py +188 -188
  32. kotonebot/client/implements/uiautomator2.py +85 -81
  33. kotonebot/client/implements/windows.py +176 -172
  34. kotonebot/client/protocol.py +69 -69
  35. kotonebot/client/registration.py +24 -24
  36. kotonebot/config/base_config.py +96 -96
  37. kotonebot/config/manager.py +36 -36
  38. kotonebot/errors.py +76 -71
  39. kotonebot/interop/win/__init__.py +10 -3
  40. kotonebot/interop/win/_mouse.py +311 -0
  41. kotonebot/interop/win/message_box.py +313 -313
  42. kotonebot/interop/win/reg.py +37 -37
  43. kotonebot/interop/win/shortcut.py +43 -43
  44. kotonebot/interop/win/task_dialog.py +513 -513
  45. kotonebot/logging/__init__.py +2 -2
  46. kotonebot/logging/log.py +17 -17
  47. kotonebot/primitives/__init__.py +17 -17
  48. kotonebot/primitives/geometry.py +862 -290
  49. kotonebot/primitives/visual.py +63 -63
  50. kotonebot/tools/mirror.py +354 -354
  51. kotonebot/ui/file_host/sensio.py +36 -36
  52. kotonebot/ui/file_host/tmp_send.py +54 -54
  53. kotonebot/ui/pushkit/__init__.py +3 -3
  54. kotonebot/ui/pushkit/image_host.py +88 -87
  55. kotonebot/ui/pushkit/protocol.py +13 -13
  56. kotonebot/ui/pushkit/wxpusher.py +54 -53
  57. kotonebot/ui/user.py +148 -148
  58. kotonebot/util.py +436 -436
  59. {kotonebot-0.4.0.dist-info → kotonebot-0.5.0.dist-info}/METADATA +82 -81
  60. kotonebot-0.5.0.dist-info/RECORD +71 -0
  61. {kotonebot-0.4.0.dist-info → kotonebot-0.5.0.dist-info}/licenses/LICENSE +673 -673
  62. kotonebot-0.4.0.dist-info/RECORD +0 -70
  63. {kotonebot-0.4.0.dist-info → kotonebot-0.5.0.dist-info}/WHEEL +0 -0
  64. {kotonebot-0.4.0.dist-info → kotonebot-0.5.0.dist-info}/top_level.txt +0 -0
@@ -1,513 +1,513 @@
1
- """
2
- Windows Task Dialog interop module.
3
-
4
- This module provides Windows TaskDialog functionality and is only available on Windows systems.
5
- """
6
-
7
- import platform
8
- import warnings
9
-
10
- from kotonebot.util import is_windows
11
-
12
- # 检查是否在 Windows 平台上
13
- if not is_windows():
14
- _WINDOWS_ONLY_MSG = (
15
- f"TaskDialog is only available on Windows systems. "
16
- f"Current system: non-Windows\n"
17
- "To use Windows TaskDialog features, please run this code on a Windows system."
18
- )
19
-
20
-
21
- # 提供虚拟类以避免导入错误
22
- class TaskDialog:
23
- def __init__(self, *args, **kwargs):
24
- raise ImportError(_WINDOWS_ONLY_MSG)
25
-
26
- # 导出所有常量作为 None
27
- __all__ = [
28
- "TaskDialog",
29
- "TDCBF_OK_BUTTON", "TDCBF_YES_BUTTON", "TDCBF_NO_BUTTON", "TDCBF_CANCEL_BUTTON",
30
- "TDCBF_RETRY_BUTTON", "TDCBF_CLOSE_BUTTON",
31
- "IDOK", "IDCANCEL", "IDABORT", "IDRETRY", "IDIGNORE", "IDYES", "IDNO", "IDCLOSE",
32
- "TD_WARNING_ICON", "TD_ERROR_ICON", "TD_INFORMATION_ICON", "TD_SHIELD_ICON"
33
- ]
34
-
35
- # 设置所有常量为 None 或保留为模块级变量
36
- TDCBF_OK_BUTTON = TDCBF_YES_BUTTON = TDCBF_NO_BUTTON = TDCBF_CANCEL_BUTTON = None
37
- TDCBF_RETRY_BUTTON = TDCBF_CLOSE_BUTTON = None
38
- IDOK = IDCANCEL = IDABORT = IDRETRY = IDIGNORE = IDYES = IDNO = IDCLOSE = None
39
- TD_WARNING_ICON = TD_ERROR_ICON = TD_INFORMATION_ICON = TD_SHIELD_ICON = None
40
-
41
- # 阻止模块加载
42
- raise ImportError(_WINDOWS_ONLY_MSG)
43
-
44
- # 如果是 Windows,继续正常加载
45
- import ctypes
46
- from ctypes import wintypes
47
- import time
48
- from typing import List, Tuple, Optional
49
- from typing import Literal
50
-
51
- __all__ = [
52
- "TaskDialog",
53
- "TDCBF_OK_BUTTON", "TDCBF_YES_BUTTON", "TDCBF_NO_BUTTON", "TDCBF_CANCEL_BUTTON",
54
- "TDCBF_RETRY_BUTTON", "TDCBF_CLOSE_BUTTON",
55
- "IDOK", "IDCANCEL", "IDABORT", "IDRETRY", "IDIGNORE", "IDYES", "IDNO", "IDCLOSE",
56
- "TD_WARNING_ICON", "TD_ERROR_ICON", "TD_INFORMATION_ICON", "TD_SHIELD_ICON"
57
- ]
58
-
59
- # --- Windows API 常量定义 ---
60
-
61
- # 常用按钮
62
- TDCBF_OK_BUTTON = 0x0001
63
- TDCBF_YES_BUTTON = 0x0002
64
- TDCBF_NO_BUTTON = 0x0004
65
- TDCBF_CANCEL_BUTTON = 0x0008
66
- TDCBF_RETRY_BUTTON = 0x0010
67
- TDCBF_CLOSE_BUTTON = 0x0020
68
-
69
- # 对话框返回值
70
- IDOK = 1
71
- IDCANCEL = 2
72
- IDABORT = 3
73
- IDRETRY = 4
74
- IDIGNORE = 5
75
- IDYES = 6
76
- IDNO = 7
77
- IDCLOSE = 8
78
-
79
-
80
- # 标准图标 (使用 MAKEINTRESOURCE 宏)
81
- def MAKEINTRESOURCE(i: int) -> wintypes.LPWSTR:
82
- return wintypes.LPWSTR(i)
83
-
84
-
85
- TD_WARNING_ICON = MAKEINTRESOURCE(65535)
86
- TD_ERROR_ICON = MAKEINTRESOURCE(65534)
87
- TD_INFORMATION_ICON = MAKEINTRESOURCE(65533)
88
- TD_SHIELD_ICON = MAKEINTRESOURCE(65532)
89
-
90
- # Task Dialog 标志
91
- TDF_ENABLE_HYPERLINKS = 0x0001
92
- TDF_USE_HICON_MAIN = 0x0002
93
- TDF_USE_HICON_FOOTER = 0x0004
94
- TDF_ALLOW_DIALOG_CANCELLATION = 0x0008
95
- TDF_USE_COMMAND_LINKS = 0x0010
96
- TDF_USE_COMMAND_LINKS_NO_ICON = 0x0020
97
- TDF_EXPAND_FOOTER_AREA = 0x0040
98
- TDF_EXPANDED_BY_DEFAULT = 0x0080
99
- TDF_VERIFICATION_FLAG_CHECKED = 0x0100
100
- TDF_SHOW_PROGRESS_BAR = 0x0200
101
- TDF_SHOW_MARQUEE_PROGRESS_BAR = 0x0400
102
- TDF_CALLBACK_TIMER = 0x0800
103
- TDF_POSITION_RELATIVE_TO_WINDOW = 0x1000
104
- TDF_RTL_LAYOUT = 0x2000
105
- TDF_NO_DEFAULT_RADIO_BUTTON = 0x4000
106
- TDF_CAN_BE_MINIMIZED = 0x8000
107
-
108
- # Task Dialog 通知
109
- TDN_CREATED = 0
110
- TDN_NAVIGATED = 1
111
- TDN_BUTTON_CLICKED = 2
112
- TDN_HYPERLINK_CLICKED = 3
113
- TDN_TIMER = 4
114
- TDN_DESTROYED = 5
115
- TDN_RADIO_BUTTON_CLICKED = 6
116
- TDN_DIALOG_CONSTRUCTED = 7
117
- TDN_VERIFICATION_CLICKED = 8
118
- TDN_HELP = 9
119
- TDN_EXPANDO_BUTTON_CLICKED = 10
120
-
121
- # Windows 消息
122
- WM_USER = 0x0400
123
- TDM_SET_PROGRESS_BAR_POS = WM_USER + 114
124
-
125
- CommonButtonLiteral = Literal["ok", "yes", "no", "cancel", "retry", "close"]
126
- IconLiteral = Literal["warning", "error", "information", "shield"]
127
-
128
-
129
- # --- C 结构体定义 (使用 ctypes) ---
130
-
131
- class TASKDIALOG_BUTTON(ctypes.Structure):
132
- _pack_ = 1
133
- _fields_ = [("nButtonID", ctypes.c_int),
134
- ("pszButtonText", wintypes.LPCWSTR)]
135
-
136
-
137
- # 定义回调函数指针原型
138
- PFTASKDIALOGCALLBACK = ctypes.WINFUNCTYPE(
139
- ctypes.HRESULT, # 返回值
140
- wintypes.HWND, # hwnd
141
- ctypes.c_uint, # msg
142
- ctypes.c_size_t, # wParam
143
- ctypes.c_size_t, # lParam
144
- ctypes.c_ssize_t # lpRefData
145
- )
146
-
147
-
148
- class TASKDIALOGCONFIG(ctypes.Structure):
149
- _pack_ = 1
150
- _fields_ = [
151
- ("cbSize", ctypes.c_uint),
152
- ("hwndParent", wintypes.HWND),
153
- ("hInstance", wintypes.HINSTANCE),
154
- ("dwFlags", ctypes.c_uint),
155
- ("dwCommonButtons", ctypes.c_uint),
156
- ("pszWindowTitle", wintypes.LPCWSTR),
157
- ("pszMainIcon", wintypes.LPCWSTR),
158
- ("pszMainInstruction", wintypes.LPCWSTR),
159
- ("pszContent", wintypes.LPCWSTR),
160
- ("cButtons", ctypes.c_uint),
161
- ("pButtons", ctypes.POINTER(TASKDIALOG_BUTTON)),
162
- ("nDefaultButton", ctypes.c_int),
163
- ("cRadioButtons", ctypes.c_uint),
164
- ("pRadioButtons", ctypes.POINTER(TASKDIALOG_BUTTON)),
165
- ("nDefaultRadioButton", ctypes.c_int),
166
- ("pszVerificationText", wintypes.LPCWSTR),
167
- ("pszExpandedInformation", wintypes.LPCWSTR),
168
- ("pszExpandedControlText", wintypes.LPCWSTR),
169
- ("pszCollapsedControlText", wintypes.LPCWSTR),
170
- ("pszFooterIcon", wintypes.LPCWSTR),
171
- ("pszFooter", wintypes.LPCWSTR),
172
- ("pfCallback", PFTASKDIALOGCALLBACK), # 使用定义好的原型
173
- ("lpCallbackData", ctypes.c_ssize_t),
174
- ("cxWidth", ctypes.c_uint)
175
- ]
176
-
177
-
178
- # --- 加载 comctl32.dll 并定义函数原型 ---
179
-
180
- comctl32 = ctypes.WinDLL('comctl32')
181
- user32 = ctypes.WinDLL('user32')
182
-
183
- TaskDialogIndirect = comctl32.TaskDialogIndirect
184
- TaskDialogIndirect.restype = ctypes.HRESULT
185
- TaskDialogIndirect.argtypes = [
186
- ctypes.POINTER(TASKDIALOGCONFIG),
187
- ctypes.POINTER(ctypes.c_int),
188
- ctypes.POINTER(ctypes.c_int),
189
- ctypes.POINTER(wintypes.BOOL)
190
- ]
191
-
192
-
193
- # --- Python 封装类 ---
194
-
195
- class TaskDialog:
196
- """
197
- 一个用于显示 Windows TaskDialog 的 Python 封装类。
198
- 支持自定义按钮、单选按钮、进度条、验证框等。
199
- """
200
-
201
- def __init__(self,
202
- parent_hwnd: Optional[int] = None,
203
- title: str = "Task Dialog",
204
- main_instruction: str = "",
205
- content: str = "",
206
- common_buttons: int | List[CommonButtonLiteral] = TDCBF_OK_BUTTON,
207
- main_icon: Optional[wintypes.LPWSTR | int | IconLiteral] = None,
208
- footer: str = "",
209
- custom_buttons: Optional[List[Tuple[int, str]]] = None,
210
- default_button: int = 0,
211
- radio_buttons: Optional[List[Tuple[int, str]]] = None,
212
- default_radio_button: int = 0,
213
- verification_text: Optional[str] = None,
214
- verification_checked_by_default: bool = False,
215
- show_progress_bar: bool = False,
216
- show_marquee_progress_bar: bool = False
217
- ):
218
- """初始化 TaskDialog 实例。
219
-
220
- :param parent_hwnd: 父窗口的句柄。
221
- :param title: 对话框窗口的标题。
222
- :param main_instruction: 对话框的主要指令文本。
223
- :param content: 对话框的详细内容文本。
224
- :param common_buttons: 要显示的通用按钮。可以是以下两种形式之一:
225
- 1. TDCBF_* 常量的按位或组合 (例如 TDCBF_OK_BUTTON | TDCBF_CANCEL_BUTTON)
226
- 2. 字符串列表,支持 "ok", "yes", "no", "cancel", "retry", "close"
227
- :param main_icon: 主图标。可以是以下几种形式之一:
228
- 1. TD_*_ICON 常量之一
229
- 2. HICON 句柄
230
- 3. 字符串:"warning", "error", "information", "shield"
231
- :param footer: 页脚区域显示的文本。
232
- :param custom_buttons: 自定义按钮列表。每个元组包含 (按钮ID, 按钮文本)。
233
- :param default_button: 默认按钮的ID。可以是通用按钮ID (例如 IDOK) 或自定义按钮ID。
234
- :param radio_buttons: 单选按钮列表。每个元组包含 (按钮ID, 按钮文本)。
235
- :param default_radio_button: 默认选中的单选按钮的ID。
236
- :param verification_text: 验证复选框的文本。如果为 None,则不显示复选框。
237
- :param verification_checked_by_default: 验证复选框是否默认勾选。
238
- :param show_progress_bar: 是否显示标准进度条。
239
- :param show_marquee_progress_bar: 是否显示跑马灯式进度条。
240
- """
241
- self.config = TASKDIALOGCONFIG()
242
- self.config.cbSize = ctypes.sizeof(TASKDIALOGCONFIG)
243
- self.config.hwndParent = parent_hwnd
244
- self.config.dwFlags = TDF_ALLOW_DIALOG_CANCELLATION | TDF_POSITION_RELATIVE_TO_WINDOW
245
- self.config.dwCommonButtons = self._process_common_buttons(common_buttons)
246
- self.config.pszWindowTitle = title
247
- self.config.pszMainInstruction = main_instruction
248
- self.config.pszContent = content
249
- self.config.pszFooter = footer
250
-
251
- self.progress: int = 0
252
- if show_progress_bar or show_marquee_progress_bar:
253
- # 进度条暂时还没实现
254
- raise NotImplementedError("Progress bar is not implemented yet.")
255
- self.config.dwFlags |= TDF_CALLBACK_TIMER
256
- if show_progress_bar:
257
- self.config.dwFlags |= TDF_SHOW_PROGRESS_BAR
258
- else:
259
- self.config.dwFlags |= TDF_SHOW_MARQUEE_PROGRESS_BAR
260
-
261
- # 将实例方法转为 C 回调函数指针。
262
- # 必须将其保存为实例成员,否则会被垃圾回收!
263
- self._callback_func_ptr = PFTASKDIALOGCALLBACK(self._callback)
264
- self.config.pfCallback = self._callback_func_ptr
265
- # 将本实例的id作为lpCallbackData传递,以便在回调中识别
266
- self.config.lpCallbackData = id(self)
267
-
268
- # --- 图标设置 ---
269
- processed_icon = self._process_main_icon(main_icon)
270
- if processed_icon is not None:
271
- if isinstance(processed_icon, wintypes.LPWSTR):
272
- self.config.pszMainIcon = processed_icon
273
- else:
274
- self.config.dwFlags |= TDF_USE_HICON_MAIN
275
- self.config.hMainIcon = processed_icon
276
-
277
- # --- 自定义按钮设置 ---
278
- self.custom_buttons_list = []
279
- if custom_buttons:
280
- self.config.cButtons = len(custom_buttons)
281
- button_array_type = TASKDIALOG_BUTTON * len(custom_buttons)
282
- self.custom_buttons_list = button_array_type()
283
- for i, (btn_id, btn_text) in enumerate(custom_buttons):
284
- self.custom_buttons_list[i].nButtonID = btn_id
285
- self.custom_buttons_list[i].pszButtonText = btn_text
286
- self.config.pButtons = self.custom_buttons_list
287
-
288
- if default_button:
289
- self.config.nDefaultButton = default_button
290
-
291
- # --- 单选按钮设置 ---
292
- self.radio_buttons_list = []
293
- if radio_buttons:
294
- self.config.cRadioButtons = len(radio_buttons)
295
- radio_array_type = TASKDIALOG_BUTTON * len(radio_buttons)
296
- self.radio_buttons_list = radio_array_type()
297
- for i, (btn_id, btn_text) in enumerate(radio_buttons):
298
- self.radio_buttons_list[i].nButtonID = btn_id
299
- self.radio_buttons_list[i].pszButtonText = btn_text
300
- self.config.pRadioButtons = self.radio_buttons_list
301
-
302
- if default_radio_button:
303
- self.config.nDefaultRadioButton = default_radio_button
304
-
305
- # --- 验证复选框设置 ---
306
- if verification_text:
307
- self.config.pszVerificationText = verification_text
308
- if verification_checked_by_default:
309
- self.config.dwFlags |= TDF_VERIFICATION_FLAG_CHECKED
310
-
311
- def _process_common_buttons(self, common_buttons: int | List[CommonButtonLiteral]) -> int:
312
- """处理 common_buttons 参数,支持常量和字符串列表两种形式"""
313
- if isinstance(common_buttons, int):
314
- # 直接使用 Win32 常量
315
- return common_buttons
316
- elif isinstance(common_buttons, list):
317
- # 处理字符串列表
318
- result = 0
319
- for button in common_buttons:
320
- # 使用 match 和 assert_never 进行类型检查
321
- match button:
322
- case "ok":
323
- result |= TDCBF_OK_BUTTON
324
- case "yes":
325
- result |= TDCBF_YES_BUTTON
326
- case "no":
327
- result |= TDCBF_NO_BUTTON
328
- case "cancel":
329
- result |= TDCBF_CANCEL_BUTTON
330
- case "retry":
331
- result |= TDCBF_RETRY_BUTTON
332
- case "close":
333
- result |= TDCBF_CLOSE_BUTTON
334
- case _:
335
- # 这在实际中不会发生,因为类型检查会阻止它
336
- from typing import assert_never
337
- assert_never(button)
338
- return result
339
- else:
340
- raise TypeError("common_buttons must be either an int or a list of strings")
341
-
342
- def _process_main_icon(self, main_icon: Optional[wintypes.LPWSTR | int | IconLiteral]) -> Optional[wintypes.LPWSTR | int]:
343
- """处理 main_icon 参数,支持常量和字符串两种形式"""
344
- if main_icon is None:
345
- return None
346
- elif isinstance(main_icon, (wintypes.LPWSTR, int)):
347
- # 直接使用 Win32 常量或 HICON 句柄
348
- return main_icon
349
- elif isinstance(main_icon, str):
350
- # 处理字符串
351
- match main_icon:
352
- case "warning":
353
- return TD_WARNING_ICON
354
- case "error":
355
- return TD_ERROR_ICON
356
- case "information":
357
- return TD_INFORMATION_ICON
358
- case "shield":
359
- return TD_SHIELD_ICON
360
- case _:
361
- # 这在实际中不会发生,因为类型检查会阻止它
362
- from typing import assert_never
363
- assert_never(main_icon)
364
- else:
365
- raise TypeError("main_icon must be None, a Windows constant, or a string")
366
-
367
- def _callback(self, hwnd: wintypes.HWND, msg: int, wParam: int, lParam: int, lpRefData: int) -> int:
368
- # 仅当 lpRefData 指向的是当前这个对象实例时才处理
369
- if lpRefData != id(self):
370
- return 0 # S_OK
371
-
372
- if msg == TDN_TIMER:
373
- # 更新进度条
374
- if self.progress < 100:
375
- self.progress += 5
376
- # 发送消息给对话框来更新进度条位置
377
- user32.SendMessageW(hwnd, TDM_SET_PROGRESS_BAR_POS, self.progress, 0)
378
- else:
379
- # 示例:进度达到100%后,可以模拟点击OK按钮关闭对话框
380
- # from ctypes import wintypes
381
- # user32.PostMessageW(hwnd, wintypes.UINT(1125), IDOK, 0) # TDM_CLICK_BUTTON
382
- pass
383
-
384
- elif msg == TDN_DESTROYED:
385
- # 对话框已销毁
386
- pass
387
-
388
- return 0 # S_OK
389
-
390
- def show(self) -> Tuple[int, int, bool]:
391
- """
392
- 显示对话框并返回用户交互的结果。
393
-
394
- :return: 一个元组 (button_id, radio_button_id, verification_checked)
395
- - button_id: 用户点击的按钮ID (例如 IDOK, IDCANCEL)。
396
- - radio_button_id: 用户选择的单选按钮的ID。
397
- - verification_checked: 验证复选框是否被勾选 (True/False)。
398
- """
399
- pnButton = ctypes.c_int(0)
400
- pnRadioButton = ctypes.c_int(0)
401
- pfVerificationFlagChecked = wintypes.BOOL(False)
402
-
403
- hr = TaskDialogIndirect(
404
- ctypes.byref(self.config),
405
- ctypes.byref(pnButton),
406
- ctypes.byref(pnRadioButton),
407
- ctypes.byref(pfVerificationFlagChecked)
408
- )
409
-
410
- if hr == 0: # S_OK
411
- return pnButton.value, pnRadioButton.value, bool(pfVerificationFlagChecked.value)
412
- else:
413
- raise ctypes.WinError(hr)
414
-
415
-
416
- # --- 示例用法 ---
417
- if __name__ == '__main__':
418
-
419
- print("--- 示例 1: 简单信息框 ---")
420
- dlg_simple = TaskDialog(
421
- title="操作成功",
422
- main_instruction="您的操作已成功完成。",
423
- content="文件已保存到您的文档目录。",
424
- common_buttons=["ok"],
425
- main_icon="information"
426
- )
427
- result_simple, _, _ = dlg_simple.show()
428
- print(f"用户点击了按钮: {result_simple} (1=OK)\n")
429
-
430
- print("--- 示例 2: 确认框 ---")
431
- dlg_confirm = TaskDialog(
432
- title="确认删除",
433
- main_instruction="您确定要永久删除这个文件吗?",
434
- content="这个操作无法撤销。文件将被立即删除。",
435
- common_buttons=["yes", "no", "cancel"],
436
- main_icon="warning",
437
- default_button=IDNO
438
- )
439
- result_confirm, _, _ = dlg_confirm.show()
440
- if result_confirm == IDYES:
441
- print("用户选择了“是”。")
442
- elif result_confirm == IDNO:
443
- print("用户选择了“否”。")
444
- elif result_confirm == IDCANCEL:
445
- print("用户选择了“取消”。")
446
- print(f"返回的按钮ID: {result_confirm}\n")
447
-
448
- # 示例 3
449
- print("--- 示例 3: 自定义按钮 ---")
450
- CUSTOM_BUTTON_SAVE_ID = 101
451
- CUSTOM_BUTTON_DONT_SAVE_ID = 102
452
- my_buttons = [
453
- (CUSTOM_BUTTON_SAVE_ID, "保存并退出"),
454
- (CUSTOM_BUTTON_DONT_SAVE_ID, "不保存直接退出")
455
- ]
456
- dlg_custom = TaskDialog(
457
- title="未保存的更改",
458
- main_instruction="文档中有未保存的更改,您想如何处理?",
459
- custom_buttons=my_buttons,
460
- common_buttons=["cancel"],
461
- main_icon="warning",
462
- footer="这是一个重要的提醒!"
463
- )
464
- result_custom, _, _ = dlg_custom.show()
465
- if result_custom == CUSTOM_BUTTON_SAVE_ID:
466
- print("用户选择了“保存并退出”。")
467
- elif result_custom == CUSTOM_BUTTON_DONT_SAVE_ID:
468
- print("用户选择了“不保存直接退出”。")
469
- elif result_custom == IDCANCEL:
470
- print("用户选择了“取消”。")
471
- print(f"返回的按钮ID: {result_custom}\n")
472
-
473
- # 示例 4: 带单选按钮和验证框的对话框
474
- print("--- 示例 4: 单选按钮和验证框 ---")
475
- RADIO_BTN_WORD_ID = 201
476
- RADIO_BTN_EXCEL_ID = 202
477
- RADIO_BTN_PDF_ID = 203
478
-
479
- radio_buttons = [
480
- (RADIO_BTN_WORD_ID, "保存为 Word 文档 (.docx)"),
481
- (RADIO_BTN_EXCEL_ID, "保存为 Excel 表格 (.xlsx)"),
482
- (RADIO_BTN_PDF_ID, "导出为 PDF 文档 (.pdf)")
483
- ]
484
-
485
- dlg_radio = TaskDialog(
486
- title="选择导出格式",
487
- main_instruction="请选择您想要导出的文件格式。",
488
- content="选择一个格式后,点击“确定”继续。",
489
- common_buttons=["ok", "cancel"],
490
- main_icon="information",
491
- radio_buttons=radio_buttons,
492
- default_radio_button=RADIO_BTN_PDF_ID, # 默认选中PDF
493
- verification_text="设为我的默认导出选项",
494
- verification_checked_by_default=True
495
- )
496
- btn_id, radio_id, checked = dlg_radio.show()
497
-
498
- if btn_id == IDOK:
499
- print(f"用户点击了“确定”。")
500
- if radio_id == RADIO_BTN_WORD_ID:
501
- print("选择了导出为 Word。")
502
- elif radio_id == RADIO_BTN_EXCEL_ID:
503
- print("选择了导出为 Excel。")
504
- elif radio_id == RADIO_BTN_PDF_ID:
505
- print("选择了导出为 PDF。")
506
-
507
- if checked:
508
- print("用户勾选了“设为我的默认导出选项”。")
509
- else:
510
- print("用户未勾选“设为我的默认导出选项”。")
511
- else:
512
- print("用户点击了“取消”。")
513
- print(f"返回的按钮ID: {btn_id}, 单选按钮ID: {radio_id}, 验证框状态: {checked}\n")
1
+ """
2
+ Windows Task Dialog interop module.
3
+
4
+ This module provides Windows TaskDialog functionality and is only available on Windows systems.
5
+ """
6
+
7
+ import platform
8
+ import warnings
9
+
10
+ from kotonebot.util import is_windows
11
+
12
+ # 检查是否在 Windows 平台上
13
+ if not is_windows():
14
+ _WINDOWS_ONLY_MSG = (
15
+ f"TaskDialog is only available on Windows systems. "
16
+ f"Current system: non-Windows\n"
17
+ "To use Windows TaskDialog features, please run this code on a Windows system."
18
+ )
19
+
20
+
21
+ # 提供虚拟类以避免导入错误
22
+ class TaskDialog:
23
+ def __init__(self, *args, **kwargs):
24
+ raise ImportError(_WINDOWS_ONLY_MSG)
25
+
26
+ # 导出所有常量作为 None
27
+ __all__ = [
28
+ "TaskDialog",
29
+ "TDCBF_OK_BUTTON", "TDCBF_YES_BUTTON", "TDCBF_NO_BUTTON", "TDCBF_CANCEL_BUTTON",
30
+ "TDCBF_RETRY_BUTTON", "TDCBF_CLOSE_BUTTON",
31
+ "IDOK", "IDCANCEL", "IDABORT", "IDRETRY", "IDIGNORE", "IDYES", "IDNO", "IDCLOSE",
32
+ "TD_WARNING_ICON", "TD_ERROR_ICON", "TD_INFORMATION_ICON", "TD_SHIELD_ICON"
33
+ ]
34
+
35
+ # 设置所有常量为 None 或保留为模块级变量
36
+ TDCBF_OK_BUTTON = TDCBF_YES_BUTTON = TDCBF_NO_BUTTON = TDCBF_CANCEL_BUTTON = None
37
+ TDCBF_RETRY_BUTTON = TDCBF_CLOSE_BUTTON = None
38
+ IDOK = IDCANCEL = IDABORT = IDRETRY = IDIGNORE = IDYES = IDNO = IDCLOSE = None
39
+ TD_WARNING_ICON = TD_ERROR_ICON = TD_INFORMATION_ICON = TD_SHIELD_ICON = None
40
+
41
+ # 阻止模块加载
42
+ raise ImportError(_WINDOWS_ONLY_MSG)
43
+
44
+ # 如果是 Windows,继续正常加载
45
+ import ctypes
46
+ from ctypes import wintypes
47
+ import time
48
+ from typing import List, Tuple, Optional
49
+ from typing import Literal
50
+
51
+ __all__ = [
52
+ "TaskDialog",
53
+ "TDCBF_OK_BUTTON", "TDCBF_YES_BUTTON", "TDCBF_NO_BUTTON", "TDCBF_CANCEL_BUTTON",
54
+ "TDCBF_RETRY_BUTTON", "TDCBF_CLOSE_BUTTON",
55
+ "IDOK", "IDCANCEL", "IDABORT", "IDRETRY", "IDIGNORE", "IDYES", "IDNO", "IDCLOSE",
56
+ "TD_WARNING_ICON", "TD_ERROR_ICON", "TD_INFORMATION_ICON", "TD_SHIELD_ICON"
57
+ ]
58
+
59
+ # --- Windows API 常量定义 ---
60
+
61
+ # 常用按钮
62
+ TDCBF_OK_BUTTON = 0x0001
63
+ TDCBF_YES_BUTTON = 0x0002
64
+ TDCBF_NO_BUTTON = 0x0004
65
+ TDCBF_CANCEL_BUTTON = 0x0008
66
+ TDCBF_RETRY_BUTTON = 0x0010
67
+ TDCBF_CLOSE_BUTTON = 0x0020
68
+
69
+ # 对话框返回值
70
+ IDOK = 1
71
+ IDCANCEL = 2
72
+ IDABORT = 3
73
+ IDRETRY = 4
74
+ IDIGNORE = 5
75
+ IDYES = 6
76
+ IDNO = 7
77
+ IDCLOSE = 8
78
+
79
+
80
+ # 标准图标 (使用 MAKEINTRESOURCE 宏)
81
+ def MAKEINTRESOURCE(i: int) -> wintypes.LPWSTR:
82
+ return wintypes.LPWSTR(i)
83
+
84
+
85
+ TD_WARNING_ICON = MAKEINTRESOURCE(65535)
86
+ TD_ERROR_ICON = MAKEINTRESOURCE(65534)
87
+ TD_INFORMATION_ICON = MAKEINTRESOURCE(65533)
88
+ TD_SHIELD_ICON = MAKEINTRESOURCE(65532)
89
+
90
+ # Task Dialog 标志
91
+ TDF_ENABLE_HYPERLINKS = 0x0001
92
+ TDF_USE_HICON_MAIN = 0x0002
93
+ TDF_USE_HICON_FOOTER = 0x0004
94
+ TDF_ALLOW_DIALOG_CANCELLATION = 0x0008
95
+ TDF_USE_COMMAND_LINKS = 0x0010
96
+ TDF_USE_COMMAND_LINKS_NO_ICON = 0x0020
97
+ TDF_EXPAND_FOOTER_AREA = 0x0040
98
+ TDF_EXPANDED_BY_DEFAULT = 0x0080
99
+ TDF_VERIFICATION_FLAG_CHECKED = 0x0100
100
+ TDF_SHOW_PROGRESS_BAR = 0x0200
101
+ TDF_SHOW_MARQUEE_PROGRESS_BAR = 0x0400
102
+ TDF_CALLBACK_TIMER = 0x0800
103
+ TDF_POSITION_RELATIVE_TO_WINDOW = 0x1000
104
+ TDF_RTL_LAYOUT = 0x2000
105
+ TDF_NO_DEFAULT_RADIO_BUTTON = 0x4000
106
+ TDF_CAN_BE_MINIMIZED = 0x8000
107
+
108
+ # Task Dialog 通知
109
+ TDN_CREATED = 0
110
+ TDN_NAVIGATED = 1
111
+ TDN_BUTTON_CLICKED = 2
112
+ TDN_HYPERLINK_CLICKED = 3
113
+ TDN_TIMER = 4
114
+ TDN_DESTROYED = 5
115
+ TDN_RADIO_BUTTON_CLICKED = 6
116
+ TDN_DIALOG_CONSTRUCTED = 7
117
+ TDN_VERIFICATION_CLICKED = 8
118
+ TDN_HELP = 9
119
+ TDN_EXPANDO_BUTTON_CLICKED = 10
120
+
121
+ # Windows 消息
122
+ WM_USER = 0x0400
123
+ TDM_SET_PROGRESS_BAR_POS = WM_USER + 114
124
+
125
+ CommonButtonLiteral = Literal["ok", "yes", "no", "cancel", "retry", "close"]
126
+ IconLiteral = Literal["warning", "error", "information", "shield"]
127
+
128
+
129
+ # --- C 结构体定义 (使用 ctypes) ---
130
+
131
+ class TASKDIALOG_BUTTON(ctypes.Structure):
132
+ _pack_ = 1
133
+ _fields_ = [("nButtonID", ctypes.c_int),
134
+ ("pszButtonText", wintypes.LPCWSTR)]
135
+
136
+
137
+ # 定义回调函数指针原型
138
+ PFTASKDIALOGCALLBACK = ctypes.WINFUNCTYPE(
139
+ ctypes.HRESULT, # 返回值
140
+ wintypes.HWND, # hwnd
141
+ ctypes.c_uint, # msg
142
+ ctypes.c_size_t, # wParam
143
+ ctypes.c_size_t, # lParam
144
+ ctypes.c_ssize_t # lpRefData
145
+ )
146
+
147
+
148
+ class TASKDIALOGCONFIG(ctypes.Structure):
149
+ _pack_ = 1
150
+ _fields_ = [
151
+ ("cbSize", ctypes.c_uint),
152
+ ("hwndParent", wintypes.HWND),
153
+ ("hInstance", wintypes.HINSTANCE),
154
+ ("dwFlags", ctypes.c_uint),
155
+ ("dwCommonButtons", ctypes.c_uint),
156
+ ("pszWindowTitle", wintypes.LPCWSTR),
157
+ ("pszMainIcon", wintypes.LPCWSTR),
158
+ ("pszMainInstruction", wintypes.LPCWSTR),
159
+ ("pszContent", wintypes.LPCWSTR),
160
+ ("cButtons", ctypes.c_uint),
161
+ ("pButtons", ctypes.POINTER(TASKDIALOG_BUTTON)),
162
+ ("nDefaultButton", ctypes.c_int),
163
+ ("cRadioButtons", ctypes.c_uint),
164
+ ("pRadioButtons", ctypes.POINTER(TASKDIALOG_BUTTON)),
165
+ ("nDefaultRadioButton", ctypes.c_int),
166
+ ("pszVerificationText", wintypes.LPCWSTR),
167
+ ("pszExpandedInformation", wintypes.LPCWSTR),
168
+ ("pszExpandedControlText", wintypes.LPCWSTR),
169
+ ("pszCollapsedControlText", wintypes.LPCWSTR),
170
+ ("pszFooterIcon", wintypes.LPCWSTR),
171
+ ("pszFooter", wintypes.LPCWSTR),
172
+ ("pfCallback", PFTASKDIALOGCALLBACK), # 使用定义好的原型
173
+ ("lpCallbackData", ctypes.c_ssize_t),
174
+ ("cxWidth", ctypes.c_uint)
175
+ ]
176
+
177
+
178
+ # --- 加载 comctl32.dll 并定义函数原型 ---
179
+
180
+ comctl32 = ctypes.WinDLL('comctl32')
181
+ user32 = ctypes.WinDLL('user32')
182
+
183
+ TaskDialogIndirect = comctl32.TaskDialogIndirect
184
+ TaskDialogIndirect.restype = ctypes.HRESULT
185
+ TaskDialogIndirect.argtypes = [
186
+ ctypes.POINTER(TASKDIALOGCONFIG),
187
+ ctypes.POINTER(ctypes.c_int),
188
+ ctypes.POINTER(ctypes.c_int),
189
+ ctypes.POINTER(wintypes.BOOL)
190
+ ]
191
+
192
+
193
+ # --- Python 封装类 ---
194
+
195
+ class TaskDialog:
196
+ """
197
+ 一个用于显示 Windows TaskDialog 的 Python 封装类。
198
+ 支持自定义按钮、单选按钮、进度条、验证框等。
199
+ """
200
+
201
+ def __init__(self,
202
+ parent_hwnd: Optional[int] = None,
203
+ title: str = "Task Dialog",
204
+ main_instruction: str = "",
205
+ content: str = "",
206
+ common_buttons: int | List[CommonButtonLiteral] = TDCBF_OK_BUTTON,
207
+ main_icon: Optional[wintypes.LPWSTR | int | IconLiteral] = None,
208
+ footer: str = "",
209
+ custom_buttons: Optional[List[Tuple[int, str]]] = None,
210
+ default_button: int = 0,
211
+ radio_buttons: Optional[List[Tuple[int, str]]] = None,
212
+ default_radio_button: int = 0,
213
+ verification_text: Optional[str] = None,
214
+ verification_checked_by_default: bool = False,
215
+ show_progress_bar: bool = False,
216
+ show_marquee_progress_bar: bool = False
217
+ ):
218
+ """初始化 TaskDialog 实例。
219
+
220
+ :param parent_hwnd: 父窗口的句柄。
221
+ :param title: 对话框窗口的标题。
222
+ :param main_instruction: 对话框的主要指令文本。
223
+ :param content: 对话框的详细内容文本。
224
+ :param common_buttons: 要显示的通用按钮。可以是以下两种形式之一:
225
+ 1. TDCBF_* 常量的按位或组合 (例如 TDCBF_OK_BUTTON | TDCBF_CANCEL_BUTTON)
226
+ 2. 字符串列表,支持 "ok", "yes", "no", "cancel", "retry", "close"
227
+ :param main_icon: 主图标。可以是以下几种形式之一:
228
+ 1. TD_*_ICON 常量之一
229
+ 2. HICON 句柄
230
+ 3. 字符串:"warning", "error", "information", "shield"
231
+ :param footer: 页脚区域显示的文本。
232
+ :param custom_buttons: 自定义按钮列表。每个元组包含 (按钮ID, 按钮文本)。
233
+ :param default_button: 默认按钮的ID。可以是通用按钮ID (例如 IDOK) 或自定义按钮ID。
234
+ :param radio_buttons: 单选按钮列表。每个元组包含 (按钮ID, 按钮文本)。
235
+ :param default_radio_button: 默认选中的单选按钮的ID。
236
+ :param verification_text: 验证复选框的文本。如果为 None,则不显示复选框。
237
+ :param verification_checked_by_default: 验证复选框是否默认勾选。
238
+ :param show_progress_bar: 是否显示标准进度条。
239
+ :param show_marquee_progress_bar: 是否显示跑马灯式进度条。
240
+ """
241
+ self.config = TASKDIALOGCONFIG()
242
+ self.config.cbSize = ctypes.sizeof(TASKDIALOGCONFIG)
243
+ self.config.hwndParent = parent_hwnd
244
+ self.config.dwFlags = TDF_ALLOW_DIALOG_CANCELLATION | TDF_POSITION_RELATIVE_TO_WINDOW
245
+ self.config.dwCommonButtons = self._process_common_buttons(common_buttons)
246
+ self.config.pszWindowTitle = title
247
+ self.config.pszMainInstruction = main_instruction
248
+ self.config.pszContent = content
249
+ self.config.pszFooter = footer
250
+
251
+ self.progress: int = 0
252
+ if show_progress_bar or show_marquee_progress_bar:
253
+ # 进度条暂时还没实现
254
+ raise NotImplementedError("Progress bar is not implemented yet.")
255
+ self.config.dwFlags |= TDF_CALLBACK_TIMER
256
+ if show_progress_bar:
257
+ self.config.dwFlags |= TDF_SHOW_PROGRESS_BAR
258
+ else:
259
+ self.config.dwFlags |= TDF_SHOW_MARQUEE_PROGRESS_BAR
260
+
261
+ # 将实例方法转为 C 回调函数指针。
262
+ # 必须将其保存为实例成员,否则会被垃圾回收!
263
+ self._callback_func_ptr = PFTASKDIALOGCALLBACK(self._callback)
264
+ self.config.pfCallback = self._callback_func_ptr
265
+ # 将本实例的id作为lpCallbackData传递,以便在回调中识别
266
+ self.config.lpCallbackData = id(self)
267
+
268
+ # --- 图标设置 ---
269
+ processed_icon = self._process_main_icon(main_icon)
270
+ if processed_icon is not None:
271
+ if isinstance(processed_icon, wintypes.LPWSTR):
272
+ self.config.pszMainIcon = processed_icon
273
+ else:
274
+ self.config.dwFlags |= TDF_USE_HICON_MAIN
275
+ self.config.hMainIcon = processed_icon
276
+
277
+ # --- 自定义按钮设置 ---
278
+ self.custom_buttons_list = []
279
+ if custom_buttons:
280
+ self.config.cButtons = len(custom_buttons)
281
+ button_array_type = TASKDIALOG_BUTTON * len(custom_buttons)
282
+ self.custom_buttons_list = button_array_type()
283
+ for i, (btn_id, btn_text) in enumerate(custom_buttons):
284
+ self.custom_buttons_list[i].nButtonID = btn_id
285
+ self.custom_buttons_list[i].pszButtonText = btn_text
286
+ self.config.pButtons = self.custom_buttons_list
287
+
288
+ if default_button:
289
+ self.config.nDefaultButton = default_button
290
+
291
+ # --- 单选按钮设置 ---
292
+ self.radio_buttons_list = []
293
+ if radio_buttons:
294
+ self.config.cRadioButtons = len(radio_buttons)
295
+ radio_array_type = TASKDIALOG_BUTTON * len(radio_buttons)
296
+ self.radio_buttons_list = radio_array_type()
297
+ for i, (btn_id, btn_text) in enumerate(radio_buttons):
298
+ self.radio_buttons_list[i].nButtonID = btn_id
299
+ self.radio_buttons_list[i].pszButtonText = btn_text
300
+ self.config.pRadioButtons = self.radio_buttons_list
301
+
302
+ if default_radio_button:
303
+ self.config.nDefaultRadioButton = default_radio_button
304
+
305
+ # --- 验证复选框设置 ---
306
+ if verification_text:
307
+ self.config.pszVerificationText = verification_text
308
+ if verification_checked_by_default:
309
+ self.config.dwFlags |= TDF_VERIFICATION_FLAG_CHECKED
310
+
311
+ def _process_common_buttons(self, common_buttons: int | List[CommonButtonLiteral]) -> int:
312
+ """处理 common_buttons 参数,支持常量和字符串列表两种形式"""
313
+ if isinstance(common_buttons, int):
314
+ # 直接使用 Win32 常量
315
+ return common_buttons
316
+ elif isinstance(common_buttons, list):
317
+ # 处理字符串列表
318
+ result = 0
319
+ for button in common_buttons:
320
+ # 使用 match 和 assert_never 进行类型检查
321
+ match button:
322
+ case "ok":
323
+ result |= TDCBF_OK_BUTTON
324
+ case "yes":
325
+ result |= TDCBF_YES_BUTTON
326
+ case "no":
327
+ result |= TDCBF_NO_BUTTON
328
+ case "cancel":
329
+ result |= TDCBF_CANCEL_BUTTON
330
+ case "retry":
331
+ result |= TDCBF_RETRY_BUTTON
332
+ case "close":
333
+ result |= TDCBF_CLOSE_BUTTON
334
+ case _:
335
+ # 这在实际中不会发生,因为类型检查会阻止它
336
+ from typing import assert_never
337
+ assert_never(button)
338
+ return result
339
+ else:
340
+ raise TypeError("common_buttons must be either an int or a list of strings")
341
+
342
+ def _process_main_icon(self, main_icon: Optional[wintypes.LPWSTR | int | IconLiteral]) -> Optional[wintypes.LPWSTR | int]:
343
+ """处理 main_icon 参数,支持常量和字符串两种形式"""
344
+ if main_icon is None:
345
+ return None
346
+ elif isinstance(main_icon, (wintypes.LPWSTR, int)):
347
+ # 直接使用 Win32 常量或 HICON 句柄
348
+ return main_icon
349
+ elif isinstance(main_icon, str):
350
+ # 处理字符串
351
+ match main_icon:
352
+ case "warning":
353
+ return TD_WARNING_ICON
354
+ case "error":
355
+ return TD_ERROR_ICON
356
+ case "information":
357
+ return TD_INFORMATION_ICON
358
+ case "shield":
359
+ return TD_SHIELD_ICON
360
+ case _:
361
+ # 这在实际中不会发生,因为类型检查会阻止它
362
+ from typing import assert_never
363
+ assert_never(main_icon)
364
+ else:
365
+ raise TypeError("main_icon must be None, a Windows constant, or a string")
366
+
367
+ def _callback(self, hwnd: wintypes.HWND, msg: int, wParam: int, lParam: int, lpRefData: int) -> int:
368
+ # 仅当 lpRefData 指向的是当前这个对象实例时才处理
369
+ if lpRefData != id(self):
370
+ return 0 # S_OK
371
+
372
+ if msg == TDN_TIMER:
373
+ # 更新进度条
374
+ if self.progress < 100:
375
+ self.progress += 5
376
+ # 发送消息给对话框来更新进度条位置
377
+ user32.SendMessageW(hwnd, TDM_SET_PROGRESS_BAR_POS, self.progress, 0)
378
+ else:
379
+ # 示例:进度达到100%后,可以模拟点击OK按钮关闭对话框
380
+ # from ctypes import wintypes
381
+ # user32.PostMessageW(hwnd, wintypes.UINT(1125), IDOK, 0) # TDM_CLICK_BUTTON
382
+ pass
383
+
384
+ elif msg == TDN_DESTROYED:
385
+ # 对话框已销毁
386
+ pass
387
+
388
+ return 0 # S_OK
389
+
390
+ def show(self) -> Tuple[int, int, bool]:
391
+ """
392
+ 显示对话框并返回用户交互的结果。
393
+
394
+ :return: 一个元组 (button_id, radio_button_id, verification_checked)
395
+ - button_id: 用户点击的按钮ID (例如 IDOK, IDCANCEL)。
396
+ - radio_button_id: 用户选择的单选按钮的ID。
397
+ - verification_checked: 验证复选框是否被勾选 (True/False)。
398
+ """
399
+ pnButton = ctypes.c_int(0)
400
+ pnRadioButton = ctypes.c_int(0)
401
+ pfVerificationFlagChecked = wintypes.BOOL(False)
402
+
403
+ hr = TaskDialogIndirect(
404
+ ctypes.byref(self.config),
405
+ ctypes.byref(pnButton),
406
+ ctypes.byref(pnRadioButton),
407
+ ctypes.byref(pfVerificationFlagChecked)
408
+ )
409
+
410
+ if hr == 0: # S_OK
411
+ return pnButton.value, pnRadioButton.value, bool(pfVerificationFlagChecked.value)
412
+ else:
413
+ raise ctypes.WinError(hr)
414
+
415
+
416
+ # --- 示例用法 ---
417
+ if __name__ == '__main__':
418
+
419
+ print("--- 示例 1: 简单信息框 ---")
420
+ dlg_simple = TaskDialog(
421
+ title="操作成功",
422
+ main_instruction="您的操作已成功完成。",
423
+ content="文件已保存到您的文档目录。",
424
+ common_buttons=["ok"],
425
+ main_icon="information"
426
+ )
427
+ result_simple, _, _ = dlg_simple.show()
428
+ print(f"用户点击了按钮: {result_simple} (1=OK)\n")
429
+
430
+ print("--- 示例 2: 确认框 ---")
431
+ dlg_confirm = TaskDialog(
432
+ title="确认删除",
433
+ main_instruction="您确定要永久删除这个文件吗?",
434
+ content="这个操作无法撤销。文件将被立即删除。",
435
+ common_buttons=["yes", "no", "cancel"],
436
+ main_icon="warning",
437
+ default_button=IDNO
438
+ )
439
+ result_confirm, _, _ = dlg_confirm.show()
440
+ if result_confirm == IDYES:
441
+ print("用户选择了“是”。")
442
+ elif result_confirm == IDNO:
443
+ print("用户选择了“否”。")
444
+ elif result_confirm == IDCANCEL:
445
+ print("用户选择了“取消”。")
446
+ print(f"返回的按钮ID: {result_confirm}\n")
447
+
448
+ # 示例 3
449
+ print("--- 示例 3: 自定义按钮 ---")
450
+ CUSTOM_BUTTON_SAVE_ID = 101
451
+ CUSTOM_BUTTON_DONT_SAVE_ID = 102
452
+ my_buttons = [
453
+ (CUSTOM_BUTTON_SAVE_ID, "保存并退出"),
454
+ (CUSTOM_BUTTON_DONT_SAVE_ID, "不保存直接退出")
455
+ ]
456
+ dlg_custom = TaskDialog(
457
+ title="未保存的更改",
458
+ main_instruction="文档中有未保存的更改,您想如何处理?",
459
+ custom_buttons=my_buttons,
460
+ common_buttons=["cancel"],
461
+ main_icon="warning",
462
+ footer="这是一个重要的提醒!"
463
+ )
464
+ result_custom, _, _ = dlg_custom.show()
465
+ if result_custom == CUSTOM_BUTTON_SAVE_ID:
466
+ print("用户选择了“保存并退出”。")
467
+ elif result_custom == CUSTOM_BUTTON_DONT_SAVE_ID:
468
+ print("用户选择了“不保存直接退出”。")
469
+ elif result_custom == IDCANCEL:
470
+ print("用户选择了“取消”。")
471
+ print(f"返回的按钮ID: {result_custom}\n")
472
+
473
+ # 示例 4: 带单选按钮和验证框的对话框
474
+ print("--- 示例 4: 单选按钮和验证框 ---")
475
+ RADIO_BTN_WORD_ID = 201
476
+ RADIO_BTN_EXCEL_ID = 202
477
+ RADIO_BTN_PDF_ID = 203
478
+
479
+ radio_buttons = [
480
+ (RADIO_BTN_WORD_ID, "保存为 Word 文档 (.docx)"),
481
+ (RADIO_BTN_EXCEL_ID, "保存为 Excel 表格 (.xlsx)"),
482
+ (RADIO_BTN_PDF_ID, "导出为 PDF 文档 (.pdf)")
483
+ ]
484
+
485
+ dlg_radio = TaskDialog(
486
+ title="选择导出格式",
487
+ main_instruction="请选择您想要导出的文件格式。",
488
+ content="选择一个格式后,点击“确定”继续。",
489
+ common_buttons=["ok", "cancel"],
490
+ main_icon="information",
491
+ radio_buttons=radio_buttons,
492
+ default_radio_button=RADIO_BTN_PDF_ID, # 默认选中PDF
493
+ verification_text="设为我的默认导出选项",
494
+ verification_checked_by_default=True
495
+ )
496
+ btn_id, radio_id, checked = dlg_radio.show()
497
+
498
+ if btn_id == IDOK:
499
+ print(f"用户点击了“确定”。")
500
+ if radio_id == RADIO_BTN_WORD_ID:
501
+ print("选择了导出为 Word。")
502
+ elif radio_id == RADIO_BTN_EXCEL_ID:
503
+ print("选择了导出为 Excel。")
504
+ elif radio_id == RADIO_BTN_PDF_ID:
505
+ print("选择了导出为 PDF。")
506
+
507
+ if checked:
508
+ print("用户勾选了“设为我的默认导出选项”。")
509
+ else:
510
+ print("用户未勾选“设为我的默认导出选项”。")
511
+ else:
512
+ print("用户点击了“取消”。")
513
+ print(f"返回的按钮ID: {btn_id}, 单选按钮ID: {radio_id}, 验证框状态: {checked}\n")