kotones-auto-assistant/kotonebot/interop/win/message_box.py

314 lines
8.1 KiB
Python

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'
)