前回までに、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接続+ボタンコマンド実装
今回は 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 を実行してみる
以下のように表示されれば成功です!
「Insert」「Delete」ボタンをを試してください。
次回予告
障害対応、デバッグに必須な Log4net を実装する予定です。