溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

如何使用Python子進(jìn)程關(guān)閉Excel自動(dòng)化中的彈窗

發(fā)布時(shí)間:2021-10-09 15:38:33 來(lái)源:億速云 閱讀:339 作者:柒染 欄目:編程語(yǔ)言

本篇文章為大家展示了如何使用Python子進(jìn)程關(guān)閉Excel自動(dòng)化中的彈窗,內(nèi)容簡(jiǎn)明扼要并且容易理解,絕對(duì)能使你眼前一亮,通過這篇文章的詳細(xì)介紹希望你能有所收獲。

利用Python進(jìn)行Excel自動(dòng)化操作的過程中,尤其是涉及VBA時(shí),可能遇到消息框/彈窗(MsgBox)。此時(shí)需要人為響應(yīng),否則代碼卡死直至超時(shí) [^1] [^2]。根本的解決方法是VBA代碼中不要出現(xiàn)類似彈窗,但有時(shí)我們無(wú)權(quán)修改被操作的Excel文件,例如這是我們進(jìn)行自動(dòng)化測(cè)試的對(duì)象。所以本文記錄從代碼角度解決此類問題的方法。

假想場(chǎng)景

使用xlwings(或者其他自動(dòng)化庫(kù))打開Excel文件test.xlsm,讀取Sheet1!A1單元格內(nèi)容。很簡(jiǎn)單的一個(gè)操作:

import xlwings as xw  wb = xw.Book('test.xlsm')  msg = wb.sheets('Sheet1').range('A1').value  print(msg)  wb.close()

然而不幸的是,打開工作簿時(shí)進(jìn)行了熱情的歡迎儀式:

Private Sub Workbook_Open()      MsgBox "Welcome"      MsgBox "to open"      MsgBox "this file."  End Sub

第一個(gè)彈窗Welcome就卡住了Excel,Python代碼相應(yīng)卡死在第一行。

如何使用Python子進(jìn)程關(guān)閉Excel自動(dòng)化中的彈窗

基本思路

主程序中不可能直接處理或者繞過此類問題,也不能奢望有人隨時(shí)蹲守點(diǎn)擊下一步——那就開啟一個(gè)子線程來(lái)護(hù)航吧。因此,解決方案是利用子線程監(jiān)聽并隨時(shí)關(guān)閉彈窗,直到主程序圓滿結(jié)束。

解決這個(gè)問題,需要以下兩個(gè)知識(shí)點(diǎn)(基礎(chǔ)知識(shí)請(qǐng)課外學(xué)習(xí)):

  •  Python多線程(本文采用threading.Thread)

  •  Python界面自動(dòng)化庫(kù)(本文涉及pywinauto和pywin32)

pywinauto方案

pywinauto顧名思義是Windows界面自動(dòng)化庫(kù),模擬鼠標(biāo)和鍵盤操作窗體和控件 [^3]。不同于先獲取句柄再獲取屬性的傳統(tǒng)方式,pywinauto的API更加友好和pythonic。例如,兩行代碼搞定窗口捕捉和點(diǎn)擊:

from pywinauto.application import Application  win = Application(backend="win32").connect(title='Microsoft Excel')  win.Dialog.Button.click()

本文采用自定義線程類的方式,啟動(dòng)線程后自動(dòng)執(zhí)行run()函數(shù)來(lái)完成上述操作。具體代碼如下,注意構(gòu)造函數(shù)中的兩個(gè)參數(shù):

  •  title 需要捕捉的彈窗的標(biāo)題,例如Excel默認(rèn)彈窗的標(biāo)題為Microsoft Excel

  •  interval 監(jiān)聽的頻率,即每隔多少秒檢查一次 

# listener.py  import time  from threading import Thread, Event  from pywinauto.application import Application  class MsgBoxListener(Thread):      def __init__(self, title:str, interval:int):          Thread.__init__(self)          self._title = title           self._interval = interval           self._stop_event = Event()         def stop(self): self._stop_event.set()      @property      def is_running(self): return not self._stop_event.is_set()      def run(self):          while self.is_running:              try:                  time.sleep(self._interval)                  self._close_msgbox()              except Exception as e:                  print(e, flush=True)      def _close_msgbox(self):          '''Close the default Excel MsgBox with title "Microsoft Excel".'''                 win = Application(backend="win32").connect(title=self._title)          win.Dialog.Button.click()  if __name__=='__main__':     t = MsgBoxListener('Microsoft Excel', 3)      t.start()      time.sleep(10)      t.stop()

