XAMLで始めるWPF入門:アプリの終了時にReactivePropertyをDisposeするコード

コンピュータ

ReactivePropertyはWPFのViewModelでバインド可能なプロパティを簡単に生やすことが出来て非常に便利です。ただ、IDisposableなので使用後にDispose()を呼ぶ必要があります。
使い方次第ですが、アプリの終了時にすべてDispose()されるようになっていいると、Dispose忘れを防止出来ると思われます。

プロジェクトの作成

cd (mkdir プロジェクト名)
dotnet add package Microsoft.Xaml.Behaviors.Wpf
dotnet add package ReactiveProperty.WPF

ソースコード

ファイル名:wpfRxTemplate.csproj

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>net8.0-windows</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
    <UseWPF>true</UseWPF>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.135" />
    <PackageReference Include="ReactiveProperty.WPF" Version="9.7.0" />
  </ItemGroup>

</Project>

BehaviorsとReactivePropertyパッケージが追加されています。

ファイル名:Behavior\WindowCloseBehavior.cs

using Microsoft.Xaml.Behaviors;
using System.Windows;

namespace wpfRxTemplate;

public class WindowCloseBehavior : Behavior<Window>
{
    protected override void OnAttached()
    {
        base.OnAttached();
        this.AssociatedObject.Closed += this.WindowClosed;
    }

    private void WindowClosed(object? sender, EventArgs e)
    {
        (this.AssociatedObject.DataContext as IDisposable)?.Dispose();
    }

    protected override void OnDetaching()
    {
        base.OnDetaching();
        this.AssociatedObject.Closed -= this.WindowClosed;
    }
}

Behaviorで、Windowのクローズイベントで、DataContextがIDisposableの場合Dispose()を呼び出しています。

ファイル名:ViewModels\BaseViewModel.cs

using System.ComponentModel;
using System.Reactive.Disposables;

namespace wpfRxTemplate;
public class BaseWindowViewModel : INotifyPropertyChanged, IDisposable
{
    // INotifyPropertyChanged
    public event PropertyChangedEventHandler? PropertyChanged;
    protected virtual void OnPropertyChanged(string name) =>
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
    // IDisposable
    protected CompositeDisposable Disposable { get; } = [];
    public void Dispose() =>Disposable.Dispose();
}

必須ではないですが、ViewModelのベースクラスを作って見ました。
INotifyPropertyChangedとIDisposableを実装しており、Dispose()を定義していてDisposable.Dispose()を呼び出しています。

ファイル名:ViewModels\MainWindowViewModel.cs

using Reactive.Bindings;
using Reactive.Bindings.Extensions;
using System.Reactive.Linq;

namespace wpfRxTemplate;

// Disposeの確認クラス
public class DisposeTest : IDisposable
{
    public void Dispose()
    {
        System.Diagnostics.Debug.Print("Dispose()が呼ばれた。");
    }
}
public class MainWindowViewModel : BaseWindowViewModel
{
    // 入力値
    public ReactiveProperty<string> Name { get; }

    // 出力メッセージ
    public ReadOnlyReactiveProperty<string?> Greeting { get; }

    // コマンド
    public ReactiveCommand SayHelloCommand { get; }

    // Disposeの確認
    public DisposeTest Dummy { get; }

    public MainWindowViewModel()
    {
        // 初期値 = 空文字列
        Name = new ReactiveProperty<string>("")
            .AddTo(Disposable);

        // コマンド:常に実行可能
        SayHelloCommand = new ReactiveCommand()
            .AddTo(Disposable);

        // ボタン押下イベント → Greeting更新
        Greeting = SayHelloCommand
            .WithLatestFrom(Name, (_, name) => $"Hello, {name}!")
            .ToReadOnlyReactiveProperty()
            .AddTo(Disposable);

        // AddTo(Disposable)の動作確認
        Dummy = new DisposeTest()
            .AddTo(Disposable);

    }    
}

ViewModelです。ReactivePropertyのオブジェクトがDispose()されているはずですが、確認のためにDummyプロパティを作成しています。
Dispose()が正しく動作していれば、アプリ終了時にメッセージが表示されるようにしてあります。

ファイル名:MainWindow.xaml

<Window x:Class="wpfRxTemplate.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:wpfRxTemplate"
        xmlns:i="clr-namespace:Microsoft.Xaml.Behaviors;assembly=Microsoft.Xaml.Behaviors"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.DataContext>
        <local:MainWindowViewModel />
    </Window.DataContext>
    <i:Interaction.Behaviors>
        <local:WindowCloseBehavior />
    </i:Interaction.Behaviors>

    <!-- Body ここから-->
    <StackPanel Margin="20">
        <TextBox Text="{Binding Name.Value, UpdateSourceTrigger=PropertyChanged}" />
        <Button Content="Say Hello" Command="{Binding SayHelloCommand}" Margin="0,10,0,0"/>
        <TextBlock Text="{Binding Greeting.Value}" FontSize="16" FontWeight="Bold"/>
    </StackPanel>
</Window>

View部分になります。WindowCloseBehaviorを組み込むことで、Windowの終了時に発火するコードをビヘイビアで実行します。

実行結果

Dispose()が呼ばれたようでメッセージが表示されました。
image

コメント