トップ回答者
カスタムコントロールの初期処理について

質問
-
いつもお世話になります。
某社の数値入力項目コントロールのカスタムコントロールを作成中です。画面起動時の初期処理として、デザイナで設定されたプロパティの値によって
違う処理を行いたいのですが、コンストラクタやInitLayoutメソッドでは
デザイン時に設定された値は取得できません。そこで、苦肉の策としてOnEnterで上記の処理を行っています。
(初期処理実行済みかどうかはフラグで判断します。)でも、OnEnterに初期処理を記述するのは
なんとなく気持ちが悪いのですが、他に方法はないでしょうか?ちなみに、画面側で初期処理メソッドを呼ぶ等は行いたくありません。
すでに実装済みのシステムで使用している入力項目の動作を改造したいので、
入力項目を継承したカスタムコントロールを作成し
デザイナファイルの一括置換でコントロール名だけ変更するつもりにしていて、
プログラムソースは一切触らない方法を取りたいからです。デザイナで設定した値を取得できるタイミングで
初回のみ実行されるイベントがあればベストなのですが・・・。アドバイスよろしくお願い致します。
回答
-
ISupportInitialzie は多重実装すると不具合が発生します。
一般的には、このようなインターフェースの実装では派生クラスが作成されることを考慮して、初期化イベントや初期化のvirtualメソッドを準備することで対応し、多重実装にならないようにします。
どうしても多重実装してしまった場合は、上書き元の ISupportInitialize を呼び出す必要があります。
> 某社の数値入力項目コントロールのカスタムコントロールを作成中です。
特に公開でいない事情がないのであれば「某社の」などと書かずにきちんと利用しているコントロールを明記すれば、同じ製品を利用している方や、運が良ければその製品の開発者などから、よりよい的確な助言を得られる可能性があります。(こういうぼかした書き方をすることは、デメリットしかありません)
- 回答としてマーク yurubon 2012年8月21日 2:04
すべての返信
-
もっと良い方法がある気もしますが、とりあえず思いついたので失礼します。
①そのカスタムコントロールに初期化時に行いたい処理をpublicメソッドで用意しておき、
FormのLoadイベントの中で、Form上のコントロール一覧を取得して上記のメソッドを呼んでいく。②OnPaintをオーバーライドして1回しか処理されない様にした上で実装する。
※メンバ変数としてm_initializedとかを用意(初期値false)しておき、protected override void OnPaint(PaintEventArgs e) { base.OnPaint(e); if (m_initialized) { return; } m_initialized = true; //行いたい処理 }
とするとか・・・
※未確認です。- 編集済み aviator__ 2012年8月17日 5:17
-
①の方はどうですか?
例えば・・・
private List<TextBox> GetControls(Control parent) { List<カスタムコントロールの型> list = new List<カスタムコントロールの型>(); foreach (Control control in parent.Controls) { if (control.GetType() == typeof(カスタムコントロールの型) || control.GetType().IsSubclassOf(typeof(カスタムコントロールの型))) { list.Add((カスタムコントロールの型)control); } list.AddRange(GetControls(control)); } return list; }
としておいて、ロードイベント内で
List<カスタムコントロールの型> list = GetControls(this); foreach (カスタムコントロールの型 control in list) { control.カスタムコントロールに用意した初期化用publicメソッド(); }
としてあげるのは? -
>aviator__様、ご回答有難うございます。
今回は実装済みのシステムに対し入力項目の動作変更をしたかったので
Form側の処理は極力・・・というか一切変更しない方向で修正をしたく、
Form側で何かをする案は優先順位が下がっていました。
言葉が足りなかったようで申し訳ありません。
>なちゃ様、ご回答有難うございます。http://www.geocities.jp/mnow/cs_usercontrol13.html
↑を参考に、ISupportInitialize を派生元に加え、
EndInit メソッド を実装してみました。
無事、デザイナで設定された値を取得する事が出来ました。私が書いたソースはこんな感じです。
public partial class カスタムコントロール名 : 派生元コントロール名, ISupportInitialize
{
<中略>public void EndInit()
{
<初期処理>
}}
(※こんな感じであってますでしょうか?
ご指摘ある方いらっしゃいましたらよろしくお願い致します。)ただ、少し気になるのは、今回私が作成しているカスタムコントロールの
派生元が継承しているコントロールで(ややこしくてすみません)
すでにISupportInitializeが継承されていました。奥の方で(?)継承しているクラスを
私が作成しているコントロールで継承しても大丈夫なのか
ちょっと心配です・・・。今の所普通に動いていますが・・・。
ISupportInitialize は interface みたいなので
大丈夫なのでしょうか?
- 編集済み yurubon 2012年8月20日 0:46
-
ISupportInitialzie は多重実装すると不具合が発生します。
一般的には、このようなインターフェースの実装では派生クラスが作成されることを考慮して、初期化イベントや初期化のvirtualメソッドを準備することで対応し、多重実装にならないようにします。
どうしても多重実装してしまった場合は、上書き元の ISupportInitialize を呼び出す必要があります。
> 某社の数値入力項目コントロールのカスタムコントロールを作成中です。
特に公開でいない事情がないのであれば「某社の」などと書かずにきちんと利用しているコントロールを明記すれば、同じ製品を利用している方や、運が良ければその製品の開発者などから、よりよい的確な助言を得られる可能性があります。(こういうぼかした書き方をすることは、デメリットしかありません)
- 回答としてマーク yurubon 2012年8月21日 2:04
-
>K. Takaoka様
ご回答有難うございます。ご指摘の通り、
「System.ComponentModel.ISupportInitialize.EndInit()の間に呼び出されるメソッド」
が「OnEndInit」として準備されていました。>こういうぼかした書き方をすることは、デメリットしかありません
確かにおっしゃる通りですね。
ついつい「この投稿がお客様の目に留まったら・・・」
と思ってしまいましたが、遠回りになってしまう事もありますよね。今回も最初から製品を明記していれば、
すんなりメソッド名を教えてもらえたかも知れません。
他の回答下さった方にも余計な手間をかけさせてしまいました。
以後、質問させて頂く際には気を付けます。
大変申し訳ありませんでした。皆様有難うございました。
- 編集済み yurubon 2012年8月21日 2:07
-
どうしても多重実装してしまった場合は、上書き元の ISupportInitialize を呼び出す必要があります。
質問者さんの挙げている記述をすれば、コンパイラ警告CS0108が出ますね。C#言語ではなるべくスマートな設計がされていますが、プログラマーが陥りやすい問題については警告できるよう設計されていて、今回の場合もK. Takaokaさんは「多重継承」という言葉を使われていますが、「継承されたメンバーの隠ぺい」が発生し、隠ぺいしたいなら new修飾子を、継承したいのならoverride修飾子を書くことになっています。
# どちらも書かれていないのならプログラマーがポカしてることになる。new修飾子を書いておいて上書き元のISupportInitializeを呼び出さなかった場合は救いようがありませんね。
public void EndInit(){ ((派生元コントロール名)this).EndInit(); <初期処理> }
-
どうしても多重実装してしまった場合は、上書き元の ISupportInitialize を呼び出す必要があります。
質問者さんの挙げている記述をすれば、コンパイラ警告CS0108が出ますね。
警告がでるかどうかは親クラスの実装次第です。CS0108 が発生するのはメンバーの隠ぺいが発生した場合であって、インターフェースの多重実装が行われた場合には警告は発生しません。(.NET 3.5, .NET4 の C# コンパイラの場合)
また、多重継承ではなく、多重実装です。継承したインターフェースは1つなのに、そのインターフェースに該当するメソッドの実装が2つ以上ある状態になります。この場合、言語機能の this や base からは該当のインターフェースのメソッドを取得できなくなるので、継承元クラスの実装していたインターフェースのメソッドを取得するためには、typeof(派生元型).GetInterfaceMap() を使用する必要があります。
# yurubon さんは警告について書かれていないので、明示的実装によって隠ぺいが発生していなくて、
# 多重実装された ISupportInitialize に隠ぺいは発生していないのではないか?と思います。- 編集済み K. Takaoka 2012年8月21日 4:05 「2つ以上」の前に「実装が」を補記