1

I have a Property on my User Control that is hidden in design mode like so

<Browsable(False)>
Public Property MyProperty As Object

What i would like to do is change it to True depending on the value of another Property.

Something like

Private _otherProperty As Boolean
Public Property OtherProperty() As Boolean
    Get
        Return _otherProperty
    End Get
    Set(ByVal value As Boolean)
        _otherProperty = value
        If value = True Then
            'Set MyProperty Browsable Attribute True here
        Else
            'Set MyProperty Browsable Attribute False here
        End If
    End Set
End Property

Is what I had in mind.

Basically i want a Property to only be available at design time when another Property is set to True but can't find a way to change the attribute value in design mode.

Pete
  • 469
  • 7
  • 18
  • 3
    No, it is not possible. An attribute value is part of the type definition. It's not data stored in individual instances. – jmcilhinney Jun 13 '19 at 14:34
  • OK thank you, I guess i'll just have to settle for having it visible all the time. At least i can give it a description to say it's works in conjuction with another property when that property is set. – Pete Jun 13 '19 at 20:10
  • You can have validation on the property such that it will not be possible to set it if that other property value isn't set appropriately. I'm guessing you've seen error messages pop up when setting properties inappropriately at design-time. – jmcilhinney Jun 13 '19 at 23:05

2 Answers2

2

The properties displayed in the WinForm designer's PropertyGrid are managed via PropertyDescriptors. You can control the descriptors returned by the inspection mechanisms by a couple of different ways. A relatively simple (while tedious) way is have your class implement the ICustomTypeDescriptor Interface.

Let us assume your usercontrol class is defined as follows:

Imports System.ComponentModel
Public Class DemoUC
  Public Sub New()
    InitializeComponent()
  End Sub

  <RefreshProperties(RefreshProperties.All)>
  Public Property OtherProperty As Boolean

  <Browsable(False)>
  Public Property MyProperty As String
End Class

Notice the RefreshPropertiesAttribute decorating OtherProperty. This will tell the PropertyGrid to pull all properties each time this property changes. This is needed so that logic to display the MyProperty property when OtherProperty is true will work.

In another class file, add the following partial class that implements the ICustomTypeDescriptor Interface.

Imports System.ComponentModel

