【WPF】MVVMパターンによる WPF DataGrid の実装

【WPF】MVVMパターンによる WPF DataGrid の実装

はじめに

前回は WPF で MVVM パターンによる構成でコンボボックスを使用する方法について紹介しました。

今回の記事では、同じく MVVM パターンによる構成で DataGrid を使用する方法について紹介していきます。

最終的な成果物のイメージです。

datagrid-sample.gif

MVVM に関しては下記の記事で紹介しているので、興味のある方は見てみて下さい。

開発環境

  • .NET6
  • Visual Studio 2022(v17.6.4)

ファイル構成

ファイル構成は以下の用になっています。

project-failes.png

ダミーデータとして CSV を使用します。

SQLServer に接続するようなクラスがありますが、中身はインターフェースを実装しただけのクラスになっています。今回は特に使用しません。

"SQLServerClient.cs"
using DataGrid_Sample.Enitities;
using DataGrid_Sample.Repositories;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;

namespace DataGrid_Sample.SQL
{
    public sealed class SQLServerClient : IDataGridRepository
    {

        public SQLServerClient()
        {
            // サーバーに接続する処理など
        }

        public ObservableCollection<UserEnitity> Get()
        {
            // 未実装の例外はそのままにしておく
            throw new NotImplementedException();
        }
    }
}

ダミーデータは前回と同じく個人情報テストデータジェネレーターというサービスを利用して作成しました。

画面の実装

MainWindow.xamlを以下のように記述します。

"MainWindow.xaml"
<Window
    x:Class="DataGrid_Sample.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:Behaviors="http://schemas.microsoft.com/xaml/behaviors"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:local="clr-namespace:DataGrid_Sample"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    Title="DataGrid-Sample"
    Width="500"
    Height="450"
    WindowStartupLocation="CenterScreen"
    mc:Ignorable="d">
    <Window.DataContext>
        <local:MainWindowViewModel />
    </Window.DataContext>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="50" />
            <RowDefinition Height="300" />
            <RowDefinition />
        </Grid.RowDefinitions>
        <Label
            Grid.Row="0"
            HorizontalAlignment="Center"
            VerticalAlignment="Bottom"
            Content="DataGridテスト"
            FontSize="26" />

        <DataGrid
            x:Name="dataGridForUserData"
            Grid.Row="1"
            d:ItemsSource="{d:SampleData ItemCount=5}"
            AutoGenerateColumns="False"
            Cursor="Hand"
            GridLinesVisibility="All"
            IsReadOnly="True"
            ItemsSource="{Binding UserEntityData}"
            VerticalScrollBarVisibility="Auto">
            <Behaviors:Interaction.Triggers>
                <Behaviors:EventTrigger EventName="SelectionChanged">
                    <Behaviors:InvokeCommandAction Command="{Binding ExecuteRowSelectedCommand}" CommandParameter="{Binding ElementName=dataGridForUserData, Path=SelectedItem}" />
                </Behaviors:EventTrigger>
            </Behaviors:Interaction.Triggers>

            <DataGrid.Columns>
                <DataGridTextColumn
                    Width="80"
                    Binding="{Binding Id}"
                    FontSize="20"
                    Header="ID" />
                <DataGridTextColumn
                    Binding="{Binding Name}"
                    FontSize="20"
                    Header="名前" />

                <DataGridTextColumn
                    Binding="{Binding Age}"
                    FontSize="20"
                    Header="年齢" />

                <DataGridTextColumn
                    Binding="{Binding BrithDay}"
                    FontSize="20"
                    Header="誕生日" />

                <DataGridTextColumn
                    Binding="{Binding Sex}"
                    FontSize="20"
                    Header="性別" />

                <DataGridTextColumn
                    Binding="{Binding Email}"
                    FontSize="20"
                    Header="Email" />

                <DataGridTextColumn
                    Binding="{Binding Tell}"
                    FontSize="20"
                    Header="電話番号" />

            </DataGrid.Columns>
        </DataGrid>

        <StackPanel Grid.Row="2" Orientation="Horizontal">

            <Button
                Width="150"
                Height="40"
                Margin="10,0,0,0"
                Command="{Binding ExecuteGetData}"
                Content="データを取得する"
                Cursor="Hand" />

        </StackPanel>

    </Grid>
</Window>

コードの内容について解説していきます。

ItemsSource

DataGrid に表示させるデータはItemsSourceプロパティに指定します。ここでは、ItemsSource="{Binding UserEntityData}"と記述しUserEntityDataという ViewModel 側のプロパティをデータバインディングしています。

その実態はUserEnitityクラスで、CSV やデータベースから取得してきたデータを格納するクラスです。

"UserEnitity.cs"
using System;

namespace DataGrid_Sample.Enitities
{
    public sealed class UserEnitity
    {
        public int Id { get; }

        public string Name { get; }

        public int Age { get; }

        public string BrithDay { get; }

        public string Sex { get; }

        public string Email { get; }

        public string Tell { get; }

        public UserEnitity(int id, string name, string age, string brithDay, string sex, string email, string tell)
        {
            Id = id;
            Name = name;
            Age = Int32.Parse(age);
            BrithDay = brithDay;
            Sex = sex;
            Email = email;
            Tell = tell;
        }
    }
}

引数に指定している値はValueObjectとしてクラスに変換してプロパティに格納したほうが、振る舞いを持たせたい(値を表示する際にロジックを持たせたい)ときにロジックが散らばったりしないのですが、今回は簡易に実装するためプリミティブな形のまま格納します。

DataGrid のItemsSourceプロパティに指定できるデータはIEnumerableが実装されたクラスを指定する必要があるので、UserEnitityクラスをObservableCollection<T>に格納します。

