[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