過去記事インデックス
- Reactive Extensions再入門 その1
- Reactive Extensions再入門 その2「IObservableインターフェースとIObserverインターフェース」
- Reactive Extensions再入門 その3「IObservableのファクトリメソッド」
- Reactive Extensions再入門 その4「Timer系のファクトリメソッド」
- Reactive Extensions再入門 その5「HotとCold」
- Reactive Extensions再入門 その6「HotなIObservableを作成するファクトリ」
- Reactive Extensions再入門 その7「LINQスタイルの拡張メソッド」
- Reactive Extensions再入門 その8「SkipとTakeメソッド」
はじめに
しばらく間があいてしまったReactive Extensions再入門ですが、飽きたり忘れたりしてるわけじゃぁないです。12/3の発表用の資料作りのほうを優先させてるのでこちらの更新が滞っている状態ですm(_ _)m
12/3のRIA アーキテクチャ研究会が終わったらまた、前のペースで書きたいな〜と思ってます。ということで今回は、GUIのプログラムで誰もが一度はやるであろうウィンドウ内をマウスでドラッグしたときの処理を、これまでやってきたこと+αで実装してみようと思います。
SkipUntilとTakeUntilを使ったドラッグの処理
ここでは、SkipUntilとTakeUntilを使ってWPFアプリケーションでマウスのドラッグを処理してみます。通常のアプリケーションでは恐らくMouseDown, MouseUp, MouseMoveのイベントを処理して実現すると思います。MouseDownでフラグ変数を立ててマウスをキャプチャし、MouseUpでフラグ変数を下げてマウスのキャプチャを解放します。そして、MouseMoveイベントではフラグ変数を見てマウスボタンが押されているかどうか判定してドラッグ中の処理を記述します。このように単純なドラッグの処理を行うだけでも、フラグを使用したプログラムになってしまいます。Reactive Extensionsを使うとイベントを組み合わせて下記のように記述出来ます。
まず、MouseDown, MouseUp, MouseMoveのイベントをFromEventメソッドを使ってIObservableに変換します。
// マウスダウン、マウスアップ、マウスムーブのIObservableを作る var mouseDown = Observable.FromEvent<MouseButtonEventHandler, MouseButtonEventArgs>( h => (s, e) => h(e), h => this.MouseDown += h, h => this.MouseDown -= h); var mouseMove = Observable.FromEvent<MouseEventHandler, MouseEventArgs>( h => (s, e) => h(e), h => this.MouseMove += h, h => this.MouseMove -= h); var mouseUp = Observable.FromEvent<MouseButtonEventHandler, MouseButtonEventArgs>( h => (s, e) => h(e), h => this.MouseUp += h, h => this.MouseUp -= h);
ドラッグ処理は、マウスが押された状態でマウスが動いている間なので、SkipUntilでMouseMoveイベントをMouseDownが行われるまで読み飛ばし、TakeUntilでMouseUpが行われるまでTakeすることで表しています。これだけだと、一度きりの処理なのでRepeatメソッドを使って繰り返し処理をするようにしています。コードを下記に示します。
var drag = mouseMove // マウスムーブをマウスダウンまでスキップ。マウスダウン時にマウスをキャプチャ .SkipUntil(mouseDown.Do(_ => this.CaptureMouse())) // マウスアップが行われるまでTake。マウスアップでマウスのキャプチャをリリース .TakeUntil(mouseUp.Do(_ => this.ReleaseMouseCapture())) // ドラッグが終了したタイミングでCompletedを表示 .Finally(() => textBlock.Text = "Completed") // これを繰り返す .Repeat();
TakeUntilの後にFinallyメソッドという使ったことの無いメソッドを使用していますが、このメソッドはIObservable
最後に、このdragをSubscribeしてドラッグ中の処理を記述します。
// ドラッグ中は、イベント引数から座標を取り出して表示用に整えてTextBlockに設定 drag.Select(e => e.GetPosition(null)) .Select(p => string.Format("X: {0}, Y:{1}", p.X, p.Y)) .Subscribe(s => textBlock.Text = s);
イベント引数から座標を取り出し、座標を表示用文字列に整形して、TextBlockに表示させています。因みに、上記の処理はWindow1.xaml.csのコンストラクタに記載しています。Window1.xamlのXAMLは下記のようになっています。
<Window x:Class="DragSample.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525"> <Grid Name="layoutRoot"> <TextBlock Name="textBlock" /> </Grid> </Window>
実行結果
このプログラムを実行すると、下記のように何も表示されないウィンドウが表示されます。
このウィンドウ上でドラッグを行うと下図のようにドラッグしている箇所の座標がリアルタイムで表示されます。
ドラッグを終了すると下図のようにCompletedと表示されます。
このようにSkipUntilとTakeUntilとRepeat, Selectなどのこれまで紹介してきたメソッドを組み合わせることで通常はフラグなどを使用して記載する処理を非常にシンプルに記載できました。
最後に、MainWindow.xaml.csのコードの全体を下記に示します。
namespace DragSample { using System; using System.Reactive.Linq; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; using System.Windows.Shapes; /// <summary> /// MainWindow.xaml の相互作用ロジック /// </summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); // マウスダウン、マウスアップ、マウスムーブのIObservableを作る var mouseDown = Observable.FromEvent<MouseButtonEventHandler, MouseButtonEventArgs>( h => (s, e) => h(e), h => this.MouseDown += h, h => this.MouseDown -= h); var mouseMove = Observable.FromEvent<MouseEventHandler, MouseEventArgs>( h => (s, e) => h(e), h => this.MouseMove += h, h => this.MouseMove -= h); var mouseUp = Observable.FromEvent<MouseButtonEventHandler, MouseButtonEventArgs>( h => (s, e) => h(e), h => this.MouseUp += h, h => this.MouseUp -= h); var drag = mouseMove // マウスムーブをマウスダウンまでスキップ。マウスダウン時にマウスをキャプチャ .SkipUntil(mouseDown.Do(_ => this.CaptureMouse())) // マウスアップが行われるまでTake。マウスアップでマウスのキャプチャをリリース .TakeUntil(mouseUp.Do(_ => this.ReleaseMouseCapture())) // ドラッグが終了したタイミングでCompletedを表示 .Finally(() => textBlock.Text = "Completed") // これを繰り返す .Repeat(); // ドラッグ中は、イベント引数から座標を取り出して表示用に整えてTextBlockに設定 drag.Select(e => e.GetPosition(null)) .Select(p => string.Format("X: {0}, Y:{1}", p.X, p.Y)) .Subscribe(s => textBlock.Text = s); } } }
まとめ
ドラッグの処理を1つもフラグ変数を使うことなくすっきりと書くことが出来ました。個人的には、このドラッグを処理するプログラムを初めて見たときにReactive Extensionsすげ〜〜!!ってなったのですがどうでしょう。
次回は、この他のIObservable