5

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;
Beto Neto
  • 3,962
  • 7
  • 47
  • 81
  • 2
    Have you considered NOT updating the WebBrowser on every keystroke? I would use a separate button instead, only update the WebBrowser when the user is done typing and tells the app to update. – Remy Lebeau Jul 30 '14 at 16:03
  • 2
    Going off of Remy's point, you can use a timer, set to for example, 1 second (1000 ms). On every keystroke, reset the timer by setting Enabled False then True again. Then, on timer execute, refresh the browser and disable the timer until more user input. – Jerry Dodge Jul 30 '14 at 16:14
  • Jerry, the problem continues to occur. Remy, this is the point, we want to give real-time feedback to the user about what is resulting about his code. – Beto Neto Jul 30 '14 at 16:33
  • 1
    set `panel1.Enabled := False` (the TWebBrowsers parent) – kobik Jul 30 '14 at 16:47
  • @BetoNeto I wasn't mentioning that as a proposed solution to your problem, but as a proposed solution to the point that Remy rose. – Jerry Dodge Jul 30 '14 at 17:15

2 Answers2

1

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;
MartynA
  • 30,454
  • 4
  • 32
  • 73
0

The only way I found to solve this situation was to adopt the solution below:

  1. Add a TButton and a TEdit to the WEBBrowser form.

  2. Leave these components outside the visible area of the form, assigning a negative value to the Top property of both (Keep the properties Visible=True).

  3. In the Button's OnClick event, add:

    • Edit1.SetFocus;
    • Edit1.Text:=DateTimeToStr(Now);
  4. In the WebBrowser's CommandStateChange event, add:

    • ToolButton1.Click;
ChrisGPT was on strike
  • 127,765
  • 105
  • 273
  • 257