[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)
- 什么是Pywinauto
- 入门指南
- 如何
- 等待长时间操作
- 远程执行指南
- 每种不同控制类型可用的方法
- 贡献者
- 开发笔记
- 待办项目
- 更新日志
- 基本用户输入模块
- pywinauto.mouse
- pywinauto.keyboard
- 主要用户模块
- pywinauto.application
- pywinauto.findbestmatch
- pywinauto.findwindows
- pywinauto.timings
- 特定功能
- pywinauto.clipboard
- pywinauto.win32_hooks
- 控件参考
- pywinauto.base_wrapper
- pywinauto.controls.hwndwrapper
- pywinauto.controls.menuwrapper
- pywinauto.controls.common_controls
- pywinauto.controls.win32_controls
- pywinauto.controls.uiawrapper
- pywinauto.controls.uia_controls
- Pre-supplied Tests
- pywinauto.tests.allcontrols
- pywinauto.tests.asianhotkey
- pywinauto.tests.comboboxdroppedheight
- pywinauto.tests.comparetoreffont
- pywinauto.tests.leadtrailspaces
- pywinauto.tests.miscvalues
- pywinauto.tests.missalignment
- pywinauto.tests.missingextrastring
- pywinauto.tests.overlapping
- pywinauto.tests.repeatedhotkey
- pywinauto.tests.translation
- pywinauto.tests.truncation
- 后端内部实施模块
- pywinauto.backend
- pywinauto.element_info
- pywinauto.win32_element_info
- pywinauto.uia_element_info
- pywinauto.uia_defines
- 内部模块
- pywinauto.controlproperties
- pywinauto.handleprops
- pywinauto.xml_helpers
- pywinauto.fuzzydict
- pywinauto.actionlogger
- pywinauto.sysinfo
- pywinauto.remote_memory_block