xin9le.net

Microsoft の製品/技術が大好きな Microsoft MVP な管理人の技術ブログです。

Rx入門 (18) - 効率的な表示更新

今回は、Explorerなどでよく見かけるMaster-Detailパターンでの効率的な表示更新のサンプルを紹介します。「とあるオブジェクトの一覧がリストに表示されていて、選択されているものの内容の詳細が横に表示される」というアレです。

詳細表示の描画に時間がかかるような場合、選択イベントのタイミング毎に詳細表示の更新をしてしまうとUIがカクカクしてしまい非常に不快です。このように連続的に選択変更イベントが発行される場合は、途中の処理を適度にスキップして効率的に更新したいものです。実際、Explorerなどはそうなっています。

サンプルコード

以下のコード例を示します。XAMLは一覧表示用のListBoxと詳細表示用のTextBlockが置いてあるだけの簡単なものです。

<Window x:Class="Sample37_EfficientMasterDetail.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Sample37_EfficientMasterDetail" Height="200" Width="200">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <TextBlock Name="display" />
        <ListBox Name="list" Grid.Row="1" />
    </Grid>
</Window>

次に、データ登録と更新用のコードを示します。今回のポイントはObservable.Throttleメソッドです。コード中のコメントにも書いてありますが、「指定時間内に複数個値が通過した場合、それらの値を無視する」というものです。今回のサンプルだと、「200ms以内にイベントが複数回発生した場合は無視」となります。実際にサンプルを実行してみると、連続的に選択変更を行った場合にTextBlockが更新されないのが確認できると思います。また、Observable.Throttleメソッドをコメントアウトすると、TextBlockが即時更新されることが確認できます。

using System;
using System.Linq;
using System.Reactive.Linq;
using System.Threading;
using System.Windows;
 
namespace Sample37_EfficientMasterDetail
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            this.InitializeComponent();
            foreach (var value in Enumerable.Range(1, 100))
                this.list.Items.Add(value); //--- データを登録しておく
 
            Observable.FromEventPattern(this.list, "SelectionChanged")
            .Select(_ => this.list.SelectedItem.ToString()) //--- 文字列を取得
            .Throttle(TimeSpan.FromMilliseconds(200))       //--- 指定時間内に1個だけ通過した場合後続へ配信
            .ObserveOn(SynchronizationContext.Current)      //--- UIスレッドへ戻す
            .Subscribe(value => this.display.Text = value); //--- 表示更新
        }
    }
}

実行例

以下に実行例を示します。キーボードの矢印キーでListBoxの選択を変更しても、表示が変わっていないことが確認できます。

EfficientMasterDetail

個人的には、とてもRxチックなObservable.Throttleメソッドが大好きです。ユーザーフレンドリーな表示更新をするのに、全然苦労せず実現できるなんて素晴らしいですね!