Вызов был отклонен вызываемым пользователем в win32com, если открыто диалоговое окно или Excel ожидает другого пользователя.
Мне нужно определить, готов ли Excel принять COM-объект от win32com в Python. Например, если в Excel открыто диалоговое окно, любой вызов функции win32com вызовет ошибку "Вызов отклонен вызываемым пользователем".
Методом проб и ошибок я обнаружил, что если в Excel (фактически я предполагаю, что любой продукт Office) открыто диалоговое окно, любой вызов win32com приведет к ошибке.
После долгого поиска в Google я нашел много вопросов о том, что означает открывающиеся диалоговые окна. то есть выполнение Excel.SaveAs() откроет диалоговое окно на листе, и вы застрянете, пока пользователь не закроет его.
В моем случае у меня есть пользователь, который открыл диалоговое окно или каким-то другим образом взаимодействовал с Excel и оставил его в ожидании ввода. Что-то простое: начало ввода формулы в строке формул приведет к возврату функции win32com с ошибкой.
Итак, пара вопросов: есть ли способ определить, готов ли Excel к команде? Есть ли способ узнать, какой ящик открыт (чего ожидает Excel?) Есть ли способ закрыть ящик через win32com ... помните, что, насколько я могу сказать, все, что я делаю с win32com, выдаст ошибку, когда оно находится в этом состоянии
Я знаю, что мог бы попробовать: catch: но мне это нужно было бы для каждой функции win32com (на данный момент их много). Я думаю, что такой подход сделает код излишне длинным и сложным.
0 ответов
Я боролся с той же проблемой, но теперь я нашел решение, которое работает для меня до сих пор.
Я создал класс ComWrapper, в который обертываю COM-объект Excel. Он автоматически оборачивает каждый вложенный объект и вызов в 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.