Partial Public Class DemoUC : Implements ICustomTypeDescriptor

  Public Function GetAttributes() As AttributeCollection Implements ICustomTypeDescriptor.GetAttributes
    Return TypeDescriptor.GetAttributes(Me, True)
  End Function

  Public Function GetClassName() As String Implements ICustomTypeDescriptor.GetClassName
    Return TypeDescriptor.GetClassName(Me, True)
  End Function

  Public Function GetComponentName() As String Implements ICustomTypeDescriptor.GetComponentName
    Return TypeDescriptor.GetComponentName(Me, True)
  End Function

  Public Function GetConverter() As TypeConverter Implements ICustomTypeDescriptor.GetConverter
    Return TypeDescriptor.GetConverter(Me, True)
  End Function

  Public Function GetDefaultEvent() As EventDescriptor Implements ICustomTypeDescriptor.GetDefaultEvent
    Return TypeDescriptor.GetDefaultEvent(Me, True)
  End Function

  Public Function GetDefaultProperty() As PropertyDescriptor Implements ICustomTypeDescriptor.GetDefaultProperty
    Return TypeDescriptor.GetDefaultProperty(Me, True)
  End Function

  Public Function GetEditor(editorBaseType As Type) As Object Implements ICustomTypeDescriptor.GetEditor
    Return TypeDescriptor.GetEditor(Me, editorBaseType, True)
  End Function

  Public Function GetEvents() As EventDescriptorCollection Implements ICustomTypeDescriptor.GetEvents
    Return TypeDescriptor.GetEvents(Me, True)
  End Function

  Public Function GetEvents(attributes() As Attribute) As EventDescriptorCollection Implements ICustomTypeDescriptor.GetEvents
    Return TypeDescriptor.GetEvents(Me, attributes, True)
  End Function

  Public Function GetProperties() As PropertyDescriptorCollection Implements ICustomTypeDescriptor.GetProperties
    Return GetProperties({})
  End Function

  Public Function GetProperties(attributes() As Attribute) As PropertyDescriptorCollection Implements ICustomTypeDescriptor.GetProperties
    Dim basePDs As New PropertyDescriptorCollection(Nothing, False)
    For Each pd As PropertyDescriptor In TypeDescriptor.GetProperties(Me, attributes, True)
      basePDs.Add(pd)
    Next
    If Me.DesignMode AndAlso Me.OtherProperty Then
      Dim pd As PropertyDescriptor = TypeDescriptor.GetProperties(Me, True).Cast(Of PropertyDescriptor).Where(Function(desc As PropertyDescriptor) desc.Name.Equals(NameOf(Me.MyProperty))).FirstOrDefault()
      If basePDs.Contains(pd) Then
        basePDs.Remove(pd)
      End If
      basePDs.Add(New BrowsableDescriptor(pd))
    End If
    Return basePDs
  End Function

  Public Function GetPropertyOwner(pd As PropertyDescriptor) As Object Implements ICustomTypeDescriptor.GetPropertyOwner
    Return Me
  End Function

  Class BrowsableDescriptor : Inherits PropertyDescriptor
    Private src As PropertyDescriptor
    Public Sub New(src As PropertyDescriptor)
      MyBase.New(src.Name, Nothing)
      Me.src = src
      Dim attribs As New List(Of Attribute)
      For Each att As Attribute In src.Attributes
        If TypeOf att Is BrowsableAttribute Then Continue For
        attribs.Add(att)
      Next
      attribs.Add(BrowsableAttribute.Yes)
      MyBase.AttributeArray = attribs.ToArray
    End Sub

    Public Overrides ReadOnly Property IsBrowsable As Boolean
      Get
        Return True
      End Get
    End Property

    Public Overrides ReadOnly Property ComponentType As Type
      Get
        Return src.ComponentType
      End Get
    End Property

    Public Overrides ReadOnly Property IsReadOnly As Boolean
      Get
        Return src.IsReadOnly
      End Get
    End Property

    Public Overrides ReadOnly Property PropertyType As Type
      Get
        Return src.PropertyType
      End Get
    End Property

    Public Overrides Sub ResetValue(component As Object)
      src.ResetValue(component)
    End Sub

    Public Overrides Sub SetValue(component As Object, value As Object)
      src.SetValue(component, value)
    End Sub

    Public Overrides Function CanResetValue(component As Object) As Boolean
      Return src.CanResetValue(component)
    End Function

    Public Overrides Function GetValue(component As Object) As Object
      Return src.GetValue(component)
    End Function

    Public Overrides Function ShouldSerializeValue(component As Object) As Boolean
      Return src.ShouldSerializeValue(component)
    End Function
  End Class

End Class

The majority of the implementation just returns what the base TypeDescriptor would provide. The GetProperties function is where the logic is implemented to replace the non-browsable PropertyDescriptor for the MyProperty property with a browsable one takes place.

Once this is code is compiled, the DemoUC control will display like this in the PropertyGrid. Observe that MyProperty is shown/Hidden based on the value of OtherProperty.

ConditionalProperty GIF

TnTinMn
  • 11,522
  • 3
  • 18
  • 39
  • Thank you this, I really appreciate you taking the time to help me out. I have got most of the way there with the answer provided to me first but having slight trouble with re opening the `Form` again. So I will give this answer a go ASAP and report back when I can. I see similarities between the 2 answers but yours does provide more code so perhaps it's what I am missing. – Pete Jun 15 '19 at 19:29
  • @Pete, as I stated in the answer, there are many ways to control the PropertyDescriptors. If you encounter any issues, let me know as a different approach may be warranted. Minor note, if you look at the time stamps, you will see this was the first answer that you received. – TnTinMn Jun 15 '19 at 20:05
  • My apologies, I only see 'answered yestersday' on both and i assumed the one at the top was first. – Pete Jun 15 '19 at 22:48
  • It may of taken me a while to get back around to doing this but thank you so much! Works perfectly and have had no issues re opening the form after setting the properties. – Pete Jun 25 '19 at 15:48
