VB のたまご

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


デザイン時サービス(1)


IServiceContainer

  •  前回出てきたサービス管理サービスを紹介します。ここはさらっとポイントだけ記載します。
  • ' 画面デザインに関する窓口的なクラスを宣言
    Dim _DesignSurface = New DesignSurface(GetType(Form))
    
    ' ★ IServiceContainer
    ' 各サービスを管理するコンテナです。
    Dim serviceContainer = CType(_DesignSurface.GetService(GetType(IServiceContainer)), IServiceContainer)
    
    '  IDesignerHost
    ' デザイン画面にアクセスするためのサービス機能
    ' ★serviceContainer から取得するように変更した
    Dim designerHost = CType(serviceContainer.GetService(GetType(IDesignerHost)), IDesignerHost)
    

  •  このように、DesignSurface が担当していた GetService() 部分を代わりに担当します。IServiceContainer インターフェース自体は、DesignSurface から取得してきます。


IToolboxService

  •  デザイン画面ができたので、次はコントロールを配置したくなりますね。コントロールを選択するためのツールボックスを作成します。

  •  IToolboxService はツールボックスコントロール自体ではなく、ツールボックスと連携するデザイン時アーキテクチャのサービスとなります。 DesignSurface と連携するサービスです。

  •  このことは、ツールボックスコントロールの作成と IToolboxService との連携処理をおこなうという2つの作業が必要であるという事なのですが、サンプルでは、1つにまとめて扱うように作成しました。 使う方としては扱いが楽な方として作成してしまいましたが、今思うと、ツールボックスで1つ作成して、IToolboxService で1つ作成するべきだったかなと反省しました。 こちらの方が、実装者側としては理解しやすかったと思います。もう作成してしまったので、進めます。

  •  サービスの呼び出し側は簡単です。
  • '  ツールボックスとコントロール配置サービスの役割
    serviceContainer.AddService(GetType(IToolboxService), New WinformToolboxService(TreeView1, Panel1))
    

  •  IToolboxService サービスはデフォルトで登録されていないため、AddService() よりサービスを登録して使います。 その後ろの WinformToolboxService クラスが、ツールボックスと IToolboxService をまとめて作成したものになります。

  •  TreeView1 がツールボックス役となるコントロールで、Panel1 はマウスカーソルの編集のために渡しています。 TreeView1 を渡しているのは、配置コントロールの選択(ノードクリック、AfterSelect イベント)との連携をおこないたいためです。

  •  それで、WinformToolboxService の中身を理解するのが、本シリーズ一番の難易度となります。

  •  とりあえず、以下サンプルです。
  • Imports System.ComponentModel.Design
    Imports System.Drawing.Design
    
    Public Class WinformToolboxService
        Implements IToolboxService
    
        ' 配置コントロールのリストと、選択中の配置コントロール
        Private _ToolboxItemCollection As ToolboxItemCollection
        Private _SelectedToolboxItem As ToolboxItem
    
        ' 配置コントロールの種類となるカテゴリ名リストと、選択中のコントロールの種類となるカテゴリ名
        Public ReadOnly Property CategoryNames As CategoryNameCollection Implements IToolboxService.CategoryNames
        Public Property SelectedCategory As String Implements IToolboxService.SelectedCategory
    
        ' 配置コントロールの選択機能を提供するコントロール
        Public Property ToolBox As TreeView
    
        ' 配置コントロール選択時、マウスカーソルを変更するための用途
        Public Property DesignControl As Control
    
        ' コンストラクタ
        ' カテゴリ名や配置コントロールの準備
        Public Sub New(toolBox As TreeView, designControl As Control)
    
            CategoryNames = New CategoryNameCollection(New String() {"全てのカテゴリー"})
            SelectedCategory = CategoryNames(0)
            Me.DesignControl = designControl
    
            If Me.ToolBox IsNot Nothing Then
                RemoveHandler Me.ToolBox.AfterSelect, AddressOf Me.ToolBox_AfterSelect
            End If
    
            Me.ToolBox = toolBox
            InitializeToolBox()
            AddHandler Me.ToolBox.AfterSelect, AddressOf Me.ToolBox_AfterSelect
    
        End Sub
    
        Private Sub InitializeToolBox()
    
            ' ToolboxItemCollection のコレクション初期化子の書き方が分からなかったため分割
            Dim items = New List(Of ToolboxItem) From {
                New ToolboxItem With {.DisplayName = "ポインター"},
                New ToolboxItem(GetType(Button)),
                New ToolboxItem(GetType(CheckBox)),
                New ToolboxItem(GetType(TextBox)),
                New ToolboxItem(GetType(RadioButton))
                }
    
            _ToolboxItemCollection = New ToolboxItemCollection(items.ToArray())
    
            ' TreeView には、カテゴリ名(ここでは1つだけ)をトップノードとして登録して、
            ' その下に、子ノードとして配置コントロールを登録する
            Me.ToolBox.Nodes.Clear()
            Dim rootNode = Me.ToolBox.Nodes.Add(SelectedCategory)
    
            ' ※rootNode に新しい子ノードを追加しつつ、そのノードを受け取れるので、タグに ToolboxItem インスタンスを入れておく
            For Each item As ToolboxItem In _ToolboxItemCollection
                Dim childNode = rootNode.Nodes.Add(item.DisplayName)
                childNode.Tag = item
            Next
    
            Me.ToolBox.ExpandAll()
    
        End Sub
    
        ' TreeView のノードが選択されたら、IToolboxService サービス側も同期を取る
        Private Sub ToolBox_AfterSelect(sender As Object, e As TreeViewEventArgs)
    
            If 0 < e.Node.Level AndAlso TypeOf e.Node.Tag Is ToolboxItem Then
                Dim item = CType(e.Node.Tag, ToolboxItem)
                SetSelectedToolboxItem(item)
            End If
    
        End Sub
    
    
    
        ' 指定したデータ形式の新しいツールボックス アイテムの作成者を追加します。
        Public Sub AddCreator(creator As ToolboxItemCreatorCallback, format As String) Implements IToolboxService.AddCreator
    
        End Sub
    
        ' 指定したデータ形式とデザイナー ホストの新しいツールボックス アイテムの作成者を追加します。
        Public Sub AddCreator(creator As ToolboxItemCreatorCallback, format As String, host As IDesignerHost) Implements IToolboxService.AddCreator
    
        End Sub
    
        ' 指定したプロジェクトにリンクされたツールボックス項目をツールボックスに追加します。
        Public Sub AddLinkedToolboxItem(toolboxItem As ToolboxItem, host As IDesignerHost) Implements IToolboxService.AddLinkedToolboxItem
    
        End Sub
    
        ' 指定したプロジェクトにリンクされたツールボックス項目を指定したカテゴリのツールボックスに追加します。
        Public Sub AddLinkedToolboxItem(toolboxItem As ToolboxItem, category As String, host As IDesignerHost) Implements IToolboxService.AddLinkedToolboxItem
    
        End Sub
    
        ' 指定したツールボックス項目をツールボックスに追加します。
        Public Sub AddToolboxItem(toolboxItem As ToolboxItem) Implements IToolboxService.AddToolboxItem
    
        End Sub
    
        ' 指定したツールボックス項目を指定したカテゴリのツールボックスに追加します。
        Public Sub AddToolboxItem(toolboxItem As ToolboxItem, category As String) Implements IToolboxService.AddToolboxItem
    
        End Sub
    
        ' 指定したオブジェクトをシリアル化された形式で、ツールボックス項目を表すからツールボックス項目を取得します。
        Public Function DeserializeToolboxItem(serializedObject As Object) As ToolboxItem Implements IToolboxService.DeserializeToolboxItem
            Return DeserializeToolboxItem(serializedObject, Nothing)
        End Function
    
        ' 指定したデザイナー ホストを使用してシリアル化の形式でツールボックス項目を表す指定したオブジェクトから、ツールボックス項目を取得します。
        Public Function DeserializeToolboxItem(serializedObject As Object, host As IDesignerHost) As ToolboxItem Implements IToolboxService.DeserializeToolboxItem
            Return Nothing
        End Function
    
        ' 現在選択されているツールボックス項目を取得します。
        Public Function GetSelectedToolboxItem() As ToolboxItem Implements IToolboxService.GetSelectedToolboxItem
            Return GetSelectedToolboxItem(Nothing)
        End Function
    
        ' すべてのデザイナーに表示されている場合、または指定したデザイナーがサポートされている場合は、現在選択されているツールボックス項目を取得します。
        Public Function GetSelectedToolboxItem(host As IDesignerHost) As ToolboxItem Implements IToolboxService.GetSelectedToolboxItem
            Return _SelectedToolboxItem
        End Function
    
        ' ツールボックスのツールボックス項目のコレクション全体を取得します。
        Public Function GetToolboxItems() As ToolboxItemCollection Implements IToolboxService.GetToolboxItems
            Return GetToolboxItems("")
        End Function
    
        ' ツールボックスから、指定したデザイナー ホストに関連付けられているツールボックス項目のコレクションを取得します。
        Public Function GetToolboxItems(host As IDesignerHost) As ToolboxItemCollection Implements IToolboxService.GetToolboxItems
            Return GetToolboxItems("")
        End Function
    
        ' 指定したカテゴリに一致するツールボックスのツールボックス項目のコレクションを取得します。
        Public Function GetToolboxItems(category As String) As ToolboxItemCollection Implements IToolboxService.GetToolboxItems
            Return GetToolboxItems(category, Nothing)
        End Function
    
        ' 指定したデザイナー ホストと、ツールボックスからカテゴリに関連付けられているツールボックス項目のコレクションを取得します。
        Public Function GetToolboxItems(category As String, host As IDesignerHost) As ToolboxItemCollection Implements IToolboxService.GetToolboxItems
            Return _ToolboxItemCollection
        End Function
    
        ' 指定したデザイナー ホストによってシリアル化されたツールボックス項目を表す、指定したオブジェクトを使用できるかどうかを示す値を取得します。
        Public Function IsSupported(serializedObject As Object, host As IDesignerHost) As Boolean Implements IToolboxService.IsSupported
            Return True
        End Function
    
        ' 指定した属性がシリアル化されたツールボックス項目を表す、指定したオブジェクトに一致するかどうかを示す値を取得します。
        Public Function IsSupported(serializedObject As Object, filterAttributes As ICollection) As Boolean Implements IToolboxService.IsSupported
            Return True
        End Function
    
        ' 指定したオブジェクトがシリアル化されたツールボックス項目であるかどうかを示す値を取得します。
        Public Function IsToolboxItem(serializedObject As Object) As Boolean Implements IToolboxService.IsToolboxItem
            Return IsToolboxItem(serializedObject, Nothing)
        End Function
    
        ' 指定したオブジェクトが指定したデザイナー ホストを使用して、シリアル化されたツールボックスのアイテムであるかどうかを示す値を取得します。
        Public Function IsToolboxItem(serializedObject As Object, host As IDesignerHost) As Boolean Implements IToolboxService.IsToolboxItem
            Return False
        End Function
    
        ' ツールボックス項目の状態を更新します。
        Public Sub Refresh() Implements IToolboxService.Refresh
    
        End Sub
    
        ' 指定したデータ形式の以前に追加したツールボックス項目の作成者を削除します。
        Public Sub RemoveCreator(format As String) Implements IToolboxService.RemoveCreator
    
        End Sub
    
        ' 指定したデータ形式と指定したデザイナー ホストに関連付けられている以前に追加したツールボックス クリエーターを削除します。
        Public Sub RemoveCreator(format As String, host As IDesignerHost) Implements IToolboxService.RemoveCreator
    
        End Sub
    
        ' ツールボックスから、指定したツールボックス項目を削除します。
        Public Sub RemoveToolboxItem(toolboxItem As ToolboxItem) Implements IToolboxService.RemoveToolboxItem
    
        End Sub
    
        ' ツールボックスから、指定したツールボックス項目を削除します。
        Public Sub RemoveToolboxItem(toolboxItem As ToolboxItem, category As String) Implements IToolboxService.RemoveToolboxItem
    
        End Sub
    
        ' 選択したツールが使用されたことをツールボックスのサービスに通知します。
        Public Sub SelectedToolboxItemUsed() Implements IToolboxService.SelectedToolboxItemUsed
            _SelectedToolboxItem = Nothing
        End Sub
    
        ' 指定したツールボックス項目を表すシリアル化可能なオブジェクトを取得します。
        Public Function SerializeToolboxItem(toolboxItem As ToolboxItem) As Object Implements IToolboxService.SerializeToolboxItem
            Return Nothing
        End Function
    
        ' 現在選択されているツールを表すようにカーソルを現在のアプリケーションのカーソルを設定します。
        Public Function SetCursor() As Boolean Implements IToolboxService.SetCursor
    
            If _SelectedToolboxItem Is Nothing OrElse _SelectedToolboxItem.DisplayName = "ポインター" Then
                DesignControl.Cursor = Cursors.Default
                Return False
            End If
    
            ' 今回は、それぞれのコントロール用のカーソルアイコンを持っていないため、一律同じカーソルアイコンを設定しておく
            DesignControl.Cursor = Cursors.Cross
            Return True
    
        End Function
    
        ' 指定したツールボックス項目を選択します。
        Public Sub SetSelectedToolboxItem(newSelectedItem As ToolboxItem) Implements IToolboxService.SetSelectedToolboxItem
    
            If _SelectedToolboxItem IsNot Nothing AndAlso _SelectedToolboxItem.Equals(newSelectedItem) Then
                Return
            End If
    
            _SelectedToolboxItem = newSelectedItem
    
            ' 通常は、TreeView クリックからの WinformToolboxService.SetSelectedToolboxItem() の順番なので問題ないはずだが、
            ' WinformToolboxService 始まりの場合(あるのか?)、TreeView 側も同期を取る
            If ToolBox.SelectedNode Is Nothing Then
                Dim results = ToolBox.Nodes.Find(_SelectedToolboxItem.DisplayName, True)
                ToolBox.SelectedNode = results(0)
                Return
            End If
    
            Dim selectedItem = CType(ToolBox.SelectedNode.Tag, ToolboxItem)
            If selectedItem.DisplayName <> _SelectedToolboxItem.DisplayName Then
                Dim results = ToolBox.Nodes.Find(_SelectedToolboxItem.DisplayName, True)
                ToolBox.SelectedNode = results(0)
            End If
    
        End Sub
    
    End Class
    

  •  多いですね・・・。ポイントごとに説明したいと思います。


