delphiwebbrowser-controlc++buildertwebbrowser

Arrow keys switching control in TWebBrowser


I have a problem with TWebBrowser (MSHTML/IE) handling of arrow-keys.

Basically, if I host TWebBrowser and load a HTML file, it displays it incorrectly and arrow keys work. If I add a registry key FEATURE_BROWSER_EMULATION or use X-UA-Compatible meta header it renders the HTML properly but arrow keys stop working (they do work, but they want to "tab" to other control so content scrolling no longer works). It looks as if the keys drop to the main form before (or after) being processed by the TWebBrowser.

I found a solution by handling keydown event and then using something like:

WebBrowser1->Document->parentWindow->scrollBy(0, 100);

This solution works, but I found something better which I'm trying to translate into Delphi/C++ Builder:

This C# code I found does the following:

private void webBrowser1_PreviewKeyDown(object sender, PreviewKeyDownEventArgs e)
    {
    if (e.KeyCode == Keys.Down || e.KeyCode == Keys.Up || e.KeyCode == Keys.Left || e.KeyCode == Keys.Right)
        {
            e.IsInputKey = true;
            return;
        }
    }

I can do something similar with KeyPreview set to true on the form and then handling the VK_LEFT / VK_RIGHT / VK_DOWN / VK_UP keys in the KeyDown event of the form, or use other message handling or ApplicationEvents, to set the Key to 0 (or Handled to true, same effect) for example:

void __fastcall TForm1::ApplicationEventsMessage(tagMSG &Msg, bool &Handled)
{
if (Msg.message == WM_KEYDOWN && ActiveControl &&
   ActiveControl->InheritsFrom(__classid(TWebBrowser))
   )
    {
    if (Msg.wParam == VK_LEFT)  {Handled = true; return;}
    if (Msg.wParam == VK_RIGHT) {Handled = true; return;}
    if (Msg.wParam == VK_UP)    {Handled = true; return;}
    if (Msg.wParam == VK_DOWN)  {Handled = true; return;}
    }
}

Problem is, it is not the same thing. IsInputKey, if set to true, appears to process the keys only for the TWebBrowser control in the above C# code, but there is no such equivalent that I've found in Delphi/C++ Builder.

Any idea how I can drop key processing for the Delphi/C++Builder main form which hosts the TWebBrowser and only let the TWebBrowser do the processing of the key event only for the arrow keys above?

A test HTML to load into WebBrowser control (if it is not scrollable for testing with arrow keys, simply increase the size of the font in the <div> tag:

<html>
  <head>
    <!-- This meta tag ensures that TWebBrowser runs in IE-11 mode -->
    <!-- Which causes issues with scrolling with arrow keys -->
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
  </head>
  <body>
    <div style="font-size:36px;">
      01<br>
      02<br>
      03<br>
      04<br>
      05<br>
      06<br>
      07<br>
      08<br>
      09<br>
      10<br>
      11<br>
      12<br>
      13<br>
      14<br>
      15<br>
      16<br>
      17<br>
      18<br>
      19<br>
      20<br>
      21<br>
      22<br>
      23<br>
      24<br>
      25<br>
      26<br>
      27<br>
      28<br>
      29<br>
      30<br>
    </div>
  </body>
</html>

Code to reproduce - Create a form with a TWebBrowser and a TButton and add this into TButton code: (clicking into control - mouse wheel works, page up/down works, arrows up/down want to "tab" into the button (or other control):

void __fastcall TForm1::Button1Click(TObject *Sender)
{
WebBrowser1->Navigate("about:<html><head><meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\"></head><body><div style=\"font-size:36px;\">01<br>02<br>03<br>04<br>05<br>06<br>07<br>08<br>09<br>10<br>11<br>12<br>13<br>14<br>15<br>16<br>17<br>18<br>19<br>20<br>21<br>22<br>23<br>24<br>25<br>26<br>27<br>28<br>29<br>30<br></div></body></html>");
}

Solution

  • Thanks to @whosrdaddy - The following solution has worked (taken from here). The key was IsDialogMessage function.

    // FIX voor webbrowser keys, install application wide message handler
    constructor TBrowserHelper.Create(ADebug : TDebuggerSlot);
    begin
     Debug := ADebug;
     if Assigned(Debug) then
      Debug.OutputL(Self, 'Create', '', LVL_SPARSE);
     OldMessageHandler := Application.OnMessage;
     Application.OnMessage :=  MsgHandler;
    end;
     
    destructor TBrowserHelper.Destroy;
    begin
     if Assigned(Debug) then
      Debug.OutputL(Self, 'Destroy', '', LVL_SPARSE);
     Application.OnMessage := OldMessageHandler;
     inherited;
    end;
     
    procedure TBrowserHelper.MsgHandler(var Msg: TMsg; var Handled: Boolean);
     
    const StdKeys = [VK_BACK, VK_UP, VK_DOWN, VK_LEFT, VK_RIGHT];
     
    var IOIPAO: IOleInPlaceActiveObject;
        Browser : TEmbeddedWb;
     
    begin
     try
      Browser := nil;
      if Assigned(Screen.ActiveForm) then
       begin
        if Screen.ActiveForm is TFrm_Browser then
         Browser := TFrm_Browser(Screen.ActiveForm).Browser;
        if Assigned(Browser) then
         begin
          Handled := IsDialogMessage(Browser.Handle, Msg);
          if Handled then//and (not Browser.Busy) then
           begin
    //       if Assigned(Debug) then
    //        Debug.OutputL(Self, 'MsgHandler', Format('Message: %x, wParam: %x, lParam: %x', [Msg.message, Msg.wParam, Msg.lParam]), LVL_FULL);
            if ((Msg.message = WM_KEYDOWN) or (Msg.message = WM_KEYUP)) and (Msg.wParam in StdKeys) then
             begin
              //nothing  -  do not pass on Backspace, Left, Right, Up, Down arrows
             end
            else
             begin
              IOIPAO := (Browser.Application as IOleInPlaceActiveObject);
              if Assigned(IOIPAO)then
               IOIPAO.TranslateAccelerator(Msg)
             end;
           end;
         end;
       end;
     except
      //Handled := False;  // leave it for other handlers
     end;
    end;
     
    initialization
    begin
     iCaptSize := GetSystemMetrics(SM_CYCAPTION);
     iBorderSize := GetSystemMetrics(SM_CXBORDER);
     iBorderThick := GetSystemMetrics(SM_CXSIZEFRAME);
     BrowserHelper := TBrowserHelper.Create(nil);
    end;
     
    finalization
    begin
     if Assigned(BrowserHelper) then
      FreeAndNil(BrowserHelper);
    end;