背景--為什么要自動化操作?
工作中總是遇到反復重復性的工作?怎么用程序把它變成自動化操作?將程序掛在一旁,執行自動化操作的同時,還能處理其他的任務?提高工作效率,讓自己的時間變得可控?
只能運用於 MFC 和 windows 消息機制下
最近的工作中,遇到了需要比對c++程序的運行結果與matlab運行結果的事項。
目前需要校驗的c++程序並沒用引入軟件測試這一步驟,需要手動去操作程序獲取數據,由於數據量比較大,想考慮使用程序自動運行。
偶然間想到了可以給MFC發送消息,實現軟件模擬手工操作來完成這項工作。
方法--怎么實現自動化操作?
操作C++程序(模擬手動點擊輸入等等)
操作 windows 程序主要是利用了 windows 的消息機制,用程序向程序發送消息的方式替代人工點擊輸入操作。那怎么去使用呢?
- 找到 windows 的窗體,需要利用 windows 的查找窗口
FindWindowEx
這個 api - 點擊按鈕的操作,需要使用
PostMessage
這個 windows api - 設置輸入文本,需要使用
SendMessage
這個 windows api - 關閉對話框,需要發送
WM_CLOSE
的消息
查找窗體
查找窗體是這個操作很重要的一步,對話框上的按鈕、文本框等等都是窗體,都需要使用 FindWindowEx
去進行查找。
只有找到了需要控制的窗體,才能執行下面的控制操作。
參考資料:FindWindowEx
import win32gui
import time
def FindWindow(parent_wid,child_after_wid,class_name,window_name,try_cnt=100):
openid = 0
cnt = 0
while not openid and cnt < try_cnt:
openid = win32gui.FindWindowEx(parent_wid,child_after_wid,class_name,window_name)
time.sleep(0.2)
cnt+=1
if cnt >= try_cnt:
print('"%s->%s" not found!' % (class_name,window_name))
else:
print('"%s->%s" found!' % (class_name, window_name))
return openid
操作方法
可以使用 visual studio 的 spy++ 工具來檢索窗體的名稱,如下為windows
openid = win32gui.FindWindowEx(None,None,None,'windows')
發送消息
發送消息主要涉及 PostMessage
和 SendMessage
這兩個發送消息的 api。
其中 PostMessage
為異步發送,發送消息立刻返回。
SendMessage
為同步發送,等到消息被處理才返回。
參考資料:
PostMessage
SendMessage
- 點擊窗體
鼠標的點擊操作是我們最常用的一個操作,主要是向窗體發送WM_LBUTTONDOWN
加上WM_LBUTTONUP
這兩個消息來模擬鼠標左鍵按下和松開的操作。
save_as_wid = FindWindow(None, None,None,'確認另存為',10)
btn_wid = FindWindow(save_as_wid, None,'Button','是(&Y)') if save_as_wid else 0
if btn_wid:
win32gui.PostMessage(btn_wid, win32con.WM_LBUTTONDOWN, win32con.MK_LBUTTON,0)
time.sleep(0.02)
win32gui.PostMessage(btn_wid, win32con.WM_LBUTTONUP, win32con.MK_LBUTTON,0)
- 菜單操作
菜單操作也算一個鼠標的點擊操作,但是卻和鼠標操作不太一樣。
要進行菜單操作得知道我們要操作的菜單資源值是多少。
首先要找到窗體,假設窗體名字是 'windows'。
然后在C++源碼中找到需要操作的菜單的資源值 45046。
最后向窗體發送 WM_COMMAND
消息,這時就完成了菜單的點擊操作。
hid = FindWindow(None, None,None,'windows')
win32api.PostMessage(hid,win32con.WM_COMMAND,45046,0)
- 設置文本
設置文本則是模擬我們往文本框中輸入的操作,向文本框窗體發送WM_SETTEXT
的消息
win32gui.SendMessage(wid, win32con.WM_SETTEXT, None, text)
獲取文本
這一部分倒是找了很久才驗證成功的,現在已經可以正確地讀取文本內容。
主要是從一個窗體中,獲取窗體的文本。
def GetWindowText(wid):
text_length = win32gui.SendMessage(wid, win32con.WM_GETTEXTLENGTH, 0,0) + 1
buffer = '\0'*text_length
lpid = id(buffer)
buf = win32gui.PyGetMemory(lpid,text_length)
win32gui.SendMessage(wid, win32con.WM_GETTEXT,text_length,buf)
address, text_length = win32gui.PyGetBufferAddressAndLen(buf)
buffer = win32gui.PyGetString(address,text_length)
return buffer
總結
- 收獲
有時候一些突發奇想的事情,做了,慢慢地就會發展成一個對自己有用地東西。堅決不忽略自己的想法,每一件事都會默默地成為自己的能力。
這一次的開發中,讓我對 windows 消息機制的理解更進一步了,同時也讓我更加地愛上 python。 - 想法
這其實是一個機緣巧合,讓我使用python+windows消息的方式生成效率工具。但是這個程序也是有局限性,只能用於windows消息機制的程序,其他機制的程序也許會有其他的方式實現。
python+windows消息機制的方式也只是臨時使用的工具,主要的對象還是不能進行大變更的程序的控制。
當然最好的方式是重構原程序,向外開放接口,引入軟件測試來實現上面所說的功能。
語言只是一個工具,用 python 實現的上述功能其實完全也可以用 C++ 實現
歡迎大家一起交流代碼呀