カスタムコンポーネントを作ると、フォームエディタ上でのコンポーネントの操作のために、コンポーネントのコンテキストメニューに独自の項目を追加したい場合があります。この場合、カスタムコンポーネントエディタを実装することになります。カスタムコンポーネントエディタの作り方もカスタムプロパティエディタと同様にC++Builderのヘルプに載っているものの、どちらも説明はわかりにくく、ヘルプにサンプルさえ載っていない点もカスタムプロパティエディタと同様です。市販の書籍でも状況はカスタムプロパティエディタと同様のようです。
ここでは、カスタムコンポーネントエディタの作り方について、完全ではないですが私が理解できた範囲で説明します。
おしながき.
全体の流れ.
IDEに組み込めるコンポーネントエディタ作成の全体的な手順を示します。
カスタムコンポーネントを作成
↓
コンポーネントエディタをIDEに登録
↓
コンポーネントエディタをデバッグ
ユニットの作成.
コンポーネントエディタのコードもカスタムコンポーネントと同じソースファイルに記述してもいいのですが、以下のようにユニット(ファイル)を分けることをお勧めします。
- カスタムコンポーネント本体
- コンポーネントエディタ
理由はプロパティエディタと同様で、設計時のみしか使用されないコンポーネントエディタを、アプリケーションの実行ファイルから排除するためです。
コンポーネントエディタをIDEに登録.
コンポーネントエディタをIDEに登録すると、オブジェクトインスペクタからコンポーネントエディタを呼び出せるようになります。そのためにはTComponentEditorクラスから派生したクラスを作成し、そのクラスをIDEに登録します。
コンポーネントエディタのクラスを派生.
まずは必要なヘッダファイルをインクルードします。
基底クラスのTComponentEditorが定義されているDesignEditors.hppを、作成したユニットのヘッダでインクルードします。
#include <DesignEditors.hpp>
ここで作成するクラスでは、作成するコンポーネントエディタに固有の実装となる部分を、メソッドのオーバーライドで定義します。次のソースはそのサンプルです。オーバーライドの必要な最低限のメソッドは、次のサンプルソースのとおりだと思います。
class PACKAGE TPageSheetComponent : public TComponentEditor
{
public:
int __fastcall GetVerbCount(void);
AnsiString __fastcall GetVerb(int verbIndex);
void __fastcall ExecuteVerb(int verbIndex);
void __fastcall PrepareItem(int verbIndex, const _di_IMenuItem AItem);
};
コンストラクタの定義.
コンストラクタは何もすることがなければ、独自に定義しなくてもいいようです。
GetVerbCountメソッドのオーバーライド.
GetVerbCountメソッドは、コンポーネントエディタがコンテキストメニューに追加するメニューの項目数を返すメソッドです。これをオーバーライドします。
次のソースは、TPagePanelのTPageSheetクラスのコンポーネントエディタの該当部分です。TPagePanelではページ切り替えのメニュー項目がコンポーネントエディタが呼び出された時点でのページ数により変化しますので、毎回動的にカウントしています。
#define MENUNUMSHEET 4 // TPageSheet固有のメニュー項目数
int __fastcall TPageSheetComponent::GetVerbCount(void)
{
TPagePanel *pagepanel;
pagepanel = (TPagePanel *)(((TPageSheet *)Component)->Parent);
return MENUNUMSHEET + pagepanel->PageCount;
}
GetVerbメソッドのオーバーライド.
GetVerbメソッドは、コンポーネントエディタがコンテキストメニュー追加する各メニュー項目の項目名を、文字列で返すメソッドです。前節で説明したGetVerbCountをオーバーライドして1以上の値を返すと、コンポーネントエディタ呼び出しのタイミングで本メソッドがコールされるので、このメソッドもオーバーライドが必要になります。 次のソースは、TPagePanelのTPageSheetクラスのコンポーネントエディタの該当部分です。TPagePanelと同じ理由により本メソッドも、メニュー項目名の文字列を毎回動的に取得しています。TMenuItem::Captionと同様に、項目名をハイフン"-"にすると、その項目をメニューのセパレータにすることが出来ます。
AnsiString __fastcall TPageSheetComponent::GetVerb(int verbIndex)
{
static const char *menuitems[] = {
"ページ新規作成 (&W)",
"ページ削除 (&D)",
"-",
"全ページ非表示 (&I)"
};
TPagePanel *pagepanel;
AnsiString menuitem, pagemenu;
int sheetindex;
pagepanel = (TPagePanel *)(((TPageSheet *)Component)->Parent);
if (verbIndex < MENUNUMSHEET) {
menuitem = menuitems[verbIndex];
} else
{
sheetindex = verbIndex - MENUNUMSHEET;
menuitem.sprintf("ページ%d : ", sheetindex);
menuitem = menuitem + pagepanel->Pages[sheetindex]->Name;
}
return menuitem;
}
PrepareItemメソッドのオーバーライド.
PrepareItemメソッドは、コンポーネントエディタがコンテキストメニューに追加した各項目の状態を変更するためのメソッドです。本メソッドも前節のGetVerbメソッド同様、前節で説明したGetVerbCountをオーバーライドして1以上の値を返すと、コンポーネントエディタ呼び出しのタイミングで本メソッドがコールされます。ただしGetVerbメソッドと異なり、メニュー項目の状態を変更する必要がないなら、オーバーライドは必要ないようです。
次のソースは、TPagePanelのTPageSheetクラスのコンポーネントエディタの該当部分です。
void __fastcall TPageSheetComponent::PrepareItem(int verbIndex, const _di_IMenuItem AItem)
{
TPagePanel *pagepanel;
pagepanel = (TPagePanel *)(((TPageSheet *)Component)->Parent);
if ((verbIndex >= MENUNUMSHEET) && (verbIndex == (pagepanel->ActivePageIndex + MENUNUMSHEET)))
AItem->Checked = true;
else
AItem->Checked = false;
}
ExecuteVerbメソッドのオーバーライド.
ExecuteVerbメソッドは、コンポーネントエディタのコンテキストメニューの各項目が選択されたときに呼び出されるメソッドです。前節で説明したGetVerbCountをオーバーライドして1以上の値を返すと、本メソッドがコールされるようになるので、このメソッドもオーバーライドが必要になります。
次のソースは、TPagePanelのTPageSheetクラスのコンポーネントエディタの該当部分です。セパレータが選択されることはないと思いますが、念のため何もしないコードを記述しています(例では case 2)。コンポーネントエディタがコンポーネントの内容になんらかの変更を行ったとき、IDesigner::Modified()メソッドでIDEに変更を通知する必要があります。
void __fastcall TPageSheetComponent::ExecuteVerb(int verbIndex)
{
TPagePanel *pagepanel;
TPageSheet *sheet;
AnsiString sheetname;
bool findmatch;
int sheetindex;
int i;
pagepanel = (TPagePanel *)(((TPageSheet *)Component)->Parent);
switch (verbIndex) {
case 0: // ページ追加
(中略)
Designer->Modified();
break;
case 1: // ページ削除
(中略)
Designer->Modified();
break;
case 2: // セパレータなので何もしない
break;
case 3: // 全ページを非表示
pagepanel->ActivePageIndex = -1;
Designer->Modified();
break;
default:
// 直接ページ選択
sheetindex = verbIndex - MENUNUMSHEET;
pagepanel->ActivePageIndex = sheetindex;
Designer->Modified();
break;
}
}
IDEへの登録.
最後に、カスタムコンポーネント本体の登録と同様に、Register関数内でRegisterComponentEditor関数によって、作成したカスタムコンポーネントエディタを登録します。RegisterComponentEditor関数についてはC++Builderに十分な説明があるので、ここでは割愛します。
// コンポーネントエディタの登録
RegisterComponentEditor(__classid(TPageSheet), __classid(TPageSheetComponent));
RegisterComponentEditorメソッドの仕様を見ればわかるように、コンポーネントエディタはコンポーネントクラスと対で登録することになります。そのため、独自のカスタムコンポーネントエディタを使用したいコンポーネントは、たとえ同等のコンポーネントがすでにあったとしても、コンポーネントも独自に作成するほかないようです。
その他.
階層化されたメニュー項目.
PrepareItemメソッドの説明には、AItem引数によって示されるクラスのAddItemメソッドを使用して、階層化されたメニューを作成できるとの記述があります。しかしヘルプには_di_IMenuItemクラスの説明が見当たりませんので、どのようにすべきなのか不明でした。まだあまり試していないという面もありますが。
階層化されたメニューを作成する方法をご存じの方、ぜひ教えてください。
オブジェクトツリーでの編集.
オブジェクトツリーでコンポーネントを右クリックした場合も、コンポーネントエディタが起動されます。ここに記述した範囲の対処をしておけば、IDEが勝手にカスタムコンポーネントエディタを呼び出してくれます。
しかしオブジェクトツリーでコンポーネントをドラッグ&ドロップした場合については、対応の方法がわかりませんでした。こちらについても、どなたかご存じの方おられれば、ぜひご一報ください。
デバッグ.
プロパティエディタと同様に、デバッグ中はデバッガの機能が使えませんので、デバッグは厄介です。この問題への対処法はプロパティエディタと同様、簡単に使えるダイアログで紹介したダイアログでデバッグライトを入れてみるなどになります。
Copyright 2005-2016, yosshie.
