SourceChord

C#とXAML好きなプログラマの備忘録。最近はWPF系の話題が中心です。

バインディングのDelayプロパティで、更新通知の頻度を制御する

WPF4.5でBinding構文に追加されたDelayプロパティというものがあります。
これ、地味に便利だけど、あまり知られてなさそうなのでメモしときます。

BindingBase.Delay プロパティ (System.Windows.Data)

このDelayプロパティの効果は↓の図のようなイメージです。
f:id:minami_SC:20141028004825p:plain

WPF4.5の新機能の説明では、以下のページのように説明されてますが、ちょっとわかりにくいですね。
データ バインディングのソースの自動更新

このWPF4.5の新機能のページも、サンプルコードとか合わせて説明してくれればいいのに。。。とホント思います。
4.5で追加になった便利機能って結構あるのに。。

Delayプロパティの働き、

バインディングターゲット側の値が最後に変化してから、Delayプロパティで設定した数値(ms単位)だけ待って、その間値が変化してなければバインディング・ソースへバインディングの反映が行われます。

この動作って、要は↓みたいなReactive Extensionsでいう所の、Throttleメソッドと同じものですね。
Reactive Extensions再入門 その28「落ち着いたら流すThrottleメソッド」 - かずきのBlog@hatena


Sliderのように、UI操作により頻繁に値の更新が行われすぎて困る!!というような時に便利な機能です。
バインディングにより値が更新されたら重たい処理が走る、というような場合には特に有効かと思います。

サンプルコード

f:id:minami_SC:20141028004436p:plain:w300
SliderのValueプロパティをVMの整数型のValueプロパティにバインドしています。
そして、このBinding構文の中でDelayプロパティを設定しています。
Sliderの下のTextBlockも同様にVMのValueプロパティにバインドしておき、DelayプロパティによりVMへの更新がどのタイミングで行われているか確認できるようにしています。

あとはButtonを一個置いてますが、このボタンを押すとChangeValueCommandが実行されて、VMのValueプロパティを50にセットします。


実行してみるとわかりますが、スライダーを動かし続けている間は、Delayプロパティの効果のため、バインディングによるVMへの値の更新が行われません。そのため、この間TextBlockの値はそのままです。
そして、スライダーを動かすのをやめて500ms経過すると、VMへのバインディングの反映が行われ、TextBlockの値も更新されるのが確認できます。

一方、Button押下でVMのValueプロパティを更新すると、Slider/TextBlockともに即座に値の更新が行われます。

MainWindow.xaml
<Window x:Class="WpfBaseTemplate1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfBaseTemplate1"
        Title="MainWindow"
        Width="400"
        Height="250">
    <Window.DataContext>
        <local:MainWindowViewModel />
    </Window.DataContext>
    <Grid>
        <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
            <Slider Width="262"
                    Maximum="100"
                    SmallChange="1"
                    Value="{Binding Value,
                                    Mode=TwoWay,
                                    Delay=500}" />
            <TextBlock Margin="0, 10" Text="{Binding Value}" />
            <Button Width="75"
                    Margin="0, 10"
                    Command="{Binding ChangeValueCommand}"
                    Content="Button" />
        </StackPanel>
    </Grid>
</Window>
MainWindowViewModel.cs
    class MainWindowViewModel : BindableBase
    {
        private int value;
        public int Value
        {
            get { return value; }
            set { this.SetProperty(ref this.value, value); }
        }


        private RelayCommand changeValueCommand;
        public RelayCommand ChangeValueCommand
        {
            get { return changeValueCommand = changeValueCommand ?? new RelayCommand(ChangeValue); }
        }
        private void ChangeValue()
        {
            this.Value = 50;
        }
    }