C#でようやくLinq to Eventsが少し理解できた気がする。

Push型のLinq to Objectsとかと違って、
Pull型のLinq to Eventsは動きが想像し辛かったので忘れないようにメモとして残しておく。

//
// 大きい画像をマウスのドラッグでスクロールさせるサンプル
//
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using System.Drawing;

namespace RxTest
{
    static class Program
    {
        [STAThread]
        static void Main()
        {
            Form form = new Form();
            Panel panel = new Panel { Parent = form, AutoScroll = true, Dock = DockStyle.Fill };
            PictureBox pict = new PictureBox {
                Parent = panel,
                Location = new Point(0, 0),
                Image = new Bitmap(@"D:\test.bmp"),
            };
            pict.Size = pict.Image.Size;
            MouseEventRegister(panel, pict);
            form.ShowDialog();
        }

        private static  void MouseEventRegister(Panel panel,PictureBox pict)
        {
            // マウスによる画像スクロール(Linq to Events)
            // マウスダウンにより発火
            var action = pict.GetMouseDown()
                // ただし左ボタンが押下されていること
                .Where(e => e.EventArgs.Button == MouseButtons.Left)
                // マウスキャプチャ開始(ウィンドウからはみ出てもメッセージを受け取れる)
                .Do(e => pict.Capture = true)
                // イベントの合流(MouseDown + MouseMove)
                .SelectMany(pict.GetMouseMove()
                    // 左ボタンが押されていること
                    .Where(e => e.EventArgs.Button == MouseButtons.Left)
                    // データはEventArgsしかいらない
                    .Select(e => e.EventArgs)
                    // マウスがどれだけ動いたかを保持するために2個ずつの組に変換
                    .BufferWithCount(2)
                    // MouseUpが来るまでがんばる
                    .TakeUntil(pict.GetMouseUp()
                        // 左ボタンのイベント
                        .Where(e => e.EventArgs.Button == MouseButtons.Left)
                        // キャプチャの解除
                        .Do(e => pict.Capture = false)
                ))
                // 移動した大きさを取得
                .Select(v => new Point(v[1].X - v[0].X, v[1].Y - v[0].Y))
                // 購読
                .Subscribe(p => {
                    var pos = panel.AutoScrollPosition;
                    pos.X = 0 - pos.X - p.X;
                    pos.Y = 0 - pos.Y - p.Y;
                    panel.AutoScrollPosition = pos;
                });

        }
    }
    // FromEvent隠蔽用
    internal static class ControlExtensions
    {
        public static IObservable<IEvent<MouseEventArgs>> GetMouseDown(this Control that)
        {
            return Observable.FromEvent<MouseEventArgs>(that, "MouseDown");
        }

        public static IObservable<IEvent<MouseEventArgs>> GetMouseUp(this Control that)
        {
            return Observable.FromEvent<MouseEventArgs>(that, "MouseUp");
        }

        public static IObservable<IEvent<MouseEventArgs>> GetMouseMove(this Control that)
        {
            return Observable.FromEvent<MouseEventArgs>(that, "MouseMove");
        }

    }
}

自分的なポイントは

  • SelectManyでイベントの合流。
  • TakeUntilでイベントが発生するまで続ける。
  • FromEventは拡張メソッドとして定義しておくと楽。(自動生成とかできないかなぁ)

拡張メソッドの部分は以下の記事を参考にさせて頂きました。
http://neue.cc/2009/09/04_197.html