简体   繁体   中英

Custom context menu for WPF WebBrowser Control

Hi I need to create a custom context menu for a web browser control in wpf. Here is my xaml code which is not working:

<WebBrowser x:Name="EmailBox"  ap:BrowserBehavior.HtmlString="{Binding Message, Mode=OneWay}">
    <WebBrowser.ContextMenu>
        <ContextMenu>
            <MenuItem Header="Copy" Command="ApplicationCommands.Copy"/>
            <MenuItem Header="Copy to Customer Reference ID" 
                  Command="{Binding CopyID}"
                  CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}, 
                  Path=PlacementTarget.Selection.Text}">
                <MenuItem.Icon>
                    <Image Source="{StaticResource CopyImageSource}" Width="16" />
                </MenuItem.Icon>
            </MenuItem>
            <MenuItem Header="Copy to Comments"
                  Command="{Binding CopyToCommentsCommand}"
                  CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}, 
                  Path=PlacementTarget.Selection.Text}">
                <MenuItem.Icon>
                    <Image Source="{StaticResource NoteCopyI}" Width="16" />
                </MenuItem.Icon>
            </MenuItem>
        </ContextMenu>
    </WebBrowser.ContextMenu>
</WebBrowser>

I copied the context menu code from somewhere else. This works in other controls but not for the webbrowser control. Is it possible to make this work?

Hi You have to add reference to Microsoft HTML Object Library and than...

XAML

<Window x:Class="WPFCustomContextMenuInWebBrowser.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WPFCustomContextMenuInWebBrowser"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
  <Window.Resources>
    <ContextMenu x:Key="MnuCustom" StaysOpen="True">
      <MenuItem Header="Custom 1"></MenuItem>
      <MenuItem Header="Custom 1"></MenuItem>
      <MenuItem Header="Custom 3"></MenuItem>
    </ContextMenu>
  </Window.Resources>
    <Grid>
        <WebBrowser x:Name="Wb"></WebBrowser>
    </Grid>
</Window>

C#

using System.Windows.Controls;
using MSHTML;

namespace WPFCustomContextMenuInWebBrowser {
  public partial class MainWindow {
    private HTMLDocumentEvents2_Event _docEvent;
    public MainWindow() {
      InitializeComponent();
      Wb.Navigate("http://google.com");
      Wb.LoadCompleted += delegate {
        if (_docEvent != null) {
          _docEvent.oncontextmenu -= _docEvent_oncontextmenu;
        }
        if (Wb.Document != null) {
          _docEvent = (HTMLDocumentEvents2_Event)Wb.Document;
          _docEvent.oncontextmenu += _docEvent_oncontextmenu;
        }
      };
    }

    bool _docEvent_oncontextmenu(IHTMLEventObj pEvtObj) {
      WbShowContextMenu();
      return false;
    }

    public void WbShowContextMenu() {
      ContextMenu cm = FindResource("MnuCustom") as ContextMenu;
      if (cm == null) return;
      cm.PlacementTarget = Wb;
      cm.IsOpen = true;
    }
  }
}

No, It's not possible to make this work.
WebBrowser control is a very thin wrapper around native WebBrowser ActiveX component, which is a part of Internet Explorer subsystem. It is hosted in it's own window host (WPF window and WebBrowser have different HWNDs), so WPF knows only about focus entering and leaving WebBrowser, but has no knowledge of any keyboard/mouse events. Also, there is so called 'airspace problem': WPF-rendered and native regions of screen area cannot overlap.
Therefore you cannot use WPF ContextMenu with WebBrowser, because:

  1. WPF doesn't recieve Mouse Right Click event to open Context Menu
  2. WPF cannot draw Context Menu above WebBrowser

Also, I don't think there is easy way to emulate ContextMenu with html/js in browser's content - as I recall, ActiveX component uses IE5 (quirk) rendering mode, and it is not possible to change that without changing registry files.
You can try to use ActiveX API with WebBrowser.Document object to disable native context menu and draw another one yourself through WinAPI, which is not an easy task.
So, I would recommend to look for other, pure-WPF browser controls or HTML renderers, such as awesomium

The XAML as follows:

<!--WebBrowser to Display Chat Messages-->
    <WebBrowser Name="webBrowser"
                    Source="http://stakoverflow.com"                        
                    Navigated="webBrowser_Navigated"
                    Navigating="webBrowser_Navigating"
                    LoadCompleted="webBrowser_LoadCompleted">
        <WebBrowser.ContextMenu>
            <ContextMenu x:Name="wbContextMenu" >
                <MenuItem x:Name="menuItem1" Header="Test Item" Click="menuItem1_Click" />
            </ContextMenu>
        </WebBrowser.ContextMenu            
    </WebBrowser>

Code as follows:

using mshtml;

private mshtml.HTMLDocumentEvents2_Event documentEvents;

in constructor or xaml set your LoadComplete event:

