VB のたまご

作成日: 2018/11/06, 更新日: 2018/11/06


デザイナ

  • この投稿は Visual Basic Advent Calendar 2018 の 6 日目の記事です。
    ・ 5 日目の記事: 属性
    ・ 7 日目の記事: 型コンバータ

  •  誤解する単語だと思うのですが、ここで言うデザイナとは、画面デザイナーのことではなく、 作成したコントロールを画面デザイン上に配置した後、(プロパティウィンドウを使わずに)独自に操作するための機能のことをいいます。 配置したコントロールを操作して値を更新する → つまり設計する → デザイナ!と命名したのかなと思いますが、言葉だけでは分かりませんね。

表示プロパティの制限

  •  2通りくらいケーススタディしてみます。1つ目は、任意のメンバーをプロパティウィンドウに表示させたくない場合です。 前回やった属性では、Browsable 属性を付与して対応していましたね。同じ事ですが、以下のサンプルを見ていきます。

  • Imports System.ComponentModel
    Imports System.Windows.Forms.Design ' System.Design.dll を参照追加
    
    ' 自前コントロールをターゲットとします。
    <Designer(GetType(ButtonExDesigner))>
    Public Class ButtonEx
        Inherits Button
    
    End Class
    
    ' 画面デザイン時のコントロール操作関連を提供します。
    Public Class ButtonExDesigner
        Inherits ControlDesigner
    
        ' 指定のプロパティ項目を削除します。
        Protected Overrides Sub PostFilterProperties(properties As IDictionary)
    
            Dim controlType = Me.Control.GetType()
            Dim propertyTypes = controlType.GetProperties()
    
            ' プロパティウィンドウに表示するメンバーをほとんど削除して、一部のみ残してみる
            For Each propertyType In propertyTypes
    
                If propertyType.Name = "Text" Then Continue For
                If propertyType.Name = "Size" Then Continue For
                If propertyType.Name = "Location" Then Continue For
    
                properties.Remove(propertyType.Name)
    
            Next
    
            MyBase.PostFilterProperties(properties)
    
        End Sub
    
    End Class
    

    イメージ
  •  今回は、既存メンバー(Button 側のプロパティ)をほとんど非表示にしてしまうサンプルです。 ソースコード上では触る必要があるが、画面デザイン上では触ってほしくない、という場面があるかと思いますが、そういう時に使います。 ここでは最低限のプロパティを残して、それ以外のプロパティを非表示にしてみました。

  •  デザイナを使う場合は、System.Windows.Forms.Design.ControlDesigner を継承して作ります。作ったデザイナは、付与したいコントロールに属性として追加します。 プロパティを表示させたくない場合は、PostFilterProperties() をオーバーライドして使います。 引数で受け取った辞書に、ターゲットとなるコントロールの各メンバーが含まれているので、非表示にしたいプロパティは辞書から削除することで非表示にすることができます。 ※あくまでプロパティウィンドウに表示されなくなるだけで、ターゲットとなるコントロールのプロパティは削除されません。


