Rx環境の構築とRxイロハのロ

前回からの続き

Rxを使ってマウスのドラッグアンドドロップを一般化したかった俺はRxの高くて険しい山に登る準備をネットからサンプルコードを引っ張ってきて慣れるところからスタートしようとしていた。
・・・あらすじ終わり。

ドラッグ&ドロップの全容は大変なので、とりあえずRxの動作がわかる最少コードを抽出するところからってことでMouseMoveイベントを捕まえてマウス座標をコンソールに表示するだけのコード

using System;
using System.Reactive.Linq;
using System.Windows;
using System.Windows.Input;

namespace RxDragDrop
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            var mouseMove = Observable.FromEvent<MouseEventHandler, MouseEventArgs>(
                h => (s, e) => h(e),
                h => this.MouseMove += h,
                h => this.MouseMove -= h);

            mouseMove.Subscribe(arg => Console.WriteLine(arg.GetPosition(null)));
        }
    }
}

実行結果は↓こんな感じ。

この時点でもかなりの黒魔術感が漂う。ラムダ式に慣れてない(≠わかってない)と結構つらい。
とりあえず、var mouseMove はMouseMoveイベントを変数として定義してると思い込む。
で、それにSubscribeしたらマウスが移動するたびにイベントが呼び出されるイメージ。(要するに、Subscribeメソッドが無限ループしてイベントを見張ってる感じ)

なので、もうちょっと平易に書き直してみる。

public MainWindow()
{
    InitializeComponent();

    IObservable<MouseEventArgs> mouseMove = Observable.FromEvent<MouseEventHandler, MouseEventArgs>(
        h => (s, e) => h(e),
        h => this.MouseMove += h,
        h => this.MouseMove -= h);

    mouseMove.Subscribe(Mouse_Move);
}

private void Mouse_Move(MouseEventArgs arg)
{
    Console.WriteLine((arg.GetPosition(null)));
}

var mouseMove ってのは結局 IObservable 型の変数で、その定義はラムダ式バリバリなんでとりあえずはブラックボックスとして受け入れる。
ほんでそのmouseMoveのSubscribeってメソッドにイベントハンドラを渡してるだけ。

中身の仕組みがどうなってるかは別にして、動作的に何が起こってるかと言うと
1.MouseMoveイベントを監視する何かを定義する
2.そいつにSubscribeメソッドを使って通知するコールバックメソッドを与える
3.MouseMoveイベントが発生されるたびに通知される

だから何なん?って思わへん?単なるイベントハンドラやんかこれやったら!
まあ、もうちょっとだけ変えてみる。

そないに意味があるわけじゃないんやけど、マウスカーソルの座標が X, Y ともに偶数の場合だけ表示してどっちが奇数の時は何もしないってコードにする。

public MainWindow()
{
    InitializeComponent();

    IObservable<MouseEventArgs> mouseMove = Observable.FromEvent<MouseEventHandler, MouseEventArgs>(
        h => (s, e) => h(e),
        h => this.MouseMove += h,
        h => this.MouseMove -= h);

    mouseMove.Subscribe(Mouse_Move);
}

private void Mouse_Move(MouseEventArgs arg)
{
    var pos = arg.GetPosition(null);
    if (pos.X % 2 == 1) return;
    if (pos.Y % 2 == 1) return;
    Console.WriteLine(pos);
}

実行結果

だから何なん?(2度目)なんやけど、もうちょっとだけ我慢して。
座標の偶数フィルターをLINQのWhereメソッドでさらに書き換える。

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        IObservable<MouseEventArgs> mouseMove = Observable.FromEvent<MouseEventHandler, MouseEventArgs>(
            h => (s, e) => h(e),
            h => this.MouseMove += h,
            h => this.MouseMove -= h);

        mouseMove
            .Where(args => args.GetPosition(null).X % 2 == 0 && args.GetPosition(null).Y % 2 == 0)
            .Subscribe(Mouse_Move);
    }

    private void Mouse_Move(MouseEventArgs arg)
    {
        Console.WriteLine((arg.GetPosition(null)));
    }
}

どういうことかって言うと、そもそもの var mouseMove って変数は"めっちゃざっくり書けば"「MouseMoveイベントの配列」みたいなもんなのな。
LINQってのは配列みたいなもんを同じように扱うインターフェースの技術なんで、Rxを使えばイベントをLINQで処理出来る。うれしいか?

・・・だよね。最後にLINQのZipメソッドを組み合わせてみる。
だけど、その前に今やりたいことの前提としてMouseMoveイベントの引数の中で座標しか必要無いんで座標だけ取り出せるようにLINQで変形しとく。

マウスが移動したときの全部の座標を表示するコード

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        var mouseMove = Observable.FromEvent<MouseEventHandler, MouseEventArgs>(
            h => (s, e) => h(e),
            h => this.MouseMove += h,
            h => this.MouseMove -= h);

        var mouseMovePos = mouseMove.Select(a => a.GetPosition(null));
        mouseMovePos.Subscribe(pos => Console.WriteLine(pos));
    }
}

で、改めてZipを使う。
LINQにおけるZipっつーのは、2つの"配列"を使ってごにょごにょするためのメソッドで、a.Zip(b, (a1, b1) => ごにょごにょ って書式になる。
で、ここでaの配列をMoseMovePos、bの配列をMoseMovePos.Skip(1)にする。要するに"配列"のインデックス i と i + 1 を受け取るってこと。

実際のコードはこんな感じ。

public MainWindow()
{
    InitializeComponent();

    var mouseMove = Observable.FromEvent<MouseEventHandler, MouseEventArgs>(
        h => (s, e) => h(e),
        h => this.MouseMove += h,
        h => this.MouseMove -= h);

    
    var mouseMovePos = mouseMove.Select(a => a.GetPosition(null));
    var mouseMotion = mouseMovePos.Zip(mouseMovePos.Skip(1), (prev, cur) =>
      new { Dx = cur.X - prev.X, Dy = cur.Y - prev.Y});

    mouseMotion .Subscribe(pos => Console.WriteLine(pos));
}

イベントの"配列"から、マウス座標の"配列"に変換して、さらにマウス座標の変化の"配列"に変換してる。
途中を説明変数で受けてるけど、一気にドドンと連続でチェーンしちゃうことも当然出来る。

実行結果は

今日はここまで。