【.NET 5】【WPF】Try WebView2

masanori_msl

Masui Masanori

Posted on January 22, 2021

【.NET 5】【WPF】Try WebView2

Intro

This time, I try drawing the values what I had gotten from a spreadsheet.

Environments

  • .NET ver.5.0.102
  • WebView2 Runtime
  • Microsoft.Extensions.DependencyInjection ver.5.0.1
  • Microsoft.Xaml.Behaviors.Wpf ver.1.1.31
  • Newtonsoft.Json ver.12.0.3
  • Microsoft.Web.WebView2 ver.1.0.664.37

package.json

{
    "dependencies": {
        "ts-loader": "^8.0.14",
        "tsc": "^1.20150623.0",
        "typescript": "^4.1.3",
        "webpack": "^5.16.0",
        "webpack-cli": "^4.4.0"
    }
}
Enter fullscreen mode Exit fullscreen mode

Use DataGrid?

First I tried DataGrid.

But I felt it was difficult to resolve all of these conditions.

  • Adding or deleting columns and rows dynamically
  • Changing columns' width and rows' height dynamically
  • Merging cells
  • Drawing borders at specific side of cell
  • Changing background color
  • Changing texts' color

Though I want to try using DataGrid, I try another way in this time.

Use WebViiew2

I found I could use HTML and CSS grid to do that.

To draw HTML pages on WPF applications, I can use WebView2.

To use WebView2, I must install WebView2 Runtime on my PC and Microsoft.Web.WebView2 on my project.

Now I can add WebView2.

MainWindow.xaml

<Window x:Class="PdfPrintSample.Main.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:Behaviors="http://schemas.microsoft.com/xaml/behaviors"
        xmlns:wv2="clr-namespace:Microsoft.Web.WebView2.Wpf;assembly=Microsoft.Web.WebView2.Wpf"
        xmlns:local="clr-namespace:PdfPrintSample.Main"
        mc:Ignorable="d"
        Title="{Binding Title}" Height="450" Width="800">
...
    <DockPanel>
        <wv2:WebView2 Name="webView"
                    Source="{DEFAULT_URL}">
        </wv2:WebView2>
    </DockPanel>
</Window>
Enter fullscreen mode Exit fullscreen mode

Show local HTML files

As same as web browsers, WebView2 also can open local HTML files.
One important thing is the path must be absolute.

...
        <wv2:WebView2 Name="webView"
                    Source="C:\Users\example\OneDrive\Documents\Index.html">
        </wv2:WebView2>
...
Enter fullscreen mode Exit fullscreen mode

Index.html

<!DOCTYPE html>
<html>
    <head>
        <title>Hello</title>
        <script src="js/mainPage.js"></script>
        <meta content="text/html;charset=utf-8" http-equiv="Content-Type" />
    </head>
    <body>
        Hello World!
    </body>
</html>
Enter fullscreen mode Exit fullscreen mode

Alt Text

Because "Source" of WebView2 can data binding.
So I can set the path from ViewModel class.

MainWindow.xaml

...
    <DockPanel>
        <wv2:WebView2 Name="webView"
                    Source="{Binding WebViewSource}">
        </wv2:WebView2>
    </DockPanel>
</Window>
Enter fullscreen mode Exit fullscreen mode

MainViewModel.cs

using System;
using System.IO;
using PdfPrintSample.Spreadsheets;
using PdfPrintSample.Spreadsheets.Values;

namespace PdfPrintSample.Main
{
    public class MainViewModel
    {
        public string WebViewSource { get; set; }
...
        public MainViewModel(ISpreadsheetLoader spreadsheets)
        {
            WebViewSource = Path.Combine(Environment.CurrentDirectory, "WebViews/Index.html");
...
        }
...
    }
}
Enter fullscreen mode Exit fullscreen mode

Copy files into the output directory

I want to copy view files into the output directory automatically.
So I add "Content" in the csproj.

PdfPrintSample.csproj

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>net5.0-windows10.0.19041.0</TargetFramework>
    <UseWPF>true</UseWPF>
    <Nullable>enable</Nullable>
  </PropertyGroup>
  <ItemGroup>
    <Content Include="Nlog.config">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </Content>
    <Content Include="WebViews/*">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </Content>
    <Content Include="WebViews/js/*">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </Content>
  </ItemGroup>
  <ItemGroup>
    <PackageReference Include="NLog" Version="4.7.6"/>
    <PackageReference Include="PdfSharpCore" Version="1.2.10"/>
    <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.1"/>
    <PackageReference Include="ClosedXML" Version="0.95.4"/>
    <PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.31"/>
    <PackageReference Include="Newtonsoft.Json" Version="12.0.3"/>
    <PackageReference Include="Microsoft.Web.WebView2" Version="1.0.664.37"/>
  </ItemGroup>