webBrowser.LoadCompleted += webBrowser_LoadCompleted;

then in that method create your new webbrowser document object and view the available properties and create new events as follows:

private void webBrowser_LoadCompleted(object sender, NavigationEventArgs e)
{
    documentEvents = (HTMLDocumentEvents2_Event)webBrowserChat.Document; // this will access the events properties as needed
    documentEvents.oncontextmenu += webBrowserChat_ContextMenuOpening;
}

private bool webBrowserChat_ContextMenuOpening(IHTMLEventObj pEvtObj)
{
    wbContextMenu.PlacementTarget = pEvtObj as ContextMenu; // Creates target spot where contextmenu will appear
    wbContextMenu.IsOpen = true; // Opens contextmneu
    return false; // ContextMenu wont open
    // return true;  ContextMenu will open
    // Here you can create your custom contextmenu or whatever you want
}

I've taken the solution provided by @MartinHoly and i've encountered the problem: to context menu can be popped up only once (if you right-click on the scrollbar, for example, or select a custom menu item - next time you right-click the WebBrowser brings the standard IE menu). I have made the following workaround: The xaml:

<Window x:Class="WPFCustomContextMenuInWebBrowser.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:WPFCustomContextMenuInWebBrowser"
    mc:Ignorable="d"
    Title="MainWindow" Height="350" Width="525">
<Window.Resources>
    <ContextMenu x:Key="MnuCustom" StaysOpen="True">
      <MenuItem Header="Custom 1"></MenuItem>
      <MenuItem Header="Custom 2"></MenuItem>
      <MenuItem Header="Custom 3"></MenuItem>
    </ContextMenu>
  </Window.Resources>
<Grid>
    <WebBrowser x:Name="Browser"></WebBrowser>
</Grid>

The code behind:

using System.Windows.Controls;
using MSHTML;

namespace WPFCustomContextMenuInWebBrowser {
  public partial class MainWindow {

    public MainWindow() 
    {
      InitializeComponent();
      Browser.LoadCompleted += BrowserOnLoadCompleted;
    }

    void BrowserOnLoadCompleted(object sender, NavigationEventArgs navigationEventArgs)
    {
        var mshtmlDoc = Browser.Document as HTMLDocument;
        if (mshtmlDoc == null) return;
                    var doc2event = mshtmlDoc as HTMLDocumentEvents2_Event;
        if (doc2event != null)
        {               
            doc2event.onfocusin += FocusInContextMenu;
        }            
    }

    bool OpenContextMenu(IHTMLEventObj pEvtObj)
    {
        WbShowContextMenu(pEvtObj as ContextMenu);
        return false;
    }

    void FocusInContextMenu(IHTMLEventObj pevtobj)
    {
        var mshtmlDoc = Browser.Document as HTMLDocument;
        var doc2event = mshtmlDoc as HTMLDocumentEvents2_Event;
        if (doc2event != null)
        {
            doc2event.oncontextmenu -= OpenContextMenu;
            doc2event.onfocusin -= FocusInContextMenu;
            doc2event.oncontextmenu += OpenContextMenu;
            doc2event.onfocusin += FocusInContextMenu;
        }
    }

    public void WbShowContextMenu() 
    {
      ContextMenu cm = FindResource("MnuCustom") as ContextMenu;
      if (cm == null) return;
      cm.PlacementTarget = Browser;
      cm.IsOpen = true;
    }
  }
}

I have a indirect implementation, which involves calling each other in C# and javascript.

xaml:

    <WebBrowser x:Name="webbrowser">
        <WebBrowser.ContextMenu>
            <ContextMenu>
                <MenuItem Header="item1"/>
            </ContextMenu>
        </WebBrowser.ContextMenu>
    </WebBrowser>

c#:

    using System.Runtime.InteropServices;


    public MainWindow()
    {
        InitializeComponent();
        //webbrowser.Navigate(new Uri("https://www.google.com"));
        webbrowser.ObjectForScripting = new ScriptManager(this);
        webbrowser.LoadCompleted += Webbrowser_LoadCompleted;
    }

    private void Webbrowser_LoadCompleted(object sender, NavigationEventArgs e)
    {
        //call C# method and disable the default contextmenu here.
        webbrowser.InvokeScript("eval", new object[] { "document.oncontextmenu = function() { window.external.ShowContextMenu(); return false; };" });
    }

    [ComVisible(true)]
    public class ScriptManager
    {
        private MainWindow mainWindow;
        public ScriptManager(MainWindow MainWindow)
        {
            mainWindow = MainWindow;
        }
        public void ShowContextMenu()
        {
            mainWindow.webbrowser.ContextMenu.IsOpen = true;
        }
    }

But it still has a native contextmenu when page is loading because LoadCompleted event hasn't triggered.

So if the html page is writed by self, you can add this line directly to the script section in html and don't need the LoadCompleted event:

document.oncontextmenu = function() { window.external.ShowContextMenu(); return false; };

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM