今回は、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の選択を変更しても、表示が変わっていないことが確認できます。
個人的には、とてもRxチックなObservable.Throttleメソッドが大好きです。ユーザーフレンドリーな表示更新をするのに、全然苦労せず実現できるなんて素晴らしいですね!