0

I'm working on a Winforms project that uses CefSharp as a Gui. For several reasons I would like to implement a custom context menu using the Winforms ContextMenu class; rendering the menu in Html or customizing the ChromiumWebBrowser's context menu (using CefSharp.IContextMenuHandler) are not an option.

The context menu it triggered by Javascript code that calls a method on a .net object I passed to RegisterAsyncJsObject; the default context menu is prevented using Javascript. I'm invoking the method call on the Gui thread, because the call over the "javascript bridge" to the registered object comes from a different thread.

My problem: when manually showing the Winforms context menu over the CefSharp.WinForms.ChromiumWebBrowser the context menu does not get the keyboard focus (e.g. selecting items with the arrow key doesn't work nor can I close the contextmenu using Esc); instead the keyboard focus remains with the ChromiumWebBrowser control. And, if I click on the ChromiumWebBrowser's control area the context menu doesn't close either. I can only close the context menu by selecting an item with the mouse or clicking on another control within the form (in which the ChromiumWebBrowser is contained) or somewhere completely else (e.g. desktop or another application).

If I trigger the context menu from elsewhere in my code - ultimately using the same method that calls myContextMenu.Show() - the context menu gets the keyboard focus as desired. But one problem still remains: it doesn't close when I click within the ChromiumWebBrowser control.

I haven't used IFocusHander, IContextMenuHandler, IKeyboardHandler - should I?

I'm using CEF 3.2454.1344.g2782fb8, Chromium 45.0.2454.101 and .net 4.5.1.

Unfortunately extracting demo code isn't reasonably possible.

Anyone any ideas?

EDIT1: After reading the comments, I decided to describe the code flow more precisely:

  • When right clicking Javascript sends a message to the registered .net object, containing the mouse coordinates. The default context menu is prevented by setting preventDefault on the MouseEvent arguments of the ContextMenu event.
  • The registered .net object receives the messages and calls windowForm.Invoke(Sub() ... ), because the message is not received on the Main/Gui thread, but must be processed there for the context menu to appear correctly.
  • The contextmenu is created and assigned to the ContextMenuStrip property of the UserControl that contains the actual ChromiumWebBrowser control.
  • It is displayed using ContextMenuStrip.Show(location) method.

Issues:

  • The context menu has no keyboard-focus.
  • All mouse events appear to be "swallowed" by the ChromiumWebBrowser: clicking there does not close the context menu.
  • Opening the context menu identically except for using a different "trigger" works fine, except for the 2nd issue.
mike
  • 1,627
  • 1
  • 14
  • 37
  • Firstly, the default is to run the message loop in it's own thread, that causes problems in certain scenarios, like interacting with menus. There is another option, see https://github.com/cefsharp/CefSharp/commit/fe11f2eada542f49e4eef0feed9b2b0978446bbf#diff-f142d024925e73816b0fabd620d2f71d for some rough details. Start with that, then report back. I personally would look at modifying the existing context menu, adding, removing commands through `IContextMenuHandler`. – amaitland Feb 03 '16 at 08:42
  • For reference, providing a working example may be required, your problem is complex, and a little outside the box in terms of what your doing. The `MinimalExample` project can be used as a reference point. https://github.com/cefsharp/CefSharp.MinimalExample Obviously start with changing the message loop and see how far you can get yourself. – amaitland Feb 03 '16 at 08:44
  • @amaitland: Thanks for your feedback! Using `IContextMenuHandler` isn't a feasible solution, because the context menu uses several custom menu item classes (that were implemented when the application was Gdi-only) and also the browser's context menu looks different: I'm aming for a seamless integration, i.e. the user should not notice that the Gui is actually Html. – mike Feb 03 '16 at 14:22
  • @amaitland#2: I tried setting `MultiThreadedMessageLoop = False`, but then the screen simply stays black. Which is probably "by design", but a design I don't understand. I read a bit about `CefDoMessageLoopWork` etc., but couldn't really wrap my head around it. Also, because I have the feeling that the default setting probably makes more sense. Maybe you could briefly explain how to handle the alternative setting? – mike Feb 03 '16 at 14:23
  • @amaitland#3: I still can't see myself getting a working example together, but described the code flow in a bit more detail in an edit; maybe that'll help. – mike Feb 03 '16 at 14:23

1 Answers1

0

In the end the solution is simple; everything works as implemented and desired, if the following steps are added:

Before showing the context menu disable the UserControl with the ChromiumWebBrowser and set the focus to the owning form; something like this:

Private Sub showContextMenu(position As Point)
    Me.ctrlCefBrowser.Enabled = False
    Me.Focus()

    myContextMenu.Show(position)
End Sub

That takes the focus away from the ChromiumWebBrowser, giving the context menu a chance to respond to the keyboard inputs. And also, by disabling the control, the mouse events are not "swallowed" anymore so clicking on the browser area causes the context menu to go away again.

Then, finally, add an event handler to the context menu to re-enable the browser control again:

Private Sub myContextMenu_Closed(sender As Object, e As ToolStripDropDownClosedEventArgs) Handles myContextMenu.Closed
    Me.ctrlCefBrowser.Enabled = True
    Me.ctrlCefBrowser.Focus()
End Sub

That did the trick for me, now I have a fully customizable Gdi context menu for my webbrowser control :o)

Note: A similar problem arises when using other menus as well, e.g. in a main menu or tool bar: clicking on the ChromiumWebBrowser control will not close the menu (because the mouse event is also "swallowed"). The same solution can be applied: when opening a drop down menu deactivate (Enabled = False) the web browser control. And when it closes, reactivate it. For my menus I used a derived class (Inherits ToolStripMenuItem) that adds listeners to the according events. That takes care of the problem in a global and simple way.

EDIT: The proposed solution above left the problem that the click on the disabled browser control closed the menu as intended, but got lost, i.e. the browser couldn't process it. My current workaround now is:

  • Do not disable the browser control.
  • Using the openening events of menu items and context menus, keep track of which menu is currently open.
  • When the browser receives the focus (obtainable by intercepting WndProc messages) close the opened menu.

Implementing the actual solution caused some headaches in the details, but maybe that helps someone along anyhow...

mike
  • 1,627
  • 1
  • 14
  • 37