カスタムコンポーネントを作ると、プロパティの型が他のコンポーネントであるサブコンポーネントになることがあります。このサブコンポーネントであるプロパティの値を編集するため、カスタムプロパティエディタを実装することが望ましい場合があります。カスタムプロパティエディタの作り方はC++Builderのヘルプに載っているものの、カスタムコンポーネント自身の作り方の説明と同様にわかりにくく、さらにカスタムコンポーネントと違ってヘルプにサンプルさえ載っていないという状況です。市販の書籍でも、カスタムコンポーネントの作り方が載っている書籍でさえ、カスタムプロパティエディタの作り方までは載っていないことがほとんどで、カスタムプロパティエディタの作り方に関する情報の少なさには困らされます。
ここでは、カスタムプロパティエディタの作り方について、完全ではないですが私が理解できた範囲で説明します。
おしながき.
C++Builderでの制限.
VCLがDelphiで作られていることからも何となく想像できなくもないのですが、Delphiに比べて、C++Builderでプロパティエディタを作る上では、いくらかの制約があるようです。最初に、気付いた範囲でそれらの制約をあげておきます。
- int/short/long/bool/float/double型のような組み込み型用のプロパティエディタが作れない。
- AnsiString型用のプロパティエディタが作れない。
- 集合型(Set型)用のプロパティエディタが作れない。
つまり、クラスに対するものしか作れないようです。
その理由は次のとおり。
プロパティエディタの登録関数RegisterPropertyEditorは4つの引数をとりますが、最初の引数PropertyTypeは通常__typeinfoマクロを使用して取得します。__typeinfoマクロは引数にTObjectから継承したクラスのクラス名をとりますので、TObjectから継承していない上記の型に対しては__typeinfoマクロが正常に動作しません。
C++Builderのヘルプには「ほかの型の場合は,同じ型のパブリッシュプロパティから型情報ポインタを取得できます。次に例を示します。(例は省略)」と記述されているのですが、その例でも__typeinfoマクロを使用しているので、上記の型に対しては同じ状況でした。
つまり上記の組み込み型に対するプロパティエディタは、正確には「作れない」のではなく、「登録できない」ようです。
Delphiでは問題なく登録できるようなので、残念です。ネットでカスタムプロパティエディタについて検索してみると、「アプリケーションはC++Builderで作るけど、プロパティエディタ・コンポーネントエディタを含めてカスタムコンポーネントはDelphiで作る」という方もおられました。C++Builderでの開発環境をどこまでも追求したいのなら、Delphiも必要なのかもしれません。
全体の流れ.
IDEに組み込めるプロパティエディタ作成の全体的な手順を示します。以下のとおりでなくてもいい部分もあるのですが、説明の都合もありますので。
カスタムコンポーネントを作成
↓
プロパティエディタのダイアログを作成
↓
プロパティエディタをIDEに登録
↓
プロパティエディタをデバッグ
ユニットの作成.
2つ以上のプロパティがプロパティエディタを必要とする時、これらのプロパティの型が異なる、あるいは必要とされる機能が大きく異なるなら、これらのプロパティには別々のプロパティエディタを作成する方がいいでしょう。
プロパティエディタのコードもカスタムコンポーネントと同じソースファイルに記述してもいいのですが、以下のようにユニット(ファイル)を分けることをお勧めします。
- カスタムコンポーネント本体
- プロパティエディタのフォーム
- プロパティエディタの登録
理由は2つあります。
カスタムコンポーネント本体と違い、プロパティエディタとコンポーネントエディタは、IDEに組み込んでしまうとデバッガが使えません。ステップ実行はもちろん、ブレークポイントも使えませんので、デバッグに苦労します。カスタムコンポーネントのテスト用のフォームから、プロパティエディタのダイアログのフォームを生成することで、プロパティエディタをIDEに組み込む前に、ダイアログだけでもテストが出来るようになります。プロパティエディタの使用するダイアログだけでも、通常のアプリケーションの1つのフォームとしてIDEに組み込む前にデバッグできれば、デバッグの労力を少しでも軽減することが出来ると思います。プロパティエディタの作成作業のほとんどは、プロパティ値編集用のダイアログの処理になるはずですから、これでプロパティエディタの作成はかなり楽になります。
また、カスタムコンポーネントを使用したアプリケーションを配布する場合、設計時のみ使用されるプロパティエディタ・コンポーネントエディタは、アプリケーションの実行ファイルには必要ありません。実行ファイルまたは実行時パッケージからこれらを排除することで実行ファイルサイズと実行時メモリを小さくすることが出来るようになります。
プロパティエディタの登録のコードをデバッグ用のアプリケーションのいずれかのユニットに入れても特に問題はないようですが、プロパティエディタの登録のコードはデバッグ用のアプリケーションには本来必要ないものなので、別のユニットに入れるのがモジュール化の観点からも良いかと思います。
これらの工夫・注意点を反映したユニット構成が、上記のリストです。
プロパティエディタのダイアログを作成.
最初は、プロパティエディタの中心となるプロパティ値編集用のダイアログを作ります。
といっても作り方は、普通のダイアログとあまり変りませんので、注意点だけ記述します。
プロパティ値の受け渡し.
ただし、IDEとプロパティ値を受け渡しする必要がありますので、フォームの宣言のpublic部にプロパティの型のメンバを用意します。次のソースはTPageSheetクラス(サブコンポーネント)用のプロパティエディタの、ヘッダの例です。
class TPagesPropertyBox : public TForm { public: TStringList *Pages;
ダイアログの生成・初期化.
ダイアログを通常のウィンドウと同様にC++Builderのフォームエディタで作るのなら、ダイアログの生成は*.dfmファイルによって自動化されています。プログラマが記述する必要があるのは、上記IDEとプロパティ値を受け渡しするためのメンバのように、自分で定義したメンバの初期化程度です。
C++BuilderのヘルプでTFormのコンストラクタを参照すると、次のように3つの構文が載っています。
__fastcall TForm(Classes::TComponent* AOwner) : Forms::TCustomForm(AOwner) { } __fastcall TForm(Classes::TComponent* AOwner, int Dummy) : Forms::TCustomForm(AOwner, Dummy) { } __fastcall TForm(HWND ParentWindow) : TCustomForm(ParentWindow) { }
通常のウィンドウやダイアログは最初の構文で生成されますが、なぜかプロパティエディタは3番目の構文で生成されます。
そのため、前述したようにデバッグのために、プロパティエディタの使用するダイアログをIDEに組み込む前に、通常のアプリケーションの1つのフォームとしても使用するのなら、次の例のように、1番目の構文と3番目の構文の2つのコンストラクタを記述する必要があります。
__fastcall TPagesPropertyBox::TPagesPropertyBox(TComponent* AOwner) : TForm(AOwner) { Pages = new TStringList; } __fastcall TPagesPropertyBox::~TPagesPropertyBox() { delete Pages; }
ダイアログの実行時処理.
実行時の処理は通常のダイアログと同じです。
編集前のプロパティ値はプロパティ値の受け渡しで追加したメンバから取得します。ユーザ操作によって編集されたプロパティ値もこのメンバをとおしてIDEに渡すので、編集後の値もこのメンバに書き込めばOk。
実行時の処理手順は、次のようになるのではないでしょうか。
- 編集前のプロパティ値をダイアログに反映。
- ダイアログを表示、ユーザが値を編集してダイアログを閉じる。
- ダイアログから編集後のプロパティ値を取得。
この手順をダイアログ実行時のメソッドとして、1つのメソッドに記述しておくと良いと思います。
ユーザ操作への対応.
その他に、ダイアログ内のコントロールをユーザが操作した場合の処理行うメソッドも必要になります。これらは通常のウィンドウやダイアログと同じです。
ダイアログのデバッグ.
ここまで来れば、通常のアプリケーションの1つのダイアログとして、作成したダイアログを単体で実行できます。最初にも記述したように、プロパティエディタはデバッグが困難ですから、プロパティエディタとしてIDEに登録する前に、ダイアログだけでもデバッグしておくことをお勧めします。
その方法は、プロパティエディタとは別にデバッグ用のアプリケーションのプロジェクトを作成し、そのウィンドウからプロパティエディタのダイアログをコールするだけです。このデバッグ用プロジェクトは、カスタムコンポーネント本体のデバッグ用プロジェクトでいいでしょう。
プロパティエディタをIDEに登録.
プロパティエディタをIDEに登録すると、オブジェクトインスペクタからプロパティエディタを呼び出せるようになります。そのためにはTPropertyEditorクラスから派生したクラスを作成し、そのクラスをIDEに登録します。
ここで作成するクラスは、ユニットの作成の節で「プロパティエディタの登録」として説明したユニット内に記述します。
プロパティエディタのクラスを派生.
まずは必要なヘッダファイルをインクルードします。
基底クラスのTPropertyEditorが定義されているDesignEditors.hppを、作成したユニットのヘッダでインクルードします。
#include <DesignEditors.hpp>
ここで作成するクラスでは、作成するプロパティエディタに固有の実装となる部分を、メソッドのオーバーライドで定義します。次のソースはそのサンプルです。オーバーライドの必要な最低限のメソッドは、次のサンプルソースのとおりだと思います。
class PACKAGE TPageSheetProperty : public TPropertyEditor { public: __fastcall TPageSheetProperty(const _di_IDesigner ADesigner, int APropCount); virtual AnsiString __fastcall GetValue(void); virtual void __fastcall Edit(void); virtual TPropertyAttributes __fastcall GetAttributes(void); };
コンストラクタの定義.
コンストラクタは何もすることがなければ、空のままでもいいようです。もちろん初期化など必要なら、それを記述します。
__fastcall TPagesProperty::TPagesProperty(const _di_IDesigner ADesigner, int APropCount) { }
GetValueメソッドのオーバーライド.
GetValueメソッドは、文字列としてプロパティ値を読み出すために、IDEによって呼び出されます。この値はオブジェクトインスペクタに表示されるプロパティ値そのもののようです。
このメソッドをオーバーライドしない場合、オブジェクトインスペクタには「(不明)」と表示されます。オブジェクトインスペクタで文字列型でのプロパティ値の編集に対応しない場合、適当に決まりきった値(文字列)を返すだけで構わないようです。例えばこんな感じ。
AnsiString __fastcall TPagesProperty::GetValue(void) { return "(TPages)"; // これがないと"(不明)"と表示される }
Editメソッドのオーバーライド.
Editメソッドはユーザがオブジェクトインスペクタでプロパティ値をダブルクリックするか、プロパティ値右の「…」ボタンを押した時に、IDEによって呼び出されるメソッドです。Editメソッドですべきことは、以下のとおりです。
- プロパティ値を編集するためのダイアログを生成。
- 現在のプロパティ値を生成したダイアログに反映。
- ダイアログを表示し、ユーザ操作に対応。
- ユーザ操作の結果を返し、値が変更されたことをIDEに通知。
- ダイアログの破棄。
現在のプロパティ値の取得にはGetOrdValueメソッドを使用できます。C++BuilderのヘルプにはGetOrdValueメソッドは「オブジェクトリスト内の現在編集している最初のオブジェクトのプロパティ値を取得できます」と説明されていますが、これはIDEで複数のオブジェクトが選択されている可能性を考慮してこのような表現になっています。選択されているオブジェクトが1つでも、GetOrdValueメソッドで現在のプロパティ値を取得できます。
C++Builderでの制限で述べたように、カスタムプロパティエディタを作成しようとしているプロパティの型は何らかのクラスでしょうから、GetOrdValueメソッドが返す値は、実際にはクラスへのポインタです。GetOrdValueメソッド自身が返す型はint型ですので、キャストして使用します。
その他にTPropertyEditorクラスのメソッドで、編集対象のプロパティ・オブジェクト・フォームに関する情報を取得・設定できます。探してみてください。
値が変更されたことをIDEに通知するには、Modifiedメソッドをコールするだけです。
GetAttributesプロパティのオーバーライド.
カスタムプロパティエディタとしてダイアログを提供したいのなら、GetAttributesメソッドをオーバーライドして値「paDialog」を返す必要があります。
TPropertyAttributes __fastcall TPagesProperty::GetAttributes(void) { return TPropertyAttributes() << paDialog; }
IDEへの登録.
最後に、カスタムコンポーネント本体の登録と同様に、Register関数内でRegisterPropertyEditor関数によって、作成したカスタムプロパティエディタを登録します。RegisterPropertyEditor関数についてはC++Builderに十分な説明があるので、ここでは割愛します。
// プロパティエディタの登録 RegisterPropertyEditor(__typeinfo(TPageSheet), __classid(TPagePanel), "Pages", __classid(TPagesProperty));
他のコンポーネントによってすでにプロパティエディタが用意されていれば、ここで説明したIDEへの登録だけでいいはずです。
その他.
ダイアログ以外のカスタムプロパティエディタ.
C++Builderのヘルプにはダイアログを表示するもの以外にも、プロパティエディタでドロップダウンリストを表示するものなどの説明がありますが、C++Builderでの制限で述べたとおりC++Builderでは、それらのすべてに対応できるわけではないのかもしれません。もしDelphiを使う以外に、この制限を回避する方法をご存じの方がおられましたら、ぜひ教えてください。
デバッグ.
最初に述べたとおり、プロパティエディタのデバッグではデバッガの機能が使えませんので、デバッグは厄介です。しかし簡単に使えるダイアログで紹介したダイアログは使えますので、怪しい箇所でデバッグライトを入れてみる手は有効です。
Copyright 2005-2016, yosshie.