Hi,

I am creating a UserControl with rich design-time support that should eventually look like the Options window in Visual Studio (or many other applications). Basically a split container, to the left is a TreeView with 'option categories', and each node in the treeview corresponds to a 'panel' to the right with certain options.

Just for terminology, the nodes in the TreeView are OptionsNode objects, the panels (containing the controls that determine the options) are OptionsPanel controls. My UserControl itself is called OptionsView and is the control that contains the treeview as well as a panel that contains the OptionsPanels.

I have some experience in design-time coding, and I have gotten pretty far. The OptionsView control contains a property Panels that returns the ControlCollection of the right side of the split container. The user can add/remove OptionsPanels via this property (and automatically an OptionsNode is created). The property uses a custom CollectionEditor that tells the designer to create instances of type OptionsPanel (instead of just Control which is the usual collection type of ControlCollection). Furthermore, in the CreateInstance method I use the DesignerHost object and its CreateComponent method to create the panels, instead of just creating New OptionsPanel objects. This way the OptionsPanel created is selectable and editable (via property grid) during design-time:

vb.net Code:
  1. Protected Overrides Function CreateInstance(ByVal itemType As System.Type) As Object
  2.             If itemType Is GetType(OptionsPanel) Then
  3.                 ' Use the IDesignerHost service to create a new component
  4.                 ' This way it's editable during design-time
  5.                 Dim designerHost = DirectCast(Me.GetService(GetType(IDesignerHost)), IDesignerHost)
  6.                 Dim panel = DirectCast(designerHost.CreateComponent(GetType(OptionsPanel)), OptionsPanel)
  7.                 Dim view = DirectCast(Me.Context.Instance, OptionsView)
  8.  
  9.                 ' Also set some properties
  10.                 panel.Owner = view
  11.                 panel.Dock = DockStyle.Fill
  12.  
  13.                 'Create node
  14.                 Dim node As New OptionsNode()
  15.                 node.Panel = panel
  16.                 panel.Node = node
  17.  
  18.                 Return panel
  19.             End If
  20.  
  21.             Return MyBase.CreateInstance(itemType)
  22.         End Function

As you can see, this is also where I create the OptionsNode, and most importantly: the connection between an OptionsNode and its OptionsPanel. The panel has a Node property and the node has a Panel property, which are set here so that a Node knows which Panel belongs to it and vice versa.

Finally, when a control is added to the right side of the split container (ControlAdded event), I cast it to an OptionsPanel and add its Node to the TreeView.

Now, during design-time, this works perfectly. I can add OptionsPanels via the Panels property, they appear fully selectable on the right side of my control, and nodes appear in the TreeView. I even wrote a custom designer to make the nodes selectable during design-time (basically just return True in the GetHitTest method when the mouse is over the TreeView), so I can select nodes (and thus panels) also during design-time (when the treeview selection changes, I hide all panels except the panel associated with that OptionsNode).


The problem is when I try to run the application. The form Designer file and the InitializeComponent method is simply wrong. The error is quite simple: the OptionsPanels as well as the OptionsNodes are created just fine, but the connection between them is nowhere to be found! Furthermore, even IF that connection was made, it would be made in the wrong place. Here's a bit of the InitializeComponent method:

vb.net Code:
  1. Private Sub InitializeComponent()
  2.         Dim OptionsNode1 As OptionsViewLibrary.OptionsNode = New OptionsViewLibrary.OptionsNode()
  3.         Dim OptionsNode2 As OptionsViewLibrary.OptionsNode = New OptionsViewLibrary.OptionsNode()
  4.         Dim OptionsNode3 As OptionsViewLibrary.OptionsNode = New OptionsViewLibrary.OptionsNode()
  5.         Me.OptionsView1 = New OptionsViewLibrary.OptionsView()
  6.         Me.OptionsPanel1 = New OptionsViewLibrary.OptionsPanel()
  7.         Me.OptionsPanel2 = New OptionsViewLibrary.OptionsPanel()
  8.         Me.OptionsPanel3 = New OptionsViewLibrary.OptionsPanel()
  9.         Me.SuspendLayout()
  10.         '
  11.         'OptionsView1
  12.         '
  13.         Me.OptionsView1.Location = New System.Drawing.Point(360, 84)
  14.         Me.OptionsView1.Name = "OptionsView1"
  15.         Me.OptionsView1.Panels.Add(Me.OptionsPanel1)
  16.         Me.OptionsView1.Panels.Add(Me.OptionsPanel2)
  17.         Me.OptionsView1.Panels.Add(Me.OptionsPanel3)
  18.         Me.OptionsView1.SelectedPanel = Nothing
  19.         Me.OptionsView1.Size = New System.Drawing.Size(462, 373)
  20.         Me.OptionsView1.TabIndex = 0
  21.         '...
  22.     End Sub

