Извиняюсь за длину этого поста, но я думаю, что предыстория необходима, чтобы передать то, чего я пытаюсь достичь.
Мне поручили обновить старую надстройку Excel UDF (ранее она использовала API-код из книги Стива Далтона с JNI и почти всеми другими мыслимыми технологиями). Функции были очень несимпатичны расчетной модели Excel — эти функции брали несколько диапазонов и записывали данные обратно в них (в другом потоке), в то же время позволяя пользователям редактировать эти данные (которые затем сохранялись и загружались на удаленный сервер). Все это ужасно замедляло загрузку, но давало пользователям необходимую гибкость для изменения данных по мере необходимости.
Я перенес его на C# (он берет данные с удаленного Java-сервера через WSDL), но обнаружил, что большинство функций вызывались более 50 раз, и он работал намного, намного медленнее, чем исходная надстройка (это надстройка автоматизации, использующая Extensibility.IDTExtensibility2 — так что здесь нет никаких трюков с VSTO).
В порыве отчаяния я решил попробовать переписать множество UDF, чтобы они были функциями массива и не принимали ввод (Excel жаловался на перезапись массива) — очевидно, теперь это на несколько порядков быстрее, но отсутствует ключевое требование, чем у пользователей. иметь возможность изменять выходные данные.
Понимание того, что Excel не предоставлял никаких событий обратного вызова проверки перед редактированием-зафиксирован (я поэкспериментировал с Worksheet.OnEntry и добавил VBComponent, но он не срабатывает до ошибки о перезаписи массива или проверки данных списка).
Я предположил, что будет достаточно просто реализовать глобальный хук клавиатуры, поэтому начал писать некоторые формы Windows для перехвата изменений (только TextBox для отдельных записей формы и ComboBox для ячеек с проверкой данных списка), а также получил копирование данных из буфера обмена в выбранный диапазон.
В настоящее время все это управляется из сделанных на заказ записей контекстного меню (или двойного щелчка), которые не будут приняты пользователями — я должен, как минимум, уметь перехватывать F2, Ctl + V и прямой ввод в активной ячейке. . Но я понятия не имею, как зарегистрировать глобальный хук клавиатуры в надстройке автоматизации.
Итак, мой вопрос: Можно ли перехватить каждую попытку редактирования и предоставить собственную обработку? Или, в противном случае, как я могу зарегистрировать глобальный хук клавиатуры для перехвата F2, Ctl+V и прямого ввода в активной ячейке?
Я попробовал хук, найденный здесь Использование перехватчик клавиатуры (WH_KEYBOARD_LL) в WPF/C#, но не может заставить App.xaml + App.xaml.cs работать в этом контексте (это моя первая встреча с C# и программированием Windows в целом), так что может быть кто-то просто нужно просветить меня относительно конфигурации App.xaml + App.xaml.cs ().
Обратите внимание. это не надстройка VSTO, она реализована с помощью Extensibility.IDTExtensibility2.
РЕДАКТИРОВАНИЕ ОБНОВЛЕНИЯ: @TimWilliams и @CharlesWilliams поинтересовались, почему моя предыдущая версия, которая выполняла чтение и запись аргументов своих функций, вызывала так много повторных вызовов.
Все функции имеют обязательный ключевой параметр id, большинство также принимают дату или диапазон дат, вот что произошло (в довольно большой книге ~30 листов с графиками):
Когда рабочая книга загружается впервые, функция вызывает весь огонь с устаревшими (ранее сохраненными) значениями, но они игнорируются, поскольку первая строка C# в каждой функции представляет собой (быстрый) тест относительно модели поддержки, чтобы увидеть, загружена ли модель, никакие аргументы функции не проверяются/не сортируются.
Пользователь выбирает загрузку модели через веб-сервисы или ранее сериализованный файл.
Визуальные обновления отключены, и лист настройки заполняется некоторыми данными по порядку; некоторые ключевые даты (начало и конец дат - различные диапазоны дат на разных листах рассчитываются с помощью EMONTH +/- 12), некоторые другие статические данные (имя автора/редактора и т. д.) и, наконец, обязательный идентификатор ключа (который идентифицирует данные модели )
Теперь метод каждой функции в надстройке автоматизации имеет ключ id, поэтому, если данные найдены, они возвращаются, в противном случае используются значения по умолчанию из параметров функции. (Примечание. Модель поддерживает копию полей объектов, запрошенных функциями, поэтому она знает, что было выведено. Для дальнейших вызовов, если данные присутствуют на уровне кэша, они обновляются (свернутый вручную клон либо исходных данных модели, либо предыдущих). значение кеша) с входными параметрами функции - разница между уровнем кеша и данными модели позже загружается в веб-службу) - Не очень хорошо объясненный «кэш» на самом деле является оболочкой той же структуры данных, что и данные модели.
Если данные возвращаются (что указывает на первую загрузку), они помещаются в очередь блокировки, которая обслуживается рабочим потоком, записывающим данные обратно в указанные диапазоны.
Ужасная производительность, по-видимому, связана с очень длинными цепочками зависимых функций (сбивает с толку Excel?) и тем фактом, что функции, по-видимому, вызываются до их зависимостей, например.
Данный DATE_RANGE представляет собой цепочку A1-An из An=EMONTH(An-1,12), где A1 является константой LAST_DATE со страницы настройки, которая уже была заполнена.
Затем fn(MODEL_ID, DATE_RANGE) вызывается после того, как именованная ячейка MODEL_ID заполнена, но DATE_RANGE имеет неверные значения, и fn вызывается повторно по мере завершения каждого EMONTH, а методы функции пытаются преобразовать диапазоны в даты (если недействительные даты возвращаются раньше). ). Тем временем рабочий поток начинает генерировать исключения занятости приложения (и, таким образом, повторно ставить в очередь записи диапазонов и спать в течение произвольного периода 250 мс). В конце концов разногласия утихнут, но у вас будет возможность сначала приготовить и начать пить кофе (возможно, даже смолоть зерна).
Написав этот ужасный код, я решил просто записать даты в лист настройки, а затем дождаться остановки вычислений перед обновлением MODEL_ID — это могло бы каким-то образом уменьшить количество вызовов функций. Однако перехват только изменений, сохранение этих обновлений в модели и пометка соответствующего диапазона как грязного казались намного чище.
Я думаю, что доступны следующие варианты;
- В версии перехвата редактирования попробуйте использовать vb hook OnKey с вызовом каждой возможной функции ASCII для обратного вызова в параметризованную команду C# (код VB может быть по крайней мере сгенерирован в цикле)
- Попробуйте версию перехвата редактирования в качестве надстройки VSTO (это должно дать мне привязки клавиш)
- Используйте ExcelDNA - это выглядит заманчиво для (предыдущей) версии параметра диапазона чтения-записи (которая может доказать адекватную производительность (что, вероятно, укажет на логическую ошибку в моем коде обработки Excel).
(еще раз извиняюсь за длину и неясность)