Display HTML in WPF and CefSharp Tutorial Part 2 - CodeProject

:

Source code downloads from GutHub:

Introduction

This article continues a series that started with a previous article, which explains what CefSharp is and how it can be used to display HTML documents in WPF [1]. This part of the tutorial is focused on the ResourceHandler [2] in CefSharp. A ResourceHandler is one way to handle network requests.

Background

Sample Applications of the ResourceHandler in CefSharp

A ResourceHandler is useful for displaying static HTML. At time of writing ResourceHandler's are executed in a Sync fashion so should be used sparingly for network requests as they are blocking. An area of application for this scenario is for example an about page that could appear when the user enters the string "about" in the URL section of your browser application.

But an about page is certainly not the limit. Another interesting area of application for the ResourceHandler interface in CefSharp is a viewer application that translates the content of a file (eg.: MarkDown) into HTML for viewing purposes. There are also other use cases like implementing a Control Panel page as it can be found in Windows. But a use case like this is not covered here and it could also be more adventegious to implement this with a ShemeHandler as we will explain in the next article of this series.

Using the code

Sample 2 About ResourceHandler Demo Project

The Sample 2 About ResourceHandler project demos the simple case in which we want to associate a URL with a static content that is known at implementation time. The application shows an About page when it starts up and allows to load pages linked from that page. There is also a separate "Test URL 2" page which can be loaded through a mouse click.

Here is the XAML code that drives the application in the MainWindow.xaml file:

<Grid>
  <Grid.RowDefinitions>
    <RowDefinition Height="Auto" />
    <RowDefinition Height="*" />
    <RowDefinition Height="Auto" />
  </Grid.RowDefinitions>

  <Grid Grid.Row="0">
    <Grid.Resources>
      <conv:InverseBooleanConverter x:Key="InverseConv" />
    </Grid.Resources>
    <Grid.ColumnDefinitions>
      <ColumnDefinition Width="Auto" />
      <ColumnDefinition Width="Auto" />
      <ColumnDefinition Width="Auto" />
    </Grid.ColumnDefinitions>

    <Button Content="About"

      Command="{Binding TestUrlCommand}"

      IsEnabled="{Binding ElementName=browser, Path=IsLoading, Converter={StaticResource InverseConv}}"/>

    <Button Content="MarkDown" Grid.Column="1"

       Command="{Binding TestUrl1Command}"

       CommandParameter="{Binding ElementName=browser}"

       IsEnabled="{Binding ElementName=browser, Path=IsLoading, Converter={StaticResource InverseConv}}"/>

    <Button Content="Dev Tools" Grid.Column="2"

       Command="{Binding DevToolsCommand}"

       CommandParameter="{Binding ElementName=browser}"

       IsEnabled="{Binding ElementName=browser, Path=IsLoading, Converter={StaticResource InverseConv}}"/>
  </Grid>

  <cefSharp:ChromiumWebBrowser Grid.Row="1"

     Address="{Binding BrowserAddress, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"

     Title="{Binding BrowserTitle, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"

     Name="browser" />

  <StatusBar Grid.Row="2" >
     <TextBlock Name="Status" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
   </StatusBar>
</Grid>

We can see that the MainWindow consists of 3 parts:

  • The list of buttons at the top,
  • the CefSharp browser control named browser in the middle, and
  • the StatusBar which shows some information on whether a page is currently loaded or done loading.

Lets see how the buttons at the top work.

The MainWindows.xaml code is bound to a viewModel class named AppViewModel stored in "KnowledgeBase/ViewModels/AppViewModel.cs". This viewmodel provides 2 command properties called TestUrlCommand and TestUrl1Command. The first command is invoked when the "About" button is clicked and the second command is invoked when the when the "Test URL 2" button is clicked.

Each of the above commands do not do much more than setting the BrowserAddress property of the AppViewModel class:

BrowserAddress = TestResourceUrl;
BrowserAddress = TestUnicodeResourceUrl;

