.netpropertygridtypeconverteruitypeeditor

Derive PropertyGrid dropdown list from property that is an object class, not just a string


How do I have properties which are objects (not just a string) dropdown in property grid? I got so far, but then got stuck! Consider the code below:

Public Class mOrganisation

    Public Property ID as String
    Public Property Name as String

End Class

Public Class mSystem

    Public Property ID as string
    Public Property Name as String
    Public Property Developer as mOrganisation

     Public Overrides Function ToString() As String
        Return Name
    End Function

End Class

Public Class mGame

    Public Property ID as string
    Public Property Name as String

    <TypeConverter(GetType(SystemConverter))>
    Public Property System as mSystem

End Class

Public Class Main

    Public Systems as List(of mSystem) = [...list gatehring code here]

End Class


Public Class SystemConverter
Inherits TypeConverter

    Public Overrides Function GetStandardValuesSupported(ByVal context As ITypeDescriptorContext) As Boolean
        Return True
    End Function

    Public Overrides Function GetStandardValuesExclusive(ByVal context As ITypeDescriptorContext) As Boolean
        Return False
    End Function

    Public Overrides Function GetStandardValues(ByVal context As ITypeDescriptorContext) As TypeConverter.StandardValuesCollection
        Return New StandardValuesCollection(Main.Systems)
    End Function

End Class

mOrgnisation is there just to introduce some complication to the mSystem Class. Now this code does drop down the values:

enter image description here

But when I select a value, I get a PropertyGrid error "Object of type 'System.String' cannot be converted to type 'mSystem'"

This had led me down a rabbit hole, particularly trying to apply various permutations of Convert From and Convert To. However, I couldn't find a decent solution. One attempt via ConvertFrom had the drop down menu loading very slowly, one item at a time (I think it was being fired for each and every item).

I would make a custom UITypeEditor but I can't find a way to get the PropertyGrid inherent resize method/handle like on the standard dropdown (and tried coding my own resize routine, but proved sticky and flickery I think because of the interaction of PropGrid + the control)

What is the best/most elegant way to achieve this?


Solution

  • Eventually ditched TypeConverter and found a solution. Like most coding nightmares, it was 5 simple lines put in exactly the right place! The key was accessing the PropertyGrid built-in resize handle. You can adjust the code below to include building in handling custom objects, as value is any object. However, example below is a string, for simplicity:

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

    Here's how to achieve:

    Step 1: Create a new UITypeEditor

    This interfaces the property to the custom editor. value contains the value passed from the property. It can be of any object type. You can therefore pass this to your custom control to inform any editing functions therein.

    Create a new class:

    Imports System.ComponentModel
    Imports System.Drawing.Design
    Imports System.Windows.Forms.Design
    
    Public Class TestUIEditor
        Inherits UITypeEditor
    
        ' Indicate that we display a dropdown.
        Public Overrides Function GetEditStyle(ByVal context As ITypeDescriptorContext) _
            As UITypeEditorEditStyle
    
            Return UITypeEditorEditStyle.DropDown
    
        End Function
    
        Public Overrides Function EditValue(ByVal context As ITypeDescriptorContext, ByVal provider As IServiceProvider, ByVal value As Object) As Object
    
            ' Get an IWindowsFormsEditorService object.
            Dim editor_service As IWindowsFormsEditorService = CType(provider.GetService(GetType(IWindowsFormsEditorService)), IWindowsFormsEditorService)
            If editor_service Is Nothing Then
                Return MyBase.EditValue(context, provider, value)
            End If
    
            'Setup the editor
            Dim editorService As IWindowsFormsEditorService = Nothing
            If provider IsNot Nothing Then
                editorService = TryCast(provider.GetService(GetType(IWindowsFormsEditorService)), IWindowsFormsEditorService)
            End If
    
            If editorService IsNot Nothing Then
    
                Dim testUI As New TestPropGridUI(editorService)
                'Populate the existing value to the dropdown control
                testUI.Text = value
                'Drop down the control
                editorService.DropDownControl(testUI)
                'Update property value once dropdown closed
                value = testUI.Text
    
            End If
    
            Return value
    
        End Function
    
        Public Overrides ReadOnly Property IsDropDownResizable As Boolean
            Get
                'Ensures control is resizable
                Return True
            End Get
        End Property
    
    End Class
    

    Step 2: Create your custom editor:

    In this example, I have just used a custom class that inherits TextBox . However, you can use any controls, even a custom  UserControl.

    Imports System.Windows.Forms.Design
    
    Public Class TestPropGridUI
        Inherits TextBox
    
        ' The editor service displaying this control.
        Private m_EditorService As IWindowsFormsEditorService
    
        Public Sub New(ByVal editor_service As IWindowsFormsEditorService)
            MyBase.New()
    
            m_EditorService = editor_service
            Dock = DockStyle.Fill
    
        End Sub
    
        Public Sub returnPressed(sender As Object, e As KeyEventArgs) Handles Me.KeyDown
    
            'Closes the dropdown when Enter pressed
            If e.KeyCode = Keys.Enter Then
                m_EditorService.CloseDropDown()
            End If
    
        End Sub
    
    End Class
    

    Note m_EditorService.CloseDropDown . Put this wherever you want the dropdown to close itself (e.g. after a value selection).

    Step 3: Tell your class property to use the custom editor:

    Public Class TestObject
    
        Public Property ID as String
        Public Property Quantity as Integer
    
        <Editor(GetType(TestUIEditor), GetType(UITypeEditor))>
        Public Property TestText As String
    
    End Class
    

    And that's it!