Results 1 to 2 of 2

Thread: Properties set during design-time are not serialized to Designer file

  1. #1

    Thread Starter
    PowerPoster
    Join Date
    Apr 2007
    Location
    The Netherlands
    Posts
    5,070

    Properties set during design-time are not serialized to Designer file

    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

  2. #2

    Thread Starter
    PowerPoster
    Join Date
    Apr 2007
    Location
    The Netherlands
    Posts
    5,070

    Re: Properties set during design-time are not serialized to Designer file

    News:

    I have managed to get a LITTLE bit further. I found out I can declare functions called 'ShouldSerialize<property>' where <property> is replaced by the name of a property. When I add ShouldSerializeNode to the OptionsPanel class for example, the Node property is set to the right panel.

    However, as I feared, not at the right time. It is set after the panels are already added to the OptionsView control, too late.

    I also tried having the constructor of OptionsNode accept a Panel, so I could create their connection in that constructor, but I did not manage to let my TypeConverter return the correct InstanceDescriptor; I tried getting the constructor with a single OptionsPanel as a parameter and tried passing the Panel, but it keps generating a parameterless constructor, which no longer exists and thus creates errors.


    I found out about two more very promising methods just now, which I will try tomorrow. Perhaps before that time someone else can comment on my ideas:

    1. I can let my control implement ISupportInitialize which should cause the designer to call BeginInit and EndInit methods before and after setting its properties. I could then add the nodes in the EndInit method, instead of when the panels are added to the control. At that point, hopefully, the nodes would be set and I can use them.

    2. I found out one can control the InitializeComponent code serialization using a custom CodeDomSerializer. I haven't looked into this in detail, it seems quite complicated, but it also seems like it might be just what I need.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Click Here to Expand Forum to Full Width