WPF(C#) .NET6.0 LocalDB のデータを ListView で表示しよう

[前回](https://qiita.com/noriseto/items/2043f87ae9be32c4bb25)までに、WPF の ListView と LocalDB 接続を行ったので、今回は LocalDB のデータの更新と、ListView にリアルタイムに反映させる部分を作成します。

### View の作成
- \<Grid.RowDefinitions\> でデータ追加ボタンの行とListViewの行を作成

```
<Grid.RowDefinitions>
    <RowDefinition Height="30"/>
    <RowDefinition Height="*"/>
</Grid.RowDefinitions>
```

- \<StackPanel\> で \<TextBox\>\<Button\>コントロールを横並びに

```
<StackPanel Grid.Row="0" Orientation="Horizontal">
```
- \<TextBox\> のデータは ViewModelの変数をバインド

```
<TextBox x:Name="UpdateFirstName" Text="{Binding InsertFirstName}" />
```
- 「Insert」ボタンのコマンドは、ICommand で ViewModel の関数にバインド

```
<Button Content="Insert" Command="{Binding InsertCommand}" />
```

- ListView 各行の4カラム目に削除ボタン実装
  コマンドのパラメータとして選択した行の Id をバインド

```
<Button Content="Delete" Command="{Binding DataContext.ListTrashCommand, ElementName=SampleListView}" CommandParameter="{Binding Id}" />
```
以下、今回作成したソースコードになります。

```MainWindows.xaml
<Window x:Class="SampleWPF.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:SampleWPF"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="30"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <StackPanel Grid.Row="0" Orientation="Horizontal">
            <Label Content="First Name:" Width="70" Height="30" Margin="5,0,0,0" />
            <TextBox x:Name="UpdateFirstName" Text="{Binding InsertFirstName}" Width="100" Height="20" Margin="0,0,0,0" />
            <Label Content="Last Name:" Width="70" Height="30" Margin="5,0,0,0" />
            <TextBox x:Name="UpdateLastName" Text="{Binding InsertLastName}" Width="100" Height="20" Margin="0,0,0,0" />
            <Button Content="Insert"  Height="20" Width="60" Margin="5,0,0,0" Command="{Binding InsertCommand}" />
        </StackPanel>

        <ListView Grid.Row="1" x:Name="SampleListView" ItemsSource="{Binding UserList}">
            <ListView.View>
                <GridView>
                    <GridViewColumn Width="30" Header="Id"  DisplayMemberBinding="{Binding Id}" />
                    <GridViewColumn Width="100" Header="First Name"  DisplayMemberBinding="{Binding FirstName}" />
                    <GridViewColumn Width="100" Header="Last Name"  DisplayMemberBinding="{Binding LastName}" />
                    <GridViewColumn Width="60">
                        <GridViewColumn.CellTemplate>
                            <DataTemplate>
                                <Button Content="Delete" Height="20" Width="45" Command="{Binding DataContext.ListTrashCommand, ElementName=SampleListView}" CommandParameter="{Binding Id}" />
                            </DataTemplate>
                        </GridViewColumn.CellTemplate>
                    </GridViewColumn>
                </GridView>
            </ListView.View>
        </ListView>
    </Grid>
</Window>
```

### 前回のLocalDB へ初期データ投入部分は削除
- View と ViewModel との連携部分のみに戻します。
```
this.DataContext = new MainWindowVM();
```

```MainWindows.xaml.cs
MainWindows.xaml.cs
using SampleWPF.Models;
using SampleWPF.ViewModels;
using System;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;

namespace SampleWPF
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            // ViewModel と連携
            this.DataContext = new MainWindowVM();
        }
    }
}
```

### View - ViewModel のコマンドを連携

ViewModels フォルダ内に ICommand を継承した ForwardedCommand クラスを実装
- パラメータ有りのジェネリッククラスとパラメータ無し版を実装
- 「CS8767 参照型の NULL 値の許容」警告を抑制

```ViewModels\ForwardedCommand.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;

namespace SampleWPF.ViewModels
{
    public class ForwardedCommand : ICommand
    {
        private Action action;
        private Func<bool> canExecute;
        public ForwardedCommand(Action a) : this(a, () => true) { }
        public ForwardedCommand(Action a, Func<bool> cx)
        {
            action = a;
            canExecute = cx;
        }
        public event EventHandler? CanExecuteChanged
        {
            add { CommandManager.RequerySuggested += value; }
            remove { CommandManager.RequerySuggested -= value; }
        }
#pragma warning disable CS8767
        public bool CanExecute(object parameter)
#pragma warning restore CS8767
        {
            return canExecute();
        }
#pragma warning disable CS8767
        public void Execute(object parmeter)
#pragma warning restore CS8767
        {
            action();
        }

    }

    // Parameter 付き Command
    public class ForwardedCommand<T> : ICommand
    {
        private Action<T> action;
        private Func<bool> canExecute;
        public ForwardedCommand(Action<T> a) : this(a, () => true) { }

        public ForwardedCommand(Action<T> a, Func<bool> cx)
        {
            action = a;
            canExecute = cx;
        }

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

#pragma warning disable CS8767
        public bool CanExecute(object parameter)
#pragma warning restore CS8767
        {
            return canExecute();
        }

#pragma warning disable CS8767
        public void Execute(object parameter)
#pragma warning restore CS8767
        {
            action((T)parameter);
        }
    }

}
```

### MainWindowVM にDB接続＋ボタンコマンド実装
:::note info
今回は ObservableCollection 自体の更新のみなので INotifyPropertyChanged の実装は割愛しました。
:::

- ForwardedCommand で、View からのコマンドを ViewModel 関数へ通知 

```
public ICommand InsertCommand { get; private set; }
public void ExecuteInsert()
{
    // コマンド実行時の処理
}

InsertCommand = new ForwardedCommand(ExecuteInsert);
```

- ListView のデータ更新は ObservableCollection 型の UserList 自体を入れ替えれ更新を通知

```
using (var dbcx = new SampleDbContext())
{
    UserList.Clear();
    ObservableCollection<User> uList = new ObservableCollection<User>(dbcx.Users.ToList());
    foreach (var user in uList) this.UserList.Add(user);
}
```
- 「CS8618 コンストラクターの終了時に null の可能性」警告をクラス変数の初期値を空代入して抑制

```
insertFirstName = "";
insertLastName = "";
```

以下がMainWindowVM.cs全体となります。
```ViewModels\MainWindowVM.cs
using SampleWPF.Models;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;

namespace SampleWPF.ViewModels
{
    public class MainWindowVM    {

        public static ObservableCollection<User>? userList { get; set; }
        public ObservableCollection<User> UserList
        {
#pragma warning disable CS8603 // Null 参照戻り値である可能性があります。
            get => userList;
#pragma warning restore CS8603 // Null 参照戻り値である可能性があります。
            set
            {
                userList = value;
            }
        }

        // Insert用データ
        private string insertFirstName;
        public string InsertFirstName
        {
            get => insertFirstName;
            set
            {
                insertFirstName = value;
            }
        }
        private string insertLastName;
        public string InsertLastName
        {
            get => insertLastName;
            set
            {
                insertLastName = value;
            }
        }

        private void UpdateUserList()
        {
            using (var dbcx = new SampleDbContext())
            {
                UserList.Clear();
                ObservableCollection<User> uList = new ObservableCollection<User>(dbcx.Users.ToList());
                foreach (var user in uList) this.UserList.Add(user);
            }

        }

        // ForwardedCommand
        public ICommand InsertCommand { get; private set; }
        public void ExecuteInsert()
        {
            using (var dbcx = new SampleDbContext())
            {
                // LocalDB 更新
                User insertUser = new()
                {
                    // Id は LocalDB で自動的に付与される
                    FirstName = InsertFirstName,
                    LastName = InsertLastName
                };
                dbcx.Users.Add(insertUser);
                dbcx.SaveChanges();
            }
            UpdateUserList();

        }

        public ICommand ListTrashCommand { get; private set; }
        public void ExecuteTrash(int id)
        {
            using (var dbcx = new SampleDbContext())
            {
                // LocalDB 更新
                var user = dbcx.Users.Single(x => x.Id == id);
                dbcx.Users.Remove(user);

                dbcx.SaveChanges();
            }
            UpdateUserList();
        }

        public MainWindowVM()
        {
            UserList = new ObservableCollection<User>();

            InsertCommand = new ForwardedCommand(ExecuteInsert);
            ListTrashCommand = new ForwardedCommand<int>(ExecuteTrash);

            // CS8618 対応
            insertFirstName = "";
            insertLastName = "";

            // UserList 初期化
            UpdateUserList();
        }

    }
}
```
### SampleWPF を実行してみる
以下のように表示されれば成功です！
![1.ListViewLocalDB.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/184036/fa94ea20-51b2-aa1c-00f5-13cee079e3f8.png)
「Insert」「Delete」ボタンをを試してください。

### 次回予告
障害対応、デバッグに必須な Log4net を実装する予定です。