This property in turn is bound to the CefSharp web browser control as we have seen in the MainWindow.xaml listing. So, how come CefSharp knows what each URL means and why does it actually display custom HTML for these? The answer to that question is hidden in the MainWindow constructor where the AppViewModel.RegisterTestResources(browser); method is called with the ChromiumWebBrowser instance as parameter:

public MainWindow()
{
  InitializeComponent();
  
  AppViewModel.RegisterTestResources(browser);
  
  DataContext = new AppViewModel();
  
  browser.StatusMessage += BrowserStatusMessage;
  browser.NavStateChanged += BrowserNavStateChanged;
}

The code implemented to register each ResourceHandlers is fairly simple:

public static void RegisterTestResources(IWebBrowser browser)
{
  var factory = browser.ResourceHandlerFactory;

  if (factory != null)
  {
    const string responseBody =
    "<html><body><h1>About</h1>"
    ...
    + "</body></html>";

    factory.RegisterHandler(TestResourceUrl, ResourceHandler.FromString(responseBody));

    const string unicodeResponseBody = "<html><body>整体满意度</body></html>";
    factory.RegisterHandler(TestUnicodeResourceUrl, ResourceHandler.FromString(unicodeResponseBody));
  }
}

The code takes a URL (defined in TestResourceUrl or TestUnicodeResourceUrl) and associates it with HTML content defined in a string (responseBody or unicodeResponseBody). So, whenever the IWebBrowser control (which is effectively the ChromiumWebBrowser instance from MainWindow.xaml) is requested to load either URL, it implements this by loading the above pre-defined HTML string.

Sample 2 MarkDown ResourceHandler

The Sample2 folder also contains a Sample 2 MarkDown ResourceHandler application which can be used to view MarkDown content in a WPF application. This sample application contains a copy of the MarkDown converter project [3]. Here is a screenshot of the content that is displayed when the user clicks the MarkDown button:

This does not really look too interesting, its just plain HTML after all, but this HTML is based on the MarkDown syntax which is much more simple to edit than HTML. The actual source of the content is stored in the KnowledgeBase/SampleData/Readme.md file of this sample application.

The sample application works very similar to the About sample application discussed above. But it is slightly different in some corners and places. This application starts up through a App.xaml.cs startup method configured in App.xaml:

private void Application_Startup(object sender, StartupEventArgs e)
{
  var mainWindow = new MainWindow();
  var viewModel = new AppViewModel();

  viewModel.RegisterTestResources(mainWindow.browser);

  mainWindow.DataContext = viewModel;

  mainWindow.Show();
}

This RegisterTestResources method in the AppViewModel class loads a sample markdown text from the Readme.md file stored in the SampleData sub-folder of the KnowledgeBase project:. This application works very similar to the about sample application discussed above, except, the application can generate semi static content based on the Readme.md file stored in the file system (in the bin/Debug or bin/Release folder).

The sample application also implements a kind of refresh functionality. You can actually edit and save the Readme.md file at run-time and click the MarkDown button to see the results of your edits in the sample application's window.

The important code parts to understand this refresh function are the following lines:

RegisterMarkdownTestResources(browser);

BrowserAddress = TestMarkDown2HTMLConversion;

inside the TestUrl1Command property. This command is executed when the user clicks on the Markup. The first line relaods the MarkDown file, converts it into HTML and stores it in a string, and re-registers the new content. The second line changes the current address to tell the browser about the new request.

Remarks

Be sure to review the BrowserNavStateChanged(object sender, NavStateChangedEventArgs e) and BrowserStatusMessage(object sender, StatusMessageEventArgs e) methods in MainWindow.xaml.cs to understand how the StatusBar and title section of the MainWindow is updated.

Ir is worth pointing out that the sample application extends the http address scheme. The ResourceHandler in CefSharp could also be used with a custom URI, eg: about, but this requires a custom SchemeHandler as we will explain in the next article of this series.

 

The sample also contains a Dev Tools button and command section in the AppViewModel class:

This Chrome development tool can be used to verify and debug the state of the HTML, Javascript, or CSS styles loaded in the application. Just hover the mouse over a section, for example h1 as in the above screenshot, and see the corresponding highlighting in the application's main window.

References