Ловушка Global Keyboard для надстройки автоматизации (Excel) (не VSTO)

Извиняюсь за длину этого поста, но я думаю, что предыстория необходима, чтобы передать то, чего я пытаюсь достичь.

Мне поручили обновить старую надстройку 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 листов с графиками):

  1. Когда рабочая книга загружается впервые, функция вызывает весь огонь с устаревшими (ранее сохраненными) значениями, но они игнорируются, поскольку первая строка C# в каждой функции представляет собой (быстрый) тест относительно модели поддержки, чтобы увидеть, загружена ли модель, никакие аргументы функции не проверяются/не сортируются.

  2. Пользователь выбирает загрузку модели через веб-сервисы или ранее сериализованный файл.

  3. Визуальные обновления отключены, и лист настройки заполняется некоторыми данными по порядку; некоторые ключевые даты (начало и конец дат - различные диапазоны дат на разных листах рассчитываются с помощью EMONTH +/- 12), некоторые другие статические данные (имя автора/редактора и т. д.) и, наконец, обязательный идентификатор ключа (который идентифицирует данные модели )

  4. Теперь метод каждой функции в надстройке автоматизации имеет ключ id, поэтому, если данные найдены, они возвращаются, в противном случае используются значения по умолчанию из параметров функции. (Примечание. Модель поддерживает копию полей объектов, запрошенных функциями, поэтому она знает, что было выведено. Для дальнейших вызовов, если данные присутствуют на уровне кэша, они обновляются (свернутый вручную клон либо исходных данных модели, либо предыдущих). значение кеша) с входными параметрами функции - разница между уровнем кеша и данными модели позже загружается в веб-службу) - Не очень хорошо объясненный «кэш» на самом деле является оболочкой той же структуры данных, что и данные модели.

  5. Если данные возвращаются (что указывает на первую загрузку), они помещаются в очередь блокировки, которая обслуживается рабочим потоком, записывающим данные обратно в указанные диапазоны.

Ужасная производительность, по-видимому, связана с очень длинными цепочками зависимых функций (сбивает с толку 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).

(еще раз извиняюсь за длину и неясность)


person earcam    schedule 24.10.2011    source источник
comment
По какой-то причине SO задает вопрос, имеющий право на награду через 2 дня, я с радостью присужу награду любому, кто решит это до этого (вам просто нужно доверять мне в течение 2 дней).   -  person earcam    schedule 24.10.2011
comment
Рассматривали ли вы кеширование результатов UDF, чтобы, если данный вызов был сделан в течение x времени после предыдущего идентичного вызова, вы бы просто вернули кешированный результат вместо того, чтобы совершать еще один круговой обход?   -  person Tim Williams    schedule 24.10.2011
comment
@TimWilliams - Спасибо, но весь набор данных загружается заранее (он не изменяется на стороне сервера), поэтому время поиска функции настолько короткое, насколько это возможно (буквально функции просто ищут данные в словаре с помощью некоторого linq ). Профилирование показывает, что это миллион повторных вызовов (все они нуждаются в сортировке, поскольку параметры могут быть ссылками на диапазоны или литералы).   -  person earcam    schedule 24.10.2011
comment
Почему вы получаете миллионы повторных звонков?   -  person Charles Williams    schedule 24.10.2011
comment
@earcam — ваше описание надстройки, похоже, предполагает, что пользователи редактируют возвращаемые значения, пока функции все еще работают. Как это работает - они заменяют формулы рабочего листа фактическими значениями? Было ли оригинальное решение в VBA? Наличие кода в одном и том же процессе определенно дает огромное преимущество в производительности.   -  person Tim Williams    schedule 25.10.2011
comment
Чарльз, я обновил Q с подробностями. Тим, первая (выпущенная) версия использовала C++ в XLL (из книги Стива Далтона) и запускала JVM, с которой он общался с использованием JNI, в рабочих книгах были различные VBA/COM/и т. д. для привязки элементов управления и т. д. на место. Моя первая версия была написана на C# в качестве надстройки автоматизации с функциями, имеющими параметры чтения-записи и слишком медленной (редактирование вопроса), моя вторая версия работает хорошо (без записи в параметры), и хотя перехват редактирования/вставки ячеек функционально/визуально в порядке отсутствие сочетаний клавиш делает его слишком неудобным для использования   -  person earcam    schedule 25.10.2011


