🤖

WPF で Generic Host を使ってみる

2024/12/09に公開

はじめに

多分、多くのケースではここで紹介されているライブラリを使うので良いと思います。

https://zenn.dev/nuits_jp/articles/wpf-extensions-hosting

私は諸事情で野良 OSS が使えなかったので、自前で Generic Host を使って WPF アプリを作ってみたので、その時にメモになります。

Generic Host を使う方法

WPF アプリを新規作成して以下のパッケージを追加します。

  • Microsoft.Extensions.Hosting

App.xamlStartupUri を削除して以下のようにします。

App.xaml
<Application x:Class="WpfApp1.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:WpfApp1">
    <Application.Resources>
         
    </Application.Resources>
</Application>

App.xaml にリソースなどを追加する場合は App.xaml.csInitializeComponent の呼び出しを追加します。

App.xaml.cs
using System.Windows;

namespace WpfApp1;

/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
    public App()
    {
        InitializeComponent();
    }
}

Program.cs を作成して以下のようなコードを書きます。

Program.cs

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System.Windows;
using WpfApp1;

Thread.CurrentThread.SetApartmentState(ApartmentState.Unknown);
Thread.CurrentThread.SetApartmentState(ApartmentState.STA);

var builder = Host.CreateApplicationBuilder(args);

builder.Services.AddHostedService<WpfBootstrapper>(); // 下で定義しているクラス

// App と MainWindow を登録
builder.Services.AddSingleton<App>();
builder.Services.AddTransient<MainWindow>();

var app = builder.Build();
app.Run();

// WPF の起動をする
class WpfBootstrapper(
    App app, 
    MainWindow mainWindow, 
    IHostApplicationLifetime hostApplicationLifetime) : BackgroundService
{
    protected override Task ExecuteAsync(CancellationToken stoppingToken)
    {
        app.Startup += App_Startup;
        app.Exit += App_Exit;
        app.Run();
        return Task.CompletedTask;
    }
    private void App_Startup(object sender, StartupEventArgs e)
    {
        mainWindow.Show();
    }
    private void App_Exit(object sender, ExitEventArgs e)
    {
        hostApplicationLifetime.StopApplication();
    }
}

これで実行をすると真っ白なウィンドウが表示されるはずです。

ViewModel などを使う場合

任意の MVVM ライブラリを使って ViewModel を作成しても良いですが、今回は INotifyPropertyChanged を直接実装します。

MainWindowViewModel.cs
using System.ComponentModel;

namespace WpfApp1;
public class MainWindowViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler? PropertyChanged;

    private string _input = "";
    public string Input
    {
        get => _input;
        set
        {
            _input = value;
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Input)));
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Output)));
        }
    }

    public string Output => _input.ToUpperInvariant();
}

そして、この ViewModelProgram.cs で DI コンテナに登録します。

Program.cs
builder.Services.AddTransient<MainWindowViewModel>();

MainWindow.xaml.cs では以下のように ViewModel を使います。

MainWindow.xaml.cs
using System.Windows;

namespace WpfApp1;

/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
    /// <summary>
    ///  デザイナー用コンストラクタ
    /// </summary>
    public MainWindow()
    {
        InitializeComponent();
    }

    public MainWindow(MainWindowViewModel viewModel) : this()
    {
        DataContext = viewModel;
    }
}

あとは適当に MainWindow.xaml でバインディングを設定してください。

MainWindow.xaml
<Window x:Class="WpfApp1.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:WpfApp1"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800"
        d:DataContext="{d:DesignInstance Type={x:Type local:MainWindowViewModel}, IsDesignTimeCreatable=False}">
    <StackPanel>
        <TextBox Text="{Binding Input, UpdateSourceTrigger=PropertyChanged}" />
        <TextBlock Text="{Binding Output}" />
    </StackPanel>
</Window>

実行すると以下のようになります。ちゃんとバインドが動いていることがわかります。

まとめ

意外と簡単に WPF で Generic Host が使えました。
ライブラリ化されているものがあるので、それを使うのがいいとは思いますがどうしても使えないときには、これくらいの手間で使えるということを覚えておいてもいいかなと思います。

Microsoft (有志)

Discussion