泥庭

2014年6月24日

ReactivePropertyを使いたい人のための、ReactiveExtensions入門(その2)

Filed under: .NET, LINQ, WPF — タグ: , , , , — yone64 @ 1:12 AM

第2回があったとは。。。

それはさておき、Rxが便利な分野といえば、イベントと非同期ですね。非同期はTaskとasync/awaitに譲った感もありますが、イベント合成はRxの独壇場です。(たぶん
そして、Rxでイベント合成のサンプルで間違いなく出てくるのが、マウスのDragですね。MouseDown→MouseMove→MouseUpの流れをうまく表現できるのが素晴らしい。

というわけで、いきなりコード

<Window x:Class="ReactiveWPF.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Window1" Height="300" Width="300">
    <Canvas x:Name="Canvas" Background="Transparent">
        <Polygon Points="{Binding Points.Value}" Stroke="Black" StrokeThickness="2"/>
    </Canvas>
</Window>

XAMLは、何のひねりもないので問題ないと思います。

/// <summary>
/// Window1.xaml の相互作用ロジック
/// </summary>
public partial class Window1 : Window
{
    public Window1()
    {
        InitializeComponent();
        
        this.Points = Canvas.MouseDownAsObservable().Do(_ => Canvas.CaptureMouse()).Select(e => e.GetPosition(Canvas))
            .SelectMany(_ => Canvas.MouseMoveAsObservable().Select(e => e.GetPosition(Canvas)), (s, e) => new {s, e})
            .Select(a => new PointCollection(new []{a.s, new Point(a.s.X, a.e.Y), a.e, new Point(a.e.X, a.s.Y) }))
            .TakeUntil(Canvas.MouseUpAsObservable().Do(_ => Canvas.ReleaseMouseCapture()))
            .ToReactiveProperty();

        this.Points
            .Subscribe(
            _ => Console.WriteLine("Next:" + _),
            () => Console.WriteLine("Complete"));

        this.DataContext = this;
    }

    public ReactiveProperty<PointCollection> Points { get; private set; }
}

public static class FrameworkElementEx
{
    public static IObservable<MouseButtonEventArgs> MouseDownAsObservable(this FrameworkElement source)
    {
        return Observable.FromEvent<MouseButtonEventHandler, MouseButtonEventArgs>(
            h => (s, e) => h(e),
            h => source.MouseDown += h, 
            h => source.MouseDown -= h);
    }

    public static IObservable<MouseEventArgs> MouseMoveAsObservable(this FrameworkElement source)
    {
        return Observable.FromEvent<MouseEventHandler, MouseEventArgs>(
            h => (s, e) => h(e),
            h => source.MouseMove += h,
            h => source.MouseMove -= h);
    }

    public static IObservable<MouseButtonEventArgs> MouseUpAsObservable(this FrameworkElement source)
    {
        return Observable.FromEvent<MouseButtonEventHandler, MouseButtonEventArgs>(
            h => (s, e) => h(e),
            h => source.MouseUp += h,
            h => source.MouseUp -= h);
    }
}

C#側もそんなに問題ないですね。詳しくは、@ITの@neueccさんの記事とか@xin9leさんのブログとかを参照してください。ほぼ、そのままですね。
# 手抜きなので、イベントを拾ってプロパティーを変更して、自分自身とBindingしてるという微妙なコードになっていますが、ここではそこは大きな問題ではないです。
これで、Mouseによる範囲選択ができた。と思ったのですが、現実はそんなに甘くありませんでした。

MouseでDrag処理を行った場合、MouseUp時に何かしら確定処理を行うことがほとんどだと思います。
#矩形に含まれるオブジェクトを選択するとか、動的オブジェクトを永続オブジェクトに変更するとか…

しかし、この終了処理を記述する良い場所がないのです。
まず思いつくのが、SubscribeのCompleteだと思うのですが、これはMouseUpイベントが発生し、一連のMouseイベント購読が終わっても、呼び出されることがありません。(Hotだから?
次に、ReleaseMouseCapture()を呼び出している、Doメソッドの内部ですが、ここではObservableなStreamを流れてくるPointCollectionに対してアクセスすることができません。

解決策は、Rxの外部に変数を宣言して、変数のキャプチャを行うことになると思うのですが、これはこれでいまいちですよね。

広告

3件のコメント »

  1. […] → 第1回、第2回 […]

    ピンバック by ReactivePropertyを使いたい人のための、ReactiveExtensions入門(その3) | 泥庭 — 2014年7月1日 @ 10:25 PM

  2. […] → 第1回、第2回 […]

    ピンバック by ReactivePropertyを使いたい人のための、ReactiveExtensions入門(その3) | 泥庭 — 2014年7月1日 @ 10:25 PM

  3. […] 以前のバージョンは→第1回、第2回、第3回 […]

    ピンバック by ReactivePropertyを使いたい人のための、ReactiveExtensions入門(その4) | 泥庭 — 2014年7月27日 @ 11:45 AM


RSS feed for comments on this post. TrackBack URI

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト / 変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト / 変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト / 変更 )

Google+ フォト

Google+ アカウントを使ってコメントしています。 ログアウト / 変更 )

%s と連携中

WordPress.com Blog.

%d人のブロガーが「いいね」をつけました。