はじめに
本記事はRx初学者の筆者がRxをただただ使ってみて学びや気づきをまとめたものです。
なお、本記事はこちらの記事の続編の位置づけです。
イベント関連の機能を使ってみる
前回はRxを使って非同期処理を実現しました。今回はイベント関連の機能を使ってみます。
Rxではイベントはシーケンスとして表現されるので、連続して発生するイベントを合成する、発生するイベントを時間を使ってフィルタリングするといったことが簡単にできるといった特徴があります。
個人的に利用する機会が多そうと感じた、発生するイベントを時間を使ってフィルタリングする例を以下に示します。
WPFのListBoxの選択変更イベントのハンドリングを間引いて、連続してイベント発行がされたときのハンドリング回数を最小限に抑える例
以下のように左側のListBoxで選択されているListBoxItemのContentプロパティ(表示名)を右側のTextBlockに表示する簡単なサンプルを考えます。
上図のMainWindowをWPF標準のイベントハンドラを使って実現すると以下のようなxaml並びにコードビハインドになります。(サンプルとしての説明をしやすくするためにコードビハインドに記載をしています。MVVMに従った作り方になっていないためご承知ください。)
- MainWindow.xaml
<Window x:Class="RxEventSample.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"
mc:Ignorable="d"
Title="MainWindow" Height="150" Width="300">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<!-- リストボックス -->
<!-- 選択変更イベントハンドラを登録 -->
<ListBox x:Name="ListBox"
Grid.Column="0"
SelectionChanged="ListBox_OnSelectionChanged">
<ListBoxItem Content="Item1"/>
<ListBoxItem Content="Item2"/>
<ListBoxItem Content="Item3"/>
</ListBox>
<!-- リストボックスで選択する要素のContentを表示するテキストブロック -->
<TextBlock x:Name="SelectedItemContent"
Grid.Column="1"/>
</Grid>
</Window>
- MainWindow.xaml.cs
namespace RxEventSample
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
/// <summary>
/// コンストラクタ
/// </summary>
public MainWindow()
{
InitializeComponent();
}
/// <summary>
/// リストボックスの選択変更イベントハンドラ
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void ListBox_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
// 選択した要素が取得できない場合は何もしない
if (e.AddedItems.Count == 0) return;
// 選択した要素のContentをテキストブロックに表示
var selectedItem = e.AddedItems[0] as ListBoxItem;
SelectedItemContent.Text = selectedItem?.Content as string;
}
}
}
上のコードでは、xaml上からListBoxのSelectionChangedイベントをハンドリングしています。ハンドラ内で、TextBlockのTextプロパティを選択されたListBoxItemのContentプロパティ(表示名)の値で更新しています。この場合、ListBoxで選択変更をした瞬間にTextBlockのTextプロパティが更新されて、描画更新されます。
選択変更イベントハンドラの処理がTextBlockのTextプロパティの更新だけだから問題ないですが、仮に重い処理を選択変更イベントハンドラで行いたい場合には、ListBoxでの選択変更の瞬間にその都度イベントハンドラが処理されるとパフォーマンスに影響を与えかねません。そのような場合には、ある一定時間内に連続して発生した選択変更イベントを間引いて処理したいといったことがしたくなります。例えば、選択変更が発生してから1秒間、次の選択変更が発行しなかった場合に初めて選択変更イベントをハンドリングするといった感じです。
このようにパフォーマンス向上のためにイベントのハンドリング処理の回数を間引くことがRxを使うことで簡単にできます。
実際にListBoxの選択変更が発生してから1秒間、次の選択変更が発行しなかった場合に選択変更イベントハンドラ処理を行うように修正するとMainWindow.xamlとMainWindow.xaml.csは以下のようになります。
- MainWindow.xaml
<Window x:Class="RxEventSample.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"
mc:Ignorable="d"
Title="MainWindow" Height="150" Width="300">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<!-- リストボックス -->
<!-- イベントハンドラの登録はコードビハインドで実施するよう変更 -->
<ListBox x:Name="ListBox"
Grid.Column="0">
<ListBoxItem Content="Item1"/>
<ListBoxItem Content="Item2"/>
<ListBoxItem Content="Item3"/>
</ListBox>
<!-- リストボックスで選択する要素のContentを表示するテキストブロック -->
<TextBlock x:Name="SelectedItemContent"
Grid.Column="1"/>
</Grid>
</Window>
- MainWindow.xaml.cs
namespace RxEventSample
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
/// <summary>
/// コンストラクタ
/// </summary>
public MainWindow()
{
InitializeComponent();
// Rxを使ってリストボックスの選択変更イベントを間引いて処理する
// 選択変更イベント発行に1秒の間が空いたタイミングでハンドラで処理する
Observable.FromEventPattern<SelectionChangedEventArgs>(ListBox, nameof(ListBox.SelectionChanged))
.Throttle(TimeSpan.FromSeconds(1))
.Subscribe(unit => OnSelectionChanged(unit.EventArgs));
}
/// <summary>
/// 選択変更イベントハンドラ
/// </summary>
/// <param name="args"></param>
private void OnSelectionChanged(SelectionChangedEventArgs args)
{
// コントロールを操作するためにUIスレッドに処理を委譲する
Dispatcher.Invoke(() =>
{
// 選択した要素が取得できない場合は何もしない
if (args.AddedItems.Count == 0) return;
// 選択した要素のContentをテキストブロックに表示
var selectedItem = args.AddedItems[0] as ListBoxItem;
SelectedItemContent.Text = selectedItem?.Content as string;
});
}
}
コードビハインドのコンストラクタ内でLitBoxのSelectionChangedイベントを1秒間遅延させてハンドリングするための登録処理をしています。この登録処理でSubscribeメソッドに指定しているデリゲート処理がイベントハンドラ処理となります。指定しているデリゲート処理は、発生したイベントの引数を使ってOnSelectionChangedメソッドを呼び出すというものです。OnSelectionChangedメソッド内ではUIスレッド上で、TextBlockのTextプロパティを更新しています。
このようにRxを利用することで連続発生するイベントを間引いてハンドリングするといったことが簡単に実現できます。
さいごに
Rxを使うことでイベントのフィルタリングが簡単に行えることが分かりました。
今回は時間でのフィルタリングでしたが、Linqのように条件を満たすイベント発行のみをハンドリングすることが可能になります。
イベントのフィルタリングはあくまで、イベントに関してRxを使ってできることのほんの一部なので、引き続きイベント関連でRxを有効活用できるケースに関して記事を作っていく予定です。