読者です 読者をやめる 読者になる 読者になる

No more Death March

あるSEのチラシの裏 C# WPF

WPF Bind Modeの動きを確認

BindingクラスのBindingModeについて、改めて動きを確認してみました。

まずビュー側

<Window x:Class="WhatIsBindingMode.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:WhatIsBindingMode"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <Style TargetType="Label">
            <Setter Property="Height" Value="24"/>
            <Setter Property="HorizontalAlignment" Value="Right"/>
        </Style>
        <Style TargetType="TextBox">
            <Setter Property="Height" Value="24"/>
        </Style>
    </Window.Resources>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto" />
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <StackPanel Grid.Column="0">
            <Label Content="Mode = Default:"/>
            <Label Content="Mode = OneTime:"/>
            <Label Content="Mode = OneWay:"/>
            <Label Content="Mode = OneWayToSource:"/>
            <Label Content="Mode = TwoWay:"/>
        </StackPanel>
        <StackPanel Grid.Column="1">
            <TextBox Text="{Binding Path=ModeDefault ,Mode=Default}"/>
            <TextBox Text="{Binding Path=ModeOneTime ,Mode=OneTime}"/>
            <TextBox Text="{Binding Path=ModeOneWay ,Mode=OneWay}"/>
            <TextBox Text="{Binding Path=ModeOneWayToSource ,Mode=OneWayToSource}"/>
            <TextBox Text="{Binding Path=ModeTwoWay ,Mode=TwoWay}"/>
        </StackPanel>
    </Grid>
</Window>

このViewの実行画像が以下の通り、各テキストボックスに異なるModeプロパティを設定しています。
f:id:nomoredeathmarch:20170305165515p:plain

続いてViewModel側、Modeプロパティの値と対応するプロパティを持っています。
呼び出しのタイミングを確認するため、プロパティ内のステップの先頭とプロパティの値が変更された場合にメッセージ出力をします。

using System.Diagnostics;
using System.ComponentModel;

namespace WhatIsBindingMode
{
    public sealed class ViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        private void RaisePropertyChanged(string propertyName)
        {
            if (this.PropertyChanged == null) return;
            this.PropertyChanged(
                this, 
                new PropertyChangedEventArgs(propertyName)
                );
            var message = string.Format("Property {0} Changed", propertyName);
            Debug.WriteLine(message);
        }

        private string _modeDefault = string.Empty;
        public string ModeDefault
        {
            get
            {
                Debug.WriteLine("ModeDefault.get Called");
                return this._modeDefault;
            }               
            set
            {
                Debug.WriteLine("ModeDefault.set Called");
                if (this._modeDefault == value) return;
                this._modeDefault = value;
                this.RaisePropertyChanged(nameof(this.ModeDefault));
            }
        }
        private string _modeOneTime = string.Empty;
        public string ModeOneTime
        {
            get
            {
                Debug.WriteLine("ModeOneTime.get Called");
                return this._modeOneTime;
            }
            set
            {
                Debug.WriteLine("ModeOneTime.set Called");
                if (this._modeOneTime == value) return;
                this._modeOneTime = value;
                this.RaisePropertyChanged(nameof(this.ModeOneTime));
            }
        }
        private string _modeOneWay = string.Empty;
        public string ModeOneWay
        {
            get
            {
                Debug.WriteLine("ModeOneWay.get Called");
                return this._modeOneWay;
            }
            set
            {
                Debug.WriteLine("ModeOneWay.set Called");
                if (this._modeOneWay == value) return;
                this._modeOneWay = value;
                this.RaisePropertyChanged(nameof(this.ModeOneWay));
            }
        }
        private string _modeOneWayToSource = string.Empty;
        public string ModeOneWayToSource
        {
            get
            {
                Debug.WriteLine("ModeOneWayToSource.get Called");
                return this._modeOneWayToSource;
            }
            set
            {
                Debug.WriteLine("ModeOneWayToSource.set Called");
                if (this._modeOneWayToSource == value) return;
                this._modeOneWayToSource = value;
                this.RaisePropertyChanged(nameof(this.ModeOneWayToSource));
            }
        }
        private string _modeTwoWay = string.Empty;
        public string ModeTwoWay
        {
            get
            {
                Debug.WriteLine("ModeTwoWay.get Called");
                return this._modeTwoWay;
            }
            set
            {
                Debug.WriteLine("ModeTwoWay.set Called");
                if (this._modeTwoWay == value) return;
                this._modeTwoWay = value;
                this.RaisePropertyChanged(nameof(this.ModeTwoWay));
            }
        }
    }
}

このViewModelをMainWindowのコードビハインドでDataContextプロパティに渡しています。

using System.Windows;

namespace WhatIsBindingMode
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            this.DataContext = new ViewModel();
        }
    }
}

これで準備完了。


画面起動時

まずは画面を起動したときのメッセージ。

ModeDefault.get Called
ModeOneTime.get Called
ModeOneWay.get Called
ModeOneWayToSource.set Called
ModeTwoWay.get Called

OneWayToSource以外はViewModel側のgetterが呼び出され、OneWayToSourceのみsetterが呼び出される。
OneWayToSourceはView⇒ViewModelの一方向のバインドなので、ViewModelの内容を問わずViewの値を更新するわけね。


DefaultとTwoWay

DefaultでバインドしているTextBoxに「hoge」と入力してタブ移動

ModeDefault.set Called
Property ModeDefault Changed
ModeDefault.get Called

TextBoxの既定値はTwoWayなのでTwoWayでも同じ

ModeTwoWay.set Called
Property ModeTwoWay Changed
ModeTwoWay.get Called

ここで少しひっかかる、PropertyChangedイベンドが発生した後にProperty ~ Changedってメッセージを出力しているのに、
~.get Calledのメッセージが出力されるのはそれよりさらに後、
イベントが発生したタイミングでGetterから値を取得するなら~.get Calledが先に出力されるのでは???
うーん・・・


OneTime

OneTimeは一度だけViewModelから値を取得するので画面で操作しても反応なし。


OneWay

OneWayはViewModelからViewへの一方的な連動なので画面で操作しても反応なし。


OneWayToSource

OneWayToSourceはOneWayの逆方向でViewからViewModelへの一方的な連動なので、
Setterのみ呼ばれ、Getterは呼ばれない。

ModeOneWayToSource.set Called
Property ModeOneWayToSource Changed


ViewModelの更新

面倒になったのでソースは出さないけど、
ViewModelのSetterで直接値を更新すると、
Default、TwoWay、OneWayが指定されているプロパティがViewに反映され、
OneTime、OneWayToSourceは反応なしになるわけですね。