2

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 Webbrowser, 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?

Jeff
  • 12,085
  • 12
  • 82
  • 152
  • Does anybody know why the focus changes? I would have thought it would have to happen by something actively changing it. Note that I am assuming that both forms are visible when an event occurs in form 1 that provokes HTML load in form 2. – David Heffernan Jul 15 '11 at 20:53
  • In that case I don't understand why invoking a method on a TWebBrowser would result in the other form being activated. – David Heffernan Jul 15 '11 at 20:59
  • @David - I don't know why, but as Cosmin observed, when the browser navigates to a page that wants to set the focus to an element, it steals the focus. Like here: http://stackoverflow.com/questions/1562619/prevent-webbrowser-control-from-stealing-focus – Sertac Akyuz Jul 15 '11 at 21:17

3 Answers3

2

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.

Sertac Akyuz
  • 54,131
  • 4
  • 102
  • 169
  • I'd like to avoid the focus-travel effect (One form gets active, then the other - it is not really visually pleasing :) ) – Jeff Jul 15 '11 at 18:23
2

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.

Cosmin Prund
  • 25,498
  • 2
  • 60
  • 104
1
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.

NGLN
  • 43,011
  • 8
  • 105
  • 200
  • I will accept the Webbrowser Enable thing, because it works. On the FormActivate of the 2nd form, I enable it again. Works like a charm :) – Jeff Jul 15 '11 at 21:19
  • You don't have the hidden caret problem? – NGLN Jul 15 '11 at 21:26
  • @Jeff - Isn't this approach wrong when navigating to pages that don't steal the focus? For example, in my tests navigating to http://stackoverflow.com does not take the focus, then you'll be left with a disabled web browser control. – Sertac Akyuz Jul 15 '11 at 21:39
  • Ok, fine then. For future readers who áre missing the caret like me with D7: change the ActiveControl back and forth, postponed by a small timer interval, and reset SelStart and SelLength. – NGLN Jul 15 '11 at 21:43
  • @Sertac You enable the WebBrowser again when needed, e.g. at form activation like OP mentions. – NGLN Jul 15 '11 at 21:46
  • @NGLN - I still don't quite like the control staying disabled for long. The pointer does not f.i. turn into a 'hand' if you don't click on the page or the form. Or you can't scroll the page without activating it (non-MS mode wheel scrolling). But it's not my call of course.. :) – Sertac Akyuz Jul 15 '11 at 21:48
  • @Sertac - You cannot control the WB actively (with mouse and keyboard) without it having focus, yes? Well, when the Webbrowser gets focus, so does the form, and on the FormActivate, you enable the webbrowser again. Works absolutely gorgeous for me! :) – Jeff Jul 15 '11 at 21:52
  • @NGLN - Mouse events no good, you might enable the control before it navigates, then it steals the focus even if you don't intend to activate. Anyway, Jeff is happy, so I'm shutting up. :) – Sertac Akyuz Jul 15 '11 at 21:58