Masui Masanori
Posted on January 22, 2021
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"
}
}
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>
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>
...
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>
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>
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");
...
}
...
}
}
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>
Call TypeScript(JavaScript) functions
WebView2 can call TypeScript(JavaScript) functions.
main.page.ts
export function load(message: string) {
alert(message);
}
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!')");
}
}
}
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);
}
}
}
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}')");
}
}
}
Posted on January 22, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.