前回はコマンドメソッドをクラスとして切り出す方法についてご紹介しました。クラスとして切り出されていればメンバ変数を活用することができるようになります。これは複雑なユーザー入力を伴うコマンドで特に威力を発揮します。

そこで本稿では、前回のサンプルに加えて、ユーザー入力系の処理をメソッドとして切り出した上でメンバ変数を活用する方法についてお伝えします。

なおサンプルコードは VisualStudio2005 の C# で作成し、 AutoCAD2007/2008/2009 で動作確認を行いました。

メンバ変数を利用したユーザー入力のサンプル

前回のサンプルコードを基に、ユーザー入力をメソッドとして切り出した上で、メンバ変数に保存するように修正してみました。今回のような単純な構造ではあまり恩恵がありませんが、複数の入力を受け付けるカスタムコマンドの場合、メンバ変数を活用するとしないとではコードの見通しが全く変わってきます。

そしてもう一つ。ユーザー入力を受け付ける場合、それぞれを別々のメソッドとして定義するようにしてください。一般にユーザー入力部分はコードが煩雑になってきますが、個別のメソッドとして定義しておけば非常にすっきりとまとめることができます。

// Command.cs
public class Command
{
    [CommandMethod( "CREATE_LINE" )]
    public void CreateLine()
    {
        // コマンド処理メソッドを呼び出す
        CreateLineCommandProcessor.Run();
    }
}
// CreateLineCommandProcessor.cs
class CreateLineCommandProcessor
{
    // 外部に公開している static メソッド
    public static void Run()
    {
        // コマンド処理クラスのインスタンスを生成する
        CreateLineCommandProcessor com_lib = new CreateLineCommandProcessor();

        // ユーザーから始点を取得する
        if( !com_lib.GetFromPosition() )
        {
            return;
        }

        // ユーザーから終点を取得する
        if( !com_lib.GetToPosition() )
        {
            return;
        }

        // コマンド処理のメインルーチンを呼び出す
        com_lib.RunCreateLine();
    }

    // インスタンスを生成されないよう、コンストラクタを private にしておく
    private CreateLineCommandProcessor()
    {
    }

    // ユーザーから始点を取得
    private bool GetFromPosition()
    {
        Editor edit = Application.DocumentManager.MdiActiveDocument.Editor;

        PromptPointOptions point_opt = new PromptPointOptions( "線分の始点を指定" );
        PromptPointResult point_res = edit.GetPoint( point_opt );
        if( point_res.Status != PromptStatus.OK )
        {
            return false;
        }

        m_FromPoint = point_res.Value;
        return true;
    }

    // ユーザーから終点を取得
    private bool GetToPosition()
    {
        Editor edit = Application.DocumentManager.MdiActiveDocument.Editor;

        PromptPointOptions point_opt = new PromptPointOptions( "線分の終点を指定" );
        point_opt.BasePoint = m_FromPoint;    // 始点をラバーバンドの基点に設定
        point_opt.UseBasePoint = true;
        point_opt.UseDashedLine = true;
        PromptPointResult point_res = edit.GetPoint( point_opt );
        if( point_res.Status != PromptStatus.OK )
        {
            return false;
        }

        m_ToPoint = point_res.Value;
        return true;
    }

    // コマンド処理のメインルーチン
    private void RunCreateLine()
    {
        Transaction trans = null;
        try
        {
            Database active_db = Application.DocumentManager.MdiActiveDocument.Database;
            trans = active_db.TransactionManager.StartTransaction();

            // エンティティを作成、登録する
            Line line = new Line( m_FromPoint, m_ToPoint ); // ユーザー入力を利用

            ObjectId blk_tbl_id = active_db.BlockTableId;
            BlockTable blk_tbl =
                (BlockTable)trans.GetObject( blk_tbl_id, OpenMode.ForRead );
            ObjectId model_space_id = blk_tbl[ BlockTableRecord.ModelSpace ];
            BlockTableRecord model_space =
                (BlockTableRecord)trans.GetObject( model_space_id, OpenMode.ForWrite );

            model_space.AppendEntity( line );
            trans.AddNewlyCreatedDBObject( line, true );

            trans.Commit();
        }
        catch( System.Exception ex )
        {
            Editor edit = Application.DocumentManager.MdiActiveDocument.Editor;
            edit.WriteMessage( "\n{0}", ex.Message );
        }
        finally
        {
            if( trans != null )
            {
                trans.Dispose();
            }
        }
    }

    Point3d m_FromPoint;    // 線分の始点
    Point3d m_ToPoint;      // 線分の終点
}

環境によっては、コンパイル時に "この参照を解決できませんでした" という警告が出ます。この場合 "acdbmgd.dll" と "acmgd.dll" を削除し、 AutoCAD のインストールディレクトリから参照しなおしてください。

サンプルコード解説

Run() メソッド

コマンド処理クラスのメンバのうち、外部に唯一公開されているメソッドです。自分自身を生成してメインルーチンを呼び出すのが役目ですが、今回はユーザー入力の受付メソッドも呼び出してみました。

ユーザー入力の受付メソッドは bool 型を返しますので、これを使って処理を継続するか否かを判定します。従来のべた書きと違って、よくまとまっていることがおわかりいただけるでしょうか。

GetFromPosition()/GetToPosition() メソッド

ユーザー入力メソッドです。ユーザー入力ルーチンはオプションをつけるととたんに煩雑になってきますが、メソッドとして切り出してあればそんなこともありません。またユーザー入力はメンバ変数に保存するようにしていますので、値の受け渡しも便利です。

応用範囲は非常に広範

前回、今回とコマンドメソッドのクラス化についてお伝えしてきました。ページの関係で単純な例しかお見せできませんでしたが、このクラスを基にすればコマンド処理系のフレームワークに育てることも可能です。

たとえばアプリケーションによっては、コマンドごとに定型の初期化・終了処理があるかもしれません。そんなときはコマンド処理クラスの基底クラスを作成し、そこで初期化・終了処理をするという方法が考えられます。そして実際の処理は派生クラスで実装するという寸法です。

実例を挙げますと、弊社で実装したフレームワークでは try-catch ブロックとトランザクションの処理を基底クラスが一手に担っています(*1)。これにより処理メソッドの実装は処理だけに集中することができる上に、動作速度も一気に向上しました(*2)

他にもユーザー入力のテンプレートを提供するなど、この構造を基にして様々な応用が考えられます。

(MINERVA 深津貴成)
  1. ^ 他にも ObjectARX を介してドキュメントロックを仕掛けるなどの機能が盛り込んであります。これを全部手動でやったとすると、バグの温床になっていたはずです。
  2. ^ 一言でいえば、トップトランザクションを使い回すようにしています。従って「速度の低下を防いだ」といった方が正しいかもしれません。トランザクションの生成・破棄には大きなコストが必要ですから、それを一回で済ませようというわけです。