开发者

Make sure Form does not get focus when routine is run

开发者 https://www.devze.com 2023-03-20 11:31 出处:网络
I have 2 forms - The first form has a bunch of edits, comboboxes, etc, and the 2nd form has a Webbrowser.

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.

0

精彩评论

暂无评论...
验证码 换一张
取 消