首页 > 解决方案 > 我可以使用 xlwings 抑制来自 VBA 的消息框吗?

问题描述

我正在使用 xlwings (python 3.7) 从 VBA 调用宏。运行时,宏会填充一个消息框在此处输入图像描述

我想知道是否有办法从xlwings端抑制它(例如根本不显示消息框或自动单击确定)(无法更改宏,锁定)。我当前的设置如下所示:

app = xw.apps.active    # open application instance
app.visible = False  # Excel application not visible
app.display_alerts = False   # supress alert messages
app.screen_updating = False  # supress screen updates

谢谢!

标签: pythonvbaxlwings

解决方案


由于您无法更改宏,因此左侧选项是自动单击“确定”按钮。显然,您不能在主进程中执行此操作,因为一旦出现消息框就会卡住。因此,您需要创建一个子线程来同时执行此操作,从而使主进程从卡住中恢复。总之,你需要两件事:

  • 一个子线程,例如threading.Thread
  • 用于捕获消息框的 GUI 自动化库,pywin32例如pywinautoPyAutoGUI

当您使用xlwings时,pywin32应该已经作为依赖项安装。因此,例如在这里使用它。

整个过程如下:

import xlwings as xw
from listener import MsgBoxListener

# start child thread
listener = MsgBoxListener('Message-box-title-in-your-case', 3)
listener.start()

# main process as you did before
app = xw.apps.active    # open application instance
app.visible = False  # Excel application not visible
app.display_alerts = False   # supress alert messages
app.screen_updating = False  # supress screen updates
...

# stop listener thread
listener.stop()

MsgBoxListener子线程在哪里捕获并关闭消息框:

  • title是消息框的标题,隐藏在您的屏幕截图中
  • interval是检测是否存在消息框的频率
# listener.py

import time
from threading import Thread, Event
import win32gui
import win32con


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):
        # 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)


if __name__=='__main__':
    t = MsgBoxListener('Microsoft Excel', 1)
    t.start()
    time.sleep(10)
    t.stop()

推荐阅读