如果打开了对话框或excel正在等待用户,则win32com中的被呼叫者拒绝了呼叫

时间:2019-01-30 21:05:19

标签: python excel win32com

我需要确定Excel是否准备好在Python中接受来自win32com的COM对象。例如,如果在excel中打开一个对话框,则对win32com函数的任何调用都将导致“呼叫被被呼叫者拒绝”错误。

通过反复试验,我发现如果Excel(实际上我认为任何Office产品都已打开)都打开了对话框,则对win32com的任何调用都会导致错误。

经过大量的搜索之后,我发现了很多有关打开自我设置对话框的问题。即执行Excel.SaveAs()将在工作表上打开一个对话框,您将被卡住,直到用户关闭它为止。

对于我来说,我有一个用户打开了一个对话框,或者以其他方式与Excel进行了交互,并使其等待输入。开始在公式栏上输入公式的简单操作会使win32com函数返回错误。

几个问题: 有没有办法确定Excel是否已准备好执行命令? 有没有办法告诉打开的盒子是什么(excel还等什么?) 有没有一种方法可以通过win32com关闭盒子...请记住,据我所知,使用win32com进行的任何操作都会在此状态下返回错误

我知道我可以尝试:catch:但是我需要在每个win32com函数周围(此时有很多函数)。我认为这种方法会使代码不必要地冗长和复杂。

1 个答案:

答案 0 :(得分:0)

我一直在同一个问题上挣扎,但现在我已经提出了一种对我有效的解决方案。

我创建了一个类ComWrapper,用于包装Excel COM对象。它将自动包装每个嵌套对象并在ComWrapper中进行调用,并在将它们用作函数调用或对包装对象的赋值时对其进行拆包。包装器通过捕获“呼叫被被呼叫方拒绝”例外并重试呼叫直到达到顶部定义的超时来工作。如果超时,则最终将异常抛出到包装对象之外。

对包装对象的函数调用由_com_call_wrapper函数自动包装,这就是神奇的地方。

要使其工作,只需使用ComWrapper包装来自Dispatch的com对象,然后像在代码底部一样照常使用它。如有问题请发表评论。

import win32com.client
from pywintypes import com_error
import time
import logging

_DELAY = 0.05  # seconds
_TIMEOUT = 60.0  # seconds


def _com_call_wrapper(f, *args, **kwargs):
    """
    COMWrapper support function. 
    Repeats calls when 'Call was rejected by callee.' exception occurs.
    """
    # Unwrap inputs
    args = [arg._wrapped_object if isinstance(arg, ComWrapper) else arg for arg in args]
    kwargs = dict([(key, value._wrapped_object)
                   if isinstance(value, ComWrapper)
                   else (key, value)
                   for key, value in dict(kwargs).items()])

    start_time = None
    while True:
        try:
            result = f(*args, **kwargs)
        except com_error as e:
            if e.strerror == 'Call was rejected by callee.':
                if start_time is None:
                    start_time = time.time()
                    logging.warning('Call was rejected by callee.')

                elif time.time() - start_time >= _TIMEOUT:
                    raise

                time.sleep(_DELAY)
                continue

            raise

        break

    if isinstance(result, win32com.client.CDispatch) or callable(result):
        return ComWrapper(result)
    return result


class ComWrapper(object):
    """
    Class to wrap COM objects to repeat calls when 'Call was rejected by callee.' exception occurs.
    """

    def __init__(self, wrapped_object):
        assert isinstance(wrapped_object, win32com.client.CDispatch) or callable(wrapped_object)
        self.__dict__['_wrapped_object'] = wrapped_object

    def __getattr__(self, item):
        return _com_call_wrapper(self._wrapped_object.__getattr__, item)

    def __getitem__(self, item):
        return _com_call_wrapper(self._wrapped_object.__getitem__, item)

    def __setattr__(self, key, value):
        _com_call_wrapper(self._wrapped_object.__setattr__, key, value)

    def __setitem__(self, key, value):
        _com_call_wrapper(self._wrapped_object.__setitem__, key, value)

    def __call__(self, *args, **kwargs):
        return _com_call_wrapper(self._wrapped_object.__call__, *args, **kwargs)

    def __repr__(self):
        return 'ComWrapper<{}>'.format(repr(self._wrapped_object))


_xl = win32com.client.dynamic.Dispatch('Excel.Application')
xl = ComWrapper(_xl)

# Do stuff with xl instead of _xl, and calls will be attempted until the timeout is
# reached if "Call was rejected by callee."-exceptions are thrown.