接上一篇博文python辦公自動化系列之金蝶K3自動登入(一),我們接著聊聊利用python指令碼實現金蝶K3 Wise客戶端自動登入這一需求。
如上圖所示,自動選擇【組織機構】後,我們還需要驅動【當前賬套】、【命名使用者身份登入】、【使用者名稱】、【密碼】、【確定】這幾個控制元件,才算是完成了K3 UI自動化的第一步:自動登入。
一、設定【當前賬套】控制元件
【當前賬套】Label右邊對應的同樣是一個ThunderRT6ComboBox 類,我們選擇comboBox某個下拉項時,只有基於索引Index,但是由於集團公司隨著業務不斷變化,賬套數可能存在變化,每個公司賬套對應的索引值也會變化。因此,我們最好想辦法實現基於賬套名稱來實現驅動某個comboBox控制元件。比較通俗易懂的做法是,我們先設法拿到這個元件的所有listItem,將每個Item對應的value(文字值)和listIndex(索引)存入一個字典,那麼當使用者端隨便拋給我們一個賬套名時,我們便能基於該字典迅速知道listIndex,再基於它來選擇這個comboBox控制元件。這裡面涉及到comboBox一些屬性和方法的應用,具體示例程式碼如下:
1 import win32gui,win32con 2 def getAccountDic(currentAccountHwnd): 3 '''根據【當前賬套】的控制程式碼,得到該comboBox所有下拉選項,將選項值ItemText和對應的ItemIndex存入字典 accountDic,最終返回''' 4 accountDic={} 5 accountCnt=win32gui.SendMessage(currentAccountHwnd, win32con.CB_GETCOUNT, 0, 0) 6 for i in range(accountCnt): 7 textLen= win32gui.SendMessage(currentAccountHwnd, win32con.CB_GETLBTEXTLEN, i,0)*2 # 建立一個比combobox 文字長度兩倍的buffer,確保資料都能存進去 8 buffer = win32gui.PyMakeBuffer(textLen) 9 win32gui.SendMessage(currentAccountHwnd, win32con.CB_GETLBTEXT, i, buffer) 10 address,length=win32gui.PyGetBufferAddressAndLen(buffer[:-1]) 11 length=int((length+1)/2) 12 itemText=win32gui.PyGetString(address,length).strip() # 從記憶體中取出combobox的當前項內容 13 accountDic[itemText]=i 14 return accountDic 15 16 def setCurrentAccount(currentAccountHwnd,currentAccountName,accountDic): 17 '''根據【當前賬套】的控制程式碼,,需要選擇的賬套名稱,和賬套字典,選擇特定賬套為當前賬套''' 18 accountIndex=accountDic[currentAccountName] 19 win32gui.SendMessage(currentAccountHwnd, win32con.CB_SETCURSEL, accountIndex, 0)
二、選擇【登入方式】為【以命名使用者身份登入(D)】
根據實際需要,小爬這裡演示下以命名使用者身份登入(D),其它登入方式,手段同理。spy++觀察到它其實是一個optionButton。我們可以這樣設定:
1 def setOptionBtn(parentHwnd,optionName): 2 # optionName="以命名使用者身份登入(D)" 3 '''通過spy++得到這些optionButton的父元素控制程式碼,在基於文字optionName找到並選擇特定的optionButton''' 4 optionBtnHandle=win32gui.FindWindowEx(parentHwnd,0,None, optionName) # 以命名使用者身份登入(D) optionButton 5 win32gui.SendMessage(optionBtnHandle, win32con.WM_LBUTTONDOWN, 0, 0) # 設定登入方式為【以命名使用者身份登入(D)】 6 time.sleep(0.01) 7 win32gui.SendMessage(optionBtnHandle, win32con.WM_LBUTTONUP, 0, 0)
三、設定【使用者名稱】、【密碼】控制元件
通過spy++觀察到這兩個控制元件的類名為ThunderRT6TextBox,也算是textBox的子類。我們可以大膽使用SendMessage的WM_SETTEXT來實現這一點,唯一的難度在於基於類名來定位這兩個控制元件時稍微有些麻煩,其中【密碼框】控制元件可以通過定位父元素,然後利用findwindowEx方法,基於ThunderRT6TextBox找到第一個元素即可,而【使用者名稱框】可以通過【當前賬套】控制元件來找下一個控制元件得到。這裡比較讓人混淆的是:後臺的所有控制元件【使用者名稱】在下,【密碼】在上,與肉眼觀察到的物理位置剛好相反。具體程式碼示例如下:
1 def setUserInfo(userName,passWord,parentHwnd,currentAccountHwnd): 2 passwordHwnd=win32gui.FindWindowEx(parentHwnd,0,"ThunderRT6TextBox", None) # 密碼框,基於父元素和其類名找到的第一個即可 3 time.sleep(0.1) 4 userNameHwnd=win32gui.FindWindowEx(parentHwnd,currentAccountHwnd,None, None) # 使用者名稱框,基於【當前賬套】控制元件控制程式碼找到下一個即可 5 time.sleep(0.1) 6 win32gui.SendMessage(userNameHwnd, win32con.WM_SETTEXT, None,userName) 7 time.sleep(0.1) 8 win32gui.SendMessage(passwordHwnd, win32con.WM_SETTEXT, None,passWord)
四、傳送【回車】實現登入
小爬通過模擬給登入介面傳送【Enter鍵】來實現登入,示例程式碼如下:
1 '''根據登入介面控制程式碼sysLoginWnd,傳送Enter鍵實現登入''' 2 win32gui.PostMessage(sysLoginWnd, win32con.WM_KEYDOWN, win32con.VK_RETURN, 0) 3 time.sleep(0.01) 4 win32gui.PostMessage(sysLoginWnd, win32con.WM_KEYUP, win32con.VK_RETURN, 0)
結合上一篇博文,我們基本就完成了K3登入需要的各個方法,我們只需要依次呼叫,就可以實現K3自動登入某個賬套啦。希望小爬的分享對你們的日常工作有所幫助,完整程式碼如下,供參考,:
1 # 需要使用管理員許可權執行VScode或者Pycharm,否則無法正常使用 2 import win32gui,win32api,win32con,subprocess,time,os 3 def getK3LoginHwnd(): 4 sysLoginWnd=win32gui.FindWindow('ThunderRT6Form',"金蝶K/3系統登入") # K3系統登入窗 5 if sysLoginWnd==0: 6 subprocess.Popen(r'C:\Program Files (x86)\Kingdee\K3ERP\K3MainNet.exe') 7 8 # subprocess傳送指令啟動後需要等K3登入窗徹底可見後,再進行後續操作 9 while sysLoginWnd==0: 10 time.sleep(0.3) 11 sysLoginWnd=win32gui.FindWindow('ThunderRT6Form',"金蝶K/3系統登入") # K3系統登入窗 12 isSysLoginWndVisible=0 13 while isSysLoginWndVisible==0: 14 time.sleep(0.3) 15 sysLoginWnd=win32gui.FindWindow('ThunderRT6Form',"金蝶K/3系統登入") # K3系統登入窗 16 isSysLoginWndVisible=win32gui.IsWindowVisible(sysLoginWnd) # 判斷視窗是否已經對使用者可見 17 return sysLoginWnd 18 19 def setOrganization(sysLoginWnd,organizationIndex): 20 '''根據K3登入窗的控制程式碼sysLoginWnd和下拉框索引值organizationIndex,選擇特定的組織機構''' 21 mainHwnd1=win32gui.FindWindowEx(sysLoginWnd,0,None, '') # ThunderRT6PictureBoxDC 22 mainHwnd2=win32gui.FindWindowEx(sysLoginWnd,mainHwnd1,None, '') # ThunderRT6PictureBoxDC 23 organizationHwnd=win32gui.FindWindowEx(mainHwnd2,0,"ThunderRT6ComboBox", '') # 組織機構 24 currentOrgIndex=win32gui.SendMessage(organizationHwnd, win32con.CB_GETCURSEL) # 當前combobox選中的index 25 if currentOrgIndex!=organizationIndex: 26 win32gui.SendMessage(organizationHwnd, win32con.CB_SETCURSEL, organizationIndex, 0) 27 28 '''模擬滑鼠左鍵點選元素,啟用它''' 29 win32gui.SendMessage(organizationHwnd, win32con.WM_LBUTTONDOWN, 0, 0) 30 time.sleep(0.01) 31 win32gui.SendMessage(organizationHwnd, win32con.WM_LBUTTONUP, 0, 0) 32 33 '''小爬此處的場景中,【組織機構】有6個下拉項,那麼最大的index就是5''' 34 if organizationIndex<5: # 模擬鍵盤↓+鍵盤↑ 35 win32gui.PostMessage(organizationHwnd, win32con.WM_KEYDOWN, win32con.VK_DOWN, 0) 36 time.sleep(0.01) 37 win32gui.PostMessage(organizationHwnd, win32con.WM_KEYUP, win32con.VK_DOWN, 0) 38 time.sleep(0.1) 39 win32gui.PostMessage(organizationHwnd, win32con.WM_KEYDOWN, win32con.VK_UP, 0) 40 time.sleep(0.01) 41 win32gui.PostMessage(organizationHwnd, win32con.WM_KEYUP, win32con.VK_UP, 0) 42 else: # 模擬鍵盤↑+鍵盤↓ 43 win32gui.PostMessage(organizationHwnd, win32con.WM_KEYDOWN, win32con.VK_UP, 0) 44 time.sleep(0.01) 45 win32gui.PostMessage(organizationHwnd, win32con.WM_KEYUP, win32con.VK_UP, 0) 46 time.sleep(0.1) 47 win32gui.PostMessage(organizationHwnd, win32con.WM_KEYDOWN, win32con.VK_DOWN, 0) 48 time.sleep(0.01) 49 win32gui.PostMessage(organizationHwnd, win32con.WM_KEYUP, win32con.VK_DOWN, 0) 50 time.sleep(0.1) 51 52 def getAccountDic(currentAccountHwnd): 53 '''根據【當前賬套】的控制程式碼,得到該comboBox所有下拉選項,將選項值ItemText和對應的ItemIndex存入字典 accountDic,最終返回''' 54 accountDic={} 55 accountCnt=win32gui.SendMessage(currentAccountHwnd, win32con.CB_GETCOUNT, 0, 0) 56 for i in range(accountCnt): 57 textLen= win32gui.SendMessage(currentAccountHwnd, win32con.CB_GETLBTEXTLEN, i,0)*2 # 建立一個比combobox 文字長度兩倍的buffer,確保資料都能存進去 58 buffer = win32gui.PyMakeBuffer(textLen) 59 win32gui.SendMessage(currentAccountHwnd, win32con.CB_GETLBTEXT, i, buffer) 60 address,length=win32gui.PyGetBufferAddressAndLen(buffer[:-1]) 61 length=int((length+1)/2) 62 itemText=win32gui.PyGetString(address,length).strip() # 從記憶體中取出combobox的當前項內容 63 accountDic[itemText]=i 64 return accountDic 65 66 def setCurrentAccount(currentAccountHwnd,currentAccountName,accountDic): 67 '''根據【當前賬套】的控制程式碼,,需要選擇的賬套名稱,和賬套字典,選擇特定賬套為當前賬套''' 68 accountIndex=accountDic[currentAccountName] 69 win32gui.SendMessage(currentAccountHwnd, win32con.CB_SETCURSEL, accountIndex, 0) 70 71 def setOptionBtn(parentHwnd,optionName): 72 # optionName="以命名使用者身份登入(D)" 73 '''通過spy++得到這些optionButton的父元素控制程式碼,在基於文字optionName找到並選擇特定的optionButton''' 74 optionBtnHandle=win32gui.FindWindowEx(parentHwnd,0,None, optionName) # 以命名使用者身份登入(D) optionButton 75 win32gui.SendMessage(optionBtnHandle, win32con.WM_LBUTTONDOWN, 0, 0) # 設定登入方式為【以命名使用者身份登入(D)】 76 time.sleep(0.01) 77 win32gui.SendMessage(optionBtnHandle, win32con.WM_LBUTTONUP, 0, 0) 78 79 def setUserInfo(userName,passWord,parentHwnd,currentAccountHwnd): 80 passwordHwnd=win32gui.FindWindowEx(parentHwnd,0,"ThunderRT6TextBox", None) # 密碼框,基於父元素和其類名找到的第一個即可 81 time.sleep(0.1) 82 userNameHwnd=win32gui.FindWindowEx(parentHwnd,currentAccountHwnd,None, None) # 使用者名稱框,基於【當前賬套】控制元件控制程式碼找到下一個即可 83 time.sleep(0.1) 84 win32gui.SendMessage(userNameHwnd, win32con.WM_SETTEXT, None,userName) 85 time.sleep(0.1) 86 win32gui.SendMessage(passwordHwnd, win32con.WM_SETTEXT, None,passWord) 87 88 def sendReturn(sysLoginWnd): 89 '''根據登陸介面控制程式碼sysLoginWnd,傳送Enter鍵實現登入''' 90 win32gui.PostMessage(sysLoginWnd, win32con.WM_KEYDOWN, win32con.VK_RETURN, 0) 91 time.sleep(0.01) 92 win32gui.PostMessage(sysLoginWnd, win32con.WM_KEYUP, win32con.VK_RETURN, 0)
歡迎掃碼關注我的公眾號 獲取更多爬蟲、資料分析的知識!