Accessing properties via the designer is not very reliable!

pizzaboy

Well-known member
Joined
Jan 5, 2008
Messages
57
Programming Experience
1-3
In my usercontrol I have a panel which I can set to visible or not. The panel is set to Visible=False via the VS designer.

I've added a property so inheritted controls can also toggle the visibility:

VB.NET:
    Public Property ShowPanel As Boolean
        Get
            Return pnl.Visible
        End Get
        Set(ByVal value As Boolean)
            pnl.Visible = value
            Invalidate()
        End Set
    End Property

When I add the control to a form, somewhere is setting the ShowPanel to True!?!?! Right-clicking on designer property and selecting reset corrects the problem.

Any reasons for this occurrence? I really don't want to have to set the property for each added control (there'll be quite a few!)!!


Any help would be greatly appreciated.

Thanks.
 
When I add the control to a form, somewhere is setting the ShowPanel to True!?!?!
I don't see that happening, and I'm using VB 2010 also. Logically there should also be no problem, when you set Visible property of the panel to False in designer this is different than default value and the code to set this property value is added to the designer code (partial class) for the user control class, this code is not changed when you add an instance to a form. At this point the ShowPanel property (PanelVisible would be better name) is added to the forms designer code, since there is no default value it will add the code for any value it reads from the user control (or value set from form), so it will add the code to set 'ShowPanel' to False which was returned from the user control class.
Right-clicking on designer property and selecting reset corrects the problem.
This option is only enabled if the property has the DefaultValue attribute applied and current value is not default. For properties this is the only relevant default value in designer context, the data types default value is not relevant in this context, nor any default value returned by a backing field etc. So for example the Boolean default False value is not considered a default property value by the designer even if that is the default value returned from the class. It would be good practice to always specify a DefaultValue for a component property. There is of course a gotcha here, the DefaultValue does not set or retain the property value itself, it only tells designer which value should be considered the default, as instruction to whether the designer should generate additional code to set the value. So if your property had this attribute and set to False, while the underlying class property was returning True by default, then you would get the non-default True set initially when adding an instance to form. You must make sure the class definition is actually returning the default value it should in its initial state.

In your case you should apply the DefaultValue(False) to the 'ShowPanel' property and set the Pnl.Visible property to False in user controls designer.
 
Hmm... The problem is still occurring.

I think it only affects inherited controls. I've tried setting DefaultValues for all accessible designer properties, but as the problem isn't with the parent control, the property is still showing incorrectly.

The obvious work-around is "obviously" to use a shadow property, but "obviously" I shouldn't have to... As you said, everything appears logically correct.

Oh BTW, this is a class library project, could there be some kind of mix up during the library compilation?

When I add the control to a form (also belonging to the control library), the incorrect values are shown in bold, just like when you change a property of a control from its default!?!

Also (sorry if this sidetracks a little), in my parent control, I have 2 docked panels. The one on the left is set to Fill, the one on the right is set to Right AND it's visible property is set to false. When I add this control to a form, the control appears to require a re-paint before VS realises that the right panel is not visible? Whats going on!?!?? :eek:
 
