Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to prevent delphi TWebBrowser for stealing focus

I'm going crazy with Delphi and WebBrowser component.

I created a simple application, to type HTML in a Memo and display the result inside the WebBrowser component.

But, when I click inside the WebBrowser, each HTML code update (in memo) results in the steal of the focus by the WebBrowser component.

Here is the step-by-step to reproduce the problem:

  • create a new VCL application
  • add a TWebBrowser component to display the html
  • add a TMemo component to type the html code
  • insert on Memo1.OnChange event this:

    if WebBrowser1.Document = nil then
    begin
      WebBrowser1.Navigate('about:blank');
      while WebBrowser1.ReadyState <> READYSTATE_COMPLETE do
        Application.ProcessMessages;
    end;
    
    ms := TMemoryStream.Create;
    try
      Memo1.Lines.SaveToStream(ms);
    
      ms.Seek(0, 0);
    
      (WebBrowser1.Document as IPersistStreamInit).Load(TStreamAdapter.Create(ms)) ;
    finally
      ms.Free;
    end;
    
  • run the application

  • type the HTML inside the Memo

    <html>
    <body>
    Hello WebBrowser
    </body>
    </html>
    
  • click inside the WebBrowser content

  • go back to the memo and try type some more changes in HTML
  • here we go*, on each key press the webbrowser component steals the focus for it!

How can I solve this and prevent the "steal of the focus" ?

Ps.: the only workaround is pressing the TAB key after clicking on WebBrowser, this prevent the webbrowser the steal the focus after new changes in html code via memo.


Solved with this workaround.

Change the Memo1.OnChange code to this:

  procedure TForm1.Memo1Change(Sender: TObject);
  var
    ms: TMemoryStream;
  begin
    LockWindowUpdate(panel1.Handle); // fix: lock webbrowser parent updates

    // fix: re-set the webbrowser parent to prevent focus stealing
    TControl(WebBrowser1).Parent := nil; 
    TControl(WebBrowser1).Parent := panel1;
    // fix:eof

    if WebBrowser1.Document = nil then
    begin
      WebBrowser1.Navigate('about:blank');
      while WebBrowser1.ReadyState <> READYSTATE_COMPLETE do
        Application.ProcessMessages;
    end;

    ms := TMemoryStream.Create;
    try
      Memo1.Lines.SaveToStream(ms);

      ms.Seek(0, 0);

      (WebBrowser1.Document as IPersistStreamInit).Load(TStreamAdapter.Create(ms)) ;
    finally
      ms.Free;
    end;

    LockWindowUpdate(0); // fix: unlock webbrowser parent updates to prevent flicking!!
  end;
like image 591
Beto Neto Avatar asked Nov 11 '22 04:11

Beto Neto


1 Answers

Add a checkbox and another memo, Memo2, to your form, and point its OnChange at your handler for Memo1. Then, change your handler as shown below. You should find that even after provoking the focus-stealing using Memo1, it doesn't happen when you type into Memo2 (with the checkbox checked, of course).

I realise that what I'm doing with Memo2 isn't quite the same as what you are, but it may be preferable in that the memo isn't required to contain the outer structure of the HTML document. Depending on what you're doing, that may be an advantage or a disadvantage.

Fwiw, calling ShowCaret(Memo1.Handle) after clicking in the WB returns false - GetLastError/SysErrorMsg report 5/Access Denied, so I'm guessing that clicking in the WB somehow clobbers Memo1's caret - I mean more than you'd expect just from shifting focus. I am still looking into that.

procedure TForm1.Memo1Change(Sender: TObject);
var
  ms : TMemoryStream;
begin
  if WebBrowser1.Document = nil then
  begin
    WebBrowser1.Navigate('about:blank');
    while WebBrowser1.ReadyState <> READYSTATE_COMPLETE do
      Application.ProcessMessages;
  end;

  try
    if CheckBox1.Checked then begin
      (WebBrowser1.Document as IHtmlDocument2).Body.innerHtml := Memo2.Lines.Text;
    end
    else begin
      ms := TMemoryStream.Create;
      try
        Memo1.Lines.SaveToStream(ms);
        ms.Seek(0, 0);
        (WebBrowser1.Document as IPersistStreamInit).Load(TStreamAdapter.Create(ms)) ;
      finally
        ms.Free;
      end;
    end;
  except
    // silence any exceptions raised when using CheckBox1.Checked = True
  end;
end;

Update:

If you'd prefer something closer to your original than my suggestion above, the following is more compact and seems to run a bit faster:

procedure TForm1.HandleMemo1ChangeNew(Sender : TObject);
var
  Doc : IHtmlDocument2;
  V : OleVariant;
begin
  if WebBrowser1.Document = nil then
    WebBrowser1.Navigate('about:blank');

  Doc := WebBrowser1.Document as IHTMLDocument2;
  V := VarArrayCreate([0, 0], varVariant);
  V[0] := Memo1.Lines.Text;
  Doc.Write(PSafeArray(TVarData(v).VArray));
  Doc.Close;
end;

procedure TForm1.WebBrowser1NavigateComplete2(Sender: TObject;
  const pDisp: IDispatch; var URL: OleVariant);
begin
  WebBrowser1.Enabled := False;
end;
like image 122
MartynA Avatar answered Nov 15 '22 07:11

MartynA