(I forgot to mention I am also using a custom TypeConverter for the OptionsNode class which ensures the designer creates New OptionsNode() objects; otherwise it would create TreeNode objects and try to cast them to OptionsNode, not going to work obviously (cast from base to derived type)).

As you can see, nodes and panels are created just fine, but they are never told to whom they belong. Then, when the properties for the OptionsView itself are being set, the Panels are added to the Panels property. This is when my ControlAdded event is handled and where I try to add the Node property of each panel to the TreeView. Obviously, this Node property is Nothing at this moment because it has never been set. Even if it WOULD be set in code, that would (probably) only be when the properties of each OptionsPanel are set, which is AFTER the properties of the OptionsView is set (this is just the usual order for InitializeComponent I think?), so it is also AFTER panels are added...


So two questions:

1. How can I tell the designer to serialize the connection between nodes and panels. At the moment this is simply done by setting the Node and Panel properties, that works during design-time but the properties don't persist to the designer file. I tried using the DesignerSerializationVisibility on both the Node and Panel property (or both) but that resulted in the Nodes not being serialized at all (they are still created during design-time, but they no longer exist during run-time, so I get the same problem).


2. Once problem 1 is solved, how can I make sure the connection is made BEFORE the panels are added to the Panels property? This is a requirement because at that point I need to know what Node belongs to each Panel, this is where I add the nodes to the TreeView...



Thanks for any help!


-------
Just for completion, here's the relevant code for the OptionsView, OptionsNode and OptionsPanel classes. Nothing special really:

vb.net Code:
  1. Public Class OptionsPanel
  2.         Inherits Panel
  3.  
  4.         Private _Owner As OptionsView
  5.         Public Property Owner() As OptionsView
  6.             Get
  7.                 Return _Owner
  8.             End Get
  9.             Friend Set(ByVal value As OptionsView)
  10.                 _Owner = value
  11.             End Set
  12.         End Property
  13.  
  14.         Private _Node As OptionsNode
  15.         Public Property Node() As OptionsNode
  16.             Get
  17.                 Return _Node
  18.             End Get
  19.             Friend Set(value As OptionsNode)
  20.                 _Node = value
  21.             End Set
  22.         End Property
  23.  
  24.     End Class
  25.  
  26.     <TypeConverter(GetType(Converters.OptionsNodeConverter))> _
  27.     Public Class OptionsNode
  28.         Inherits TreeNode
  29.  
  30.         Public Sub New()
  31.        End Sub
  32.  
  33.         Private _Panel As OptionsPanel
  34.         Public Property Panel() As OptionsPanel
  35.             Get
  36.                 Return _Panel
  37.             End Get
  38.             Friend Set(value As OptionsPanel)
  39.                 _Panel = value
  40.                 Me.Text = value.Name
  41.             End Set
  42.         End Property
  43.  
  44.     End Class
  45.  
  46.     <Designer(GetType(Designers.OptionsViewDesigner))> _
  47.     Public Class OptionsView
  48.  
  49.         Public Event SelectedPanelChanged As EventHandler
  50.  
  51.         Public Sub New()
  52.  
  53.             ' This call is required by the designer.
  54.             InitializeComponent()
  55.  
  56.             ' Add any initialization after the InitializeComponent() call.
  57.             AddHandler Me.TreeView.AfterSelect, AddressOf OnNodeSelected
  58.             AddHandler Me.PanelContainer.ControlAdded, AddressOf OnPanelAdded
  59.             AddHandler Me.PanelContainer.ControlRemoved, AddressOf OnPanelRemoved
  60.         End Sub
  61.  
  62.     #Region " Properties "
  63.  
  64.         <DesignerSerializationVisibility(DesignerSerializationVisibility.Content)> _
  65.         Public ReadOnly Property TreeView As TreeView
  66.             Get
  67.                 Return Me.tvw
  68.             End Get
  69.         End Property
  70.  
  71.         Public ReadOnly Property PanelContainer As OptionsPanelContainer
  72.             Get
  73.                 Return Me.pnl
  74.             End Get
  75.         End Property
  76.  
  77.         <Editor(GetType(Editors.OptionsPanelCollectionEditor), GetType(UITypeEditor))> _
  78.         <DesignerSerializationVisibility(DesignerSerializationVisibility.Content)> _
  79.         Public ReadOnly Property Panels() As Control.ControlCollection
  80.             Get
  81.                 Return Me.PanelContainer.Controls
  82.             End Get
  83.         End Property
  84.  
  85.         Private _SelectedPanel As OptionsPanel
  86.         Public Property SelectedPanel() As OptionsPanel
  87.             Get
  88.                 Return _SelectedPanel
  89.             End Get
  90.             Set(ByVal value As OptionsPanel)
  91.                 _SelectedPanel = value
  92.                 Me.OnSelectedPanelChanged()
  93.             End Set
  94.         End Property
  95.  
  96.     #End Region
  97.    
  98.     '...
  99.     End Class