0

An implementation besed on a Custom Designer, derived from ControlDesigner, associated with a UserControl.

Overriding the ControlDesigner PostFilterProperties method we can remove existing properties from the IDictionary of properties referenced by the method:

Dim propDescriptor = DirectCast(properties("PropertyName"), PropertyDescriptor)
properties.Remove("PropertyName");

Overriding the PreFilterProperties method, it's possible to add a property (or add back, if the property has been previously removed), using a PropertyDescriptor:

properties.Add("PropertyName", propDescriptor)

The property can be removed on a condition set by the value of another property, also verifying the DesignMode status of the UserControl (or Control):

If (Not (Me.Control.Site.DesignMode) OrElse DirectCast(Me.Control, MyUserControl).SomeProperty) Then
    '(...)
End If

The Property which causes the change in the properties collection, must be decorated with a , set to RefreshProperties.All.

<RefreshProperties(RefreshProperties.All)>
Public Property MyPropertyA As Boolean = True

Sample behaviour:

UserControlDesigner

Imports System.ComponentModel
Imports System.Diagnostics
Imports System.Windows.Forms
Imports System.Windows.Forms.Design

<Designer(GetType(MyUserControlDesigner))>
Partial Public Class MyUserControl
    Inherits UserControl

    <RefreshProperties(RefreshProperties.All)>
    Public Property MyPropertyA As Boolean = True
    Public Property MyPropertyB As Boolean
End Class


<DebuggerDisplay("MyUserControlDesigner", Name:="MyUserControlDesigner")>
Public Class MyUserControlDesigner
    Inherits ControlDesigner
    Private propDescriptor As PropertyDescriptor = Nothing

    Protected Overrides Sub PreFilterProperties(properties As System.Collections.IDictionary)
        MyBase.PreFilterProperties(properties)
        If Not Me.Control.Site.DesignMode OrElse DirectCast(Me.Control, MyUserControl).MyPropertyA Then
            If Not properties.Contains("MyPropertyB") Then
                properties.Add("MyPropertyB", propDescriptor)
            End If
        End If
    End Sub

    Protected Overrides Sub PostFilterProperties(properties As System.Collections.IDictionary)
        If Me.Control.Site.DesignMode AndAlso Not DirectCast(Me.Control, MyUserControl).MyPropertyA Then
            If properties.Contains("MyPropertyB") Then
                propDescriptor = DirectCast(properties("MyPropertyB"), PropertyDescriptor)
                properties.Remove("MyPropertyB")
            End If
        End If
        MyBase.PostFilterProperties(properties)
    End Sub
End Class
Jimi
  • 29,621
  • 8
  • 43
  • 61
  • Thank you so much! This has worked except for one thing. When set `PropertyA` to `True` and `PropertyB` is revealed and set, I save the `Form` and close it. The next time I open the `Form` I get a designer error "The type 'MyUserControl" has no property named 'PropertyB'". Although in the designer code there is no error and `PropertyB` does exist in the intellisense in the designer code. I have to remove the line where it is setting `PropertyB` to be able to view the `Form` in design mode again. – Pete Jun 15 '19 at 19:22
  • Not in the designer. When `PropertyA = False`, `PropertyB` is not serialized, so it's not in the designer's code. It's maybe in your code. You need to check whether `PropertyA = True` before setting/using `PropertyB`. Both in the Form and the UserControl: when `PropertyA = False`, `PropertyB` doesn't exist. – Jimi Jun 15 '19 at 20:32