【お勉強記録】WPFデータバインディング3
コレクションのバインディング
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace _0721Study { class UserCollenction { public UserCollenction() { User user1 = new User(); user1.Name = "山田太郎"; user1.Birthday = new DateTime(1990, 7, 21); user1.Adress = "東京都中央区"; this.users.Add(user1); User user2 = new User(); user2.Name = "田中次郎"; user2.Birthday = new DateTime(1985, 11, 15); ; user2.Adress = "福岡県福岡市博多区"; this.users.Add(user2); } public List<User> Users { get { return users; } } private List<User> users = new List<User>(); } }
<Window x:Class="_0721Study.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:_0721Study" Title="MainWindow" Height="350" Width="525"> <Window.Resources> <local:UserCollenction x:Key="oUserCollenction"/> </Window.Resources> <StackPanel Margin="10" DataContext="{Binding Source={StaticResource oUserCollenction}}"> <ListBox ItemsSource="{Binding Path=Users}" > <ListBox.ItemTemplate> <DataTemplate> <Grid> <Grid.RowDefinitions> <RowDefinition /> <RowDefinition /> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="200" /> <ColumnDefinition Width="*"/> </Grid.ColumnDefinitions> <TextBlock Grid.Row="0" Grid.Column="0" Text="{Binding Path=Name}"></TextBlock> <TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding Path=Adress}"></TextBlock> </Grid> </DataTemplate> </ListBox.ItemTemplate> </ListBox> </StackPanel> </Window>
List
DataTemplateは以下のようにも書き換えられそう。
<Window x:Class="_0721Study.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:_0721Study" Title="MainWindow" Height="350" Width="525"> <Window.Resources> <local:UserCollenction x:Key="oUserCollenction"/> <DataTemplate x:Key="userTemplate"> <Grid> <Grid.RowDefinitions> <RowDefinition /> <RowDefinition /> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="200" /> <ColumnDefinition Width="*"/> </Grid.ColumnDefinitions> <TextBlock Grid.Row="0" Grid.Column="0" Text="{Binding Path=Name}"></TextBlock> <TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding Path=Adress}"></TextBlock> </Grid> </DataTemplate> </Window.Resources> <StackPanel Margin="10" DataContext="{Binding Source={StaticResource oUserCollenction}}"> <ListBox ItemsSource="{Binding Path=Users}" ItemTemplate="{StaticResource userTemplate}" > </ListBox> </StackPanel> </Window>
DataTemplateをResourcesに定義しておくことで、複数箇所で使いまわしができますね。
INotifyPopertyChanged
ソースからターゲットへの同期をするには、ソースとなるオブジェクトがINotifyPropertyChangedを実装する必要があるとのこと。 どうでもいいですが、何を実装させるかすぐにわかる素晴らしい命名ですね。
INotifyPropertyChangedは「PropertyChanged」イベントを持っていて、バインドソースに変更があった場合にターゲットに通知します。
バインディングの値の同期の方法は4つあり、そのうち「OneWay」と「TwoWay」でソースの変更されるとターゲットが再バインディングされます。
- OneWay
- TwoWay
- OneWayToSource
- OneTime
using System.ComponentModel; namespace _0721Study { class Test : INotifyPropertyChanged { public string Title { get { return title; } set { title = value; NotifyPropertyChange("Title"); } } private string title; public event PropertyChangedEventHandler PropertyChanged; protected void NotifyPropertyChange(string propertyName) { if (null != this.PropertyChanged) { PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } } } }
<Window x:Class="_0721Study.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:_0721Study" Title="MainWindow" Height="350" Width="525"> <Window.Resources> <local:Test x:Key="oTest" Title="Test"/> </Window.Resources> <StackPanel Margin="10" DataContext="{StaticResource oTest}"> <Grid> <Grid.RowDefinitions> <RowDefinition></RowDefinition> <RowDefinition></RowDefinition> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition></ColumnDefinition> <ColumnDefinition></ColumnDefinition> </Grid.ColumnDefinitions> <TextBlock Grid.Row="0" Grid.Column="0" Text="OneTime"></TextBlock> <TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding Path=Title, Mode=OneTime}"></TextBlock> <TextBlock Grid.Row="1" Grid.Column="0" Text="OneWay"></TextBlock> <TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding Path=Title, Mode=OneWay}"></TextBlock> </Grid> <Grid> <Grid.RowDefinitions> <RowDefinition></RowDefinition> <RowDefinition Height="20px"></RowDefinition> </Grid.RowDefinitions> <TextBox Grid.Row="0" Text="{Binding Path=Title ,UpdateSourceTrigger=PropertyChanged}"></TextBox> </Grid> </StackPanel> </Window>
TextBlockのうちOneTimeの方は起動時にバインドされたままになっていますが、OneWayの方はTextBoxの入力に応じて、値が変わるのが確認できました。
また上の例ではTextBoxにUpdateSourceTrigger属性をPropertyChangedにしているため、テキストを入力するたびにその場で上のTextBlockの値が変わります。 これをLostFocusにするとフォーカスがほかに移動した時点で再バインドされるようになります。
<Grid> <Grid.RowDefinitions> <RowDefinition></RowDefinition> <RowDefinition Height="20px"></RowDefinition> </Grid.RowDefinitions> <TextBox Grid.Row="0" Text="{Binding Path=Title ,UpdateSourceTrigger=LostFocus}"></TextBox> <Button Grid.Row="1" Content="Focus用"></Button> </Grid>
フォーカスをボタンに移動した時点で再バインドされてます。
何日かすこしずつコードを書きながら勉強して行くことで、WPFのBindingについておぼろげながら、掴めてきました。
まだまだ奥深いBindingですが、ここからは実際に何かしらのアプリケーションを作りつつ理解を深めていった方がいい気もします。 さて、何を作ろうか… どうせ作るならMVVMで作っていきたいですなー
【お勉強記録】WPFデータバインディング2
型コンバーター
データバインド「もと」と「さき」で異なる型になる場合があると思います。 例えば、Textプロパティに対して日付型をバインドした場合、日付型をToString()形で自動変換されたものが表示されます。
前回の例でいうとDateTime型の「誕生日」はToString()された状態で表示されていたため、日本で一般的な表記ではありませんでした。
型コンバータを使うことで、「さき」と「もと」で相互の型変換を行うことができます。 型コンバータは「IValueConverter」を実装することで使用できます。
「IValueConverter」は、以下の2つのメソッド定義を持っています。
// 概要: // カスタム ロジックをバインディングに適用する方法を提供します。 public interface IValueConverter { // 概要: // 値を変換します。 // // パラメーター: // value: // バインディング ソースによって生成された値。 // // targetType: // バインディング ターゲット プロパティの型。 // // parameter: // 使用するコンバーター パラメーター。 // // culture: // コンバーターで使用するカルチャ。 // // 戻り値: // 変換された値。 メソッドが null を返す場合は、有効な null 値が使用されています。 object Convert(object value, Type targetType, object parameter, CultureInfo culture); // // 概要: // 値を変換します。 // // パラメーター: // value: // バインディング ターゲットによって生成される値。 // // targetType: // 変換後の型。 // // parameter: // 使用するコンバーター パラメーター。 // // culture: // コンバーターで使用するカルチャ。 // // 戻り値: // 変換された値。 メソッドが null を返す場合は、有効な null 値が使用されています。 object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture); }
メソッド名を見れば大体想像できますが、Convert()はバインド「もと」から「さき」へ値を渡すときの変換を、ConvertBack()は「さき」から「もと」の変換を実装しているようです。
DateTimeの型コンバータの実装
using System; using System.Globalization; using System.Windows.Data; namespace _0721Study { [ValueConversion(typeof(DateTime), typeof(string))] class DateTimeConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { DateTime date = (DateTime)value; return date.ToString(parameter.ToString()); } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { string strValue = value.ToString(); DateTime resultDateTime; if (DateTime.TryParse(strValue, out resultDateTime)) { return resultDateTime; } return value; } } }
Convert()メソッドは受け取った値をparameterをもとにstringに変換して返しています。 ConvertBack()メソッドはstringをDateTime型に変換して返します。
xaml側を書きます。
<Window x:Class="_0721Study.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:_0721Study" Title="MainWindow" Height="350" Width="525"> <Window.Resources> <local:User x:Key="oUser" Name="山田 太郎" Birthday="1990/07/21"/> <local:DateTimeConverter x:Key="dateTimeConverter" /> </Window.Resources> <WrapPanel Orientation="Horizontal" DataContext="{StaticResource oUser}"> <Label Content="名前:" VerticalAlignment="Center" ></Label> <TextBlock Text="{Binding Path=Name}" VerticalAlignment="Center"></TextBlock> <Label Content="誕生日:" VerticalAlignment="Center" ></Label> <TextBlock Text="{Binding Path=Birthday, Converter={StaticResource dateTimeConverter},ConverterParameter=yyyy/MM/dd}" VerticalAlignment="Center"></TextBlock> </WrapPanel> </Window>
BindingでConverterにlocal:DateTimeConverter のオブジェクトと、ConverterParameterを指定しています。
実行結果を見ると誕生日がしっかり、日本人になじみのある形に変換されているのが確認できました!