【问题标题】:Get the currently focused control of a non-foreground window?获取非前景窗口的当前焦点控制?
【发布时间】:2018-11-14 06:17:43
【问题描述】:

我有一个工具可以通过使用Application.Calculate 请求 Excel 刷新来自动执行 excel - 问题是(令人惊讶的)调用 Microsoft.Office.Interop.Excel.Application.Calculate 会在用户正在编辑单元格时将其踢出当前操作,重命名工作表,键入功能区控件(如字体框)等...(此处描述的问题:Calling Application.Calculate breaks formula being edited

为了解决这个问题,我能够使用 WinApi 调用来检测几个“编辑”控件之一是否处于活动状态。如果我检测到 Excel 用户“忙碌”,我会暂停自动化以避免中断他们的编辑。

public static bool IsExcelBusy(Application xlApp, out string reason)
{
    IntPtr excelHwnd = (IntPtr)xlApp.Hwnd;
    uint excelThreadId = NativeMethods.GetWindowThreadProcessId(excelHwnd, out uint excelProcessId);
    // Get the handle of whatever window is in the foreground (system-wide)
    IntPtr foreground = NativeMethods.GetForegroundWindow();

    // Problem: If a non-excel-owned process has focus, we cannot get the focused control
    uint foregroundThreadId = NativeMethods.GetWindowThreadProcessId(foreground, out uint foregroundProcessId);
    if (foregroundProcessId != excelProcessId)
        return false; // How can we know what control has focus?
    // Otherwise, the following works:
    try
    {
        // We need to attach the thread that owns this window to get the focused control
        uint thisThreadId = NativeMethods.GetCurrentThreadId();
        NativeMethods.AttachThreadInput(foregroundThreadId, thisThreadId, true);
        IntPtr focusedControlHandle = NativeMethods.GetFocus();
        if (focusedControlHandle != IntPtr.Zero)
        {
            // Get the class name of the control that the user is currently interacting with (if any)
            StringBuilder classNameResult = new StringBuilder(256);
            NativeMethods.GetClassName(focusedControlHandle, classNameResult, 256);
            string className = classNameResult.ToString();
            // Determine if this control is at risk of being interrupted by a recalculations
            switch (className)
            {
                case "EXCEL6":
                    reason = "User is editing a cell";
                    return true;
                case "EXCEL<":
                    reason = "User is editing in the formula bar";
                    return true;
                case "RICHEDIT60W":
                    reason = "User is editing a ribbon control";
                    return true;
                case "Edit":
                    isActivitySensitive = true;
                    reason = "User is in the named range box";
                    return true;
                case "EXCEL=":
                    isActivitySensitive = true;
                    reason = "User is renaming a sheet";
                    return true;
            }
        }
    }
    finally
    {
        NativeMethods.AttachThreadInput(foregroundThreadId, thisThreadId, false);
    }
    return false;
}

问题(在上面的 cmets 中突出显示)是 GetFocus() WinApi 调用仅适用于当前前台窗口。我真正想知道的是主 Excel 应用程序窗口中的焦点是什么控件,无论该窗口当前是否处于活动状态。

例如,如果用户正在输入公式(计算暂停)并且用户 alt-tab 切换到浏览器以搜索某些内容,我不想取消暂停自动化或他们的半输入公式将丢失。

我很确定我接下来需要的是一些类似于“GetFocus”的 winapi 函数,但它可以为当前不位于前台的应用程序窗口获取“活动”或“聚焦”控件。

我试图避免在用户离开和重新进入 Excel 应用程序时监控用户的每一个动作。我正在寻找尽可能轻量级和无状态的检查,以确定在任何给定情况下,用户是否处于 Excel 应用程序中的编辑操作中间。

【问题讨论】:

  • 值得评论的是,我没有使用其他“技巧”来检测 Excel 用户是否处于编辑模式(如Application.Interactive = False)的原因是即使这些 COM 操作也会“踢" 用户退出诸如重命名工作表之类的操作。我需要一个真正不引人注目的用户活动测试,而 Excel 不提供开箱即用的测试。
  • 只有前台窗口有焦点控件。总的来说,只有一个控件具有焦点。听起来您正在使正在做的事情变得复杂,因此,命令重叠并变得混乱,
  • @ashleedawg 如何引用当您返回应用程序窗口后将拥有焦点的控件,但由于另一个应用程序具有焦点,因此当前没有焦点。我只能想到“主动控制”这个词。即使语言使我难以理解,希望您只需在 PC 上的应用程序之间跳来跳去就能明白我的意思。
  • @ashleedawg 没那么复杂。这就像一个想要向用户发送推送通知的应用程序,但如果他们正在开车,则不会。今天可能有一个 API 调用“用户是否在潜水”,但并非总是如此。所以他们需要使用启发式方法。 Excel 没有 API 调用或完善的启发式方法来确定用户是否正在编辑公式,但这并不意味着使用 Excel 自动化的善意开发人员不应该尝试找出这些信息。

标签: c# excel winapi com office-addins


【解决方案1】:

Each thread gets its own focus window(据说)但是当窗口不活动时你不能真正访问它。 GetGUIThreadInfo 在窗口不活动时返回 NULL 值:

HWND hWnd = FindWindow(...);
DWORD tid = GetWindowThreadProcessId(hWnd, NULL);
GUITHREADINFO gti;
gti.cbSize = sizeof(GUITHREADINFO);
if (GetGUIThreadInfo(tid, &gti))
{
    printf("hwndFocus=%p hwndActive=%p\n", gti.hwndFocus, gti.hwndActive);
}

在Win32级别the code has to restore the focus切换到应用程序时手动正确控制:...handling of the WM_ACTIVATE and WM_SETFOCUS messages to preserve the focus when the user switches away from the window and back

您可以尝试UI Automation,Office 通常对其提供良好的支持,并且可能会公开其控件的激活/焦点信息。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-09-23
    • 1970-01-01
    • 1970-01-01
    • 2018-05-03
    • 2012-02-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多