于是,整個(gè)過程分為三步:

  •  啟動(dòng)子線程監(jiān)聽彈窗

  •  主線程中打開Excel開始自動(dòng)化操作

  •  關(guān)閉子線程 

import xlwings as xw  from listener import MsgBoxListener  # start listen thread  listener = MsgBoxListener('Microsoft Excel', 3)  listener.start()  # main process as before  wb = xw.Book('test.xlsm')  msg = wb.sheets('Sheet1').range('A1').value  print(msg)  wb.close()  # stop listener thread  listener.stop()

到此問題基本解決,本地運(yùn)行效果完全達(dá)到預(yù)期。但我的真實(shí)需求是以系統(tǒng)服務(wù)方式在服務(wù)器上進(jìn)行Excel文件自動(dòng)化測(cè)試,后續(xù)發(fā)現(xiàn),當(dāng)以系統(tǒng)服務(wù)方式運(yùn)行時(shí),pywinauto竟然捕捉不到彈窗!這或許是pywinauto一個(gè)潛在的問題 [^4]。

win32gui方案

那就只好轉(zhuǎn)向相對(duì)底層的win32gui,所幸完美解決了上述問題。

win32gui是pywin32庫(kù)的一部分,所以實(shí)際安裝命令是:

pip install pywin32

整個(gè)方案和前文描述完全一致,只是替換MsgBoxListener類中關(guān)閉彈窗的方法:

import win32gui, win32con  def _close_msgbox(self):      # find the top window by title      hwnd = win32gui.FindWindow(None, self._title)      if not hwnd: return      # find child button      h_btn = win32gui.FindWindowEx(hwnd, None,'Button', None)      if not h_btn: return      # show text      text = win32gui.GetWindowText(h_btn)     print(text)      # click button             win32gui.PostMessage(h_btn, win32con.WM_LBUTTONDOWN, None, None)      time.sleep(0.2)      win32gui.PostMessage(h_btn, win32con.WM_LBUTTONUP, None, None)      time.sleep(0.2)

更一般的方案

更一般地,當(dāng)同時(shí)存在默認(rèn)標(biāo)題和自定義標(biāo)題的彈窗時(shí),就不便于采用標(biāo)題方式進(jìn)行捕捉了。例如

MsgBox "Message with default title.", vbInformation,   MsgBox "Message with title My App 1", vbInformation, "My App 1"  MsgBox "Message with title My App 2", vbInformation, "My App 2"

那就擴(kuò)大搜索范圍,依次點(diǎn)擊所有包含確定性描述的按鈕(例如OK,Yes,Confirm)來(lái)關(guān)閉彈窗。同理替換MsgBoxListener類的_close_msgbox()方法(同時(shí)構(gòu)造函數(shù)中不再需要title參數(shù)):

def _close_msgbox(self):     '''Click any button ("OK", "Yes" or "Confirm") to close message box.'''      # get handles of all top windows      h_windows = []      win32gui.EnumWindows(lambda hWnd, param: param.append(hWnd), h_windows)       # check each window          for h_window in h_windows:                   # get child button with text OK, Yes or Confirm of given window          h_btn = win32gui.FindWindowEx(h_window, None,'Button', None)          if not h_btn: continue          # check button text          text = win32gui.GetWindowText(h_btn)          if not text.lower() in ('ok', 'yes', 'confirm'): continue          # click button          win32gui.PostMessage(h_btn, win32con.WM_LBUTTONDOWN, None, None)          time.sleep(0.2)          win32gui.PostMessage(h_btn, win32con.WM_LBUTTONUP, None, None)          time.sleep(0.2)

最后,實(shí)例演示結(jié)束全文,以后再也不用擔(dān)心意外彈窗了。

如何使用Python子進(jìn)程關(guān)閉Excel自動(dòng)化中的彈窗

[^1]: Handling VBA popup message boxes in Microsoft Excel

[^2]: Trying to catch MsgBox text and press button in xlwings

[^3]: What is pywinauto

[^4]: Remote Execution Guide

上述內(nèi)容就是如何使用Python子進(jìn)程關(guān)閉Excel自動(dòng)化中的彈窗,你們學(xué)到知識(shí)或技能了嗎?如果還想學(xué)到更多技能或者豐富自己的知識(shí)儲(chǔ)備,歡迎關(guān)注億速云行業(yè)資訊頻道。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI