【WPF】MVVMパターンでファイル選択ダイアログをシンプルに記述する【CommunityToolkit.MVVM】

## はじめに
WPFアプリを作成しているときにファイル選択ダイアログを表示したい場合にネットを検索すると様々な方法が紹介されています。WeakReferenceMessengerを使用した方法が紹介されていますが、ほとんどの場合は面倒なMessengerを使用せずにシンプルな記述で実装可能です。

## 環境
- Visual Studio 2022
- .Net 9

## ライブラリ
- CommunityToolkit.MVVM
MVVMパターンでWPFアプリを作成する場合はCommunityToolkit.MVVMを使用するのが便利です。  
特にViewModelの記述がかなり少なくなります。

## 実装
```XML:MainWindow.xaml
<Window
    x:Class="OpenFileDialogSample.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:OpenFileDialogSample"
    d:DataContext="{d:DesignInstance Type=local:MainViewModel}"
    mc:Ignorable="d"
    Title="MainWindow" Height="450" Width="800">
    <Window.DataContext>
        <local:MainViewModel/>
    </Window.DataContext>
    <Grid>
        <StackPanel HorizontalAlignment="Stretch" VerticalAlignment="Center" Margin="16">
            <Button Content="ファイル選択" Command="{Binding FileSelectCommand}" 
            Width="100" Margin="8"/>
            <TextBox Text="{Binding SelectedFilePath}" Margin="8"/>
        </StackPanel>
    </Grid>
</Window>

```

```CSharp:MainViewModel.cs
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Microsoft.Win32;

namespace OpenFileDialogSample;

public partial class MainViewModel : ObservableObject
{
    [ObservableProperty]
    private string selectedFilePath = string.Empty;

    [RelayCommand]
    private void FileSelect()
    {
        var dialog = new OpenFileDialog();
        if (dialog.ShowDialog() == true)
        {
            SelectedFilePath = dialog.FileName;
        }
    }
}
```

CommunityToolkit.MVVMを導入していない場合、ViewModelは次のように記述します。
<details><summary>MainViewModel.cs</summary>

```CSharp:MainViewModel.cs
using System.ComponentModel;
using System.Windows.Input;
using Microsoft.Win32;

namespace OpenFileDialogSample;

public class MainViewModel : INotifyPropertyChanged
{
    private string _selectedFilePath = string.Empty;

    public string SelectedFilePath
    {
        get => _selectedFilePath;
        set
        {
            if (_selectedFilePath != value)
            {
                _selectedFilePath = value;
                OnPropertyChanged(nameof(SelectedFilePath));
            }
        }
    }

    public ICommand FileSelectCommand { get; }

    public MainViewModel()
    {
        FileSelectCommand = new RelayCommand(FileSelect);
    }

    private void FileSelect()
    {
        var dialog = new OpenFileDialog();
        if (dialog.ShowDialog() == true)
        {
            SelectedFilePath = dialog.FileName;
        }
    }

    public event PropertyChangedEventHandler? PropertyChanged;

    protected virtual void OnPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
    
    private class RelayCommand : ICommand
    {
        private readonly Action _execute;
        private readonly Func<bool>? _canExecute;

        public RelayCommand(Action execute, Func<bool>? canExecute = null)
        {
            _execute = execute ?? throw new ArgumentNullException(nameof(execute));
            _canExecute = canExecute;
        }

        public bool CanExecute(object? parameter) => _canExecute?.Invoke() ?? true;

        public void Execute(object? parameter) => _execute();

        public event EventHandler? CanExecuteChanged
        {
            add => CommandManager.RequerySuggested += value;
            remove => CommandManager.RequerySuggested -= value;
        }
    }
}
```

</details>

## さいごに
CommunityToolkit.MVVMを導入している場合、MainWindow.xamlの<Window ...>のWindowの部分で右クリックをして、クイックアクションとリファクタリングから簡単にViewModelを作成できます。