まずは下準備!

はーい、またやってきました!CommunityToolkit.Mvvm大好きの、もりゃきお姉さんだよ!

今回はパパも、基本的な画面構成以外は好きにやって良いとお許しがでましたので、バンバン変えていきますよ!

プロジェクトを作りましょう。 Visual Studioで「WPFアプリケーション」を選んで、ソリューション名は「BusinessPersonsSample」、フレームワークは「.NET 9」選択ね!

次に、NuGet経由で「CommunityToolkit.Mvvm」と「Microsoft.Extensions.DependencyInjection」をいつもの通りにインストールしましょうね!

繰り返すけど、CommunityToolkit.Mvvm V8 を使うから、エラーが出たらとりあえずリビルドしてね、お姉さんとの約束よ!

DI の設定よ

はい、これを App.xaml.cs に適切にコピペしちゃいましょう!

    public partial class App : Application
    {
        /// <summary>
        /// サービスの登録をします
        /// </summary>
        public App()
        {
            Services = ConfigureServices();
            Ioc.Default.ConfigureServices(Services);
        }

        /// <summary>
        /// 現在の App インスタンスを取得します
        /// </summary>
        public new static App Current => (App)Application.Current;

        /// <summary>
        /// サービスプロバイダです
        /// </summary>
        public IServiceProvider Services { get; }

        /// <summary>
        /// サービスを登録します
        /// </summary>
        /// <returns></returns>
        private static ServiceProvider ConfigureServices()
        {
            var services = new ServiceCollection();

            services.AddSingleton<IMessenger, WeakReferenceMessenger>();
            services.AddSingleton<IMainWindowViewModel, MainWindowViewModel>();

            return services.BuildServiceProvider();
        }
    }

フォルダの作成と準備

まずはプロジェクトに Views と ViewModels、Models フォルダを作るわよ。

で、MainWindowViewModel.cs を ViewModel フォルダに作成して以下のようにするの。

using CommunityToolkit.Mvvm.ComponentModel;

namespace BusinessPersonsSample.ViewModels
{
    public interface IMainWindowViewModel { }
    class MainWindowViewModel : ObservableObject, IMainWindowViewModel
    {
    }
}

次に App.xaml.cs の冒頭にこの一行を入れる

using BusinessPersonsSample.ViewModels;

そして、MainWindow.xaml を Views フォルダに移動!

MainWindow.xaml を以下のように書き換えるわ。

<Window
    x:Class="BusinessPersonsSample.Views.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:local="clr-namespace:BusinessPersonsSample.Views"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    Title="MainWindow"
    Width="800"
    Height="450"
    mc:Ignorable="d">
    <Grid>
    </Grid>
</Window>

そして MainWindow.xaml.cs もこのように書き換えるわ。

using System.Windows;
using BusinessPersonsSample.ViewModels;
using CommunityToolkit.Mvvm.DependencyInjection;

namespace BusinessPersonsSample.Views
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            DataContext = Ioc.Default.GetService<IMainWindowViewModel>();
        }
    }
}

最後に App.xaml の StartupUri を書き換えるわ

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

これで、CommunityToolkit.Mvvm を使うための土台が整ったわ!

画面を作るわよ!

パパは、基本的な画面構成だけは同じにするように言ってたから、UserControl なんて使わないわよ!

<Window
    x:Class="BusinessPersonsSample.Views.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:local="clr-namespace:BusinessPersonsSample.Views"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    Title="{Binding Title}"
    Width="800"
    Height="450"
    mc:Ignorable="d">
    <Grid Background="White">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto" />
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="100" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>

        <ListBox
            Grid.Row="0"
            Grid.Column="0"
            Grid.ColumnSpan="2"
            Margin="5"
            ItemsSource="{Binding PersonCollection}"
            SelectedItem="{Binding SelectedPerson}">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding ViewName}"/>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>

        <!--  First Name  -->
        <TextBlock
            Grid.Row="1"
            Grid.Column="0"
            Margin="5"
            Text="First Name:" />
        <TextBlock
            Grid.Row="1"
            Grid.Column="1"
            Margin="5"
            Text="{Binding FirstName}" />

        <!--  Last Name  -->
        <TextBlock
            Grid.Row="2"
            Grid.Column="0"
            Margin="5"
            Text="Last Name:" />
        <TextBlock
            Grid.Row="2"
            Grid.Column="1"
            Margin="5"
            Text="{Binding LastName}" />

        <!--  Age  -->
        <TextBlock
            Grid.Row="3"
            Grid.Column="0"
            Margin="5"
            Text="Age:" />
        <TextBlock
            Grid.Row="3"
            Grid.Column="1"
            Margin="5"
            Text="{Binding Age}" />
    </Grid>
</Window>

あ、まだデータバインディングは動かないわよ?

Models\BusinessPerson.cs を作るわよ

namespace BusinessPersonsSample.Models
{
    public class BusinessPerson
    {
        public string FirstName { get; set; } = string.Empty;
        public string LastName { get; set; } = string.Empty;
        public int Age { get; set; }
        // 使ってないからね
        //public DateTime LastUpdate { get; set; } = DateTime.MinValue;

        public string ViewName { get => $"{LastName}, {FirstName}"; }
    }
}

こんな感じでいいかしら?

っていうか「Business」ってクラス名じゃ、具体的に何を表してるかわからないわよね。 だからリファクタリングの一環として「BusinessPerson」に置き換えたわよ。

ViewModels\MainWindowViewModel.cs を作るわよ

using CommunityToolkit.Mvvm.ComponentModel;
using BusinessPersonsSample.Models;
using System.Collections.ObjectModel;

namespace BusinessPersonsSample.ViewModels
{
    public interface IMainWindowViewModel { }
    public partial class MainWindowViewModel : ObservableObject, IMainWindowViewModel
    {
        [ObservableProperty]
        private string title = "CommunityToolkit.Mvvm Application";

        [ObservableProperty]
        private string firstName = string.Empty;

        [ObservableProperty]
        private string lastName = string.Empty;

        [ObservableProperty]
        private int? age = null;

        public ObservableCollection<BusinessPerson> PersonCollection { get; set; }

        [ObservableProperty]
        private BusinessPerson selectedPerson = new();
        partial void OnSelectedPersonChanged(BusinessPerson value)
        {
            FirstName = value.FirstName;
            LastName = value.LastName;
            Age = value.Age;
        }

        public MainWindowViewModel()
        {
            PersonCollection = new ObservableCollection<BusinessPerson>(Enumerable.Range(0, 10).Select(age => new BusinessPerson
            {
                FirstName = $"First {age}",
                LastName = $"Last {age}",
                Age = age
            }));
        }
    }
}

はい、これでデータバインディングで同じ動きをするようになったはずよ。

リージョンの切り替えとか、メッセージングとかは必要な所だけに使うものよ! 必要以上に難解にしてる Prism サンプルは何なのかしらね。

というか、Model と ViewModel の切り分けが上手く行ってないとか、 サンプルとしての質を本気で疑うわよ!

締めるわよ、パパから一言

改めて、49歳間近のおっさんがはっちゃけてすみませんでした。

まったくもりゃきは口が悪いんだから…って俺がもりゃきだった…!

このサンプルは、特に動かす必要はないでしょうし、書いてある通りにすればいいだけですし。 GitHub には完成品を上げないことにします。

そして Models\BusinessPerson.cs が唯のデータ置き場になってしまっているのが微妙ですね。 この規模感だと、ViewModelsに合併した方が良いかもしれません。

次は WPFにおけるPrism集中講座(7) ラストスパート で、終わりとなります。