2013年10月06日

WPFサンプル:バインディング時のソースへの更新を遅延させる

   このエントリーをはてなブックマークに追加 Clip to Evernote
今回は、双方向バインディング時のソースへの更新を遅延させる方法を。

双方向のバインディングを指定した場合でも、なんらかのイベントが発生するまで、 バインディングソースへの反映を遅延させたい場合があります。
例えば、複合的なデータ検証が終了するまでは、バインディングソースのオブジェクトへ、 変更を反映させたくないといったケースです。
そのようなときに利用するのが、UpdateSourceTriggerプロパティです。
このプロパティの値をUpdateSourceTrigger.Explicitにすることで、 明示的にBindingExpressionクラスのUpdateSourceメソッドを呼び出したときに、データソースを更新するという指定になります。

INotifyPropertyChangedを実装するで取り上げたサンプルを少し変更して、この UpdateSourceTrigger の機能を確認するWPFのプログラムを作成してみます。

まずは、モデル側のクラス。

public class MyModel : INotifyPropertyChanged {
    private string _name;
    private string _webSite;
    private UpdateSourceTrigger _updateSourceTrigger = UpdateSourceTrigger.PropertyChanged;

    public string Name {
        get { return this._name; }
        set {
            if (value != this._name) {
                this._name = value;
                NotifyPropertyChanged("Name");
            }
        }
    }

    public string WebSite {
        get { return this._webSite; }
        set {
            if (value != this._webSite) {
                this._webSite = value;
                NotifyPropertyChanged("WebSite");
            }
        }
    }

    public UpdateSourceTrigger UpdateSourceTrigger {
        get { return this._updateSourceTrigger; }
        set {
            if (value != this._updateSourceTrigger) {
                this._updateSourceTrigger = value;
                NotifyPropertyChanged("UpdateSourceTrigger");
            }
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    private void NotifyPropertyChanged(String info) {
        if (PropertyChanged != null) {
            PropertyChanged(this, new PropertyChangedEventArgs(info));
        }
    }
}

INotifyPropertyChangedインターフェースを実装し、プロパティの更新通知ができるようにしています。
動的にUpdateSourceTriggerの値を変更したいのでモデル側にUpdateSourceTrigger型の プロパティを定義しています。このクラスはModelというよりは、ViewModelといったほうが いいですね。Modelが無いのにViewModelと言うのは変ですが...

次に、XAML。

<Window x:Class="UpdateSourceTriggerSample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:my="clr-namespace:UpdateSourceTriggerSample"
        xmlns:sys="clr-namespace:System;assembly=mscorlib"               
        Title="MainWindow" Height="160" Width="300" >
    <Window.Resources>       
        <ObjectDataProvider x:Key="UpdateSourceTriggerValues" MethodName="GetValues"
                        ObjectType="{x:Type sys:Enum}"  >
            <ObjectDataProvider.MethodParameters>
                <x:Type TypeName="UpdateSourceTrigger" />
            </ObjectDataProvider.MethodParameters>
        </ObjectDataProvider>
    </Window.Resources>
    <Window.DataContext>
        <my:MyModel x:Name="MyModel" Name="Google" WebSite="http://www.google.com"/>       
    </Window.DataContext>
    <StackPanel>
        <TextBox Name="textBox1" Text="{Binding Name, Mode=TwoWay,
            UpdateSourceTrigger=PropertyChanged}" />
        <TextBox Name="textBox2" Text="{Binding WebSite, Mode=TwoWay,
            UpdateSourceTrigger=PropertyChanged}"/>
        <StackPanel Orientation="Horizontal" Margin="0,10,0,0">
            <Label Content="UpdateSourceTrigger:" />
            <ComboBox Width="130"
                ItemsSource="{Binding Mode=OneWay,
                     Source={StaticResource UpdateSourceTriggerValues}}"
                SelectedItem="{Binding UpdateSourceTrigger}"
                SelectionChanged="ComboBox_SelectionChanged"  VerticalAlignment="Center" />
        </StackPanel>
        <StackPanel Orientation="Horizontal" HorizontalAlignment="Center"
                    Margin="0,10,0,0">
            <Button Content="Modelの値を確認" Height="23" Name="button1" Width="110"
                Click="buttonConfirm_Click" />
            <Button Content="更新" Height="23" Name="button3" Width="75"
                Click="buttonUpdate_Click" />
        </StackPanel>
    </StackPanel>
</Window>

二つのTextBoxはそれぞれMyModelオブジェクトの、NameとWebSiteプロパティとバインドしています。
ComboBoxは、項目一覧は、UpdateSourceTrigger列挙型の列挙子とバインドしています。
このために、Window.Resourcesで ObjectDataProviderを定義しています。
そして選択された項目は、MyModelオブジェクトのUpdateSourceTriggerとバインドしています。

