ウィザードに IDE イベントを通知する
適切に動作するウィザードを記述するうえで重要な点は,ウィザードが IDE イベントに応答できるようにすることです。特に,モジュールインターフェースを追跡するウィザードは,ウィザードがインターフェースを解放できるように,ユーザーがモジュールをいつ閉じるかを検知する必要があります。このために,ウィザードにはノーティファイアが必要です。したがって,開発者はノーティファイアクラスを記述する必要があります。
すべてのノーティファイアクラスは 1 つ以上のノーティファイアインターフェースを実装します。ノーティファイアインターフェースはコールバックメソッドを定義し,ウィザードはノーティファイアオブジェクトを Tools API で登録し,IDE は重要な出来事が発生するとノーティファイアにコールバックします。
すべてのノーティファイアインターフェースは IOTANotifier から継承しますが,そのすべてのメソッドが特定のノーティファイアに使用されるわけではありません。次の表に,すべてのノーティファイアインターフェースを示し,それぞれについて簡単に説明します。
ノーティファイアインターフェース :
インターフェース | 説明 |
---|---|
IOTANotifier |
すべてのノーティファイアの抽象基本クラス |
IOTABreakpointNotifier |
デバッガのブレークポイントの発生または変更 |
IOTADebuggerNotifier |
デバッガのプログラムの実行,あるいはブレークポイントの追加または削除 |
IOTAEditLineNotifier |
ソースエディタ内の行の移動の追跡 |
IOTAEditorNotifier |
ソースファイルの変更または保存,あるいはエディタにおけるファイルの切り替え |
IOTAFormNotifier |
フォームの保存,あるいはフォームまたはフォーム(またはデータモジュール)上の任意のコンポーネントの変更 |
IOTAIDENotifier |
プロジェクトのロード,パッケージのインストールなどのグローバル IDE イベント |
IOTAMessageNotifier |
メッセージの表示におけるタブ(メッセージグループ)の追加と削除 |
IOTAModuleNotifier |
モジュールの変更,保存,名前の変更 |
IOTAProcessModNotifier |
デバッガにおけるプロセスモジュールのロード |
IOTAProcessNotifier |
デバッガにおけるスレッドとプロセスの作成または破棄 |
IOTAThreadNotifier |
デバッガにおけるスレッドの状態の変更 |
IOTAToolsFilterNotifier |
ツールフィルタの呼び出し |
ノーティファイアの使い方を調べるために,フォームとプロジェクトの作成の例について考察します。この例では,各ソースファイルにコメントを追加するウィザードを,モジュールクリエータを使って作成します。コメントにはユニットの初期名が記載されていますが,ユーザーはほとんどの場合,別の名でファイルを保存します。この場合,ウィザードが実際のファイル名に合わせてコメントを更新すれば,ユーザーには好都合です。
これにはモジュールノーティファイアが必要です。ウィザードは CreateModule が返すモジュールインターフェースを保存し,これを使ってモジュールノーティファイアを登録します。モジュールノーティファイアはユーザーがファイルを変更または保存するときに通知を受け取りますが,これらのイベントはこのウィザードにとって重要ではありません。したがって,AfterSave と関連する関数はすべて本体が空です。重要な関数は ModuleRenamed です。IDE は,ユーザーがファイルを新しい名前で保存した場合にこの関数を呼び出します。モジュールノーティファイアクラスの宣言は次のとおりです。
TModuleIdentifier = class(TNotifierObject, IOTAModuleNotifier)
public
constructor Create(const Module: IOTAModule);
destructor Destroy; override;
function CheckOverwrite: Boolean;
procedure ModuleRenamed(const NewName: string);
procedure Destroyed;
private
FModule: IOTAModule;
FName: string;
FIndex: Integer;
end;
class ModuleNotifier : public NotifierObject, public IOTAModuleNotifier
{
typedef NotifierObject inherited;
public:
__fastcall ModuleNotifier(const _di_IOTAModule module);
__fastcall ~ModuleNotifier();
// IOTAModuleNotifier
virtual bool __fastcall CheckOverwrite();
virtual void __fastcall ModuleRenamed(const AnsiString NewName);
// IOTANotifier
void __fastcall AfterSave();
void __fastcall BeforeSave();
void __fastcall Destroyed();
void __fastcall Modified();
protected:
// IInterface
virtual HRESULT __stdcall QueryInterface(const GUID&, void**);
virtual ULONG __stdcall AddRef();
virtual ULONG __stdcall Release();
private:
_di_IOTAModule module;
AnsiString name; // 元のモジュール名を記憶
int index; // ノーティファイアのインデックス
};
ノーティファイアを記述する 1 つの方法は,コンストラクタでノーティファイアにそれ自体を自動的に登録させることです。デストラクタはノーティファイアの登録を抹消します。モジュールノーティファイアの場合は,ユーザーがファイルを閉じると IDE が Destroyed メソッドを呼び出します。この場合,はそれ自体の登録を抹消し,モジュールインターフェースへの参照を破棄します。IDE はノーティファイアへの参照を破棄します。これで参照カウントがゼロになり,オブジェクトが解放されます。したがって,デストラクタは慎重に記述する必要があります。ノーティファイアはすでに登録を抹消されている場合があります。
constructor TModuleNotifier.Create( const Module: IOTAModule);
begin
FIndex := -1;
FModule := Module;
{ このノーティファイアを登録 }
FIndex := Module.AddNotifier(self);
{ 元のモジュール名を記憶 }
FName := ChangeFileExt(ExtractFileName(Module.FileName), '');
end;
destructor TModuleNotifier.Destroy;
begin
{ ノーティファイアの登録抹消(まだの場合) }
if Findex >= 0 then
FModule.RemoveNotifier(FIndex);
end;
procedure TModuleNotifier.Destroyed;
begin
{ モジュールインターフェースの破棄とノーティファイアのクリーンアップ }
if Findex >= 0 then
begin
{ ノーティファイアの登録抹消 }
FModule.RemoveNotifier(FIndex);
FIndex := -1;
end;
FModule := nil;
end;
__fastcall ModuleNotifier::ModuleNotifier(const _di_IOTAModule module)
: index(-1), module(module)
{
// このノーティファイアを登録
index = module->AddNotifier(this);
// 元のモジュール名を記憶
name = ChangeFileExt(ExtractFileName(module->FileName), "");
}
__fastcall ModuleNotifier::~ModuleNotifier()
{
// ノーティファイアの登録抹消(まだの場合)
if (index >= 0)
module->RemoveNotifier(index);
}
void __fastcall ModuleNotifier::Destroyed()
{
// モジュールインターフェースの破棄とノーティファイアのクリーンアップ
if (index >= 0)
{
// ノーティファイアの登録抹消
module->RemoveNotifier(index);
index = -1;
}
module = 0;
}
ユーザーがファイル名を変更すると,IDE はノーティファイアの ModuleRenamed 関数にコールバックします。この関数では,新しい名前がパラメータになります。ウィザードはこの名前を使ってファイルのコメントを更新します。ソースバッファを編集するために,ウィザードは編集位置インターフェースを使用します。ウィザードが該当する位置を検出し,該当するテキストを検出したことを再確認し,このテキストを新しい名前で置き換えます。
procedure TModuleNotifier.ModuleRenamed(const NewName: string);
var
ModuleName: string;
I: Integer;
Editor: IOTAEditor;
Buffer: IOTAEditBuffer;
Pos: IOTAEditPosition;
Check: string;
begin
{ 新しいファイル名からモジュール名を取得 }
ModuleName := ChangeFileExt(ExtractFileName(NewName), '');
for I := 0 to FModule.GetModuleFileCount - 1 do
begin
{ すべてのソースエディタバッファを更新 }
Editor := FModule.GetModuleFileEditor(I);
if Supports(Editor, IOTAEditBuffer, Buffer) then
begin
Pos := Buffer.GetEditPosition;
{ モジュール名の位置はコメントの 2 行め
先頭の空白をスキップして元のモジュール名をコピー
位置が正しいことを再確認 }
Pos.Move(2, 1);
Pos.MoveCursor(mmSkipWhite or mmSkipRight);
Check := Pos.RipText('', rfIncludeNumericChars or rfIncludeAlphaChars);
if Check = FName then
begin
Pos.Delete(Length(Check)); // 元の名前を削除
Pos.InsertText(ModuleName); // 新しい名前を挿入
FName := ModuleName; // 新しい名前を記憶
end;
end;
end;
end;
void __fastcall ModuleNotifier::ModuleRenamed(const AnsiString NewName)
{
// 新しいファイル名からモジュール名を取得
AnsiString ModuleName = ChangeFileExt(ExtractFileName(NewName), "");
for (int i = 0; i < module->GetModuleFileCount(); ++i)
{
// すべてのソースエディタバッファを更新
_di_IOTAEditor editor = module->GetModuleFileEditor(i);
_di_IOTAEditBuffer buffer;
if (editor->Supports(buffer))
{
_di_IOTAEditPosition pos = buffer->GetEditPosition();
// モジュール名の位置はコメントの 2 行め
// 先頭の空白をスキップして元のモジュール名をコピー
// 位置が正しいことを再確認
pos->Move(2, 1);
pos->MoveCursor(mmSkipWhite | mmSkipRight);
AnsiString check = pos->RipText("", rfIncludeNumericChars | rfIncludeAlphaChars);
if (check == name)
{
pos->Delete(check.Length()); // 元の名前を削除
pos->InsertText(ModuleName); // 新しい名前を挿入
name = ModuleName; // 新しい名前を記憶
}
}
}
}
ユーザーがモジュール名の上にコメントを挿入する場合はどうでしょうか。この場合は,編集行ノーティファイアを使ってモジュール名が記載された行番号を追跡する必要があります。これには,IOTAEditLineNotifier インターフェースと IOTAEditLineTracker インターフェースを使用します。
ノーティファイアは慎重に記述する必要があります。ノーティファイアがそのウィザードより長く存続しないようにします。たとえば,ウィザードを使って新しいユニットを作成してからウィザードをアンロードしても,ユニットに関連付けられたノーティファイアは依然として存在します。結果は予測できませんが,多くの場合,IDE がクラッシュします。したがって,ウィザードはそのすべてのノーティファイアを追跡し,ウィザードが破棄される前に,ノーティファイアの登録をすべて抹消する必要があります。一方,ユーザーがまずファイルを閉じると,モジュールノーティファイアは Destroyed 通知を受け取ります。つまり,ノーティファイアはそれ自体の登録を抹消してモジュールのすべての参照を解放する必要があります。ノーティファイアはウィザードのマスターノーティファイアリストからもそれ自体を削除しなければなりません。
ウィザードの最新バージョンの Execute 関数を次に示します。この関数は新しいモジュールを作成し,モジュールインターフェースを使ってモジュールノーティファイアを作成してから,モジュールノーティファイアをインターフェースリスト(TInterfaceList)に保存します。
procedure DocWizard.Execute;
var
Svc: IOTAModuleServices;
Module: IOTAModule;
Notifier: IOTAModuleNotifier;
begin
{ 現在のプロジェクトを返す }
Supports(BorlandIDEServices, IOTAModuleServices, Svc);
Module := Svc.CreateModule(TCreator.Create(creator_type));
Notifier := TModuleNotifier.Create(Module);
list.Add(Notifier);
end
void __fastcall DocWizard::Execute()
{
_di_IOTAModuleServices svc;
BorlandIDEServices->Supports(svc);
_di_IOTAModule module = svc->CreateModule(new Creator(creator_type));
_di_IOTAModuleNotifier notifier = new ModuleNotifier(module);
list->Add(notifier);
}
インターフェースリストについてウィザードのデストラクタを繰り返し実行し,リストにあるすべてのノーティファイアの登録を抹消します。IDE にも同じインターフェースがあるので,インターフェースリストを使ってインターフェースを解放するだけでは不十分です。ノーティファイアオブジェクトを解放するには,IDE にノーティファイアインターフェースを解放するように指示する必要があります。この場合,デストラクタはノーティファイアにそのモジュールが破棄されたと錯覚させます。さらに複雑な状況では,ノーティファイアクラス用に Unregister という別の関数を記述するとよい場合もあります。
destructor DocWizard.Destroy; override;
var
Notifier: IOTAModuleNotifier;
I: Integer;
begin
{ リストにあるすべてのノーティファイアの登録を抹消 }
for I := list.Count - 1 downto 0 do
begin
Supports(list.Items[I], IOTANotifier, Notifier);
{ Pretend the associated object has been destroyed.
That convinces the notifier to clean itself up. }
Notifier.Destroyed;
list.Delete(I);
end;
list.Free;
FItem.Free;
end;
__fastcall DocWizard::~DocWizard()
{
// リストにあるすべてのノーティファイアの登録を抹消
for (int i = list->Count; --i >= 0; )
{
_di_IOTANotifier notifier;
list->Items[i]->Supports(notifier);
// 関連のオブジェクトが破棄されたように装う
// ノーティファイアはこれを認識してノーティファイア自体をクリーンアップ
notifier->Destroyed();
list->Delete(i);
}
delete list;
delete item;
}
その他のウィザードは,ウィザードの登録,メニュー項目のインストールなどの一般的な細かい作業を行います。