🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
[TOC] # 如何 ## 定义 一些重要的定义可能对初学者有帮助。 > * **Dialog** 是一个窗口,包含其他几个GUI元素/控件,如按钮,编辑框等。对话框不一定是主窗口。 主窗体顶部的消息框也是一个对话框。 主窗口也被pywinauto视为对话框。 > * 控件是层次结构的任何级别的GUI元素。 该定义包括窗口,按钮,编辑框,表格,表格单元格,工具栏等。 > * Win32 API技术(pywinauto中的“win32”后端)为每个控件提供标识符。 这是一个名为**handle**的唯一整数. > * UI Automation API(pywinauto中的“uia”后端)可能不为每个GUI元素提供窗口**handle**。 “win32”后端看不到这样的元素。 但是`Inspect.exe`可以显示属性`NativeWindowHandle`,如果它可用的话。 ## 如何指定可用的Application实例 `Application()`实例是与您自动化的应用程序的所有工作的联系点。 因此,Application实例需要连接到进程。 有两种方法可以做到这一点: > ~~~ > start(self, cmd_line, timeout=app_start_timeout) # 实例方法: > ~~~ 或: > ~~~ > connect(self, **kwargs) # instance method: > ~~~ `start()` 在应用程序未运行且您需要启动它时使用。 以下列方式使用它: > ~~~ > app = Application().start(r"c:\path\to\your\application -a -n -y --arguments") > ~~~ timeout参数是可选的,只有在应用程序需要很长时间才能启动时才需要使用它。 `connect()` 在要启动自动化应用程序时使用。 要指定已在运行的应用程序,您需要指定以下之一: process:应用程序的进程ID,例如app = Application().connect(process=2341) handle:应用程序窗口的窗口句柄,例如,app = Application().connect(handle=0x010f0c) path:进程的可执行文件的路径(GetModuleFileNameEx用于查找每个进程的路径并与传入的值进行比较),例如:app = Application().connect(path=r"c:\windows\system32\notepad.exe") 或者指定窗口的参数的任意组合,这些都被传递给[`pywinauto.findwindows.find_elements()`](code/pywinauto.findwindows.html#pywinauto.findwindows.find_elements "pywinauto.findwindows.find_elements") 函数。 例如 > ~~~ > app = Application().connect(title_re=".*Notepad", class_name="Notepad") > ~~~ **注意**: 在使用connect*()之前,应用程序必须准备好。 在start()之后找不到应用程序时没有超时或重试。 因此,如果您在pywinauto之外启动应用程序,则需要睡眠或编程等待循环以等待应用程序完全启动。 ## 如何指定应用程序的对话框 一旦应用程序实例知道它连接到哪个应用程序,就需要指定要处理的对话框。 有很多不同的方法可以做到这一点。 最常见的是使用项目或属性访问权来根据其标题选择对话框。 例如 > ~~~ > dlg = app.Notepad > ~~~ 或者等价于 > ~~~ > dlg = app['Notepad'] > ~~~ 下一个最简单的方法是询问`top_window()`,例如 > ~~~ > dlg = app.top_window() > ~~~ 这将返回具有应用程序顶级窗口的最高Z顺序的窗口。 **注意**: 这是目前相当未经测试的,所以我不确定它会返回正确的窗口。 它绝对是应用程序的顶级窗口 - 它可能不是Z-Order中最高的窗口。 如果这不是足够的控制,那么你可以使用与传递给`findwindows.find_windows()`相同的参数,例如 > ~~~ > dlg = app.window(title_re="Page Setup", class_name="#32770") > ~~~ 最后,你可以使用最多的控制权 > ~~~ > dialogs = app.windows() > ~~~ 这将返回应用程序的所有可见,启用的顶级窗口的列表。 然后,您可以使用`handleprops`模块中的一些方法选择所需的对话框。 一旦掌握了所需的句柄,就可以使用 > ~~~ > app.window(handle=win) > ~~~ **注意**: 如果对话框的标题很长 - 那么输入的属性访问可能会很长,在这种情况下通常更容易使用 > ~~~ > app.window(title_re=".*部分标题.*") > ~~~ ## 如何在对话框上指定控件 有许多方法可以指定控件,最简单的方法是 > ~~~ > app.dlg.control > app['dlg']['control'] > ~~~ 第二个更适合非英语操作系统,你需要传递unicode字符串,例如 `app [u'对话框标题'] [u'控件标题']` 该代码根据以下内容为每个控件构建多个标识符: > * title > * friendly class > * title + friendly class 如果控件的标题文本为空(删除非char字符后),则不使用此文本。 相反,我们寻找控件上方和右侧最接近的标题文本。 并追加友好类。 所以列表变成了 > * friendly class > * closest text + friendly class 一旦为对话框中的所有控件创建了一组标识符,我们就消除它们的歧义。 使用 WindowSpecification.print_control_identifiers() 方法 例如 ~~~ app.YourDialog.print_control_identifiers() ~~~ 示例输出 ~~~ Button - Paper (L1075, T394, R1411, B485) 'PaperGroupBox' 'Paper' 'GroupBox' Static - Si&ze: (L1087, T420, R1141, B433) 'SizeStatic' 'Static' 'Size' ComboBox - (L1159, T418, R1399, B439) 'ComboBox' 'SizeComboBox' Static - &Source: (L1087, T454, R1141, B467) 'Source' 'Static' 'SourceStatic' ComboBox - (L1159, T449, R1399, B470) 'ComboBox' 'SourceComboBox' Button - Orientation (L1075, T493, R1171, B584) 'GroupBox' 'Orientation' 'OrientationGroupBox' Button - P&ortrait (L1087, T514, R1165, B534) 'Portrait' 'RadioButton' 'PortraitRadioButton' Button - L&andscape (L1087, T548, R1165, B568) 'RadioButton' 'LandscapeRadioButton' 'Landscape' Button - Margins (inches) (L1183, T493, R1411, B584) 'Marginsinches' 'MarginsinchesGroupBox' 'GroupBox' Static - &Left: (L1195, T519, R1243, B532) 'LeftStatic' 'Static' 'Left' Edit - (L1243, T514, R1285, B534) 'Edit' 'LeftEdit' Static - &Right: (L1309, T519, R1357, B532) 'Right' 'Static' 'RightStatic' Edit - (L1357, T514, R1399, B534) 'Edit' 'RightEdit' Static - &Top: (L1195, T550, R1243, B563) 'Top' 'Static' 'TopStatic' Edit - (L1243, T548, R1285, B568) 'Edit' 'TopEdit' Static - &Bottom: (L1309, T550, R1357, B563) 'BottomStatic' 'Static' 'Bottom' Edit - (L1357, T548, R1399, B568) 'Edit' 'BottomEdit' Static - &Header: (L1075, T600, R1119, B613) 'Header' 'Static' 'HeaderStatic' Edit - (L1147, T599, R1408, B619) 'Edit' 'TopEdit' Static - &Footer: (L1075, T631, R1119, B644) 'FooterStatic' 'Static' 'Footer' Edit - (L1147, T630, R1408, B650) 'Edit' 'FooterEdit' Button - OK (L1348, T664, R1423, B687) 'Button' 'OK' 'OKButton' Button - Cancel (L1429, T664, R1504, B687) 'Cancel' 'Button' 'CancelButton' Button - &Printer... (L1510, T664, R1585, B687) 'Button' 'Printer' 'PrinterButton' Button - Preview (L1423, T394, R1585, B651) 'Preview' 'GroupBox' 'PreviewGroupBox' Static - (L1458, T456, R1549, B586) 'PreviewStatic' 'Static' Static - (L1549, T464, R1557, B594) 'PreviewStatic' 'Static' Static - (L1466, T586, R1557, B594) 'Static' 'BottomStatic' ~~~ 此示例取自test_application.py >[info]**注意** 通过此方法打印的标识符已经过使标识符唯一的过程。 因此,如果您有两个编辑框,则它们的标识符中都会列出“Edit”。 实际上,虽然第一个可以被称为“Edit”, “Edit0”, “Edit1”和第二个应该被称为“Edit2” >[info]**注意** 你不必确切! 假设我们从上面的例子中获取一个实例 > ~~~ > Button - Margins (inches) (L1183, T493, R1411, B584) > 'Marginsinches' 'MarginsinchesGroupBox' 'GroupBox' > ~~~ 让我们说你不喜欢这些 > * `GroupBox` - 太通用了,它可以是任何组合框 > * `Marginsinches` 和 `MarginsinchesGroupBox` - 这些只是看起来不对,省略‘inches’部分会更好 好吧,你可以! 代码与您使用的标识符最佳匹配,对照对话框中的所有可用标识符。 例如,如果您进入调试器,您可以看到如何使用不同的标识符 > ~~~ > (Pdb) print app.PageSetup.Margins.window_text() > Margins (inches) > (Pdb) print app.PageSetup.MarginsGroupBox.window_text() > Margins (inches) > ~~~ 这也将迎合拼写错误。 虽然你仍然需要小心,好像在对话框中有两个相似的标识符,你使用的拼写错误可能更类似于另一个控件,而不是你想到的那个。 ## 如何将pywinauto与英语以外的应用程序语言一起使用 由于Python不支持代码中的unicode标识符,因此您无法使用属性访问来引用控件,因此您必须使用项访问权或对`window()`进行显式调用。 所以不要写作 > ~~~ > app.dialog_ident.control_ident.click() > ~~~ 你必须写 > ~~~ > app['dialog_ident']['control_ident'].click() > ~~~ 或者明确地使用`window()` > ~~~ > app.window(title_re="非Ascii字符").window(title="非Ascii字符").click() > ~~~ To see an example of this check `examples\misc_examples.py get_info()` ## 如何处理未按预期响应的控件(例如OwnerDraw控件) 某些控件(尤其是Ownerdrawn控件)不会按预期响应事件。 例如,如果您查看任何HLP文件并转到“索引”选项卡(单击“搜索”按钮),您将看到一个列表框。 在此运行Spy或Winspector将向您显示它确实是一个列表框 - 但它是ownerdrawn。 这意味着开发人员告诉Windows他们将覆盖项目的显示方式并自行完成。 在这种情况下,他们已经使它无法检索字符串:-(。 那导致什么问题呢? > ~~~ > app.HelpTopics.ListBox.texts() # 1 > app.HelpTopics.ListBox.select("ItemInList") # 2 > ~~~ 1. 将返回一个空字符串列表,这一切都意味着pywinauto无法获取列表框中的字符串 2. 这将因IndexError而失败,因为ListBox的select(string)方法在文本中查找项目以了解它应该选择的项目的索引。 以下解决方法将适用于此控件 > ~~~ > app.HelpTopics.ListBox.select(1) > ~~~ 这将选择列表框中的第二项,因为它不是字符串查找,它可以正常工作。 不幸的是,即便这样也无济于事。 开发人员可以使控件不响应Select等标准事件。 在这种情况下,您可以在列表框中选择项目的唯一方法是使用TypeKeys()的键盘模拟。 这允许您将任何击键发送到控件。 所以要选择你要使用的第3个项目 > ~~~ > app.Helptopics.ListBox1.type_keys("{HOME}{DOWN 2}{ENTER}") > ~~~ * `{HOME}` 将确保突出显示第一个项目。 * `{DOWN 2}` 然后将突出显示两个项目 * `{ENTER}` 将选择突出显示的项目 如果您的应用程序广泛使用类似的控件类型,那么您可以通过从ListBox派生新类来更轻松地使用它,这可以使用有关您的特定应用程序的额外知识。 例如,在WinHelp示例中,每次在列表视图中突出显示某个项目时,其文本都会插入到列表上方的Edit控件中,并且您可以从那里获取该项目的文本,例如: > ~~~ > # 打印列表框中当前所选项目的文本 > # (只要你没有输入编辑控件!) > print app.HelpTopics.Edit.texts()[1] > ~~~ ## 如何访问系统托盘(又名SysTray,又名“通知区域”) 在时钟附近有表示正在运行的应用程序的图标,该区域通常被称为“系统托盘”。 实际上,该区域有许多不同的窗户/控制装置。 包含图标的控件实际上是一个工具栏。 它是一个带有TrayNotifyWnd类的窗口中的Pager控件的子节点,它位于另一个带有Shell_TrayWnd类的窗口中,所有这些窗口都是正在运行的Explorer实例的一部分。 谢天谢地,你不需要记住所有这些:-)。 需要记住的重要一点是,您正在“Explorer.exe”应用程序中查找一个窗口“Shell_TrayWnd”的窗口,该窗口具有标题为“通知区域”的工具栏控件。 实现此目的的一种方法是执行以下操作 > ~~~ > import pywinauto.application > app = pywinauto.application.Application().connect(path="explorer") > systray_icons = app.ShellTrayWnd.NotificationAreaToolbar > ~~~ 任务栏模块提供对系统托盘的初步访问. 它定义了以下变量: **explorer_app**:定义连接到正在运行的资源管理器的Application()对象。 您可能不需要直接使用它. **TaskBar**:任务栏的句柄(包含“开始”按钮,QuickLaunch图标,运行任务等的栏). **StartButton**:“启动我”:-)我想你可能知道这是什么! **QuickLaunch**:带有快速启动图标的工具栏. **SystemTray**:包含时钟和系统托盘图标的窗口. **Clock**:时钟. **SystemTrayIcons**: 工具栏代表系统托盘图标. **RunningApplications**: 工具栏代表正在运行的应用程序 我还在模块中提供了两个可用于单击系统托盘图标的功能: `ClickSystemTrayIcon(button)`: 您可以使用它左键单击系统托盘中的可见图标。 我不得不专门说明可见图标,因为可能有很多看不见的图标显然无法点击。 按钮可以是任何整数。 如果指定3则会找到并单击第3个可见按钮。 (现在几乎没有执行错误检查,但将来很可能会移动/重命名此方法。) `RightClickSystemTrayIcon(button)`: 与ClickSytemTrayIcon类似,但执行右键单击。 通常,当您单击/右键单击图标时,会出现一个弹出菜单。 此时需要记住的是,弹出菜单是应用程序的一部分,不会自动成为资源管理器的一部分。 例如 > ~~~ > # connect to outlook > outlook = Application.connect(path='outlook.exe') > > # click on Outlook's icon > taskbar.ClickSystemTrayIcon("Microsoft Outlook") > > # Select an item in the popup menu > outlook.PopupMenu.Menu().get_menu_path("Cancel Server Request")[0].click() > ~~~ ## COM线程模型 默认情况下,如果在导入pywinauto之前没有定义其他模型,pywinauto会在init上设置客户端多线程COM模型(MTA)。 模型可以由另一个导入的模块隐式设置或通过`sys.coinit_flags`显式指定。 通过显式设置单线程单元模型来覆盖MTA的示例。 > ~~~ > import sys > sys.coinit_flags = 2 # COINIT_APARTMENTTHREADED > > import pywinauto > ~~~ 请注意,COM模型的最终值被分配回`sys.coinit_flags`。 这是为了避免与其他模块发生冲突。 `sys.coinit_flags`的可能值: * `0` - 多线程单元模型 (MTA) * `2` - 单线程单元模型 (STA) 更多信息: * 关于Microsoft COM线程模型: [理解和使用COM线程模型](https://msdn.microsoft.com/en-us/library/ms809971.aspx) * 关于pywinauto MTA的[内部讨论](https://github.com/pywinauto/pywinauto/issues/394#issuecomment-334926345)