準備
対象
C#なんとなく分かってる。駆け出しプログラマー
目標
簡単なバインディングの流れをつかむ。なので、検索機能のみあるものを作る。
バインディング主軸で説明するので、SQLなどライブラリとかは使いません。
どのようなアプリにするか?
生徒のテストの成績を管理するアプリ。
要件
生徒はクラスIdと、出席番号と、男か女と、点数を持つ。
検索、追加、削除を行えるようにする。
設計
生徒一人の状態を表すStudentクラスを作成する。
WPFで、Xaml側への反映を行うためViewModelクラスの作成を行う。
Studentのフィールドは
変数名 | 型 | 説明 |
---|---|---|
ClassId | int | クラスの番号 |
Id | int | 出席番号 |
Gender | bool | 男か女か |
Score | int | 点数 |
ViewModelのフィールドは
変数名 | 型 | 説明 |
---|---|---|
ScoreList | ObservableCollection<Student> | 生徒全体のリスト |
ResultList | ObservableCollection<Student> | 検索結果のリスト |
ClassId | int | クラスの番号(検索用) |
Id | int | 出席番号(検索用) |
IsManChecked | bool | 男がチェックされているか |
IsWomanChecked | bool | 女がチェックされているか |
Score | int | 点数(検索用) |
コード作成
Studentクラス
publicclassStudent{/// <summary>/// クラスの番号/// </summary>publicintClassId{get;set;}/// <summary>/// 出席番号/// </summary>publicintId{get;set;}/// <summary>/// 男か女か/// </summary>publicboolGender{get;set;}/// <summary>/// 点数/// </summary>publicintScore{get;set;}}
MainWindowのVMクラスMainVM
publicclassMainVM{publicObservableCollection<Student>ScoreList{get;set;}=newObservableCollection<Student>();publicObservableCollection<Student>ResultList{get;set;}=newObservableCollection<Student>();privateint_ClassId;/// <summary>/// クラスの番号(検索用)/// </summary>publicintClassId{get=>_ClassId;set{_ClassId=value;}}privateint_Id;/// <summary>/// 出席番号(検索用)/// </summary>publicintId{get=>_Id;set{_Id=value;}}privatebool_IsManChecked;/// <summary>/// 男がチェックされているか/// </summary>publicboolIsManChecked{get=>_IsManChecked;set{_IsManChecked=value;}}privatebool_IsWomanChecked;/// <summary>/// 女がチェックされているか/// </summary>publicboolIsWomanChecked{get=>_IsWomanChecked;set{_IsWomanChecked=value;}}privateint_Score;/// <summary>/// 点数(検索用)/// </summary>publicintScore{get=>_Score;set{_Score=value;}}}
Studentクラスの方は入れ物としての役割なのでこれでいいだろう。
VMクラスではまだ色々と準備が必要になる。説明の後に記述する。
説明:なぜカプセル化を行うのか?
VMクラスでなぜprivateのカプセル化を行っているかというと、
バインディングを行うには通知を送る必要がある。VMクラスで値が代入された瞬間に通知を行うとなるとsetterに入れるのがいいが、setterに入れるとなると値の代入はコードで明示的に示してあげないと代入されてくれなくなる。
なので、明示的にClassId = value
のようにしようとするとこれがまた代入とみなされsetterが動いてしまい、無限ループのようになってエラーが起こってしまう。
なのでsetterに代入の処理を書いてあげるときはカプセル化するのがよい。
説明:なぜバインディングするときにリストを使ってはいけないのか?
上の方にあるObservableCollection
はリストのようなもの。
なぜリストを使わずにこちらを使うのかというと、intやboolであれば代入=状態の変更なので代入のときに変更通知を出してあげれば、状態変更したら変更通知が出されたことになる。
しかし、Listなどの場合Add,Removeなどがあるため代入≠状態の変更なので、代入の時に、変更通知を出してあげればいいというだけでは十分ではない。ObservableCollection
はAdd,Removeなどしたときに、変更通知を出してくれるクラスである。
INotifyPropertyChanged実装
次にバインディングを行うためのINotifyPropertyChangedインターフェースをVMクラスに実装する。
MainVMクラスを改良
publicclassMainVM:INotifyPropertyChanged{publiceventPropertyChangedEventHandlerPropertyChanged;publicvoidNotifyPropertyChanged(stringPropertyName){vare=newPropertyChangedEventArgs(PropertyName);PropertyChanged?.Invoke(this,e);}publicObservableCollection<Student>ScoreList{get;set;}=newObservableCollection<Student>();publicObservableCollection<Student>ResultList{get;set;}=newObservableCollection<Student>();privateint_ClassId;/// <summary>/// クラスの番号(検索用)/// </summary>publicintClassId{get=>_ClassId;set{_ClassId=value;NotifyPropertyChanged(nameof(ClassId));}}privateint_Id;/// <summary>/// 出席番号(検索用)/// </summary>publicintId{get=>_Id;set{_Id=value;NotifyPropertyChanged(nameof(Id));}}privatebool_IsManChecked;/// <summary>/// 男がチェックされているか/// </summary>publicboolIsManChecked{get=>_IsManChecked;set{_IsManChecked=value;NotifyPropertyChanged(nameof(IsManChecked));}}privatebool_IsWomanChecked;/// <summary>/// 女がチェックされているか/// </summary>publicboolIsWomanChecked{get=>_IsWomanChecked;set{_IsWomanChecked=value;NotifyPropertyChanged(nameof(IsWomanChecked));}}privateint_Score;/// <summary>/// 点数(検索用)/// </summary>publicintScore{get=>_Score;set{_Score=value;NotifyPropertyChanged(nameof(Score));}}}
InofiryPropertyChangedはただ一つPropertyChangedだけ持っており、それを実装する。PropertyChanged.Invoke(object, PropertyChangedEventArgs)
でPropertyChangedイベントを実行することができobjectはthisで済ませればよく、PropertyChangedEventArgsはnew PropertyChangedEventArgs(PropertyName)
のように変数名を入れて宣言してあげればよい。
変数名に関して、間違いが起こらないようにnameof演算子で実行する。
そしてメソッド化したものをlist以外の各変数のsetterに定義する。
そしてMainWindowクラスでVMクラスをグローバル変数にして、DataContextに登録してあげる。
/// <summary>/// Interaction logic for MainWindow.xaml/// </summary>publicpartialclassMainWindow:Window{privateMainVMMyVM=newMainVM();publicMainWindow(){InitializeComponent();DataContext=MyVM;}}
初期値代入
これでバインディングの準備は完了したがバインディングが成功しているかどうかわかりづらいのでMainVMクラスのカプセル化した変数とリストにそれぞれ初期値を入れてあげる。
publicclassMainVM:INotifyPropertyChanged{publiceventPropertyChangedEventHandlerPropertyChanged;publicvoidNotifyPropertyChanged(stringPropertyName){vare=newPropertyChangedEventArgs(PropertyName);PropertyChanged?.Invoke(this,e);}publicObservableCollection<Student>ScoreList{get;set;}=newObservableCollection<Student>(){newStudent(){ClassId=1,Id=1,Gender=true,Score=82},newStudent(){ClassId=1,Id=2,Gender=false,Score=89},newStudent(){ClassId=1,Id=3,Gender=true,Score=74},newStudent(){ClassId=2,Id=1,Gender=false,Score=79},newStudent(){ClassId=2,Id=2,Gender=true,Score=94},newStudent(){ClassId=2,Id=3,Gender=false,Score=87},newStudent(){ClassId=3,Id=1,Gender=true,Score=69},newStudent(){ClassId=3,Id=2,Gender=false,Score=75},newStudent(){ClassId=3,Id=3,Gender=true,Score=94}};publicObservableCollection<Student>ResultList{get;set;}=newObservableCollection<Student>();privateint_ClassId=1;/// <summary>/// クラスの番号(検索用)/// </summary>publicintClassId{get=>_ClassId;set{_ClassId=value;NotifyPropertyChanged(nameof(ClassId));}}privateint_Id=1;/// <summary>/// 出席番号(検索用)/// </summary>publicintId{get=>_Id;set{_Id=value;NotifyPropertyChanged(nameof(Id));}}privatebool_IsManChecked=true;/// <summary>/// 男がチェックされているか/// </summary>publicboolIsManChecked{get=>_IsManChecked;set{_IsManChecked=value;NotifyPropertyChanged(nameof(IsManChecked));}}privatebool_IsWomanChecked=true;/// <summary>/// 女がチェックされているか/// </summary>publicboolIsWomanChecked{get=>_IsWomanChecked;set{_IsWomanChecked=value;NotifyPropertyChanged(nameof(IsWomanChecked));}}privateint_Score=0;/// <summary>/// 点数(検索用)/// </summary>publicintScore{get=>_Score;set{_Score=value;NotifyPropertyChanged(nameof(Score));}}}
これで初期値が入ったので、画面側のxamlを作る。
<Windowx:Class="WpfApp3.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:WpfApp3"mc:Ignorable="d"Title="MainWindow"Height="450"Width="800"><Grid><StackPanelOrientation="Vertical"HorizontalAlignment="Left"><StackPanelOrientation="Horizontal"><TextBlockText="クラス"></TextBlock><TextBoxText="{Binding ClassId}"></TextBox></StackPanel><StackPanelOrientation="Horizontal"><TextBlockText="出席番号"></TextBlock><TextBoxText="{Binding Id}"></TextBox></StackPanel><StackPanelOrientation="Horizontal"><CheckBoxContent="男"IsChecked="{Binding IsManChecked}"></CheckBox><CheckBoxContent="女"IsChecked="{Binding IsWomanChecked}"></CheckBox></StackPanel><StackPanelOrientation="Horizontal"><TextBlockText="点数"></TextBlock><TextBoxText="{Binding Score}"></TextBox></StackPanel><ButtonContent="検索"Click="Button_Click"></Button></StackPanel><StackPanelOrientation="Vertical"HorizontalAlignment="Right"><DataGridItemsSource="{Binding ScoreList}"></DataGrid><DataGridItemsSource="{Binding ResultList}"></DataGrid></StackPanel></Grid></Window>
コード側全体
usingSystem.Collections.ObjectModel;usingSystem.ComponentModel;usingSystem.Windows;namespaceWpfApp3{/// <summary>/// Interaction logic for MainWindow.xaml/// </summary>publicpartialclassMainWindow:Window{privateMainVMMyVM=newMainVM();publicMainWindow(){InitializeComponent();DataContext=MyVM;}privatevoidButton_Click(objectsender,RoutedEventArgse){//まだ未実装}}publicclassMainVM:INotifyPropertyChanged{publiceventPropertyChangedEventHandlerPropertyChanged;publicvoidNotifyPropertyChanged(stringPropertyName){vare=newPropertyChangedEventArgs(PropertyName);PropertyChanged?.Invoke(this,e);}publicObservableCollection<Student>ScoreList{get;set;}=newObservableCollection<Student>(){newStudent(){ClassId=1,Id=1,Gender=true,Score=82},newStudent(){ClassId=1,Id=2,Gender=false,Score=89},newStudent(){ClassId=1,Id=3,Gender=true,Score=74},newStudent(){ClassId=2,Id=1,Gender=false,Score=79},newStudent(){ClassId=2,Id=2,Gender=true,Score=94},newStudent(){ClassId=2,Id=3,Gender=false,Score=87},newStudent(){ClassId=3,Id=1,Gender=true,Score=69},newStudent(){ClassId=3,Id=2,Gender=false,Score=75},newStudent(){ClassId=3,Id=3,Gender=true,Score=94}};publicObservableCollection<Student>ResultList{get;set;}=newObservableCollection<Student>();privateint_ClassId=1;/// <summary>/// クラスの番号(検索用)/// </summary>publicintClassId{get=>_ClassId;set{_ClassId=value;NotifyPropertyChanged(nameof(ClassId));}}privateint_Id=1;/// <summary>/// 出席番号(検索用)/// </summary>publicintId{get=>_Id;set{_Id=value;NotifyPropertyChanged(nameof(Id));}}privatebool_IsManChecked=true;/// <summary>/// 男がチェックされているか/// </summary>publicboolIsManChecked{get=>_IsManChecked;set{_IsManChecked=value;NotifyPropertyChanged(nameof(IsManChecked));}}privatebool_IsWomanChecked=true;/// <summary>/// 女がチェックされているか/// </summary>publicboolIsWomanChecked{get=>_IsWomanChecked;set{_IsWomanChecked=value;NotifyPropertyChanged(nameof(IsWomanChecked));}}privateint_Score=0;/// <summary>/// 点数(検索用)/// </summary>publicintScore{get=>_Score;set{_Score=value;NotifyPropertyChanged(nameof(Score));}}}publicclassStudent{/// <summary>/// クラスの番号/// </summary>publicintClassId{get;set;}/// <summary>/// 出席番号/// </summary>publicintId{get;set;}/// <summary>/// 男か女か/// </summary>publicboolGender{get;set;}/// <summary>/// 点数/// </summary>publicintScore{get;set;}}}
検索機能
ここから検索機能を追加する。
MainWindowクラスButton_Clickメソッドの部分
privatevoidButton_Click(objectsender,RoutedEventArgse){varlinq=newObservableCollection<Student>(MyVM.ScoreList).AsEnumerable();if(MyVM.ClassId!=0){linq=linq.Where(x=>x.ClassId==MyVM.ClassId);}if(MyVM.Id!=0){linq=linq.Where(x=>x.Id==x.Id);}//三項演算子,x.GenderがtrueであればIsMancheckedをみて、falseであればIsWomanCheckを見る。linq=linq.Where(x=>x.Gender?MyVM.IsManChecked:MyVM.IsWomanChecked);if(MyVM.Score>=0){linq=linq.Where(x=>x.Score>=MyVM.Score);}MyVM.ResultList=newObservableCollection<Student>(linq);MyVM.NotifyPropertyChanged(nameof(MyVM.ResultList));}
検索の大体はLinqでやっているので特にいうこともないが、最後のMyVM.NotifyPropertyChangedについて
ObservableCollection型の時はAdd,Removeは変更通知を出してくれるが、代入の時は変更通知を出してくれないので自分でやる必要がある。
ただし、intやboolと同じようにprivateにしてカプセル化を行うとAdd,Removeなどをした場合に状態が想定したものと一致しなくなるのでカプセル化をやるのは難しそうではある。
そこで、MainWindowクラスで直接呼び出して変更通知を行い反映させてやる。
(きちんとやればVMクラスで完結するはず。)
全コード
xaml
<Windowx:Class="WpfApp3.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:WpfApp3"mc:Ignorable="d"Title="MainWindow"Height="450"Width="800"><Grid><StackPanelOrientation="Vertical"HorizontalAlignment="Left"><StackPanelOrientation="Horizontal"><TextBlockText="クラス"></TextBlock><TextBoxText="{Binding ClassId}"></TextBox></StackPanel><StackPanelOrientation="Horizontal"><TextBlockText="出席番号"></TextBlock><TextBoxText="{Binding Id}"></TextBox></StackPanel><StackPanelOrientation="Horizontal"><CheckBoxContent="男"IsChecked="{Binding IsManChecked}"></CheckBox><CheckBoxContent="女"IsChecked="{Binding IsWomanChecked}"></CheckBox></StackPanel><StackPanelOrientation="Horizontal"><TextBlockText="点数"></TextBlock><TextBoxText="{Binding Score}"></TextBox></StackPanel><ButtonContent="検索"Click="Button_Click"></Button></StackPanel><StackPanelOrientation="Vertical"HorizontalAlignment="Right"><DataGridItemsSource="{Binding ScoreList}"></DataGrid><DataGridItemsSource="{Binding ResultList}"></DataGrid></StackPanel></Grid></Window>
C#側のコード
usingSystem.Collections.ObjectModel;usingSystem.ComponentModel;usingSystem.Linq;usingSystem.Windows;namespaceWpfApp3{/// <summary>/// Interaction logic for MainWindow.xaml/// </summary>publicpartialclassMainWindow:Window{privateMainVMMyVM=newMainVM();publicMainWindow(){InitializeComponent();DataContext=MyVM;}privatevoidButton_Click(objectsender,RoutedEventArgse){varlinq=newObservableCollection<Student>(MyVM.ScoreList).AsEnumerable();if(MyVM.ClassId!=0){linq=linq.Where(x=>x.ClassId==MyVM.ClassId);}if(MyVM.Id!=0){linq=linq.Where(x=>x.Id==x.Id);}//三項演算子,x.GenderがtrueであればIsMancheckedをみて、falseであればIsWomanCheckを見る。linq=linq.Where(x=>x.Gender?MyVM.IsManChecked:MyVM.IsWomanChecked);if(MyVM.Score>=0){linq=linq.Where(x=>x.Score>=MyVM.Score);}MyVM.ResultList=newObservableCollection<Student>(linq);MyVM.NotifyPropertyChanged(nameof(MyVM.ResultList));}}publicclassMainVM:INotifyPropertyChanged{publiceventPropertyChangedEventHandlerPropertyChanged;publicvoidNotifyPropertyChanged(stringPropertyName){vare=newPropertyChangedEventArgs(PropertyName);PropertyChanged?.Invoke(this,e);}publicObservableCollection<Student>ScoreList{get;set;}=newObservableCollection<Student>(){newStudent(){ClassId=1,Id=1,Gender=true,Score=82},newStudent(){ClassId=1,Id=2,Gender=false,Score=89},newStudent(){ClassId=1,Id=3,Gender=true,Score=74},newStudent(){ClassId=2,Id=1,Gender=false,Score=79},newStudent(){ClassId=2,Id=2,Gender=true,Score=94},newStudent(){ClassId=2,Id=3,Gender=false,Score=87},newStudent(){ClassId=3,Id=1,Gender=true,Score=69},newStudent(){ClassId=3,Id=2,Gender=false,Score=75},newStudent(){ClassId=3,Id=3,Gender=true,Score=94}};publicObservableCollection<Student>ResultList{get;set;}=newObservableCollection<Student>();privateint_ClassId=0;/// <summary>/// クラスの番号(検索用)/// </summary>publicintClassId{get=>_ClassId;set{_ClassId=value;NotifyPropertyChanged(nameof(ClassId));}}privateint_Id=0;/// <summary>/// 出席番号(検索用)/// </summary>publicintId{get=>_Id;set{_Id=value;NotifyPropertyChanged(nameof(Id));}}privatebool_IsManChecked=true;/// <summary>/// 男がチェックされているか/// </summary>publicboolIsManChecked{get=>_IsManChecked;set{_IsManChecked=value;NotifyPropertyChanged(nameof(IsManChecked));}}privatebool_IsWomanChecked=true;/// <summary>/// 女がチェックされているか/// </summary>publicboolIsWomanChecked{get=>_IsWomanChecked;set{_IsWomanChecked=value;NotifyPropertyChanged(nameof(IsWomanChecked));}}privateint_Score=0;/// <summary>/// 点数(検索用)/// </summary>publicintScore{get=>_Score;set{_Score=value;NotifyPropertyChanged(nameof(Score));}}}publicclassStudent{/// <summary>/// クラスの番号/// </summary>publicintClassId{get;set;}/// <summary>/// 出席番号/// </summary>publicintId{get;set;}/// <summary>/// 男か女か/// </summary>publicboolGender{get;set;}/// <summary>/// 点数/// </summary>publicintScore{get;set;}}}