レガシーコードからの脱却

レガシーな職場でも独学でどうにか頑張っていくブログです。

【お勉強記録】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>

f:id:Gappory:20170723123225p:plain

ListをListBoxにバインドしています。 DataTemplateを使うことで、どのようにバインドしたオブジェクトを表示するかを定義できます。

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>

f:id:Gappory:20170723134153p:plain

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>        

f:id:Gappory:20170723134832p:plain

フォーカスをボタンに移動した時点で再バインドされてます。

f:id:Gappory:20170723134909p:plain

何日かすこしずつコードを書きながら勉強して行くことで、WPFのBindingについておぼろげながら、掴めてきました。

まだまだ奥深いBindingですが、ここからは実際に何かしらのアプリケーションを作りつつ理解を深めていった方がいい気もします。 さて、何を作ろうか… どうせ作るならMVVMで作っていきたいですなー