Could you post the source code for a class library project that reproduces the problem? (ie as I understand, minimally a class lib with a user control containing a panel and the proxy property.)
Oh BTW, this is a class library project, could there be some kind of mix up during the library compilation?
Could be, if the control you're using in form doesn't change when you change the class lib source code and recompile then you're definately not using the correct version.
Also (sorry if this sidetracks a little), in my parent control, I have 2 docked panels. The one on the left is set to Fill, the one on the right is set to Right AND it's visible property is set to false. When I add this control to a form, the control appears to require a re-paint before VS realises that the right panel is not visible? Whats going on!?!??
I did notice you had the Invalidate() call in property setter, but didn't understand why because it shouldn't have any effect. Perhaps you can expand the sample project source to also include this 'bug', ie two panels and dock like you tried to explain (which I didn't quite get).
 
OK,

The Base UserControl is named ImageButton. Basically, its a button which allows a picture to be added and allows support for a dropdown-like panel to be toggled on or off.

2 panels worth noting. "dd" is docked to the Right and Visible=False. "pb" is docked to Fill so should occupy the controls entire area when added to the screen, but currently isn't happening - the form requires some kind of repaint to get it to work properly, so have to close the form once the control is added then re-open for VS to visually correct the problem!?!

From the Designer:
VB.NET:
    Private Sub InitializeComponent()
        Dim resources As System.ComponentModel.ComponentResourceManager = New System.ComponentModel.ComponentResourceManager(GetType(ImageButton))
        Me.bg = New System.Windows.Forms.Panel()
        Me.pb = New System.Windows.Forms.Panel()
        Me.pbIcon = New System.Windows.Forms.PictureBox()
        Me.dd = New System.Windows.Forms.Panel()
        Me.ddButton = New System.Windows.Forms.Panel()
        Me.ddIcon = New System.Windows.Forms.PictureBox()
        Me.bg.SuspendLayout()
        Me.pb.SuspendLayout()
        CType(Me.pbIcon, System.ComponentModel.ISupportInitialize).BeginInit()
        Me.dd.SuspendLayout()
        Me.ddButton.SuspendLayout()
        CType(Me.ddIcon, System.ComponentModel.ISupportInitialize).BeginInit()
        Me.SuspendLayout()
        '
        'bg
        '
        Me.bg.Controls.Add(Me.pb)
        Me.bg.Controls.Add(Me.dd)
        Me.bg.Dock = System.Windows.Forms.DockStyle.Fill
        Me.bg.Location = New System.Drawing.Point(0, 0)
        Me.bg.Name = "bg"
        Me.bg.Size = New System.Drawing.Size(25, 25)
        Me.bg.TabIndex = 17
        '
        'pb
        '
        Me.pb.Controls.Add(Me.pbIcon)
        Me.pb.Dock = System.Windows.Forms.DockStyle.Fill
        Me.pb.Location = New System.Drawing.Point(0, 0)
        Me.pb.Name = "pb"
        Me.pb.Size = New System.Drawing.Size(8, 25)
        Me.pb.TabIndex = 17
        '
        'pbIcon
        '
        Me.pbIcon.Dock = System.Windows.Forms.DockStyle.Fill
        Me.pbIcon.Location = New System.Drawing.Point(0, 0)
        Me.pbIcon.Name = "pbIcon"
        Me.pbIcon.Size = New System.Drawing.Size(8, 25)
        Me.pbIcon.SizeMode = System.Windows.Forms.PictureBoxSizeMode.StretchImage
        Me.pbIcon.TabIndex = 17
        Me.pbIcon.TabStop = False
        '
        'dd
        '
        Me.dd.BackColor = System.Drawing.Color.LightGray
        Me.dd.Controls.Add(Me.ddButton)
        Me.dd.Dock = System.Windows.Forms.DockStyle.Right
        Me.dd.Location = New System.Drawing.Point(8, 0)
        Me.dd.Name = "dd"
        Me.dd.Padding = New System.Windows.Forms.Padding(1, 0, 0, 0)
        Me.dd.Size = New System.Drawing.Size(17, 25)
        Me.dd.TabIndex = 23
        Me.dd.Visible = False
        '
        'ddButton
        '
        Me.ddButton.BackColor = System.Drawing.Color.FromArgb(CType(CType(240, Byte), Integer), CType(CType(240, Byte), Integer), CType(CType(240, Byte), Integer))
        Me.ddButton.Controls.Add(Me.ddIcon)
        Me.ddButton.Dock = System.Windows.Forms.DockStyle.Right
        Me.ddButton.Location = New System.Drawing.Point(1, 0)
        Me.ddButton.Name = "ddButton"
        Me.ddButton.Padding = New System.Windows.Forms.Padding(0, 2, 0, 0)
        Me.ddButton.Size = New System.Drawing.Size(16, 25)
        Me.ddButton.TabIndex = 20
        '
        'ddIcon
        '
        Me.ddIcon.BackgroundImage = CType(resources.GetObject("ddIcon.BackgroundImage"), System.Drawing.Image)
        Me.ddIcon.BackgroundImageLayout = System.Windows.Forms.ImageLayout.None
        Me.ddIcon.Location = New System.Drawing.Point(6, 8)
        Me.ddIcon.Name = "ddIcon"
        Me.ddIcon.Size = New System.Drawing.Size(5, 3)
        Me.ddIcon.TabIndex = 14
        Me.ddIcon.TabStop = False
        '
        'ImageButton
        '
        Me.AutoScaleDimensions = New System.Drawing.SizeF(6.0!, 13.0!)
        Me.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font
        Me.BackColor = System.Drawing.Color.Transparent
        Me.Controls.Add(Me.bg)
        Me.Cursor = System.Windows.Forms.Cursors.Hand
        Me.Name = "ImageButton"
        Me.Size = New System.Drawing.Size(25, 25)
        Me.bg.ResumeLayout(False)
        Me.pb.ResumeLayout(False)
        CType(Me.pbIcon, System.ComponentModel.ISupportInitialize).EndInit()
        Me.dd.ResumeLayout(False)
        Me.ddButton.ResumeLayout(False)
        CType(Me.ddIcon, System.ComponentModel.ISupportInitialize).EndInit()
        Me.ResumeLayout(False)

    End Sub
    Protected WithEvents bg As System.Windows.Forms.Panel
    Protected WithEvents pbIcon As System.Windows.Forms.PictureBox
    Protected WithEvents pb As System.Windows.Forms.Panel
    Friend WithEvents dd As System.Windows.Forms.Panel
    Friend WithEvents ddButton As System.Windows.Forms.Panel
    Friend WithEvents ddIcon As System.Windows.Forms.PictureBox

Code:
VB.NET:
Imports System.ComponentModel


<DefaultEvent("Click")> _
Public Class ImageButton

#Region "Appearance"
    Public Event IconChanged()
    Public Property Icon As Image
        Get
            Return pbIcon.Image
        End Get
        Set(ByVal value As Image)
            pbIcon.Image = value

            RaiseEvent IconChanged()
            Invalidate()
        End Set
    End Property

#End Region

#Region "Button"
    Public Shadows Event Click()
    Protected Sub ButtonClicked() Handles bg.Click, pb.Click, pbIcon.Click
        RaiseEvent Click()
    End Sub

#End Region

#Region "DropDown"

    <DefaultValue(False)> _
    Public Property ShowDropDown As Boolean
        Get
            Return dd.Visible
        End Get
        Set(ByVal value As Boolean)
            dd.Visible = value
            Invalidate()
        End Set
    End Property

    Public Event ToggleDropDown(ByVal ctl As ImageButton)
    Protected Sub DropDownClicked() Handles dd.Click, ddButton.Click, ddIcon.Click
        RaiseEvent ToggleDropDown(Me)
    End Sub

#End Region

End Class


The inherited UserControl is called IconButton. Basically its a button with an added label, the "Icon" can also be aligned left or right.

From the Designer:
VB.NET:
    Private Sub InitializeComponent()
        Me.lbl = New System.Windows.Forms.Label()
        Me.bg.SuspendLayout()
        CType(Me.pbIcon, System.ComponentModel.ISupportInitialize).BeginInit()
        Me.pb.SuspendLayout()
        Me.SuspendLayout()
        '
        'bg
        '
        Me.bg.Controls.Add(Me.lbl)
        Me.bg.Size = New System.Drawing.Size(120, 12)
        Me.bg.Controls.SetChildIndex(Me.pb, 0)
        Me.bg.Controls.SetChildIndex(Me.lbl, 0)
        '
        'pbIcon
        '
        Me.pbIcon.Size = New System.Drawing.Size(12, 12)
        '
        'pb
        '
        Me.pb.Dock = System.Windows.Forms.DockStyle.Left
        Me.pb.Size = New System.Drawing.Size(12, 12)
        Me.pb.Visible = False
        '
        'lbl
        '
        Me.lbl.AutoEllipsis = True
        Me.lbl.Cursor = System.Windows.Forms.Cursors.Hand
        Me.lbl.Dock = System.Windows.Forms.DockStyle.Fill
        Me.lbl.Location = New System.Drawing.Point(12, 0)
        Me.lbl.Name = "lbl"
        Me.lbl.Padding = New System.Windows.Forms.Padding(3, 0, 3, 0)
        Me.lbl.Size = New System.Drawing.Size(91, 12)
        Me.lbl.TabIndex = 24
        Me.lbl.TextAlign = System.Drawing.ContentAlignment.MiddleLeft
        '
        'IconButton
        '
        Me.AutoScaleDimensions = New System.Drawing.SizeF(6.0!, 13.0!)
        Me.Name = "IconButton"
        Me.ShowDropDown = True
        Me.Size = New System.Drawing.Size(120, 12)
        Me.bg.ResumeLayout(False)
        CType(Me.pbIcon, System.ComponentModel.ISupportInitialize).EndInit()
        Me.pb.ResumeLayout(False)
        Me.ResumeLayout(False)

    End Sub
    Protected WithEvents lbl As System.Windows.Forms.Label

The Code:
VB.NET:
Imports System.ComponentModel


Public Class IconButton

#Region "Appearance"
    Public Event CaptionChanged()
    Public Property Caption As String
        Get
            Return lbl.Text
        End Get
        Set(ByVal value As String)
            lbl.Text = value.Trim

            RaiseEvent CaptionChanged()
            Invalidate()
        End Set
    End Property

    Protected Sub ShowIcon() Handles MyBase.IconChanged
        pb.Visible = (Icon IsNot Nothing)
        Invalidate()
    End Sub

    <DefaultValue(False)> _
    Public Property IconAlignedRight As Boolean
        Get
            Return (pb.Dock = DockStyle.Right)
        End Get
        Set(ByVal value As Boolean)
            If value Then
                pb.Dock = DockStyle.Right
            Else
                pb.Dock = DockStyle.Left
            End If
        End Set
    End Property

#End Region

#Region "Button"
    Private Sub lblText_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lbl.Click
        ButtonClicked()
    End Sub

#End Region


With regards to using the Invalidate, I presumed (probably incorrectly :D) that whenever something is changed visually with the control, the Invalidate repaints the control???
 
Hmm... I'm now getting a Value cannot be null error from trying to Handle an event from the Parent control!!

Arrggghh!! Think its time for a VS re-install...
 
In ImageButton class dd.Visible = False and ShowDropDown therefore returns False also.
In IconButton class ShowDropDown is initialized to True in InitializeComponent, ie ShowDropDown property has been set to True in designer.
So if you add a ImageButton control to a form ShowDropDown initial state will be False, but if you add a IconButton control the initial state will be True as defined by that class.

edit, seems Designer for IconButton always read ShowDropDown as True and reset this to non-default True after each build, weird. I also tried creating an instance in code and ShowDropDown then returned False as it should. Looks like a bug in Designer if I'm not overlooking something here. What seems to fix it is to set ShowDropDown to False in IconButton class, this will remove the "Me.ShowDropDown = True" line from InitializeComponent. When rebuilding the library the property still shows up as True in designer, but it will not add that code line. So when consuming the control, ie adding it to a form it will behave correctly.
2 panels worth noting. "dd" is docked to the Right and Visible=False. "pb" is docked to Fill so should occupy the controls entire area when added to the screen, but currently isn't happening - the form requires some kind of repaint to get it to work properly, so have to close the form once the control is added then re-open for VS to visually correct the problem!?!
I added some color for debug (red, green and blue for bg,pb,dd) and this enabled me to see that problem. Moving or resizing the control forces a repaint in designer also, debugging shows the pb hasn't been resized yet, so a quickfix is to add the Sub New constructor and after the InitializeComponent call do this to force the fill immediately:
VB.NET:
Me.pb.Size = Me.Size
 
Thanks for that John.

Changed the modifier of "dd" to Friend, so inherited controls NEED to set the ShowDropDown to toggle the visibility of the panel.

Thanks for the quick fix on the repaint problem. Also, when exactly should Invalidate be called? When I was googling for help, it appeared everywhere was using it to repaint the form?

Any ideas on the problem with handling the event on a parent form? When I add the handler and compile it, everything seems fine, but trying to view the control visually in VS or a subsequent attempt to rebuild the project, just causes a "Value cannot be null" error. Selecting the ignor message in VS, just removes the handler?

Re-installing didn't solve any of the Designer problems... :(
 
Thanks for the quick fix on the repaint problem.
It wasn't a repaint problem, it was a resize problem.
Also, when exactly should Invalidate be called? When I was googling for help, it appeared everywhere was using it to repaint the form?
Invalidate is normally only used if you custom paint an area, and when what is to be painted has changed, to notify the control it should repaint. I don't see it has any use in these classes. In the search of the problem I was also trying to force repaint after control was initialized, until I realized the panel just had not resized.
Any ideas on the problem with handling the event on a parent form? When I add the handler and compile it, everything seems fine, but trying to view the control visually in VS or a subsequent attempt to rebuild the project, just causes a "Value cannot be null" error. Selecting the ignor message in VS, just removes the handler?
I'm not seeing this. Shadowing is something I'd be careful with though, because it breaks the inheritance, and you could easily get member mixups in different contexts where the object may be handled as a base type. For the events I'd rather leave the control Click event as it is (ie no shadow) and route the child controls Click events to it by calling the protected base OnClick like this:
VB.NET:
MyBase.OnClick(EventArgs.Empty)
While I don't get the error you describe I think it will go away when you fix the event handling like this.
 
The event handler is something different. Shouldn't be any confusion...

In the parent control:
VB.NET:
    Public Event CaptionChanged()
    Public Property Caption As String
        Get
            Return lbl.Text
        End Get
        Set(ByVal value As String)
            lbl.Text = value.Trim

            RaiseEvent CaptionChanged()
            Invalidate()
        End Set
    End Property

And the inherited control:
VB.NET:
    <DefaultValue(False)> _
    Public Property ShowLabel As Boolean
        Get
            Return lbl.Visible
        End Get
        Set(ByVal value As Boolean)
            lbl.Visible = value
        End Set
    End Property

    Private Sub OnCaptionChanged() [COLOR="red"]Handles MyBase.CaptionChanged[/COLOR]
        ShowLabel = (lbl.Text.Length > 0)
    End Sub

Can't see anything logically wrong with this either, yet the designer refuses the handling clause.
 
I don't see any problem with that, nor getting any error.
The event should be defined like this though:
VB.NET:
Public Event CaptionChanged As EventHandler
 
Back
Top