フォーム ウィザードとプロジェクト ウィザードの作成
Delphi には、いくつものフォーム ウィザードやプロジェクト ウィザードが既にインストールされています。また、ユーザーが独自に作成することもできます。 オブジェクト リポジトリを使うとプロジェクトで使用可能な静的テンプレートを作成できますが、ウィザードは動的なためさらに強力です。 ウィザードでは、ユーザーに入力を促し、その内容に応じてさまざまな種類のファイルを作成することができます。
フォーム ウィザードやプロジェクト ウィザードでは、通常、1 つ以上のファイルを新規作成します。 ただし、実際のファイルを作成するのではなく、名称未設定で未保存のモジュールを作成するのが最適です。 ユーザーが保存を行った時に、IDE によってファイル名を入力する画面が表示されます。 ウィザードでは、クリエータ オブジェクトを使ってモジュールを作成します。
クリエータ クラスは、IOTACreator を継承したクリエータ インターフェイスを実装しています。 ウィザードはクリエータ オブジェクトをモジュール サービスの CreateModule メソッドに渡し、IDE はクリエータ オブジェクトに対するコールバックを行ってモジュール作成に必要なパラメータを取得します。
たとえばフォームを新規作成するフォーム ウィザードは、通常、GetExisting で false を返し、GetUnnamed で true を返すように実装します。 そうすることで、名前を持たず(つまりファイルを保存する前にユーザーが名前を指定する必要があります)、ファイルがまだ存在しない(つまりユーザーが変更を行わなくてもファイルを保存する必要があります)モジュールが作成されます。 クリエータにはその他に、作成しているファイルの種類(プロジェクト、ユニット、フォームなど)を IDE に知らせるためのメソッドや、ファイルの内容を返すメソッド、フォーム名や上位クラス名などの重要な情報を返すメソッドなどがあります。 ウィザードではその他にもコールバックを使って、新規作成したプロジェクトにモジュールを追加したり、新規作成したフォームにコンポーネントを追加することができます。
フォーム ウィザードやプロジェクト ウィザードでは多くの場合新しいファイルを作成する必要がありますが、それには通常、新しいファイルの内容を指定しなければなりません。 そのためには、IOTAFile インターフェイスを実装した新しいクラスを作成します。 デフォルトのファイル内容で問題ない場合には、IOTAFile を返す任意の関数から nil を返すことができます。
たとえば、組織で標準のコメント ブロックが決められていて、それを各ソース ファイルの先頭に表示しなければならないとします。 これはオブジェクト リポジトリの静的テンプレートを使って行うこともできますが、その場合にはコメント ブロックの作成者およぴ作成日を手動で更新する必要があります。 それに対してクリエータを使用した場合には、ファイルの作成時にコメント ブロックの該当部分を動的に設定することができます。
まず、新しいユニットおよびフォームを作成するウィザードを記述します。 クリエータの関数の多くは、ゼロや空文字列といったデフォルト値を返すことで、ユニットやフォームの新規作成時にデフォルトの動作を使用するよう Tools API に指示しています。 GetCreatorType をオーバーライドして、ユニットとフォームのどちらの種類のモジュールを作成するかを Tools API に知らせます。 ユニットを作成するには sUnit を返します。 フォームを作成するには sForm を返します。 コードを単純化するために、クリエータの種類をコンストラクタの引数に取る 1 つのクラスを作成します。 クリエータの種類をデータ メンバに保存して、GetCreatorType がその値を返せるようにします。 必要なファイル内容を返すよう NewImplSource と NewIntfSource を実装します。
TCreator = class(TInterfacedObject, IOTAModuleCreator) public constructor Create(const CreatorType: string); { IOTAModuleCreator } function GetAncestorName: string; function GetImplFileName: string; function GetIntfFileName: string; function GetFormName: string; function GetMainForm: Boolean; function GetShowForm: Boolean; function GetShowSource: Boolean; function NewFormFile(const FormIdent, AncestorIdent: string): IOTAFile; function NewImplSource(const ModuleIdent, FormIdent, AncestorIdent: string): IOTAFile; function NewIntfSource(const ModuleIdent, FormIdent, AncestorIdent: string): IOTAFile; procedure FormCreated(const FormEditor: IOTAFormEditor); { IOTACreator } function GetCreatorType: string; function GetExisting: Boolean; function GetFileSystem: string; function GetOwner: IOTAModule; function GetUnnamed: Boolean; private FCreatorType: string; end;
class PACKAGE Creator : public IOTAModuleCreator { public: __fastcall Creator(const AnsiString creator_type) : ref_count(0), creator_type(creator_type) {} virtual __fastcall ~Creator(); // IOTAModuleCreator virtual AnsiString __fastcall GetAncestorName(); virtual AnsiString __fastcall GetImplFileName(); virtual AnsiString __fastcall GetIntfFileName(); virtual AnsiString __fastcall GetFormName(); virtual bool __fastcall GetMainForm(); virtual bool __fastcall GetShowForm(); virtual bool __fastcall GetShowSource(); virtual _di_IOTAFile __fastcall NewFormFile( const AnsiString FormIdent, const AnsiString AncestorIdent); virtual _di_IOTAFile __fastcall NewImplSource( const AnsiString ModuleIdent, const AnsiString FormIdent, const AnsiString AncestorIdent); virtual _di_IOTAFile __fastcall NewIntfSource( const AnsiString ModuleIdent, const AnsiString FormIdent, const AnsiString AncestorIdent); virtual void __fastcall FormCreated( const _di_IOTAFormEditor FormEditor); // IOTACreator virtual AnsiString __fastcall GetCreatorType(); virtual bool __fastcall GetExisting(); virtual AnsiString __fastcall GetFileSystem(); virtual _di_IOTAModule __fastcall GetOwner(); virtual bool __fastcall GetUnnamed(); protected: // IInterface virtual HRESULT __stdcall QueryInterface(const GUID&, void**); virtual ULONG __stdcall AddRef(); virtual ULONG __stdcall Release(); private: long ref_count; const AnsiString creator_type; };
TCreator のほとんどのメンバは、ゼロ、nil、または空文字列を返します。 論理型メソッドは true を返しますが、GetExisting は例外で false を返します。 最も興味深いメソッドは GetOwner です。これは現在のプロジェクト モジュールを指すポインタ(プロジェクトが存在しない場合には nil)を返します。 現在のプロジェクトや現在のプロジェクト グループを簡単に見つける方法はありません。 そのため、GetOwner では開いているモジュールすべてに対して反復処理を行う必要があります。 プロジェクト グループが見つかった場合、それは開かれている唯一のプロジェクト グループのはずなので、GetOwner はそのグループの現在のプロジェクトを返します。 プロジェクト グループが見つからなければ、最初に見つかったプロジェクト モジュール(開かれているプロジェクトがない場合には nil)を返します。
function TCreator.GetOwner: IOTAModule; var I: Integer; Svc: IOTAModuleServices; Module: IOTAModule; Project: IOTAProject; Group: IOTAProjectGroup; begin { Return the current project. } Supports(BorlandIDEServices, IOTAModuleServices, Svc); Result := nil; for I := 0 to Svc.ModuleCount - 1 do begin Module := Svc.Modules[I]; if Supports(Module, IOTAProject, Project) then begin { Remember the first project module} if Result = nil then Result := Project; end else if Supports(Module, IOTAProjectGroup, Group) then begin { Found the project group, so return its active project} Result := Group.ActiveProject; Exit; end; end; end;
_di_IOTAModule __fastcall Creator::GetOwner() { // Return the current project. _di_IOTAProject result = 0; _di_IOTAModuleServices svc = interface_cast<IOTAModuleServices>(BorlandIDEServices); for (int i = 0; i < svc->ModuleCount; ++i) begin _di_IOTAModule module = svc->Modules[i]; _di_IOTAProject project; _di_IOTAProjectGroup group; if (module->Supports(project)) { // Remember the first project module. if (result == 0) result = project; } else if (module->Supports(group)) { // Found the project group, so return its active project. result = group->ActiveProject; break; } } return result; }
クリエータの NewFormSource で nil を返しているため、デフォルトのフォーム ファイルが生成されます。 興味深いメソッドは NewImplSource と NewIntfSource です。ここではファイルの内容を返す IOTAFile のインスタンスが作成されます。
TFile クラスでは IOTAFile インターフェイスを実装しています。 ファイルの年齢として -1 を返し(ファイルがまだ存在しないことを示します)、ファイルの内容を文字列として返します。 TFile クラスを単純にしておくために、クリエータで文字列を生成し、TFile クラスではそれを受け渡すだけにします。
TFile = class(TInterfacedObject, IOTAFile) public constructor Create(const Source: string); function GetSource: string; function GetAge: TDateTime; private FSource: string; end; constructor TFile.Create(const Source: string); begin FSource := Source; end; function TFile.GetSource: string; begin Result := FSource; end; function TFile.GetAge: TDateTime; begin Result := TDateTime(-1); end;
class File : public IOTAFile { public: __fastcall File(const AnsiString source); virtual __fastcall ~File(); AnsiString __fastcall GetSource(); System::TDateTime __fastcall GetAge(); protected: // IInterface virtual HRESULT __stdcall QueryInterface(const GUID&, void**); virtual ULONG __stdcall AddRef(); virtual ULONG __stdcall Release(); private: long ref_count; AnsiString source; }; __fastcall File::File(const AnsiString source) : ref_count(0), source(source) {} AnsiString __fastcall File::GetSource() { return source; } System::TDateTime __fastcall File::GetAge() { return -1; }
ファイル内容のテキストはリソース内に格納して変更しやすくすることができますが、この例ではコードを単純にするため、ウィザード内にソース コードをハードコードしています。 下の例では、フォームが存在することを前提にソース コードを生成しています。 単純なユニットのように簡単な場合は容易に追加できます。 FormIdent を確認し、空であれば単純なユニットを作成し、空でなければフォーム ユニットを作成します。 コードの基本的なスケルトンは IDE のデフォルトと同じですが(もちろんコメントは先頭に追加されています)、必要に応じて好きなように変更できます。
function TCreator.NewImplSource( const ModuleIdent, FormIdent, AncestorIdent: string): IOTAFile; var FormSource: string; begin FormSource := '{ ----------------------------------------------------------------- ' + #13#10 + '%s - description'+ #13#10 + 'Copyright © %y Your company, inc.'+ #13#10 + 'Created on %d'+ #13#10 + 'By %u'+ #13#10 + ' ----------------------------------------------------------------- }' + #13#10 + #13#10; return TFile.Create(Format(FormSource, ModuleIdent, FormIdent, AncestorIdent)); }
_di_IOTAFile __fastcall Creator::NewImplSource( const AnsiString ModuleIdent, const AnsiString FormIdent, const AnsiString AncestorIdent) { const AnsiString form_source = "/*-----------------------------------------------------------------\n" " %m - description\n" " Copyright \xa9 %y Your company, inc.\n" " Created on %d\n" " By %u\n" " ---------------------------------------------------------------*/\n" "\n" "#include <vcl.h>\n" "#pragma hdrstop\n" "\n" "#include \"%m.h\"\n" "//-----------------------------------------------------------------\n" "#pragma package(smart_init)\n" "#pragma resource \"*.dfm\"\n" "T%f *%f;\n" "//-----------------------------------------------------------------\n" "__fastcall T%m::T%m(TComponent* Owner)\n" " : T%a(Owner)\n" "{\n" "}\n" "//----------------------------------------------------------------\n"; return new File(expand(form_source, ModuleIdent, FormIdent, AncestorIdent)); }
最後にフォーム ウィザードを 2 つ作成します。 一方はクリエータの種類として sUnit を、他方は sForm を使用します。 ユーザーにとって便利なように、INTAServices を使って、それぞれのウィザードを呼び出すためのメニュー項目を[ファイル|新規作成]メニューに追加します。 メニュー項目の OnClick イベント ハンドラで、ウィザードの Execute 関数を呼び出すことができます。
ウィザードによっては、IDE の状態に応じてメニュー項目の有効/無効を切り替える必要があります。 たとえばプロジェクトをソース コード管理システムにチェックインするウィザードであれば、IDE でファイルが何も開かれていない場合には[チェックイン]メニュー項目を無効にするべきです。 ウィザードに IDE イベントを通知することで、ウィザードにその機能を追加することができます。