ツールボックス周り

  •  ツールボックスに関する処理は以下です。
  • ' 配置コントロールの選択機能を提供するコントロール
    Public Property ToolBox As TreeView
    
    ' コンストラクタ
    ' カテゴリ名や配置コントロールの準備
    Public Sub New(toolBox As TreeView, designControl As Control)
    
        If Me.ToolBox IsNot Nothing Then
            RemoveHandler Me.ToolBox.AfterSelect, AddressOf Me.ToolBox_AfterSelect
        End If
    
        Me.ToolBox = toolBox
        InitializeToolBox()
        AddHandler Me.ToolBox.AfterSelect, AddressOf Me.ToolBox_AfterSelect
    
    End Sub
    
    Private Sub InitializeToolBox()
    
        ' ToolboxItemCollection のコレクション初期化子の書き方が分からなかったため分割
        Dim items = New List(Of ToolboxItem) From {
            New ToolboxItem With {.DisplayName = "ポインター"},
            New ToolboxItem(GetType(Button)),
            New ToolboxItem(GetType(CheckBox)),
            New ToolboxItem(GetType(TextBox)),
            New ToolboxItem(GetType(RadioButton))
            }
    
        _ToolboxItemCollection = New ToolboxItemCollection(items.ToArray())
    
        ' TreeView には、カテゴリ名(ここでは1つだけ)をトップノードとして登録して、
        ' その下に、子ノードとして配置コントロールを登録する
        Me.ToolBox.Nodes.Clear()
        Dim rootNode = Me.ToolBox.Nodes.Add(SelectedCategory)
    
        ' ※rootNode に新しい子ノードを追加しつつ、そのノードを受け取れるので、タグに ToolboxItem インスタンスを入れておく
        For Each item As ToolboxItem In _ToolboxItemCollection
            Dim childNode = rootNode.Nodes.Add(item.DisplayName)
            childNode.Tag = item
        Next
    
        Me.ToolBox.ExpandAll()
    
    End Sub
    
    ' TreeView のノードが選択されたら、IToolboxService サービス側も同期を取る
    Private Sub ToolBox_AfterSelect(sender As Object, e As TreeViewEventArgs)
    
        If 0 < e.Node.Level AndAlso TypeOf e.Node.Tag Is ToolboxItem Then
            Dim item = CType(e.Node.Tag, ToolboxItem)
            SetSelectedToolboxItem(item)
        End If
    
    End Sub
    

  •  コンストラクタで TreeView のインスタンスをもらってきます。準備処理として、TreeView にノードの登録とノードクリック処理(AfterSelect イベント)を購読します。 無いとは思いますが、すでにインスタンスがある場合イベントを購読していますので、イベント購読を解除しておきます。 その後、新しいインスタンスを受け取り、ノードを登録して、イベント購読します。

  •  InitializeToolBox() で、ノード登録を行います。

  •  親ノードはカテゴリ名で、子ノードが各コントロールになります。ここではカテゴリ名は「全てのカテゴリー」1つだけとします(カテゴリ別に分けない)。 サービス側の準備処理で、_ToolboxItemCollection コレクションに、いくつかの配置用コントロールを登録してあるので、これを見ながら、1つずつ名前とインスタンス自体をセットしていきます。

  •  イベントの購読では、ノードがクリックされたらサービス側もクリックされたと認識してほしいので、サービス側の選択されたよ処理を呼び出して、選択コントロールを渡します。あとはお任せです。


IToolboxService 周り

  •  分かっているところだけを説明すると、サービス側では、全カテゴリ名と選択中のカテゴリ名、全コントロールと選択中のコントロール、を状態管理として保持します。

  •  コンストラクタで、全カテゴリ名と選択中のカテゴリ名(というよりは初期選択しているカテゴリ名)をセットします。 後は、選択中のコントロールを返却してあげたり、配置コントロールコレクションを返却してあげたり、(DesignSurface 側からの?.NET Framework 側からの?)要求ごとに処理していきます。

  •  配置したいコントロールを選択した際の処理 SetCursor() では、選択したコントロール別にマウスカーソルアイコンを切り替えることになるのですが、アイコンを持っていないため、ポインター以外の場合は、一律同じカーソルアイコンに変えています。

  •  これ以外、作成者登録・削除や、リンクされたツールボックスの登録・削除などなどが、よく分かっていません。

  •  実行するとコントロールを配置できるようになります。

  • イメージ
  •  おしい!初期文字列が登録されていない!

  •  動作結果から分かるのですが、デフォルトデータがセットされていないコントロールが配置されてしまいました。 このことは、デフォルトデータを管理してくれるサービスを登録しないといけないことを意味しています。

  •  それは次回としましょう。今回はここまでです。