トップ回答者
拡張コントロール内でPrivate宣言されている動的生成コントロールに外部からアクセスできてしまいます。

質問
-
いつもお世話になります。
新年早々困っておりまして質問させていただきました。
開発環境:VB2005
拡張コントロールを作成しており、コントロール内で動的にコントロールを生成しています。
Private M_Button As Button
Public Sub New()
'Buttonコントロールの生成
M_Button = New Button
M_Button.Name = "Privateで生成しているButton1"
Me.Controls.Add(M_Button)
End Sub
その動的生成コントロールは内部でのみ保持したいため、Private宣言しているのですが、
外部クラスから、
Private Sub PrintControl(ByVal objParent As Object)
Dim objCtl As Object
For Each objCtl In objParent.Controls
'再帰呼出
If objCtl.HasChildren Then
PrintControl(objCtl)
End If
Debug.Print(objCtl.Name)
Next
End Sub
というように、再帰的に全コントロールを巡回すると、
動的生成コントロールのプロパティにアクセスできてしまいます。
なぜ、Private宣言されたコントロールにアクセスできてしまうのでしょうか。
また、これを回避する方法はあるのでしょうか。(拡張コントロールを1つにまとめるなど)
ご教授お願いいたします。
【以下に サンプルを掲載いたします。】
'-----拡張コントロール
Public Class CustomTextBox
Inherits TextBox'動的生成Button
Private M_Button As ButtonPublic Sub New()
'Buttonコントロールの生成
M_Button = New Button
M_Button.Name = "Privateで生成しているButton1"
Me.Controls.Add(M_Button)End Sub
End Class
'-----呼び出し側Form
Public Class Form1
Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
PrintControl(Me)
End SubPrivate Sub PrintControl(ByVal objParent As Object)
Dim objCtl As Object
For Each objCtl In objParent.Controls
'再帰呼出
If objCtl.HasChildren Then
PrintControl(objCtl)
End If
Debug.Print(objCtl.Name)
Next
End Sub
End Class
回答
-
外池と申します。ざっと見たところ・・・、
CustomTextBoxの中でM_ButtonはPrivateですけれども、Me.Controls.Add(M_Button)としちゃってますよね。これをやってしまったらPrivateにならないと思います。
オブジェクトそのものに、Privatか否かの区別はありません。オブジェクトへの参照を格納する変数、プロパティー等がPrivateか否かが問題です。
M_Buttonという変数はPrivateなので、この変数を通じてオブジェクトにアクセスすることはできませんが、Me.Controlsのコレクションにオブジェクトへの参照をAddしちゃってますので、このコレクションを通じてオブジェクトにアクセスできてしまっているわけです。
(ホームページを再開しました)- 回答としてマーク ネコ吉 2010年1月6日 9:41
-
自己レスです
> Overriding Control.Controls
> http://blogs.msdn.com/jfoscoding/articles/450843.aspxサンプルコードの
public class MyReadonlyControlCollection : Control.ControlCollection {
は public のままだとキャストで簡単に AddInternal、RemoveInternal が使えてしまいますね
private に変えて CustomTextBox クラス内に書けばとりあえずOKぽいかなと- 回答としてマーク ネコ吉 2010年1月6日 9:41
-
anningoさま、karashimaさま
ご回答ありがとうございました。
> MyBase.ControlsにAddし、
> Controlsプロパティを定義して
> MyBase.ControlsのM_Button以外のコントロールを返す様にするとか。
Me.Controlsではなく、MyBaseへAddし、
別クラスで定義した独自Controlsに上書きすることで、
外部からはControlコレクションが見えなくなり、無事回避できました。
みなさま、ご教授ありがとうございました。
【とりあえず回避したサンプルを以下に掲載します】' CustomTextBox.vb Public Class CustomTextBox Inherits TextBox '動的生成Button Private M_Button As Button Private M_Collection As New MyControlCollection(Me) Public Sub New() 'Buttonコントロールの生成 M_Button = New Button M_Button.Name = "Privateで生成しているButton1" MyBase.Controls.Add(M_Button) End Sub Public Shadows ReadOnly Property Controls() As Control.ControlCollection Get Return M_Collection End Get End Property End Class Public Class MyControlCollection Inherits Control.ControlCollection Public Sub New(ByVal owner As Control) MyBase.New(owner) End Sub End Class ' Form1.vb Public Class Form1 Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load PrintControl(Me) End Sub Private Sub PrintControl(ByVal objParent As Object) Dim objCtl As Object For Each objCtl In objParent.Controls '再帰呼出 If objCtl.HasChildren Then PrintControl(objCtl) End If Debug.Print(objCtl.Name) Next End Sub End Class
- 回答としてマーク ネコ吉 2010年1月6日 9:43
すべての返信
-
外池と申します。ざっと見たところ・・・、
CustomTextBoxの中でM_ButtonはPrivateですけれども、Me.Controls.Add(M_Button)としちゃってますよね。これをやってしまったらPrivateにならないと思います。
オブジェクトそのものに、Privatか否かの区別はありません。オブジェクトへの参照を格納する変数、プロパティー等がPrivateか否かが問題です。
M_Buttonという変数はPrivateなので、この変数を通じてオブジェクトにアクセスすることはできませんが、Me.Controlsのコレクションにオブジェクトへの参照をAddしちゃってますので、このコレクションを通じてオブジェクトにアクセスできてしまっているわけです。
(ホームページを再開しました)- 回答としてマーク ネコ吉 2010年1月6日 9:41
-
開発環境:VB2005
拡張コントロールを作成しており、コントロール内で動的にコントロールを生成しています。
Private M_Button As Button
Public Sub New()
'Buttonコントロールの生成
M_Button = New Button
M_Button.Name = "Privateで生成しているButton1"
Me.Controls.Add(M_Button)
End Sub
その動的生成コントロールは内部でのみ保持したいため、Private宣言しているのですが、
外部クラスから、
Private Sub PrintControl(ByVal objParent As Object)
Dim objCtl As Object
For Each objCtl In objParent.Controls
'再帰呼出
If objCtl.HasChildren Then
PrintControl(objCtl)
End If
Debug.Print(objCtl.Name)
Next
End Sub
というように、再帰的に全コントロールを巡回すると、
動的生成コントロールのプロパティにアクセスできてしまいます。
なぜ、Private宣言されたコントロールにアクセスできてしまうのでしょうか。
また、これを回避する方法はあるのでしょうか。(拡張コントロールを1つにまとめるなど)
たぶんこれを回避する方法はないと思います。
外池さんが
> Me.Controls.Add(M_Button)
> これをやってしまったらPrivateにならないと思います。
と仰っしゃられてますが、そもそも Form の Controls コレクションに追加しなければ画面に表示できないわけですし、
Controls コレクションには全てのコントロールの参照が登録されるので、
M_Button は Private メンバとして直接アクセスできなくても
Controls コレクション経由でアクセスはできてしまいます。
回避策として考えられるのは、フォームに直接ボタンの画像を描画してごまかす手法も考えられますが、
あまりにもスマートでない上、実装も面倒というデメリットがあります。 -
> Controlsコレクション経由でアクセスできてしまうのですね。
> 謎は解けましたが、回避策が思いつきません;;Controls を見えなくする方法はわかりませんでした。。
(public で宣言されていて、これを変える手段がない)ただしControlsコレクションに対する操作(Add、Removeなど)を隠す方法はありました
それでよければ、C# ですが下記サイトのとおりです
CreateControlsInstance をオーバーライドする方法です
このサンプルコードでは Add、Remove をおこなうとエラーになりますOverriding Control.Controls
http://blogs.msdn.com/jfoscoding/articles/450843.aspx -
自己レスです
> Overriding Control.Controls
> http://blogs.msdn.com/jfoscoding/articles/450843.aspxサンプルコードの
public class MyReadonlyControlCollection : Control.ControlCollection {
は public のままだとキャストで簡単に AddInternal、RemoveInternal が使えてしまいますね
private に変えて CustomTextBox クラス内に書けばとりあえずOKぽいかなと- 回答としてマーク ネコ吉 2010年1月6日 9:41
-
anningoさま、karashimaさま
ご回答ありがとうございました。
> MyBase.ControlsにAddし、
> Controlsプロパティを定義して
> MyBase.ControlsのM_Button以外のコントロールを返す様にするとか。
Me.Controlsではなく、MyBaseへAddし、
別クラスで定義した独自Controlsに上書きすることで、
外部からはControlコレクションが見えなくなり、無事回避できました。
みなさま、ご教授ありがとうございました。
【とりあえず回避したサンプルを以下に掲載します】' CustomTextBox.vb Public Class CustomTextBox Inherits TextBox '動的生成Button Private M_Button As Button Private M_Collection As New MyControlCollection(Me) Public Sub New() 'Buttonコントロールの生成 M_Button = New Button M_Button.Name = "Privateで生成しているButton1" MyBase.Controls.Add(M_Button) End Sub Public Shadows ReadOnly Property Controls() As Control.ControlCollection Get Return M_Collection End Get End Property End Class Public Class MyControlCollection Inherits Control.ControlCollection Public Sub New(ByVal owner As Control) MyBase.New(owner) End Sub End Class ' Form1.vb Public Class Form1 Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load PrintControl(Me) End Sub Private Sub PrintControl(ByVal objParent As Object) Dim objCtl As Object For Each objCtl In objParent.Controls '再帰呼出 If objCtl.HasChildren Then PrintControl(objCtl) End If Debug.Print(objCtl.Name) Next End Sub End Class
- 回答としてマーク ネコ吉 2010年1月6日 9:43
-
> Me.Controlsではなく、MyBaseへAddし、
> 別クラスで定義した独自Controlsに上書きすることで、
> 外部からはControlコレクションが見えなくなり、無事回避できました。
残念ながら隠蔽できませんよ。
たぶん Option Strict Off で試されたのでしょうが、以下のコードをお試しください。
PrintControl メソッドの変数の型を Object から Control に変えただけですが、
イミディエイトウィンドウにはばっちり
Privateで生成しているButton1
CustomTextBox1
と出力されました。Private Sub PrintControl(ByVal objParent As Control) For Each objCtl As Control In objParent.Controls '再帰呼出 If objCtl.HasChildren Then PrintControl(objCtl) End If Debug.Print(objCtl.Name) Next End Sub