工作自動化,替代手工操作,使用python操作MFC、windows程式

Kyle023發表於2022-01-21

背景--為什麼要自動化操作?

工作中總是遇到反覆重複性的工作?怎麼用程式把它變成自動化操作?將程式掛在一旁,執行自動化操作的同時,還能處理其他的任務?提高工作效率,讓自己的時間變得可控?

只能運用於 MFC 和 windows 訊息機制下
最近的工作中,遇到了需要比對c++程式的執行結果與matlab執行結果的事項。
目前需要校驗的c++程式並沒用引入軟體測試這一步驟,需要手動去操作程式獲取資料,由於資料量比較大,想考慮使用程式自動執行。
偶然間想到了可以給MFC傳送訊息,實現軟體模擬手工操作來完成這項工作。

方法--怎麼實現自動化操作?

操作C++程式(模擬手動點選輸入等等)

操作 windows 程式主要是利用了 windows 的訊息機制,用程式向程式傳送訊息的方式替代人工點選輸入操作。那怎麼去使用呢?

  1. 找到 windows 的窗體,需要利用 windows 的查詢視窗 FindWindowEx 這個 api
  2. 點選按鈕的操作,需要使用 PostMessage 這個 windows api
  3. 設定輸入文字,需要使用 SendMessage 這個 windows api
  4. 關閉對話方塊,需要傳送 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')

傳送訊息

傳送訊息主要涉及 PostMessageSendMessage 這兩個傳送訊息的 api。
其中 PostMessage 為非同步傳送,傳送訊息立刻返回。
SendMessage 為同步傳送,等到訊息被處理才返回。
參考資料:
PostMessage
SendMessage

  1. 點選窗體
    滑鼠的點選操作是我們最常用的一個操作,主要是向窗體傳送 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)
  1. 選單操作
    選單操作也算一個滑鼠的點選操作,但是卻和滑鼠操作不太一樣。
    要進行選單操作得知道我們要操作的選單資源值是多少。

首先要找到窗體,假設窗體名字是 'windows'。
然後在C++原始碼中找到需要操作的選單的資源值 45046。
最後向窗體傳送 WM_COMMAND 訊息,這時就完成了選單的點選操作。

hid = FindWindow(None, None,None,'windows')
win32api.PostMessage(hid,win32con.WM_COMMAND,45046,0)
  1. 設定文字
    設定文字則是模擬我們往文字框中輸入的操作,向文字框窗體傳送 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

總結

  1. 收穫
    有時候一些突發奇想的事情,做了,慢慢地就會發展成一個對自己有用地東西。堅決不忽略自己的想法,每一件事都會默默地成為自己的能力。
    這一次的開發中,讓我對 windows 訊息機制的理解更進一步了,同時也讓我更加地愛上 python。
  2. 想法
    這其實是一個機緣巧合,讓我使用python+windows訊息的方式生成效率工具。但是這個程式也是有侷限性,只能用於windows訊息機制的程式,其他機制的程式也許會有其他的方式實現。
    python+windows訊息機制的方式也只是臨時使用的工具,主要的物件還是不能進行大變更的程式的控制。
    當然最好的方式是重構原程式,向外開放介面,引入軟體測試來實現上面所說的功能。

語言只是一個工具,用 python 實現的上述功能其實完全也可以用 C++ 實現
歡迎大家一起交流程式碼呀

相關文章