简体   繁体   中英

Get the currently focused control of a non-foreground window?

I have a tool that automates excel by requesting Excel refresh with Application.Calculate - the problem is that (surprisingly) calling Microsoft.Office.Interop.Excel.Application.Calculate will kick the user out of the current action if they are in the middle of editing a cell, renaming a sheet, typing in a ribbon control (like the font box), etc... (That problem described here: Calling Application.Calculate breaks formula being edited )

To counter this, I was able to use WinApi calls to detect whether one of several "edit" controls is active. If I detect the Excel user is "Busy", I simply pause automation to avoid interrupting their editing.

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;
}

The problem (as highlighted in the comments above) is that the GetFocus() WinApi call only works on the current foreground window. What I really want to know is what control has focus in the main Excel application window, regardless of whether that window is currently active.

For example, if the user is in the middle of typing a formula (calculations pause) and the user alt-tabs over to the browser to google something, I don't want to un-suspend automation or their half-typed formula will be lost.

I'm pretty sure what I need next is some winapi function akin to "GetFocus" but that gets the "active" or "focused" control for an application window that doesn't happen to currently be in the foreground.

I'm trying to avoid having to monitor the user's every action to track as they leave and re-enter the excel application. I'm looking for as light-weight and stateless a check as possible to determine whether, at any given instance, the user is in the middle of an edit operation in the Excel application.

Each thread gets its own focus window (supposedly) but you can't really access it when the window is not active. GetGUIThreadInfo returns NULL values when the window is not active:

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);
}

At the Win32 level the code has to restore the focus to the correct control manually when you switch to the application: ... handling of the WM_ACTIVATE and WM_SETFOCUS messages to preserve the focus when the user switches away from the window and back .

You could try UI Automation , Office generally has good support for it and might expose activation/focus information for its controls.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM