获取非前景窗口的当前焦点控件?

时间:2018-06-04 20:52:16

标签: c# excel winapi com office-addins

我有一个工具可以通过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;
}

问题(如上面的评论中所强调的)是GetFocus() WinApi调用仅适用于当前前景窗口。我真正想知道的是控件在主Excel应用程序窗口中的重点,无论该窗口当前是否处于活动状态。

例如,如果用户正在键入公式(计算暂停)并且用户使用alt-tabs到浏览器谷歌某些东西,我不想取消暂停自动化或他们的一半 - 配方将丢失。

我非常确定我接下来需要的是一些类似于&#34; GetFocus&#34;但是这得到了活跃的&#34;或者&#34;专注于&#34;控制当前不在前台的应用程序窗口。

我试图避免在他们离开并重新进入excel应用程序时监控用户要跟踪的每个操作。我正在寻找尽可能轻量级和无状态的检查,以确定在任何给定的实例中,用户是否处于Excel应用程序中的编辑操作中。

1 个答案:

答案 0 :(得分: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通常会对其提供良好支持,并可能会为其控件公开激活/焦点信息。