- 2009年4月 6日 22:16
- .NET Framework | C++/CLI
.NET Framework で独自のコントロールを作りたい場合は、UserControl から派生させたクラスを用意すればよい。一般的に、自作コントロールには独自のプロパティも含まれており、そのプロパティが自作クラスになっていることも多いはず。今回は、自作クラスの配列をプロパティとして持たせたところ大ハマリしたという話。
ある自作コントロールに自作クラス MyItem の配列をプロパティとして持たせたいと思い、System::Collections::Generic::List<MyItem^>^ 型のプロパティ MyItemList を追加することにした。cli::array ではなく List を使っているのは vector っぽく使いたいためだが、この違いは本質ではない(と思う)。悲劇の始まりは次のようなコードからだ。
ref class MyItem;
ref class MyCtrl : public System::Windows::Forms::UserControl
{
// ... 色々あって、
private:
System::Collections::Generic::List<MyItem^>^ m_itemList; // リストの実体
public:
MyCtrl(void) {
InitializeComponent();
// コンストラクタで m_itemList を確保する
m_itemList = gcnew System::Collections::Generic::List<MyItem^>;
}
public:
property System::Collections::Generic::List<MyItem^>^ MyItemList { // このプロパティは
System::Collections::Generic::List<MyItem^>^ get() { // get だけしか持っていない(set は許可しない)
return m_itemList; // 中身は自由にしても構わないが、リストそのものはコチラで管理させてくれ
}
}
// ... 色々あるよね
};
C++/CLI は const まわりが貧弱なので不安でたまらなくなるが、せめてもの反抗として MyItemList プロパティからは set を取り除いてみた。ちなみに MyItem 自体はラベルやら色やらの情報が格納された単純なもの。このコントロールは MyItemList 内の MyItem 群を描画するのが目的だ。
// リストに格納されるモノ
ref class MyItem {
public:
property System::String^ Label; // ラベルやら
property System::Drawing::Color ForeColor; // 色やら
// ... その他諸々(今回は省略)
};
コンパイルは成功するし、このコントロールをフォームに貼り付けることもできる。しかも、コントロールのプロパティには「その他」の項目として MyItemList が入っているというオマケ付き。コレクションの右にあるボタンをクリックすると MyItem のコレクションエディタが起動し、MyItemList に MyItem を追加したり編集したりと、もう至れり尽くせり状態だ。
コレクションエディタ上で MyItem を追加、編集して、いざコンパイル。
「あれ? MyItem が消えたぞ」
何回やっても結果は同じ。コンパイルさえしなければ何も問題はないのだが、それでは意味がない。
色々調べた結果、MyItemList の DesignerSerializationVisibility 属性を Content にしなければならないことが分かった(デフォルト値は Visible)。 こうすることで、MyItemList の Content、つまり「中身」を永続化可能にするというわけだ。
[DesignerSerializationVisibility(DesignerSerializationVisibility::Content)] // これが必要
property System::Collections::Generic::List<MyItem^>^ MyItemList {
System::Collections::Generic::List<MyItem^>^ get() {
return m_itemList;
}
}
これで MyItemList の中身が保存されるようになったのだが、今度はコレクションの中身が空でもデザイナ上ではボールド表示、つまり「初期値と違う」と主張し始めた。MyItemList 上の右クリックで初期化しようとしても、リセット自体がグレーアウトされている。
「あぁ、確かこれは DefaultValue 属性だったな」と思ったが、今回は MyItemList を自前で管理しているため、リテラルとしての初期値が存在しない。初期値というよりは、初期化メソッドが必要なのだ。
さらに調査を進めると、こういう場合は ShouldSerialize, Reset メソッドを使えばよいことが分かった。最初は何を言っているのか良く分からなかったのだが、どうやら .NET Framework では ShouldSerializeプロパティ名 と Resetプロパティ名の2つのメソッド名を特別扱いするという仕様のようだ。
つまり、こういうこと。
// MyItemList という名前のプロパティがあれば、
// ShouldSerializeMyItemList と ResetMyItemList という2つのメソッドが特別扱いされる。
[DesignerSerializationVisibility(DesignerSerializationVisibility::Content)]
property System::Collections::Generic::List<MyItem^>^ MyItemList {
System::Collections::Generic::List<MyItem^>^ get() {
return m_itemList;
}
}
// このメソッドは「永続化すべきかどうか」つまり「初期値と違うかどうか」を返す
bool ShouldSerializeMyItemList() {
return m_itemList->Count > 0; // 空じゃなければ初期値でない
}
// 「リセット」時に呼ばれるメソッド
void ResetMyItemList() {
m_itemList->Clear(); // 空にする
}
これで万事うまくいく...はずだった。しかし、肝心の「リセット」がグレーアウトされたままになっている。ここで悩むこと数時間。「ひょっとして、MyItemList に set がないからかも」と思い、次のように set を追加したら...動いてしまった。
[DesignerSerializationVisibility(DesignerSerializationVisibility::Content)]
property System::Collections::Generic::List<MyItem^>^ MyItemList {
System::Collections::Generic::List<MyItem^>^ get() {
return m_itemList;
}
// これがないと「リセット」が有効にならない
void set(System::Collections::Generic::List<MyItem^>^ itemList) {
if (!itemList) // 仕方がないので例外を投げることにした
throw gcnew System::Exception("MyItemList must not be null.");
m_itemList = itemList;
}
}
bool ShouldSerializeMyItemList() {
return m_itemList->Count > 0;
}
void ResetMyItemList() {
m_itemList->Clear();
}
こういう仕様なのかどうかは分からないが、MyItemList に set が無いからという理由で ResetMyItemList が呼べないのは納得できない。ただでさえ const まわりが貧弱なのに、それに抵抗すると余計な労力を強いられるとは。ちなみに、現在の環境は Visual Studio 2008 SP1。次のバージョンでは何とかして欲しいところだが、今後 C++/CLI を使うかどうかは微妙なので、まぁどっちでもいいや。
- Newer: Pimpl イディオムのお手軽な実装
- Older: C++でメンバ関数テンプレートを仮想関数にできないのは何故?
コメントを表示する前に、このブログのオーナーによる承認が必要になることがあります。コメントが表示されない場合は、承認されるまでしばらくお待ちください。