Hi,

I am in need of a form that shows various options, exactly like the Options in Visual Studio. Since there are so many options I too want them categorized, with a TreeView to the left taking care of showing the right category.

The usual 'easy' approach here would be to just place a TreeView control on the form, add some nodes, and give those nodes a tag or key that corresponds to a panel or UserControl with the options for that category.
Since there will be a lot of options however, this is not really feasible design-wise; the form would be cluttered with possibly 50 panels, all of which I would need to select and bring to front from time to time to add controls to them that represent the options.


So I decided to create a custom control that does exactly that. The control is very similar to my Wizard usercontrol, users can add OptionsPanels at design time, which inherit Panel and simply represent one panel of options. When they do, the panel is added to a container panel, and at the same time a TreeNode is added to a TreeView. The control uses a custom ControlDesigner to handle design-time clicks in the Treeview, selecting a different node would select and bring to front the corresponding panel, allowing the user to add the controls he wants.

Due to the design time support the problem of having 50 panels is no longer present, only one panel will be visible at a time and selecting the right panel is as simple as selecting the corresponding TreeNode, just as during run-time.



Anyway, I got all this working, but only for a single 'level' of categories. As you can see in the Visual Studio options, there can be multiple levels of categories. For example, the Environment node has a bunch of children, where each child represents one 'options panel'. There can even be deeper nesting, see the Text Editor node for example.


I am having trouble designing this feature and would appreciate some help or ideas.



Let me begin by drawing out the basics of how my control works so far.

The main control is an OptionsView control, which contains a SplitContainer with a TreeView to the left and a OptionsPanelContainer to the right. The OptionsPanelContainer is merely a Panel to which only OptionsPanel controls can be added, and which raises events when this happens, as well as when OptionsPanels are removed from it.
An OptionsPanel also inherits Panel, and these are the actual panels the users will see in the control, one for each option category.
For now, each OptionsPanel has exactly one corresponding TreeNode (and vice versa). In the Visual Studio options, each 'parent' category usually has a 'General' node as the first child, and the parent and this General node show the same option panel, but I am ignoring that for the moment.

The OptionsView control has a property Panels that returns the ControlCollection (Controls property) of the OptionsPanelContainer (in other words: it returns a collection of OptionsPanels that are in this container panel).
vb.net Code:
  1. <Editor(GetType(Designers.OptionsPanelCollectionEditor), GetType(UITypeEditor))> _
  2.     <DesignerSerializationVisibility(DesignerSerializationVisibility.Content)> _
  3.     Public ReadOnly Property Panels As Control.ControlCollection
  4.         Get
  5.             Return Me.PanelContainer.Controls
  6.         End Get
  7.     End Property

A custom CollectionEditor for this property takes care of the designer: even though the property type is ControlCollection, the CollectionEditor knows it should create instances of the OptionsPanel control instead of just Controls.

When it does this, a corresponding TreeNode is also created and its Tag property is set to the OptionsPanel. Vice versa, the Node property of the OptionsPanel is set to the node. Hence the node and panel both know their corresponding object.
vb.net Code:
  1. Public Class OptionsPanelCollectionEditor
  2.         Inherits System.ComponentModel.Design.CollectionEditor
  3.  
  4.         Public Sub New(type As Type)
  5.             MyBase.New(type)
  6.         End Sub
  7.  
  8.         Protected Overrides Function CreateCollectionItemType() As System.Type
  9.             Return GetType(OptionsPanel)
  10.         End Function
  11.  
  12.         Protected Overrides Function CreateInstance(ByVal itemType As System.Type) As Object
  13.             If itemType Is GetType(OptionsPanel) Then
  14.                 ' Use the IDesignerHost service to create a new component
  15.                 ' This way it's editable during design-time
  16.                 Dim designerHost = DirectCast(Me.GetService(GetType(IDesignerHost)), IDesignerHost)
  17.                 Dim panel = DirectCast(designerHost.CreateComponent(GetType(OptionsPanel)), OptionsPanel)
  18.                 Dim view = DirectCast(Me.Context.Instance, OptionsView)
  19.  
  20.                 ' Also set some properties
  21.                 panel.Owner = view
  22.                 panel.Dock = DockStyle.Fill
  23.  
  24.                 Dim node As New TreeNode(panel.Name)
  25.                 node.Tag = panel
  26.                 panel.SetNode(node)
  27.  
  28.                 Return panel
  29.             End If
  30.  
  31.             Return MyBase.CreateInstance(itemType)
  32.         End Function
  33.     End Class

Finally, when an OptionsPanel is added to the OptionsPanelContainer, it raises an event and its TreeNode is added to the TreeView. The same goes for removing. When a TreeNode is selected, its Panel is set as the SelectedPanel. Setting the SelectedPanel merely hides all panels except the selected one:
vb.net Code:
  1. Private Sub container_PanelAdded(sender As System.Object, e As OptionsViewLibrary.OptionsPanelContainer.OptionsPanelEventArgs) Handles container.PanelAdded
  2.         Me.TreeView.Nodes.Add(e.Panel.Node)
  3.     End Sub
  4.  
  5.     Private Sub container_PanelRemoved(sender As System.Object, e As OptionsViewLibrary.OptionsPanelContainer.OptionsPanelEventArgs) Handles container.PanelRemoved
  6.         Me.TreeView.Nodes.Remove(e.Panel.Node)
  7.     End Sub
  8.  
  9.     Private Sub tvw_AfterSelect(sender As System.Object, e As System.Windows.Forms.TreeViewEventArgs) Handles tvw.AfterSelect
  10.         If e.Node IsNot Nothing Then
  11.             Dim panel = TryCast(e.Node.Tag, OptionsPanel)
  12.             If panel IsNot Nothing Then
  13.                 Me.SelectedPanel = panel
  14.             End If
  15.         End If
  16.     End Sub


So far so good, this all works fine. I can add Panels via the designer and when I do a new TreeNode appears in the TreeView. I can select this node and the panel becomes visible (comes to the front).


Now, I am a little stuck. How do I implement child option panels? And more importantly: how do I let the user add child panels?

The most logical choice I think is to let each OptionsPanel have a property (ChildPanels or something) that returns the child OptionsPanels for that panel. Once the user selects an OptionsPanel then, he can look in the property grid to find its ChildPanels property and add child panels to that.

There is a problem though: what would this property return? It must return a ControlCollection of some container (this is, I think, a requirement for the designer features to work, otherwise panels are not added to the Form.Designer.vb file).
But there is no container. I cannot add them to the OptionsPanel itself, that would make no sense because the parent OptionsPanel has its own set of controls (the options itself...), there cannot be another (fully docked) Panel on top of those obviously.
The container of the main OptionsView then? That is not an option either, its Controls collection holds ALL OptionsPanels, not just the children of the selected panel. I cannot 'select' only the right panels either, that would require me to return a new instance of ControlCollection, it would be impossible to return the actual ControlsCollection that holds merely a small selection of its controls.



I realize this post might be hard to understand, but if you have any idea at all just let me know, perhaps I can work with it.
Thanks!