OperationDriver

概要

ここではビュー上でのマウス操作を扱うために MoNo.RAIL が備えている仕組みについて説明します。 具体的な説明に入る前に、ここで説明する仕組みが何を目的として作られているのか、軽く説明しておきたいと思います。

一般に、マウス操作はイベントハンドラでマウス操作イベントを検知することによりプログラムできます。 しかしこの方法は原始的過ぎて、コマンドの数や種類が増えると、ハンドラの状態を管理するのが非常に煩雑になります。 それを解決するために用意されたのが MoNo.Ctrl.OperationDriver やその周辺のクラス群です。

これらは主に、次のような仕組みを提供します。

  • マウス操作コマンドAを実行中にコマンドBが起動されたとき、自動的にAのイベント処理を解除しBのイベント処理に切り替える。
  • バックグラウンドで動作するマウス操作A(例えば選択操作など)があるとして、その上に何らかのマウス操作Bが起動すると自動的にAのモードはサスペンド(休止)状態となり、Bが終了すると同時にAが自動的にレジューム(再開)される。

LINQPad による動作確認の準備

これから LINQPad を利用して OperationDriver の動作を確認しながら解説を進めていきます。 そのために、まず LINQPad の準備をしておきます。

  1. LINQPad 4 を起動します。
  2. [Query | Query Properties (F4)] メニューを開き、Add ボタンから System.Windows.Forms.dll を参照設定に追加します。
  3. ツールバーの Language を “C# Statement(s)” に設定します。
  4. エディタに下記コードをコピーし、F5 キーで実行します。

C#版

var form = new System.Windows.Forms.Form();
var label = new System.Windows.Forms.Label();
form.Controls.Add(label);
form.MouseMove += (sender, e) => { label.Text = e.Location.ToString(); };
form.Show();

F#版

let form = new System.Windows.Forms.Form()
let label = new System.Windows.Forms.Label()
form.Controls.Add(label)
form.MouseMove.Add (fun e -> label.Text <- e.Location.ToString())
form.Show()

ウィンドウが表示され、その上でマウスを動かすとその座標値がラベルに表示されます。

OperationDriver を動かしてみよう

上記と等価なコードを、 MoNo.Ctrl.OperationDriver を利用して作ってみます。

まず、必要な MoNo.RAIL のDLLを参照設定に追加する必要があります。 [Query | Query Properties (F4)] メニューから下記のDLLを参照に追加して下さい。

  • MoNo.dll
  • MoNo.Basics.dll
  • MoNo.Framework.dll

OperationDriver を用いたコードは次のようになります。

var form = new System.Windows.Forms.Form();
var label = new System.Windows.Forms.Label();
form.Controls.Add(label);

var opA = new MoNo.Ctrl.Operation(form);
opA.MouseMove += (sender, e) => { label.Text = e.Location.ToString(); };
MoNo.Ctrl.OperationDriver.Default.Run(opA);

form.Show();

F#版

let form = new System.Windows.Forms.Form()
let label = new System.Windows.Forms.Label()
form.Controls.Add(label)

let opA = MoNo.Ctrl.Operation(form)
opA.MouseMove.Add (fun e -> label.Text <- e.Location.ToString())
MoNo.Ctrl.OperationDriver.Default.Run opA

form.Show()

OperationDriver.DefaultOperationDriver のデフォルトのインスタンスです。 独自に OperationDriver インスタンスを new することも可能ですが、殆どの場合はデフォルトのインスタンスを利用することになります。 この OperationDriver の上で MoNo.Ctrl.Operation が動作している状態になります。

別のオペレーションの起動

次のようにボタンを追加し、クリックイベントで別のオペレーションを起動するようにしてみます。 ボタンを押すと、ラベルにウィンドウをクリック位置(MouseMoveではなく)を表示するオペレーションに切り替わります。

...
var button = new System.Windows.Forms.Button { Text = "Click", Dock = System.Windows.Forms.DockStyle.Bottom };
form.Controls.Add(button);
button.Click += (sender, e) => {
  var opB = new MoNo.Ctrl.Operation(form);
  opB.MouseClick += (sender2, e2) => { label.Text = e2.Location.ToString(); };
  MoNo.Ctrl.OperationDriver.Default.Run(opB);
};

form.Show();

説明のため、2つのマウスオペレーションに名前をつけておきます。

  • 元々動いていた、MouseMove イベントでマウス位置を表示するオペレーションを A とします。
  • ボタンを押すと起動する、MouseClick イベントでクリック位置を表示するオペレーションを B とします。

Run() を呼び出すと、元々動いていたAが自動的にアボートされて新しいオペレーションBに切り替わっている点に着目して下さい。 一つの OperationDriver 内でアクティブに起動できるオペレーションは1つまでです。

オペレーションのPush

上記のサンプルは、一度ボタンを押してBに切り替わってしまうと、元のAには戻れません。 これを下記のように修正すると、Bに入っても1回クリックすると元のAに戻るようになります。

...
MoNo.Ctrl.OperationDriver.Default.Run(opA);
MoNo.Ctrl.OperationDriver.Default.Push();  // (1) ここを追加
...
button.Click += (sender, e) => {
  var opB = new MoNo.Ctrl.Operation(form);
  opB.MouseClick += (sender2, e2) => {
        label.Text = e2.Location.ToString();
        opB.Abort();  // (2) ここを追加
  };
  ...
};
  1. Push() について説明します。

OperationDriverOperation をスタックで保持していて、Push() によりトップの Operation (この場合はオペレーションA)を一つ下の階層に「押し込む」ことが出来ます。

この状態でオペレーションBを起動すると、オペレーションAはアボートされるのではなくサスペンド(休止)状態となります。 そしてオペレーションBが終了すると、レジューム(再開)処理により再びオペレーションAがアクティブになります。

バックグラウンドのオペレーションスタック

上で Push について説明しました。 Push という名前から想像がつく通り、オペレーションはスタックに積まれた形式で保持されています。

../../_images/OperationDriver1.png

上図が OperationDriver の内部のイメージ図です。

Run メソッドでオペレーションを起動すると、引数で渡されたオペレーションが ActiveOperation となります。 それ以前から何らかの ActiveOperation が存在していた場合は、そのオペレーションがアボートされ新しいオペレーションが取って代わります。

ActiveOperation が設定されると、バックグラウンドのオペレーションはすべてサスペンド状態となります。

この状態で Push されると、ActiveOperation がスタックに積まれ、ActiveOperation は null になります。 逆に Pop されると、スタックのトップにあるオペレーションが取り出されて ActiveOperation に設定されます。

さて、下図は ActiveOperation がアボートされた状態、あるいは ActiveOperation が Push された状態を図示したものです。

../../_images/OperationDriver2.png

ActiveOperationnull ですので、代わりにバックグラウンドのトップにあるオペレーションがレジューム(再開)され、作動します。

Interrupt メソッド

オペレーションを起動する関数として、Run 以外に Interrupt という関数もあります。 これはその名の通り、オペレーションの「割り込み」を行う関数です。

まず null でない ActiveOperation が OperationDriver 上で起動しているとします。 そのとき別のオペレーションを Interrupt させると、次のように動作します。

  1. 起動中の ActiveOperation はバックグラウンドに Push され、Suspend されます。
  2. 新しいオペレーションが ActiveOperation となり起動します。
  3. 新しいオペレーションが終了すると、自動的に先ほど Push されたオペレーションが Pop され、再開(Resume)します。

この関数は、今実行中のオペレーションに割り込んで別のオペレーションを起動し、終了したら元の状態に戻したい、というときに有効に使えます。