スマートタグ

  •  2つ目は、スマートタグを追加して、プロパティウィンドウを使わずに、値をセットするサンプルです。

  •  スマートタグは、以下のような▶マークのことです。

  • イメージ
    Imports System.ComponentModel
    Imports System.ComponentModel.Design
    Imports System.Runtime.CompilerServices
    Imports System.Windows.Forms.Design ' System.Design.dll を参照追加
    
    
    ' 自前コントロールをターゲットとします。
    <Designer(GetType(LabelExDesigner))>
    Public Class LabelEx
        Inherits Label
    
    End Class
    
    ' 画面デザイン時のコントロール操作関連を提供します。
    Public Class LabelExDesigner
        Inherits ControlDesigner
    
        '  分野ごとに、任意の操作をまとめて1つのリストにしたものを、複数管理するためのコレクション
        '  ここでは単純に、Label に対していくつか操作を追加する、程度で OK
    
        Private _ActionLists As DesignerActionListCollection
        Public Overrides ReadOnly Property ActionLists As DesignerActionListCollection
            Get
    
                If _ActionLists Is Nothing Then
    
                    _ActionLists = New DesignerActionListCollection
                    _ActionLists.Add(New LabelExActionList(Me.Component))
    
                End If
    
                Return _ActionLists
    
            End Get
        End Property
    
    End Class
    
    ' スマートタグに表示する、アクションリストを提供します。
    Public Class LabelExActionList
        Inherits DesignerActionList
    
        Private _Label As LabelEx
    
        Public Sub New(component As IComponent)
            MyBase.New(component)
    
            _Label = CType(component, LabelEx)
    
        End Sub
    
        ' 直接 LabelEx を操作するのではなく、TypeDescriptor 経由で操作するのは、何か意味があるはず(理解できていない)
        Private Sub SetProperty(Of T)(newValue As T, <CallerMemberName> Optional propertyName As String = "")
    
            Dim descriptor = TypeDescriptor.GetProperties(_Label)(propertyName)
            descriptor.SetValue(_Label, newValue)
    
        End Sub
    
        ' スマートタグ上で、背景色を変更できるように、ヘルパープロパティを作成します。
        Public Property BackColor As Color
            Get
                Return _Label.BackColor
            End Get
            Set(value As Color)
                SetProperty(value)
            End Set
        End Property
    
        ' スマートタグ上で、前景色を変更できるように、ヘルパープロパティを作成します。
        Public Property ForeColor As Color
            Get
                Return _Label.ForeColor
            End Get
            Set(value As Color)
                SetProperty(value)
            End Set
        End Property
    
        ' スマートタグ上で、テキストを変更できるように、ヘルパープロパティを作成します。
        Public Property Text As String
            Get
                Return _Label.Text
            End Get
            Set(value As String)
                SetProperty(value)
            End Set
        End Property
    
        ' スマートタグ上で、背景色と前景色を反転させるヘルパーメソッドを作成します。
        Public Sub InvertColors()
    
            Dim currentBackColor = _Label.BackColor
            BackColor = Color.FromArgb(
                    255 - currentBackColor.R,
                    255 - currentBackColor.G,
                    255 - currentBackColor.B)
    
            Dim currentForeColor = _Label.ForeColor
            ForeColor = Color.FromArgb(
                    255 - currentForeColor.R,
                    255 - currentForeColor.G,
                    255 - currentForeColor.B)
    
        End Sub
    
        ' ヘルパープロパティやヘルパーメソッドを、提供リストとして組み込みます。
        Public Overrides Function GetSortedActionItems() As DesignerActionItemCollection
    
            Dim results = New DesignerActionItemCollection
    
            ' アクションの種類となるグループ名を登録します。
            results.Add(New DesignerActionHeaderItem("表示系"))
            results.Add(New DesignerActionHeaderItem("情報系"))
    
            '  1つずつアクションを登録していきます。
            '  メンバー名: このパネルのアイテムに関連付けられているプロパティの名前
            '  表示名: スマートタグ上で表示される名称
            '  カテゴリ名: このアクションの種類(グループ名)
            '  説明文: このアクションの説明文
            results.Add(New DesignerActionPropertyItem(NameOf(BackColor), "背景色", "表示系", "背景色を変更します。"))
            results.Add(New DesignerActionPropertyItem(NameOf(ForeColor), "前景色", "表示系", "前景色を変更します。"))
            results.Add(New DesignerActionPropertyItem(NameOf(Text), "テキスト", "表示系", "テキストを変更します。"))
    
            results.Add(New DesignerActionMethodItem(Me, NameOf(InvertColors), "背景色、前景色の反転", "表示系", "背景色と前景色を反転します。", True))
    
            results.Add(New DesignerActionTextItem($"位置: {_Label.Location}", "情報系"))
            results.Add(New DesignerActionTextItem($"サイズ: {_Label.Size}", "情報系"))
    
            Return results
    
        End Function
    
    End Class
    

    イメージ
  •  スマートタグは小さいので操作しづらいですが、コントロールを配置後に近くで編集できるところが楽ですね。 このサンプルはちょっと長いですが、役割別に捉えるとシンプルです。

  •  1つ目のケーススタディ同様、ControlDesigner を継承してデザイナを作成、任意のコントロールに属性として付与します。 今回は、Label への操作機能を追加します。

  •  スマートタグはいくつかのアクション動作のまとまりとして考えますので、それを管理するコレクションクラスである DesignerActionListCollection を用意します。 これに、1つ分のまとまりとなるアクション動作リストを登録します。アクション動作リストは DesignerActionList クラスを継承して作成します。

  •  ここではアクション動作のサンプルとして、プロパティ値の変更とメソッドの実行をおこないます。 プロパティ値の変更をするには、任意のプロパティを用意して、コントロールとのやり取りの窓口を作成します。 この中で直接コントロールの値を変更するのではなく、TypeDescriptor 経由でプロパティ値を扱うところがポイントなのですが、この理由がまだよく分かっていないです。すみません。 メソッドの実行では、前述したプロパティを利用して、色の反転をおこなう処理を記載しています。

  •  これら用意したアクション動作を公開するのが GetSortedActionItems() です。 ここで、アクション動作リストを1つにまとめて返却します。その役目を受け持つのが DesignerActionItemCollection コレクションクラスです。 このコレクションクラスに、ジャンルとなるヘッダーを必要な分登録して、1つ1つのアクション動作リストをジャンル別に登録していきます。