Ответы (2)


Вы должны быть в состоянии решить проблему с цепочкой вычислений, поскольку это звучит как дискретный набор последовательных шагов.
Если вы используете C++/XLL, вы должны указать тип аргументов функции P, что обеспечит их вычисление в Excel перед передачей в УДФ. Я думаю, что Ex cel DNA/addin Express должен иметь такой же эффект, если параметры определены как что-либо кроме Object.

Excel вычисляет ячейки в последовательности LIFO, которая устанавливается предыдущей окончательной последовательностью вычислений и любой ячейки, которые были введены/изменены: поэтому последняя измененная формула вычисляется первой.
Таким образом, вам следует вводить формулы в вашей цепочке DATE_RANGE в обратной последовательности (последняя в цепочке первой)
Предположительно, вы уже переход в ручной режим расчета в начале этого процесса. Таким образом, это может быть так же просто, как написать лист настройки и даты, затем выполнить расчет (Application.calculate), затем обновить MODEL_ID, а затем принудительно выполнить еще один расчет.

И, конечно же, с помощью ДНК Excel накладные расходы за вызов функции в любом случае будет намного меньше.
см. http://fastexcel.wordpress.com/2011/07/07/excel-udf-technology-choices-snakes-ladders-with-vba.-vb6-net-c-com-xll-interop/

person Charles Williams    schedule 25.10.2011
comment
Спасибо, Чарльз. Я получил привязки клавиш, работающие с использованием Application.OnKey, и сгенерировал модуль VB из C #, перебирая ключи, которые я хочу перехватить (хотя я был немного озадачен тем, что VB Evaluate дважды вызывает UDF - пока я не прочитал ваш ответ на stackoverflow.com/questions /2611929/ и за это спасибо!). Я не осознавал, насколько медленным было взаимодействие С#, пока не прочитал ссылку, которую вы разместили, - думаю, я перейду на ExcelDNA (не только для производительности, это избавит от второй надстройки XLAM для элементов управления ленты и предоставит справку Fn) - person earcam; 25.10.2011
comment
Принимая ответ @CharlesWilliams, это не решение глобальной перехвата клавиатуры в надстройке автоматизации (что не представляется возможным). Но его комментарии были показательными. - person earcam; 25.10.2011

Я не могу помочь вам с глобальным перехватом клавиатуры, но вам действительно следует взглянуть на Excel DNA или Addin Express, чтобы значительно повысить производительность ваших UDF C # (они взаимодействуют с .NET с помощью XLL C API, который НАМНОГО быстрее, чем автоматизация С#) .
И Excel DNA, и Addin Express также имеют темы на своих форумах поддержки, в которых обсуждается, как перезаписать данные из пользовательских функций в другие диапазоны. IIRC Excel DNA обсуждает подход с отдельными потоками, а Addin Express обсуждает использование UDF командно-эквивалентного типа для запуска скрытой функции XLM

И лично я думаю, что будет очень сложно заставить ваш глобальный подход к крючкам клавиатуры работать. ненавязчиво и эффективно также при любых обстоятельствах (открыто несколько книг, VBA, DDE и т. д. и т. д.).

person Charles Williams    schedule 24.10.2011
comment
Привет, Чарльз, спасибо за предложения, я начал опасаться, что может прийти к использованию другого подхода - я уже сжег несколько вечеров и выходных на этом, поэтому не хочу меняться на этом позднем этапе, однако я предпочел бы переписать чем любые дальнейшие кирпичные стены. Я посмотрю на ДНК и еще раз спасибо. - person earcam; 24.10.2011
comment
См. add-in-express.com/docs/net. -excel-udf-tips.php для комментариев Addin Express о пользовательских функциях, записываемых за пределы вызывающей ячейки - person Charles Williams; 25.10.2011
comment
Пройдя через мир боли, я сейчас пишу полную интеграцию Excel с ExcelDNA, Говерт оказался очень полезным ( groups.google.com/group/exceldna/browse_thread/thread/ ). Спасибо, что указали мне правильное направление. - person earcam; 27.10.2011