I have 2 forms - The first form has a bunch of edits, comboboxes, etc, and the 2nd form has a Webbrowser.
I have a routine in the 2nd form, that loads HTML into the Webbrows开发者_运维百科er, and that routine is fired in the OnChange event of all my controls on the first form.
The problem is, that my focused control on my first form looses focus, when the 2nd form is loading the HTML into the webbrowser.
How can I make sure that the 2nd form does not get focus when the routine is fired? Or, more importantly - make sure that the focused control on my first form, does not loose focus?
What you're trying to do goes against the VCL, and probably goes against usual user expectations: When a new window is shown, unless it's a tool window, the usual (and expected) behavior is to move focus to it. The Win32 Api to show a window is ShowWindow and it'll activate the window, unless the SW_SHOWNOACTIVATE
flag (or one of it's variants) is specified.
When you make a VCL form visible, a call to that function is also made. The call to ShowWindow
is buried in procedure TCustomForm.CMShowingChanged(var Message: TMessage)
, a 135 lines procedure that hard-codes the SW_SHOWNORMAL
flag (ie: an Activating flag) for the ShowWindow
call that the VCL makes. Unfortunately that's a big piece of code and overriding it is not going to be easy. If this was my program I'd probably attempt changing the code in place: I'd add a DoNotActivate:Boolean
flag to TCustomForm
and change the single line of code in CMShowingChanged
that calls ShowWindow
for non-MDI forms to take that flag into account and simply call ShowWindow(Handle, SW_SHOWNOACTIVATE)
. If changing the VCL is not something you'd do light-hearted, you can use the following hacky solution:
The trick I'm suggesting is to create the new form (the one holding TWebBrowser
) but do NOT set it's Visible
property to True
. Instead, make manual calls to ShowWindow(Handle, SW_SHOWNOACTIVATE
) to show the form without activating it. Because this code would no longer code through the usual Delphi VCL, owned controls would not automatically be created and shown, so the ShowWindow(...)
call needs to be made recursively, for all TWinControl
descendants of the form:
procedure TForm15.Button1Click(Sender: TObject);
var F: TForm16;
procedure RecursiveShowNoActivate(W: TWinControl);
var i:Integer;
begin
ShowWindow(W.Handle, SW_SHOWNOACTIVATE);
for i:=0 to W.ControlCount-1 do
if W.Controls[i] is TWinControl then
RecursiveShowNoActivate(TWinControl(W.Controls[i]));
end;
begin
F := TForm16.Create(Application);
F.Top := Top + height; // So the new form doesn't overlap mine
RecursiveShowNoActivate(F);
F.WebBrowser1.Navigate('http://msdn.microsoft.com/en-us/library/ms123401');
end;
There's an other catch with this code: Make sure you navigate to a web page that doesn't have a form that's automatically focused. Navigating to microsoft's MSDN library might be unusual for a code sample, but that canonical example (www.google.com) sets focus to the search form.
One simple solution can be to disable the form containing the web browser control. A disabled window will not gain the focus.
When the OnDocumentComplete
event of TWebControl is fired, the browser control is ready to gain focus. Disable the form here, and post yourself a message so that you can enable the form shortly:
const
UM_POSTENABLE = WM_USER + 12;
type
TForm2 = class(TForm)
WebBrowser1: TWebBrowser;
procedure WebBrowser1DocumentComplete(ASender: TObject;
const pDisp: IDispatch; var URL: OleVariant);
private
procedure UMPostEnable(var Msg: TMessage); message UM_POSTENABLE;
end;
var
Form2: TForm2;
implementation
uses Unit1;
{$R *.dfm}
procedure TForm2.WebBrowser1DocumentComplete(ASender: TObject;
const pDisp: IDispatch; var URL: OleVariant);
begin
if Screen.ActiveForm = Form1 then begin
Enabled := False;
PostMessage(Handle, UM_POSTENABLE, 0, 0);
end;
end;
procedure TForm2.UMPostEnable(var Msg: TMessage);
begin
Enabled := True;
end;
Note that, according to the documentation, OnDocumentComplete
can be fired multiple times. But since each call will receive a matching user message, this wouldn't be a problem.
procedure TForm1.FormCreate(Sender: TObject);
begin
Screen.OnActiveFormChange := ActiveFormChanged;
end;
procedure TForm1.ActiveFormChanged(Sender: TObject);
begin
if not (csDestroying in ComponentState) then
if ActiveControl <> nil then
ActiveControl.SetFocus
end;
procedure TForm1.EditOrComboChange(Sender: TObject);
begin
Form2.WebBrowser.SetFocus;
end;
Edit
Maybe this is not the most elegant way. As an alternative I tried
Form2.WebBrowser.Enabled := False;
to prevent the focus exchange at all. This keeps the focus on the edit control and strangely enough does the disabled WebBrowser update to the new page, but more mysteriously, this hides the caret in the edit control on Form1.
精彩评论