</Project>
Enter fullscreen mode Exit fullscreen mode

Call TypeScript(JavaScript) functions

WebView2 can call TypeScript(JavaScript) functions.

main.page.ts

export function load(message: string) {
    alert(message);
}
Enter fullscreen mode Exit fullscreen mode

MainWindow.xaml.cs

using System.Windows;
using Microsoft.Web.WebView2.Core;

namespace PdfPrintSample.Main
{
    public partial class MainWindow : Window
    {
        public MainWindow(MainViewModel viewModel)
        {
            DataContext = viewModel;
            InitializeComponent();
            InitializeAsync();
            webView.NavigationCompleted += NavigationCompleted;
        }
        private async void InitializeAsync()
        {
            await webView.EnsureCoreWebView2Async(null);
        }
        private async void NavigationCompleted(object? sender,
            CoreWebView2NavigationCompletedEventArgs args)
        {
            // webView.CoreWebView2 is null before completing the navigation
            await webView.CoreWebView2.ExecuteScriptAsync("Page.load('Hello World!')");
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Alt Text

Send loaded spreadsheet data to TypeScript

1.Load a spreadsheet -> 2.Send the data to view(XAML) -> 3.Send the data to TypeScript

There is nothing special.
But I should know that WebView2 may not be ready yet after the spreadsheet finishes loading.
If so, I must wait for finishing readying.

MainViewModel.cs

using System;
using System.IO;
using PdfPrintSample.Spreadsheets;
using PdfPrintSample.Spreadsheets.Values;

namespace PdfPrintSample.Main
{
    public class MainViewModel
    {
        private readonly ISpreadsheetLoader spreadsheets;
        public string Title { get; set; } = "";
        public string WebViewSource { get; set; }
        public LoadCommand Load { get; set; } = new LoadCommand();

        public Action<Spreadsheets.Values.Worksheet?>? WorksheetLoaded { get; set; }

        public MainViewModel(ISpreadsheetLoader spreadsheets)
        {
            WebViewSource = Path.Combine(Environment.CurrentDirectory, "WebViews/Index.html");
            this.spreadsheets = spreadsheets;
            Load.LoadSpreadsheetNeeded += LoadSpreadsheet;
        }
        private void LoadSpreadsheet(LoadSpreadsheetArgs args)
        {
            // Send loaded data to view
            var newWorksheet = spreadsheets.Load(args.FilePath, args.SheetName);
            WorksheetLoaded?.Invoke(newWorksheet);            
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

MainWindow.xaml.cs

using System;
using System.Windows;
using Microsoft.Web.WebView2.Core;
using Newtonsoft.Json;

namespace PdfPrintSample.Main
{
    public partial class MainWindow : Window
    {
        private EventHandler<EventArgs>? webViewReadyEvent;
        public MainWindow(MainViewModel viewModel)
        {
            DataContext = viewModel;
            InitializeComponent();
            InitializeAsync();
            viewModel.WorksheetLoaded += SpreadsheetLoadedAsync;
        }
        private async void InitializeAsync()
        {
            await webView.EnsureCoreWebView2Async(null);
        }
        private async void SpreadsheetLoadedAsync(Spreadsheets.Values.Worksheet? worksheet)
        {
            string newWorksheetJson = "{}";
            if(worksheet != null)
            {
                newWorksheetJson = JsonConvert.SerializeObject(worksheet);
            }
            if(webView.CoreWebView2 == null)
            {
                webViewReadyEvent = async (sender, args) => {
                    await webView.CoreWebView2!.ExecuteScriptAsync($"Page.load('{newWorksheetJson}')");
                    // remove
                    webView.CoreWebView2Ready -= webViewReadyEvent;                        
                };
                webView.CoreWebView2Ready += webViewReadyEvent;
                return;
            }
            await webView.CoreWebView2!.ExecuteScriptAsync($"Page.load('{newWorksheetJson}')");
        }
    }
}
Enter fullscreen mode Exit fullscreen mode
💖 💪 🙅 🚩
masanori_msl
Masui Masanori

Posted on January 22, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related