Вызов был отклонен вызываемым пользователем в win32com, если открыто диалоговое окно или Excel ожидает другого пользователя. - PullRequest
0 голосов
/ 31 января 2019

Мне нужно определить, готов ли Excel принять COM-объект от win32com в Python.Например, если диалоговое окно открыто в Excel, любой вызов функции win32com вызовет ошибку «Вызов был отклонен вызываемым абонентом».

Путем проб и ошибок я обнаружил, что если Excel (на самом деле я предполагаю, что любойОфисный продукт) имеет открытое диалоговое окно, любой вызов win32com приведет к ошибке.

После некоторого поиска в Google я нашел много вопросов о том, что значит открывать диалоговые окна, которые открываются самостоятельно.то есть выполнение Excel.SaveAs () откроет диалоговое окно на листе, и вы застрянете, пока пользователь не закроет его.

В моем случае у меня есть пользователь, который открыл диалоговое окно или в другом случае имеетвзаимодействовал с Excel и оставлял его в ожидании ввода.Простое начало ввода формулы в строке формул приведет к тому, что функция win32com вернется с ошибкой.

Итак, пара вопросов: есть ли способ определить, готов ли Excel к команде?Есть ли способ узнать, какой ящик открыт (чего ожидает Excel?) Есть ли способ закрыть ящик через win32com ... помните, что, насколько я могу сказать, все, что я делаю с win32com, выдаст ошибку, когда ононаходится в этом состоянии

Я знаю, что могу сделать попытку: поймать: но мне нужно было бы это для каждой функции win32com (на данный момент их много).Я думаю, что такой подход сделает код излишне длинным и сложным.

1 Ответ

0 голосов
/ 28 апреля 2019

Я боролся с той же проблемой, но теперь я нашел решение, которое до сих пор работает для меня.

Я создал класс ComWrapper, в который оборачиваю объект Excel COM. Он автоматическиоборачивает каждый вложенный объект и вызов в ComWrapper и разворачивает их, когда они используются в качестве аргументов для вызовов функций или присваиваний обернутым объектам.Оболочка работает, перехватывая исключения «Вызов отклонен вызываемым абонентом» и повторяя вызов, пока не истечет время ожидания, указанное в верхней части.Если время ожидания истекло, исключение, наконец, выдается за пределы объекта-оболочки.

Вызовы функций для обернутых объектов автоматически оборачиваются функцией _com_call_wrapper, в которой происходит волшебство.

Для созданияэто работает, просто оберните объект com из Dispatch с помощью ComWrapper, а затем используйте его как обычно, как в нижней части кода.Прокомментируйте, если есть проблемы.

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.
...