如何利用Python和win32程式設計避免重複性體力勞動(一)——開始、FindWindow和FindWindowEx
請移步至這裡以獲得更加的排版和閱讀體驗,謝謝您
本系列文章假設各位看官對python是足夠熟悉的,但卻不太瞭解win32程式設計。
嘛。。其實我也沒學過win32程式設計,臉請各位看官隨意招呼。
需求:最近因為做課題,要把800個FaceGen軟體生成的三維面孔儲存成圖片,以後不排除每一張面孔還要生成某個特質上連續變化的圖片。FaceGen以抽取面孔的特徵向量來構建面孔,所以儲存的檔案相當精簡,只需要300位元組就能無損儲存面孔的全部資訊。一般的三維工具包是沒有辦法解析FaceGen軟體的fg格式的,而他們自己的SDK又死貴死貴的,3K刀貌似。。嘛,自己動手,豐衣足食。
分析:要做的事情其實很簡單,fg檔案已經都準備好,我只需要用FaceGen開啟它的fg檔案,然後直接Save to Image就好了。鑑於一系列原因,不是很想用按鍵精靈之類的東西。何況利用pywin32可以很方便的利用win32的一些介面,何必再去模擬操作。
Part 0: 開始之前?
首先,來這裡安裝一個Pywin32吧,Pywin32是一個Python庫,為python提供訪問Windows API的擴充套件,提供了齊全的windows常量、介面、執行緒以及COM機制等等。安裝後自帶一個pythonwin的IDE。或者也可以來這邊下載一個ActivePython,整合了pywin32和一些其他的庫以及一大堆的支援文件,他們的文件查起來是非常方便的。當然了,WIN32的一些相關函式在MSDN上也能直接找到。
其次,為了方面查詢目標視窗的控制代碼,可以下載一個微軟自家的Spy++,這玩意兒滿大街都是。有了它,還能很方便的檢視窗體的訊息。
控制代碼
是一個32位整數,在windows中標記物件用,類似一個dict中的key,詳情參看這篇文章。訊息是windows應用的重要部分,用來告訴窗體“發生了什麼”,比如給一個按鈕傳送BN_CLICKED這麼個訊息,按鈕就知道“哦,我被點了”,才能執行相應的下一步操作。本文將大量使用訊息機制。詳情參看這篇文章。
Part 1: 查詢窗體控制代碼
貌似在win32程式設計的世界裡,包括視窗到文字框的所有控制元件就是窗體,所有的窗體都有獨立的控制代碼。要操作任意一個窗體,你都需要找到這個窗體的控制代碼,這裡,我們就可以用到FindWindow函式和FindWindowEx函式。在pywin32中,他們都屬於win32gui的模組。
- FindWindow(lpClassName=None, lpWindowName=None):
- 描述:自頂層視窗(也就是桌面)開始搜尋條件匹配的窗體,並返回這個窗體的控制代碼。不搜尋子視窗、不區分大小寫。找不到就返回0
- 引數:
- lpClassName:字元型,是窗體的類名,這個可以在Spy++裡找到。
- lpWindowName:字元型,是視窗名,也就是標題欄上你能看見的那個標題。
- 說明:這個函式我們僅能用來找主視窗。
- FindWindowEx(hwndParent=0, hwndChildAfter=0, lpszClass=None, lpszWindow=None);
- 描述:搜尋類名和窗體名匹配的窗體,並返回這個窗體的控制代碼。不區分大小寫,找不到就返回0。
- 引數:
- hwndParent:若不為0,則搜尋控制代碼為hwndParent窗體的子窗體。
- hwndChildAfter:若不為0,則按照z-index的順序從hwndChildAfter向後開始搜尋子窗體,否則從第一個子窗體開始搜尋。
- lpClassName:字元型,是窗體的類名,這個可以在Spy++裡找到。
- lpWindowName:字元型,是視窗名,也就是標題欄上你能看見的那個標題。
- 說明:找到了主視窗以後就靠它來定位子窗體啦。
有了這兩個函式,我們就可以寫出可以定義到任意一個窗體控制代碼的函式啦:
def find_idxSubHandle(pHandle, winClass, index=0):
"""
已知子視窗的窗體類名
尋找第index號個同類型的兄弟視窗
"""
assert type(index) == int and index >= 0
handle = win32gui.FindWindowEx(pHandle, 0, winClass, None)
while index > 0:
handle = win32gui.FindWindowEx(pHandle, handle, winClass, None)
index -= 1
return handle
def find_subHandle(pHandle, winClassList):
"""
遞迴尋找子視窗的控制代碼
pHandle是祖父視窗的控制代碼
winClassList是各個子視窗的class列表,父輩的list-index小於子輩
"""
assert type(winClassList) == list
if len(winClassList) == 1:
return find_idxSubHandle(pHandle, winClassList[0][0], winClassList[0][1])
else:
pHandle = find_idxSubHandle(pHandle, winClassList[0][0], winClassList[0][1])
return find_subHandle(pHandle, winClassList[1:])
這樣在後續的呼叫中,我們就能使用我們定義的finde_subHandle來方便地找到某個特定的Edit窗體控制元件。
比如我們定義一個FaceGenWindow的類:
class FaceGenWindow(object):
def __init__(self, fgFilePath=None):
self.Mhandle = win32gui.FindWindow("FaceGenMainWinClass", None)
print "FaceGen initialization compeleted"
實體化以後就可以很方便地在類的方法中呼叫find_subHandle函式來找到FaceGenWindow的子窗體了,比如某個Edit控制元件:
handle = find_subHandle(self.Mhandle, [("ComboBoxEx32", 1), ("ComboBox", 0), ("Edit", 0)])
另外,python中找回來的控制代碼都是十進位制整型,Spy++裡顯示的都是十六進位制整型,這個要注意下,除錯的時候用十六進位制輸出控制代碼,如下:
print "%x" % (handle)