今回System.Windows.Forms.Buttonクラスを継承したカスタムコントロールを作ってます。
このとき自作コントロール側で、配置してる親フォームの名前を取得したいと思って下記のようなコードを書きました。
Public Class TButton Inherits System.Windows.Forms.Button '配置しているフォームの名前 Private m_strFormName As String '.... Public Sub New() ' この呼び出しは、Windows フォーム デザイナで必要です。 InitializeComponent() ' InitializeComponent() 呼び出しの後で初期化を追加します。 '親コントロール Dim ParentObj As Windows.Forms.Control = Me.Parent 'フォームの名前取得 m_strFormName = ParentObj.GetType.FullName End Sub End Class
要はコンストラクタで、親オブジェクト(フォームであること前提)の名前を取得しているだけす。
しかし、これをするとデザイナを開いたときに「オブジェクト参照がオブジェクト インスタンスに設定されていません。 」というエラーが出てきます。
しかもエラー一覧には警告で「呼び出しのターゲットが例外をスローしました。」とでるだけ。
つくづく思うのですが、MSはもうちょっとわかりやすいエラーを出すようにしてほしいですね。
この「オブジェクト参照がオブジェクト インスタンスに設定されていません。 」というのは、あるオブジェクトのプロパティやらメソッドやらを触ろうとしたけど、オブジェクトがNull(VB的に言うとNothing?)だからできないという意味だそうです。
で、なぜ上記のコードの場合このエラーがでるかというと、自作コントロールが張り付けてあるフォームのDesignerファイルInitializeComponentメソッドを見るとわかります。
Private Sub InitializeComponent() Me.TButton1 = New TButton Me.SuspendLayout() 'TButton1 'ここで自作コントロールのプロパティを設定。 ' 'Sample_and_Test '.... Me.Controls.Add(Me.TButton1) '.... End Sub
VSが自動で生成するとこんな感じになっていると思うのですが、まず最初に自作コントロールのインスタンスが生成されます。 しかし、実際にこのコントロールがフォーム上に配置されるのは
Me.Controls.Add(Me.TButton1)
の部分です。
つまり、コントロールのインスタンス生成時には、まだコントロールはフォームに追加されていないため、コンストラクタで親フォームを探そうとしても探せない(Null)にという状態になるようです。
また、ふりっつさんのブログによると、コントラクタ内で値の初期化は2回実行、2重登録されてしまうためよろしくないようです。
これの解決策として、コントロール配置時に発生するイベントがあるみたいです。(配置時ということは親側のMe.Controls.Add(コントロールオブジェクト)が起きたタイミングか?)
それは、InitLayout()メソッド。
これはSystem.Windows.Forms.Controlに定義されており、オーバーライド可能なので、今回の場合は使えそうです。
とういことでこう書きなおしました。
Public Class TButton Inherits System.Windows.Forms.Button '配置しているフォームの名前 Private m_strFormName As String ' .... Protected Overrides Sub InitLayout() '基本クラスのInitLayoutを呼ばないとおかしくなるらしい MyBase.InitLayout() '親コントロール Dim ParentObj As Windows.Forms.Control = Me.Parent 'フォームの名前取得 m_strFormName = ParentObj.GetType.FullName End Sub Public Sub New() ' この呼び出しは、Windows フォーム デザイナで必要です。 InitializeComponent() ' InitializeComponent() 呼び出しの後で初期化を追加します。 End Sub End Class
こうするとデザイナでもきちんと表示されますし、実行しても正しく動くようになりました。
カスタムコントロールを作成するとデザイナがおかしくなることがよくありますが、やはり呼び出しのタイミングやバグが原因なんでしょうね。
たとえば、この自作コントロール(チェックボックス)が別の自作クラスで描画している場合、CheckedChangeイベントで自作クラスを用い描画するということがあるかもしれません。
この場合、デザイナ側でCheckedプロパティをTrueにしておくと、当然DesignerファイルInitializeComponentメソッド内で、○○.Cheked=Trueになります。
ということはこのコードが実行されるより前に自作クラスのインスタンスがないとやはりエラーになってしまいます。(これはコントロールのコンストラクタを使うくらいしか方法はないのでしょうが。。。)