🐥

.NET7でBlazorWebViewとWPFによるハイブリッドアプリのサンプル (2)

2023/07/30に公開

前回は、.NET7でWPFアプリケーションの中にBlazorWebViewを埋め込む方法・手順の紹介を行いました。今回は WPFアプリケーションとBlazorのコンポーネント内で C# のインスタンスをやり取りする話を記載します。

https://zenn.dev/techmadot/articles/hybrid-app-blazorwebview

めざすこと

ここでは以下の2つを目指します。また、WPFアプリケーションではなるべくコードビハインドによる実装を避けてチャレンジします。

  • WPF(C#)とBlazorの各ページ(Razorコンポーネント)で、同じデータにアクセスできること
  • WPF(C#)の関数をBlazorの世界から呼び出すこと

単純ではありますが、WPFアプリケーションとBlazorで同じインスタンスのカウンタ操作をそれぞれからやってみるというサンプルを作ります。

WPF側の対応

ボタンの配置

MVVMフレームワークである.NET Community Toolkitを使用し、WPF側のコードを作っていきます。

MainWindow.xaml の内容を以下のように書き換えて、WPFで操作する領域を用意してボタンを置いておきます。

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="*"/>
        <RowDefinition Height="Auto"/>
    </Grid.RowDefinitions>
    <blazor:BlazorWebView x:Name="blazorWebView" HostPage="wwwroot\index.html" Services="{DynamicResource services}"
                          Grid.Row="0">
        <blazor:BlazorWebView.RootComponents>
            <blazor:RootComponent Selector="#app" ComponentType="{x:Type local:MyPage}"/>
        </blazor:BlazorWebView.RootComponents>
    </blazor:BlazorWebView>

    <StackPanel Orientation="Horizontal" Grid.Row="1" Margin="10">
        <TextBlock Text="カウンタ:" FontSize="20" />
        <TextBlock Text="{Binding Count}" FontSize="20" Width="60"/>
        
        <Button Padding="10" Margin="20, 5" Command="{Binding DecCommand}">Dec (WPF)</Button>
        <Button Padding="10" Margin="20, 5" Command="{Binding AddCommand}">Add (WPF)</Button>
    </StackPanel>
</Grid>

カウンターを持っているクラスを以下のように実装しておきます。 MVVM Toolkit (v8)を使って簡単な記述ができるようになったので楽です。

public partial class MyCounter : ObservableObject
{
  [ObservableProperty]
  private int _Count;

  [RelayCommand]
  public void Add() => ++Count;

  [RelayCommand]
  public void Dec() => --Count;
}

MyCounterをサービス登録して共有の準備

MyCounter のインスタンスをWPFおよびBlazor両方で使うためにサービスに登録します。

App.xaml.cs の編集

var serviceCollection = new ServiceCollection();
serviceCollection.AddWpfBlazorWebView();
serviceCollection.AddSingleton<MyCounter>();  // 追加
Resources.Add("services", serviceCollection.BuildServiceProvider());

MainWindow.xaml.cs の編集

データコンテキストに先ほど登録したMyCounterのインスタンスを設定しておきます。下記のコードを InitializeComponent()が終わったあたりに追加します。

var provider = App.Current.Resources["services"] as IServiceProvider;
this.DataContext = provider?.GetRequiredService<MyCounter>();

ここまでが完了するとボタンを押してカウンタが動作する状態となります。

Razor 側の編集

前回作ったMyPage.razorの中身を編集します。内容は下記の通りです。

@using Microsoft.AspNetCore.Components.Web
@using System.ComponentModel
@implements IDisposable
@inject WpfAppHybrid.MyCounter myCounter

<h3>Hello,WPFApplication</h3>

<p>Current Counter is : @myCounter.Count</p>
<button @onclick="OnClickAdd">Add (MyPage)</button>
<button @onclick="OnClickDec">Dec (MyPage)</button>

@code {
  protected override void OnInitialized()
  {
    this.myCounter.PropertyChanged += CounterStateChanged;
    base.OnInitialized();
  }
  public void Dispose()
  {
    this.myCounter.PropertyChanged -= CounterStateChanged;
  }
  private void CounterStateChanged(object? sender, PropertyChangedEventArgs e)
  {
    this.StateHasChanged();
  }
  private void OnClickAdd()
  {
    myCounter.Add();
  }
  private void OnClickDec()
  {
    myCounter.Dec();
  }
}

Web側にもボタンを配置して、そのイベントハンドラの中でカウンタ用のインスタンスのAddDec関数を呼び出し値を操作しています。
この値操作がWPF側で行われたときにWeb側のページを更新するためにCounterStateChangedの実装を追加します。

結果

実装が終わり実行すると、どちらの世界のボタンを操作してもカウンタの増減ができるようになります。

まとめ

他の方が既にやっている内容ではありますが、.NET7でWPF+Blazor双方からの同じインスタンスへのアクセスができることを確認しました。まずはこの方法でインスタンス共有できますが、プログラム実行中に動的に作ったものをサービスへの登録なしにページに渡すというのは良い方法が見つからずでした。
データの受け渡しなどに、もっと良い方法がないのかは調べておく必要がありそうです。

参考

今回も以下の記事を参考にさせてもらいました。

https://zenn.dev/microsoft/articles/dotnet6-pre3-blazorwebview

Discussion