ようこそ。睡眠不足なプログラマのチラ裏です。

入れ子になったユーザーコントロールの DesignMode プロパティについて


マイクロソフトのサポートオンラインによると
入れ子になったユーザーコントロールの DesignMode プロパティは、 False に Visual Studio .NET または Visual Studio 2005 に常に設定されます。

現象
Microsoft Visual Studio .NET または Microsoft Visual Studio 2005 に
Microsoft Windows フォーム を使用する場合、ユーザーコントロールの DesignMode プロパティが 虚偽に設定できます。


原因
別のユーザーコントロールの内のユーザーコントロールをネストする場合、
Microsoft Visual Studio .NET または Microsoft Studio 2005 デザイン時にこの現象が発生します。
これを行うと、 虚偽を入れ子になったユーザーコントロールの DesignMode プロパティが返します。
ユーザーコントロールが別のユーザーコントロールを含む場合、ルートレベルユーザーコントロールのみ 真を返します。


この資料は以下の製品について記述したものです。
• Microsoft Common Language Runtime (included with the .NET Framework) 1.0
• Microsoft Visual Studio 2005 Professional Edition
• Microsoft Visual Studio 2005 Standard Edition
• Microsoft Visual Studio .NET 2003 Professional Edition
• Microsoft Visual Studio .NET 2003 Enterprise Architect
• Microsoft Visual Studio .NET 2003 Enterprise Developer
• Microsoft Visual Studio .NET 2003 Academic Edition
• Microsoft Visual Studio .NET 2002 Professional Edition
• Microsoft Visual Studio .NET 2002 Enterprise Architect
• Microsoft Visual Studio .NET 2002 Enterprise Developer
• Microsoft Visual Studio .NET 2002 Academic Edition

だそうです。これって、どうやら.NET Framework依存というよりはIDEの問題みたいですね。


サポートオンラインの訳は、いつも機械翻訳しているだけで理解に苦しむ日本語なわけですが、
つまるところ、入れ子に使うようなユーザーコントロールのLoadイベントで、
DesignModeである時に実行されると例外が発生するような実装をしてしまうと、
ユーザーコントロールを入れ子で利用することができないってことですね。
開発者という立場で率直な意見を言うならば、「なんぞこれー。使えねえ仕様だな!」と思ってしまうわけですが、
これはマイクロソフトなりに、“なにかしらの考えがあっての仕様”ということなのでしょう。たぶん。
あるいは、“ユーザーコントロールのLoadイベントでは、ごちゃごちゃやらないでね”ということでしょうか。
まあ、実際にユーザーコントロールを入れ子構造で利用しようと思ったときに、
このような現象に陥って少しハマってしまったので、何かの役に立つかもしれないので解決策をメモしておくとします。


ルートレベルユーザーコントロールのみ 真を返します。

ということなので、入れ子構造になっているユーザーコントロール間で
DesignModeプロパティを評価することができれば、解決できそうなのですが、


そもそもSystem.Windows.Forms.UserControlのDesignModeプロパティのアクセシビリティは、
Protected(protected)なので、自身および、System.Windows.Forms.UserControlクラス継承先のみでしか参照できません。
http://msdn2.microsoft.com/ja-jp/library/system.windows.forms.usercontrol_members(VS.80).aspx
ユーザーコントロールを入れ子構造にしたとき、親ユーザーコントロールから
親ユーザーコントロールに属する子ユーザーコントロールのDesignModeを評価することができません。
また、逆に子ユーザーコントロールから親ユーザーコントロールのDesignModeを評価することもできません。
どちらにしても、アクセシビリティの壁が立ちはだかることになります。


そこで、DesignModeを抽象的に参照してアクセシビリティの壁を超えることにします。
まず、System.Windows.Forms.UserControlを継承したカスタムコントロールを作成し、
System.ComponentModel.ISiteインターフェイスをインプリメントします。
http://msdn2.microsoft.com/ja-jp/library/system.componentmodel.isite_members(VS.80).aspx
System.ComponentModel.ISiteインターフェイスで実装すべきプロパティおよびメソッドの内容は
既にSystem.Windows.Forms.UserControlに実装されているので、
実際は同一名のプロパティおよびメソッドを、System.ComponentModel.ISiteインターフェイスの
抽象プロパティなり抽象メソッドなりでラップしてあげるだけで済みます。
こうすることにより入れ子構造のユーザーコントロールのDesignModeの
アクセシビリティの壁を乗り越えることができます。これが第1段階です。


次に、System.Windows.Forms.UserControlを継承したカスタムコントロールのLoadイベントにて、
自身が所属するルートレベルユーザーコントロールの参照を取得します。
これは当然、再帰的なメソッドを作成して取得するようにします。
ルートレベルユーザーコントロールの参照が取得できたら、System.ComponentModel.ISiteインターフェイスのDesignModeを
評価することによって、自身のDesignModeの真偽にかかわらず、
ルートレベルユーザーコントロールのDesignModeにしたがって振舞うことができ、
結果、ルートレベルユーザーコントロールのDesignModeがTrueのとき、
入れ子構造の子ユーザーコントロールのLoadイベント時の処理を回避することができます。


めでたし、めでたし・・・(´・ω・`)


かなり強引な方法ですが、この方法が最もスマートな感じがします。
抽象的なインターフェイスを利用することで、このようにアクセシビリティを無視することができたり、
時には循環参照の壁を越えることも可能なのですが、多用は禁物。
アクセシビリティを無視したり循環参照の壁を越えなければ実現できないという時点で、
クラスやプロジェクトの設計的な破綻があるということを意味しているわけですからね。^-^;