Вызов Application.Calculate разрывает редактируемую формулу - PullRequest
0 голосов
/ 07 мая 2018

Я работаю над надстройкой, которая периодически запрашивает пересчет, используя следующее:

((Excel.Application) xlApp).Calculate()

Если пользователь в тот момент редактировал формулу, При этом Excel нарушает формулу пользователя .

Я ожидаю, что действие завершится неудачей из-за активности пользователя, что оно и делает, но не раньше, чем странным образом прерывает любую формулу, которую вводит пользователь.


Например, если пользователь набирает текст в ячейке =SUM(1+2+, как только я запускаю приведенную выше строку кода, его ввод прерывается, и Excel жалуется, что его формула неполна или похожа.

enter image description here

Обратите внимание, что это даже не то же самое поведение, как если бы пользователь нажал "Enter", что привело бы к диалогу, подобному этому:

enter image description here

Excel делает что-то более странное и пытается выкинуть пользователя из формул ввода без всяких изысков.

Даже страннее - если пользовательская формула синтаксически верна, Excel не только исключает их из редактирования формулы, но и заменяет содержимое формулы результатом!

enter image description here

enter image description here


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

Я пробовал вызывать Calculate() в основном приложении, принадлежащем Excel, и в фоновом режиме, но оба ведут себя одинаково. Я пробовал разные методы расчета (например, CalculateFull()), но это то же самое. Я также пробовал другие действия взаимодействия, такие как xlApp.StatusBar = "Test", и они не прерывают действия пользователя, как Calculate.

Могу ли я что-нибудь сделать, чтобы предотвратить прерывание работы пользователя таким образом? Я мог бы поклясться, что это не было поведением в старых версиях Excel.

Если это имеет значение, я использую библиотеку Excel-Dna в качестве основы для моей надстройки, но я просто использую Microsoft.Office.Interop.Excel.Application для этой части.


Обновление с еще большей странностью:

Я добился небольшого успеха, используя метод, описанный здесь , чтобы проверить, могу ли я установить Application.Interactive на false, а затем обратно на true - если нет, пользователь редактирует ячейку. Это позволяет мне пропустить Calculate в тех случаях, когда они находятся в редакторе формул, но, как ни странно, не мешает Excel выбивать пользователя из других вводов.

Например, если пользователь:

  • Редактирование имени листа
  • Назначение имени именованного диапазона ячейке
  • Ввод нового имени шрифта в поле ленты "Шрифт"
  • и т.д ...

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

Обновление re: Application.Interactive

Оказывается, настройка Application.Interactive = True вызывает еще больше проблем с прерыванием работы пользователя, таких как выделение фокуса из диалогов и прерывание операций перетаскивания мышью (таких как изменение размера или перемещение окон). Не рекомендуется в качестве решения, если ваша цель - не раздражать пользователей.

1 Ответ

0 голосов
/ 01 июня 2018

EDIT

Использование Application.Interactive вызвало больше проблем, чем решило. Это решение не рекомендуется.


Это все еще в стадии разработки, но до сих пор мне приходилось прибегать к множеству хаков, чтобы определить, активно ли пользователь что-то делает в Excel, и просто не дать мне вызвать «Рассчитать», если так.

Основные проверки:

  • Окно, принадлежащее Excel, в настоящее время находится на переднем плане?
    • Если нет, то пользователь использует какую-то другую программу, и вызов Calculate не прервет их.
    • Если это так, это главное окно рабочей книги (Excel.Application.Hwnd)?
      • Если нет, пользователь находится в каком-то диалоговом окне Excel, редакторе VBA и т. Д. Не прерывать.
      • Если это так, нам нужно копать глубже.
  • Мышь пользователя в данный момент не работает?
    • Если это так, они определенно заняты (перетаскивание, нажатие, изменение размера и т. Д.). Не прерывайте.
  • Находится ли курсор пользователя в каком-либо редактируемом элементе управления? (Переименование листа, выбор шрифта из раскрывающегося списка, ввод в поле именованного диапазона и т. Д.).
    • Если это так, не перебивайте. (Пока не выяснил, как это проверить).
  • Пользователь редактирует ячейку? (Можно проверить это специально с Excel.Application.Interactive)
    • Если это так, не перебивайте.

Есть еще вещи, которые нужно обойти, но это уже очень помогает.


Вот код - надеюсь, комментарии прояснят логическую последовательность:

/// <summary>A variety of checks to see whether Excel is busy. This is required because
/// in recent versions of Excel, invoking Recalculate can interrupt user activity.</summary>
private static bool IsExcelBusy(Application xlApp)
{
    try
    {
        // Check whether the main Excel application window is currently in the foreground
        // We do this by getting Excel's native window handle, and that of the foreground window
        IntPtr xlHwnd = (IntPtr)xlApp.Hwnd;
        IntPtr foreground = NativeMethods.GetForegroundWindow();
        if (xlHwnd != foreground)
        {
            // If the main Excel window is not in the foreground, see who owns the
            // foreground window by getting the id of each window's owning process.
            NativeMethods.GetWindowThreadProcessId(xlHwnd, out uint xlProc);
            NativeMethods.GetWindowThreadProcessId(foreground, out uint foregroundProc);
            // If the foreground window is owned by the Excel application, return busy
            // If the foreground window is some other process, Excel itself is not busy.
            return xlProc == foregroundProc;
        }
        // If the main excel window is active, ensure the user isn't doing anything.

        // Check if the user currently has a downed mouse button (to avoid interrupting
        // drag operations within the Excel application)
        if (Control.MouseButtons != MouseButtons.None)
            return true;

        // TODO: Check whether the user's cursor in some other Excel control (like
        //       renaming a sheet, typing in the Named Range box, typing in the Font box, etc.

        // The user is editing a formula if Interactive is true and cannot be set to false.
        // https://www.add-in-express.com/creating-addins-blog/2011/03/23/excel-check-user-edit-cell/
        // NOTE: Even setting App.Interactive can interrupt user activity, so do this test last.
        if (xlApp.Interactive)
        {
            xlApp.Interactive = false;
            xlApp.Interactive = true;
        }

        // Otherwise, assume Excel is not busy.
        return false;
    }
    catch (AccessViolationException)
    {
        return true;
    }
    catch (COMException)
    {
        return true;
    }
}

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

...