サンプルプログラムで見るアルゴリズムの動作
Form_Main.csは、幾何学的に異なるいくつかの図形について、このアルゴリズムが実際にどう動くかを示してくれます。図形はコンテキストメニューでいつでも追加または削除できます。図1は、このアプリケーションがユーザーからどう見えるかを示しています。追加のラインやマークはないので、このアプリケーションの見た目はこのまま変わりません。ユーザーに伝える必要があるのは、すべてのものが移動可能/サイズ変更可能であるということだけです。移動/サイズ変更操作は、マウスを使って次のようにごく自然な形で行われます。
- オブジェクトを移動するには、オブジェクト内部の任意の点を押す
- サイズ変更するには、オブジェクトの境界上の任意の点を押す
このアプリケーション内のオブジェクトは任意の形を取ることができますが、それらを覆うノードの形状はあまり種類が多くありません。CoverNodeオブジェクトは円、凸多角形、角の丸い帯のいずれかの形を取ることができます。オブジェクトのカバーは、いろいろな方法で設計できます(同じクラスのオブジェクトに異なるカバーを与えてもかまいません)。カバーの構成がどうであれ、要はオブジェクトを移動/サイズ変更できればよいのです。図2は、図1の図形のカバーがどうなっているかを示しています。
図2では、すべての種類のカバーノードが使われています。
円は、オブジェクトの形を変える特別な場所(頂点など)を覆うのによく使われます。カーブを描く境界部分を大量の小さな円で覆えば、その境界上の任意の点でオブジェクトをサイズ変更できるようになります。この特殊なタイプのカバーデザインを「N-ノードカバー」と呼びます。
オブジェクトの内部の領域を、さまざまな形の凸多角形で覆えば、その任意の点でオブジェクトを移動できるようになります。帯は、サイズ変更できる境界の直線部分を覆うためによく使われます。このアルゴリズムでは、オブジェクトを移動するだけでなく、必要なら回転させることもできます。
アルゴリズム実装例
任意の要素セットの移動/サイズ変更をコントロールするには、Moverオブジェクトを宣言して初期化する必要があります。
Mover mover; mover = new Mover (this);
Moverが関知するのは、そのListに登録されたオブジェクトだけです。
mover .Insert (0, circle);
mover .Add (strip);
移動/サイズ変更プロセスをコントロールするのはマウスだけなので、次の3種類のマウスイベントのみを使用します。
private void OnMouseDown (object sender, MouseEventArgs mea)
{
mover .Catch (mea .Location);
}
private void OnMouseUp (object sender, MouseEventArgs mea)
{
mover .Release ();
}
private void OnMouseMove (object sender, MouseEventArgs mea)
{
if (mover .Move (mea .Location))
{
Invalidate ();
}
}
これは別に簡略化したものではなく、実際のコードそのままです。このコードは、移動/サイズ変更に関与するオブジェクトの数や複雑さとは関係なく、アプリケーションのほぼすべてのフォームで使われます。この3つのメソッドは、いずれもMoverの別のメソッドを呼び出します。ここで求められるのはそれだけです。ただし、特別な状況ではOnMouseDown()メソッドとOnMouseUp()メソッドに何行かのコードを別途追加する必要があります。例えば、回転に関与するオブジェクトでは、そのStartRotation()メソッドの呼び出しをOnMouseDown()メソッド内に含めなければなりません。StartRotation()メソッドは、多くの場合、1行で構成されます。具体的な例はForm_Main.csにあります。回転に関与するオブジェクトでは、オブジェクトの角度がパラメータの1つになります。StartRotation()メソッドには、このパラメータと回転開始時のマウス位置との角度差を計算して記憶する役割があります。
public void StartRotation (Point ptMouse)
{
double angleMouse = -Math .Atan2 (ptMouse.Y - ptC.Y, ptMouse.X - ptC.X);
compensation_0 = Auxi_Common .LimitedRadian (angleMouse - angle_0);
}
さまざまなコンテキストメニューを使う場合は、OnMouseUp()メソッド内にメニュー選択の呼び出しを含めます。実際、Form_Medley.csでは10種類ものコンテキストメニューを使用しています。もっとも、そのために必要な作業は、この概ね標準的でごく単純な3マウスイベントのコードに、わずかなコードを追加することだけです。
図1および図2の移動可能/サイズ変更可能なオブジェクトは、形の違いこそあれ、いずれも他のオブジェクトとは関係なく独立して動くという共通の特性を持っています。現実のアプリケーションでは、普通、さまざまな要素で構成される、もっと複雑なオブジェクトが使われます。そうした複雑なオブジェクトを移動するには、その構成要素を同時に移動しなければなりません。さらに、要素それ自体も独立して移動できます。Form_RegPolyWithComments.csにその例があります(図3)。
この多角形に対して可能な操作は、移動、サイズ変更、回転です。例によって、移動と回転は多角形の内部の任意の点から開始でき、サイズ変更は境界上の任意の点から開始できます。この多角形には、コメントをいくつでも自由に追加できます。新規のコメントを追加するには[New comment]グループを使用します。どのコメントも、その内部の点で移動または回転できます。
コメントは、その「親」であるところの多角形の上に重ねて表示されます。画面の同じ場所に複数のオブジェクトが置かれている場合、ユーザーはマウスでその場所を押したとき、1番上のオブジェクトが選択されるものと考えます。移動の対象となるオブジェクトはMoverのList内での順序に従って選択されるので、このキュー内では、すべてのコメントを対応する「親」多角形よりも前に置かなければなりません。そこで、正しい結果を得るために、多角形とコメントをキューに入れる処理は、手動ではなくpolygon.IntoMover(...)メソッドで行います。これにより、コメントがいくつあっても、MoverのList内で多角形の構成要素が正しい順序になることが保証されます。
public void IntoMover (Mover mover, int iPos)
{
mover .Insert (iPos, this);
for (int i = comments .Count - 1; i >= 0; i--)
{
mover .Insert (iPos, comments [i]);
}
}
図3の[New comment]グループは、見た目は標準のGroupBoxオブジェクトとよく似ていますが、実際はもっとずっと強力です。このサンプルアプリケーションのフォームには、図形オブジェクト、コントロール、およびコントロールグループが配置されています。図形オブジェクトが移動/サイズ変更できても、コントロールやコントロールグループが移動/サイズ変更できなければ、そのアルゴリズムは大いに問題があると言えるでしょう。もちろん、このサンプルのアルゴリズムは任意のオブジェクトを、その出所に関係なく、移動/サイズ変更できるように設計されています。標準のGroupBoxはその境界でだけ移動でき、内部の点では移動できません。こうした処理をより円滑に行えるように、移動可能/サイズ変更可能なグループを統括するクラスが開発されています。実際、図3のグループはGroupクラスに属します。この項目の詳しい説明については、CodeGuruで公開されている記事「Forms' (dialogues') customization, based on moveable / resizable elements」(移動可能/変更可能な要素を使ったフォーム(ダイアログ)のカスタマイズ)を参照ください。