IEnumerableはざっくり言うと、foreachで反復して出力できる型のことです。

ObservableCollection<T>IEnumerableが実装されたクラスです。

"MainWindowViewModel.cs"
/// <summary>
/// DataGridに表示させるリスト
/// </summary>
public ObservableCollection<UserEnitity> UserEntityData
{
    get => _userEntityData;
    set => SetProperty(ref _userEntityData, value);
}
private ObservableCollection<UserEnitity> _userEntityData;

DataGrid コントロールのプロパティにどのようなデータが指定できるかは、実際にコードを書いてみると把握できます。

datagrid-item-source

AutoGenerateColumns=“False”

AutoGenerateColumns="False"を指定することで、DataGrid はデータソースから自動的に列を生成しません。

つまり、この属性を指定した場合は<DataGrid.Columns>と指定して、出力するカラムItemsSource指定したクラスのプロパティを指定する必要があります。

AutoGenerateColumns="False"<DataGrid.Columns>を指定しなかった場合は以下のような出力になります(カラムの名前に注目してください)

UserEnitityのプロパティ名が DataGrid のカラム名になっていることが分かります。

normal-datagrid-clmun

SelectionChanged

以下のコードは DataGrid の 1 行をクリックしたときに値を取得する処理を実装しています。

"MainWindow.xaml"
<!-- 省略 -->

<DataGrid
    x:Name="dataGridForUserData"
    Grid.Row="1"
    d:ItemsSource="{d:SampleData ItemCount=5}"
    AutoGenerateColumns="False"
    Cursor="Hand"
    GridLinesVisibility="All"
    IsReadOnly="True"
    ItemsSource="{Binding UserEntityData}"
    VerticalScrollBarVisibility="Auto">
    <Behaviors:Interaction.Triggers>
        <Behaviors:EventTrigger EventName="SelectionChanged">
            <Behaviors:InvokeCommandAction Command="{Binding ExecuteRowSelectedCommand}" CommandParameter="{Binding ElementName=dataGridForUserData, Path=SelectedItem}" />
        </Behaviors:EventTrigger>
    </Behaviors:Interaction.Triggers>

<!-- 省略 -->

Microsoft.Xaml.Behaviors.Wpfというパッケージを使用して特定のイベントをトリガーしています。

どのようなイベントが設定できる調べるには、公式のDataGrid クラスを参照しても良いですが、サッと確認するときは ビジュアルデザイナー内の DataGrid コントロールを選択してプロパティからイベントを確認することが出来ます(イナズママークを選択)。

datagrid-event

SelectionChangedイベントで実行される処理の内容は以下のようになっています。

"MainWindowViewModel.cs"
private void RowSelectedCommand(object o)
{
    var entity = o as UserEnitity;

    if (entity != null)
    {
        var displayValue = $"ユーザーID: {entity.Id}\n" +
                            $"名前: {entity.Name}\n" +
                            $"年齢: {entity.Age}\n" +
                            $"誕生日:  {entity.BrithDay}\n" +
                            $"性別: {entity.Sex}\n" +
                            $"メールアドレス: {entity.Email}\n" +
                            $"電話番号: {entity.Tell}";

        MessageBox.Show($"{displayValue}",
                            "ユーザー情報",
                            MessageBoxButton.OK,
                            MessageBoxImage.Information);
    }
}

選択された行のプロパティを出力しているだけの実装となっていますが、選択された際に編集用画面を開いて選択された行の値を変更するフォームを実装するなど応用することも出来ます。

CSV から取得する

CSV からデータを取得する処理です。

注意点としては、.NET6では SJIS エンコードを使用する際にEncoding.RegisterProviderを呼ぶ必要があります。

"CSVClient.cs"
using DataGrid_Sample.Enitities;
using DataGrid_Sample.Repositories;
using System;
using System.Collections.ObjectModel;
using System.IO;
using System.Text;

namespace DataGrid_Sample.Csv
{
    public sealed class CSVClient : IDataGridRepository
    {
        /// <summary>
        /// 現在処理中の行が1行目か判定する
        /// </summary>
        private bool _isFirstLine = true;

        /// <summary>
        /// アプリケーションが実行されているディレクトリを取得し、指定した文字列を結合させる
        /// </summary>
        private string _currentDomain = AppDomain.CurrentDomain.BaseDirectory;

        public ObservableCollection<UserEnitity> Get()
        {
            var entity = new ObservableCollection<UserEnitity>();

            int indexCount = 0;

            try
            {
                Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
                using (var reader = new StreamReader(_currentDomain + "dummyData.csv", Encoding.GetEncoding("Shift_JIS")))
                {
                    // 1.whileで1行ずつ読み込む
                    // 2.最初の行はスキップする
                    // 3.split(",") でカンマを基準に各要素を配列に変換する
                    // 4.UserEntityに必要な要素だけインデックスを指定して取り出す

                    while (reader.Peek() >= 0)
                    {
                        var line = reader.ReadLine();

                        if (_isFirstLine)
                        {
                            _isFirstLine = false;

                            continue;
                        }

                        var strArray = line.Split(",");

                        indexCount++;

                        entity.Add(new UserEnitity(
                                    indexCount,
                                    strArray[0],
                                    strArray[3],
                                    strArray[4],
                                    strArray[5],
                                    strArray[7],
                                    strArray[8]
                                    )
                            );
                    }

                    return entity;
                }

            }catch (Exception)
            {
                throw;
            }
        }
    }
}

まとめ

今回は MVVM で DataGrid の実装について紹介しました。

今回使用したコードはGitHub リポジトリで公開しています。