C#でコレクションの要素の変更をバインド先のコントロールに反映する方法
C#でコレクションをListViewなどのコントロールにバインディングしているときに、コレクションの要素の変更を自動的にバインド先のコントロールに反映する方法です。
目次
ObservableCollectionクラスは要素の変更を通知しない
ListViewなどにバインドするコレクションを定義する際には、ObservableCollectionを使うと思います。
ObservableCollectionは、コレクションのアイテムが追加または削除されたとき、あるいはリスト全体が更新されたときに、通知を行います。
ただし、そのコレクションの要素のプロパティが変更された場合には、通知は行いません。
具体例
実際に通知が行われない例を見てみましょう。
ListViewとTextBoxから成るアプリで、リストの選択したアイテムをTextBoxに表示し、TextBoxを変更するとそのようにデータを書き換えるアプリです。
<Window x:Class="listview_test.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:listview_test"
mc:Ignorable="d"
Title="MainWindow" Height="200" Width="400" Loaded="Window_Loaded">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="150" />
</Grid.ColumnDefinitions>
<ListView Name="ListViewMain" Grid.Column="0" ItemsSource="{Binding}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="10" SelectionChanged="ListViewMain_SelectionChanged" >
<ListView.View>
<GridView>
<GridViewColumn DisplayMemberBinding="{Binding Path=Moji}">
<GridViewColumnHeader Content="文字" />
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
<StackPanel Grid.Column="1">
<TextBox Name="TextBox1" Margin="10,40,10,10" TextChanged="TextBox1_TextChanged" />
</StackPanel>
</Grid>
</Window>
GridViewColumnにバインディングのPathを指定します。
WindowにLoadedイベントを、ListViewにSelectionChangedイベントを、TextBoxにTextChangedイベントを設定します。
using System;
using System.Windows;
using System.Windows.Controls;
using System.Collections.ObjectModel;
namespace listview_test
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
ObservableCollection<Hoge> Piyo = new ObservableCollection<Hoge>();
private void Window_Loaded(object sender, RoutedEventArgs e)
{
Piyo.Add(new Hoge() { Moji = "いろは" });
Piyo.Add(new Hoge() { Moji = "にほへと" });
Piyo.Add(new Hoge() { Moji = "ちりぬるを" });
ListViewMain.DataContext = Piyo;
}
private void ListViewMain_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (ListViewMain.SelectedItem == null) return;
// 選択したアイテムをTextBoxに表示する
Hoge item = (Hoge)ListViewMain.SelectedItem;
TextBox1.Text = item.Moji;
}
private void TextBox1_TextChanged(object sender, TextChangedEventArgs e)
{
int i = ListViewMain.SelectedIndex;
if (i < 0) return;
// TextBoxの変更内容をデータに反映する
Piyo[i].Moji = TextBox1.Text;
}
}
public class Hoge
{
public string Moji { set; get; }
}
Windowをロードすると、Piyoオブジェクトにアイテムを3つ追加してListViewにバインディングします。
ListViewのSelectionChangedイベントが起きると、ListViewで選択されたアイテムを取り出して、そのMojiプロパティの内容をTextBoxのTextプロパティにセットします。
TextBoxのTextChangedイベントが起きると、コレクションの内容をTextプロパティの内容に書き換えます。
これを実行すると、このようになります。
TextBoxを書き換えたのに、ListViewには反映されていないですね。
実際のところ、データの方は変更されているのですが、ListViewの表示が変更されないという状態になっています。
コレクションの要素のプロパティが変更されたのに、その変更がバインド先のコントロールに反映されていないという状態です。
変更を通知する仕組みを作る
ObservableCollectionだけではプロパティの変更を通知してくれないので、コレクションを作成するクラスの方に通知イベントを定義します。
イベントを定義したクラスのコードは下記のようになります。
public class Hoge : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private string _Moji = string.Empty;
public string Moji
{
get { return this._Moji; }
set
{
if (value != this._Moji)
{
this._Moji = value;
NotifyPropertyChanged();
}
}
}
}
まず、INotifyPropertyChangedインターフェイスを継承します。
そして、PropertyChangedイベントを定義します。
さらに、NotifyPropertyChangedというprivateメソッドを定義します。このメソッドはPropertyChangedイベントを発行するメソッドです。
privateなプロパティを宣言します。(ここでは_Mojiです。)これは、プロパティの変更前の状態を保持しておくためのものです。
プロパティのgetterは、保持しているprivateプロパティの値を返します。setterに値がセットされると、変更前の値と比較して、セットされた値が変更前の値と異なる場合はNotifyPropertyChangedメソッドを呼び出します。
こうして、このクラスのプロパティのsetterに異なる値がセットされるとPropertyChangedが発行されるわけです。
実施例
このコードで実行してみましょう。
XAMLは変更ないので省略します。
using System;
using System.Windows;
using System.Windows.Controls;
using System.Collections.ObjectModel;
using System.ComponentModel;
namespace listview_test
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
ObservableCollection<Hoge> Piyo = new ObservableCollection<Hoge>();
private void Window_Loaded(object sender, RoutedEventArgs e)
{
Piyo.Add(new Hoge() { Moji = "いろは" });
Piyo.Add(new Hoge() { Moji = "にほへと" });
Piyo.Add(new Hoge() { Moji = "ちりぬるを" });
ListViewMain.DataContext = Piyo;
}
private void ListViewMain_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (ListViewMain.SelectedItem == null) return;
// 選択したアイテムをTextBoxに表示する
Hoge item = (Hoge)ListViewMain.SelectedItem;
TextBox1.Text = item.Moji;
}
private void TextBox1_TextChanged(object sender, TextChangedEventArgs e)
{
int i = ListViewMain.SelectedIndex;
if (i < 0) return;
// TextBoxの変更内容をデータに反映する
Piyo[i].Moji = TextBox1.Text;
}
}
public class Hoge : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private string _Moji = string.Empty;
public string Moji
{
get { return this._Moji; }
set
{
if (value != this._Moji)
{
this._Moji = value;
NotifyPropertyChanged();
}
}
}
}
}
usingにSystem.ComponentModelが必要なので注意してください。
クラスの定義が変わっただけです。
実行するとこうなります。
TextBoxの変更内容がListViewに反映されます。
公開日
広告
C#のコントロールカテゴリの投稿
- C#でコレクションの要素の変更をバインド先のコントロールに反映する方法
- C#のComboBoxを使ってみた
- C#のDataGridで右クリックメニューを作ってみた
- C#のDataGridの罫線と背景色を変えてみた
- C#のListBoxでCheckBoxを並べてみた
- C#のListBoxを使ってみた
- C#のListViewで列のタイトルを変える
- C#のListViewで選択したアイテムを取得する方法
- C#のListViewに文字を入力する方法
- C#のListViewのヘッダーをクリックして列をソートする方法
- C#のListViewを使ってみた
- C#のRadioButtonで選択された項目を調べる(foreach編)
- C#のRadioButtonを試してみた
- C#のTextBoxで最下行に自動でスクロールする方法
- C#のWPFのコントロール一覧
- C#のスライダコントロールを試してみた
- C#のタブをコードから切り替える
- C#のメニューのイベントを1つにまとめてみた