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

  • 質問

  • いつもお世話になります。
    新年早々困っておりまして質問させていただきました。

    開発環境: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 Button

        Public 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 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月5日 2:34

回答

  • 外池と申します。ざっと見たところ・・・、

    CustomTextBoxの中でM_ButtonはPrivateですけれども、Me.Controls.Add(M_Button)としちゃってますよね。これをやってしまったらPrivateにならないと思います。

    オブジェクトそのものに、Privatか否かの区別はありません。オブジェクトへの参照を格納する変数、プロパティー等がPrivateか否かが問題です。

    M_Buttonという変数はPrivateなので、この変数を通じてオブジェクトにアクセスすることはできませんが、Me.Controlsのコレクションにオブジェクトへの参照をAddしちゃってますので、このコレクションを通じてオブジェクトにアクセスできてしまっているわけです。
    (ホームページを再開しました)
    • 回答としてマーク ネコ吉 2010年1月6日 9:41
    2010年1月5日 3:02
  • 自己レスです

    > 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
    2010年1月5日 9:09
  • 拡張コントロールのContorolsコレクションへAddせずに、
    拡張コントロール内部で別のコントロールを保持する方法があればなぁと
    思いますが。。。

    でも、Addしなければ、表示できないですし。。。

    MyBase.ControlsにAddし、
    Controlsプロパティを定義して
    MyBase.ControlsのM_Button以外のコントロールを返す様にするとか。
    • 回答としてマーク ネコ吉 2010年1月6日 9:41
    2010年1月6日 5:20
  • 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
    2010年1月6日 9:40

すべての返信

  • 外池と申します。ざっと見たところ・・・、

    CustomTextBoxの中でM_ButtonはPrivateですけれども、Me.Controls.Add(M_Button)としちゃってますよね。これをやってしまったらPrivateにならないと思います。

    オブジェクトそのものに、Privatか否かの区別はありません。オブジェクトへの参照を格納する変数、プロパティー等がPrivateか否かが問題です。

    M_Buttonという変数はPrivateなので、この変数を通じてオブジェクトにアクセスすることはできませんが、Me.Controlsのコレクションにオブジェクトへの参照をAddしちゃってますので、このコレクションを通じてオブジェクトにアクセスできてしまっているわけです。
    (ホームページを再開しました)
    • 回答としてマーク ネコ吉 2010年1月6日 9:41
    2010年1月5日 3:02
  • 開発環境: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 コレクション経由でアクセスはできてしまいます。

    回避策として考えられるのは、フォームに直接ボタンの画像を描画してごまかす手法も考えられますが、
    あまりにもスマートでない上、実装も面倒というデメリットがあります。

    2010年1月5日 3:23
    モデレータ
  • 外池さま、ひらぽんさま
    ご返信有難うございます。

    Controlsコレクション経由でアクセスできてしまうのですね。
    謎は解けましたが、回避策が思いつきません;;

    拡張コントロールのContorolsコレクションへAddせずに、
    拡張コントロール内部で別のコントロールを保持する方法があればなぁと
    思いますが。。。

    でも、Addしなければ、表示できないですし。。。

    2010年1月5日 3:55
  • > Controlsコレクション経由でアクセスできてしまうのですね。
    > 謎は解けましたが、回避策が思いつきません;;

    Controls を見えなくする方法はわかりませんでした。。
    (public で宣言されていて、これを変える手段がない)

    ただしControlsコレクションに対する操作(Add、Removeなど)を隠す方法はありました
    それでよければ、C# ですが下記サイトのとおりです
    CreateControlsInstance をオーバーライドする方法です
    このサンプルコードでは Add、Remove をおこなうとエラーになります

    Overriding Control.Controls
    http://blogs.msdn.com/jfoscoding/articles/450843.aspx

    2010年1月5日 8:18
  • 自己レスです

    > 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
    2010年1月5日 9:09
  • なぜ、今回のことを実現したいのでしょうか。
    そして、どの程度のコスト(時間など)をかけることをお考えなのでしょうか。


    ちなみに、どれだけ頑張っても完全に隠蔽することはできないと思います。
    リフレクションを使えば、private なメンバー変数であろうと、アクセスできることが理由です。


    # 「外から勝手に操作したら動作保障しません」でいいんじゃないのかな。
    質問スレッドで解決した場合は、解決の参考になった投稿に対して「回答としてマーク」のボタンを押すことで、同じ問題に遭遇した別のユーザが役立つ投稿を見つけやすくなります。
    2010年1月5日 14:17
    モデレータ
  • 拡張コントロールのContorolsコレクションへAddせずに、
    拡張コントロール内部で別のコントロールを保持する方法があればなぁと
    思いますが。。。

    でも、Addしなければ、表示できないですし。。。

    MyBase.ControlsにAddし、
    Controlsプロパティを定義して
    MyBase.ControlsのM_Button以外のコントロールを返す様にするとか。
    • 回答としてマーク ネコ吉 2010年1月6日 9:41
    2010年1月6日 5:20
  • 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
    2010年1月6日 9:40
  • > 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
    2010年1月6日 10:03
    モデレータ
  • オーバーライドができないプロパティですので、基底の型にキャストすれば、ひらぽんさんが指摘されているように元の ControlsCollection にアクセス可能になります。

    ただ、どこまでやりたいか次第なんですよね。
    一番簡単な方法さえ防げれば良いという判断であれば、今の方法でもありでしょう。
    質問スレッドで解決した場合は、解決の参考になった投稿に対して「回答としてマーク」のボタンを押すことで、同じ問題に遭遇した別のユーザが役立つ投稿を見つけやすくなります。
    2010年1月6日 13:50
    モデレータ