では、MainWindowのコードビハインドのコードです。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace UpdateSourceTriggerSample {
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window {
        public MainWindow() {
            InitializeComponent();
            _myObject = DataContext as MyModel;
        }

        private MyModel _myObject;

        private void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e) {
            if (_myObject == null)
                return;
            ChangeUpdateSourceTrigger(textBox1, "Name");
            ChangeUpdateSourceTrigger(textBox2, "WebSite");
        }

        private void ChangeUpdateSourceTrigger(TextBox textBox, string name) {
            Binding binding = BindingOperations.GetBinding(textBox1, TextBox.TextProperty);
            BindingOperations.SetBinding(textBox, TextBox.TextProperty,
                new Binding(name) {
                    Mode = BindingMode.TwoWay,
                    UpdateSourceTrigger = _myObject.UpdateSourceTrigger
                });
        }

        private void buttonConfirm_Click(object sender, RoutedEventArgs e) {
            MessageBox.Show(string.Format("{0} {1}", _myObject.Name, _myObject.WebSite));
        }
       
        private void buttonUpdate_Click(object sender, RoutedEventArgs e) {
            BindingExpression be = textBox1.GetBindingExpression(TextBox.TextProperty);
            be.UpdateSource();
            be = textBox2.GetBindingExpression(TextBox.TextProperty);
            be.UpdateSource();          
        }

    }
}

3つのイベントハンドラを定義しています。

ComboBox_SelectionChangedイベントハンドラ
コンボボックスの選択項目が変更されたときに、 TextBoxのTextプロパティに設定されているBindオブジェクトのUpdateSourceTriggerプロパティの 値を変更するためのものです。
データバインドでは対応できないため、イベントハンドラでBindオブジェクトのUpdateSourceTriggerプロパティの値を変えています。
初期値は、PropertyChangedですが、これをComboBoxでExplicitに変更したときに、 textBox1, textBox2で設定されているBindオブジェクトのUpdateSourceTriggerプロパティ値がExplicitに変更されることになります。

buttonConfirm_Clickイベントハンドラ
MyModelオブジェクトのNameとWebSiteプロパティの値をMesageBoxに表示します。

buttonUpdate_Clickイベントハンドラ
明示的にBindingExpressionクラスのUpdateSourceメソッドを呼び出すことで、textBox1とtextBox2のTextプロパティの値をMyModelオブジェクトに設定します。


以下、実行時のスクリーンショットです。

初期状態
UpdateSourceTrigger1

PropertyChanged時 : TextBoxを書き換えた後、「Modelの値を確認」ボタンを押す
UpdateSourceTrigger2

Explicit時 : TextBoxを書き換えた後、「Modelの値を確認」ボタンを押す
UpdateSourceTrigger3

Explicit時 :「更新」ボタンを押した後、再度「Modelの値を確認」ボタンを押す UpdateSourceTrigger4


ComboBoxの値がPropertyChangedの時には、TextBoxを書き換えた後、「Modelの値を確認」ボタンを押すと、Model側の値もTextBoxの値と同じになっていることが確認できます。
一方、ComboBoxの値がExplicitの時には、TextBoxを書き換えて「Modelの値を確認」ボタンを押しても、Model側の値が更新されません。「更新」ボタンを押し、明示的にModelの値を更新してから、 「Modelの値を確認」ボタンを押せば、Modelが更新されていることが確認できます。


WPFサンプル・目次


この記事へのコメント
エンバーです。
お世話になります。

MainWindowのコードビハインドのコードを掲載していただけませんか。
解説の文章を頼りに、自分でコードを入れようかと考えたのですが、
私の実力ではまだ案出できません。
よろしくお願いします。
Posted by エンバー at 2014年09月05日 22:05
すみません。MainWindowのコードが抜け落ちていましたね。
ご指摘ありがとうございます。
記事本文にコードを追加しておきました。

Posted by Gushwell at 2014年09月08日 20:31
連投です。

>記事本文にコードを追加して・・・


とありましたが、やはりコードありません。わたしだけ???
Posted by エンバー at 2014年09月09日 17:17
あれ、ほんとだ。
直しておきました。今度はどうでしょう。
Posted by Gushwell at 2014年09月10日 07:59
エンバーです。

今度はありました。
ありがとうございます。

Posted by エンバー at 2014年09月10日 17:59
 

この記事へのトラックバックURL

http://trackback.blogsys.jp/livedoor/gushwell/52334874