Using the Gecko browser using MVVM

A similar post on using the Gecko browser via WPF can be found here:

http://www.technical-recipes.com/2017/using-the-gecko-web-browser-in-wpf/

In this post I extend these ideas a little so that the Gecko web browser, a WinForms control, can be used as part of the MVVM pattern. Some other modifications are necessary, such as incorporating ICommand implementations, and supervisor controlling patterns to get this thing running.

Step 1: Create a new WPF project

Create using a suitably recent version of .NET. In this example I use version 4.5.2.

Step 2: Install NuGet packages

Install Gecko.

In my example I install the Windows 32-bit version.

Select Tools > NuGet Package Manager > Package Manager Console

Run the following command:

Install-Package Geckofx45

Install System.Windows.Interactivity

Select Tools > NuGet Package Manager > Package Manager Console

Run the following command:

Install-Package System.Windows.Interactivity.WPF -Version 2.0.20525

Step 3: Add any other essential references

Right click your project folder and select Add References.

Make sure that System.Windows.Forms is already installed.

Select WindowsFormsIntegration as the reference to add:

Also add references to the Gecko DLLs which are installed to the ‘packages’ folder on installing via NuGet:

And also add system windows interactivity:

Step 4: Set the configuration to 32 bit

As shown in the Configuration Manager screenshot below:

Step 5: Add event handling infrastructure

Add the following classes to your project:

EventArgs.cs

using System;

namespace GeckoMvvm
{
   public class EventArgs<T> : EventArgs
   {
      public EventArgs(T value)
      {
         Value = value;
      }

      public T Value { get; private set; }
   }
}

EventRaiser.cs

using System;

namespace GeckoMvvm
{
   public static class EventRaiser
   {
      public static void Raise(this EventHandler handler, object sender)
      {
         handler?.Invoke(sender, EventArgs.Empty);
      }

      public static void Raise<T>(this EventHandler<EventArgs<T>> handler, object sender, T value)
      {
         handler?.Invoke(sender, new EventArgs<T>(value));
      }

      public static void Raise<T>(this EventHandler<T> handler, object sender, T value) where T : EventArgs
      {
         handler?.Invoke(sender, value);
      }

      public static void Raise<T>(this EventHandler<EventArgs<T>> handler, object sender, EventArgs<T> value)
      {
         handler?.Invoke(sender, value);
      }
   }
}

RelayCommand.cs

using System;
using System.Windows.Input;

namespace GeckoMvvm
{
   public class RelayCommand<T> : ICommand
   {
      private readonly Predicate<T> _canExecute;
      private readonly Action<T> _execute;

      public RelayCommand(Action<T> execute)
         : this(execute, null)
      {
         _execute = execute;
      }

      public RelayCommand(Action<T> execute, Predicate<T> canExecute)
      {
         if (execute == null)
            throw new ArgumentNullException(nameof(execute));
         _execute = execute;
         _canExecute = canExecute;
      }

      public bool CanExecute(object parameter)
      {
         return (_canExecute == null) || _canExecute((T)parameter);
      }

      public void Execute(object parameter)
      {
         _execute((T)parameter);
      }

      public event EventHandler CanExecuteChanged
      {
         add { CommandManager.RequerySuggested += value; }
         remove { CommandManager.RequerySuggested -= value; }
      }
   }

   public class RelayCommand : ICommand
   {
      private readonly Predicate<object> _canExecute;
      private readonly Action<object> _execute;

      public RelayCommand(Action<object> execute)
         : this(execute, null)
      {
         _execute = execute;
      }

      public RelayCommand(Action<object> execute, Predicate<object> canExecute)
      {
         if (execute == null)
            throw new ArgumentNullException(nameof(execute));
         _execute = execute;
         _canExecute = canExecute;
      }

      public bool CanExecute(object parameter)
      {
         return (_canExecute == null) || _canExecute(parameter);
      }

      public void Execute(object parameter)
      {
         _execute(parameter);
      }

      // Ensures WPF commanding infrastructure asks all RelayCommand objects whether their
      // associated views should be enabled whenever a command is invoked 
      public event EventHandler CanExecuteChanged
      {
         add
         {
            CommandManager.RequerySuggested += value;
            CanExecuteChangedInternal += value;
         }
         remove
         {
            CommandManager.RequerySuggested -= value;
            CanExecuteChangedInternal -= value;
         }
      }

      private event EventHandler CanExecuteChangedInternal;

      public void RaiseCanExecuteChanged()
      {
         CanExecuteChangedInternal.Raise(this);
      }
   }
}

Step 6: Implement the Supervisor Controlling Pattern

Create the IView interface to add our Gecko grid browser host:

IView.cs

using System.Windows.Forms.Integration;

namespace GeckoMvvm
{
   public interface IView
   {
      void AddHost(WindowsFormsHost host);
   }
}

And then modify the code behind in your main window view.

Don’t worry about the things like MainWindowViewModel etc that we haven’t implemented yet, we will come on to that shortly.

MainWindow.xaml.cs

using Gecko;
using System.Windows;
using System.Windows.Forms.Integration;

namespace GeckoMvvm
{
   /// <summary>
   /// Interaction logic for MainWindow.xaml
   /// </summary>
   public partial class MainWindow : Window, IView
   {
      public MainWindow()
      {
         InitializeComponent();
         Xpcom.Initialize("Firefox");
         (DataContext as MainWindowViewModel).View = this as IView;
      }

      public void AddHost(WindowsFormsHost host)
      {
         gridWeb.Children.Add(host);
      }
   }
}

Step 7: Update the MainWindow.xaml view

MainWindow.xaml

<Window x:Class="GeckoMvvm.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:GeckoMvvm"
        xmlns:intr="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" 
        mc:Ignorable="d"
        Title="MainWindow" Height="400" Width="600">
    
    <Window.DataContext>
        <local:MainWindowViewModel />
    </Window.DataContext>

    <Grid 
         Name="gridWeb">
        <intr:Interaction.Triggers>
            <intr:EventTrigger EventName="Loaded">
                <intr:InvokeCommandAction                     
                    Command="{Binding WindowLoaded}"/>
            </intr:EventTrigger>
        </intr:Interaction.Triggers>
    </Grid>
</Window>

Step 8: Add the Main Window View Model class

MainWindowViewModel.cs

using Gecko;
using System.Windows.Forms.Integration;
using System.Windows.Input;

namespace GeckoMvvm
{
   public class MainWindowViewModel
   {
      private ICommand _windowLoadedCommand;

      public ICommand WindowLoaded
      {
         get
         {
            return _windowLoadedCommand ?? (_windowLoadedCommand = new RelayCommand(
                      x => { DoStuff(); }));
         }
      }

      public IView View { get; set; }

      public void DoStuff()
      {

         WindowsFormsHost host = new WindowsFormsHost();
         GeckoWebBrowser browser = new GeckoWebBrowser();
         host.Child = browser;
         View.AddHost(host);
         browser.Navigate("http://www.google.com");
      }
   }
}

Step 9: Try it!

Having followed the instructions faithfully the embedded browser should navigate to the chosen google page as shown:

The complete package is available for download from here:


Leave a Reply