独立进程中WebControl窃取焦点问题的再探讨
Nice call splitting the WebControl into a separate subprocess to avoid memory leaks—super smart approach for handling those messy JS libraries! Let's tackle that annoying focus-stealing issue you're hitting with the IE-embedded window. Here are some actionable, untested (for your specific setup) ideas you can try, with code snippets tailored to Delphi but adaptable to .NET languages too:
WS_EX_NOACTIVATE Extended Window Style to the WebControl This is probably the cleanest fix—this style prevents the window from ever receiving activation focus, so it can't steal focus from other apps. In Delphi, you'll need to modify the WebBrowser's underlying window after it's created:
procedure TChildForm.FormCreate(Sender: TObject); var ExStyle: DWORD; begin // Get current extended window style ExStyle := GetWindowLongPtr(WebBrowser1.Handle, GWL_EXSTYLE); // Add WS_EX_NOACTIVATE to prevent activation ExStyle := ExStyle or WS_EX_NOACTIVATE; SetWindowLongPtr(WebBrowser1.Handle, GWL_EXSTYLE, ExStyle); // Also ensure the window doesn't get activated when moved/resized SetWindowPos(WebBrowser1.Handle, 0, Left, Top, Width, Height, SWP_NOACTIVATE or SWP_NOMOVE or SWP_NOSIZE); end;
For C#/VB.NET, you'd use GetWindowLongPtr and SetWindowLongPtr via P/Invoke, targeting the WebBrowser control's Handle.
If the style change doesn't work, you can subclass the WebBrowser's window to catch and block focus-related Windows messages like WM_SETFOCUS, WM_ACTIVATE, and WM_ACTIVATEAPP. Here's a Delphi example:
type TChildForm = class(TForm) WebBrowser1: TWebBrowser; procedure FormCreate(Sender: TObject); private FOldWndProc: TWndMethod; procedure NewWndProc(var Message: TMessage); end; procedure TChildForm.FormCreate(Sender: TObject); begin FOldWndProc := WebBrowser1.WindowProc; WebBrowser1.WindowProc := NewWndProc; end; procedure TChildForm.NewWndProc(var Message: TMessage); var PrevForegroundWnd: HWND; begin case Message.Msg of WM_SETFOCUS: begin // Get the window that had focus before PrevForegroundWnd := GetForegroundWindow; if PrevForegroundWnd <> 0 then SetForegroundWindow(PrevForegroundWnd); // Don't pass the message to the original proc Exit; end; WM_ACTIVATE: begin // Block activation (wParam = WA_ACTIVE or WA_CLICKACTIVE) if (Word(Message.WParam) = WA_ACTIVE) or (Word(Message.WParam) = WA_CLICKACTIVE) then begin Message.Result := 0; Exit; end; end; end; // Pass all other messages to the original handler FOldWndProc(Message); end;
This will either block the focus change entirely or immediately switch back to the user's active window.
If the above don't catch all cases (like JS-triggered focus calls that bypass the control's messages), you can set up a system-wide hook to listen for foreground window changes and revert them if the WebControl's window steals focus. Here's how to do it in Delphi:
var FHookHandle: HWINEVENTHOOK; FTargetProcessId: DWORD; FPrevForegroundWnd: HWND; // Hook callback function function WinEventHookCallback(hWinEventHook: HWINEVENTHOOK; event: DWORD; hwnd: HWND; idObject: LONG; idChild: LONG; idEventThread: DWORD; dwmsEventTime: DWORD): stdcall; var CurrentProcessId: DWORD; begin Result := 0; if event = EVENT_SYSTEM_FOREGROUND then begin GetWindowThreadProcessId(hwnd, @CurrentProcessId); // Check if the foreground window belongs to our subprocess if CurrentProcessId = FTargetProcessId then begin // Restore the previous foreground window if FPrevForegroundWnd <> 0 then SetForegroundWindow(FPrevForegroundWnd); end else begin // Update the previous window if it's not our subprocess FPrevForegroundWnd := hwnd; end; end; end; // Setup the hook in your main process before launching the subprocess procedure SetupFocusHook(ProcessId: DWORD); begin FTargetProcessId := ProcessId; FPrevForegroundWnd := GetForegroundWindow; FHookHandle := SetWinEventHook(EVENT_SYSTEM_FOREGROUND, EVENT_SYSTEM_FOREGROUND, 0, @WinEventHookCallback, ProcessId, 0, WINEVENT_OUTOFCONTEXT); end; // Don't forget to unhook when done procedure CleanupFocusHook; begin if FHookHandle <> 0 then UnhookWinEvent(FHookHandle); end;
This works across processes and will catch any focus change triggered by the subprocess, even if it's initiated from JS.
As a last resort, you can inject a script into the loaded page that overrides focus-related methods to prevent them from triggering system focus changes. For example, in Delphi, after navigating, inject this script:
procedure TChildForm.WebBrowser1DocumentComplete(Sender: TObject; const pDisp: IDispatch; var URL: OleVariant); var Doc: IHTMLDocument2; begin if WebBrowser1.Document.QueryInterface(IHTMLDocument2, Doc) = S_OK then begin Doc.parentWindow.execScript( 'HTMLElement.prototype.focus = function() {};' + 'window.focus = function() {};' + 'document.focus = function() {};', 'JavaScript' ); end; end;
This will neuter JS focus() calls, though it might break some page functionality—so only use this if the other methods fail.
All these approaches should help keep the user's active app in focus while your subprocess handles the AJAX loading. Start with the WS_EX_NOACTIVATE style since it's the least intrusive!
内容的提问来源于stack exchange,提问作者Tom

