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

387 Views Asked by At

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?

2

There are 2 best solutions below

0
stigzler On BEST ANSWER

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!

1
SHoaib Riaz On

There are some ways to to tell system "What to pick from object"

1) Use DisplayMemberPath on the ComboBox:

<ComboBox ItemsSource="{Binding Path=mSystem}" 
      DisplayMemberPath="Name"/>

2) Set ItemTemplate on the ComboBox. This is like #1, except allows you to define a template to display:

<ComboBox ItemsSource="{Binding Path=mSystem}">
<ComboBox.ItemTemplate>
    <DataTemplate>
        <Border BorderBrush="Green" BorderThickness="1" Padding="5">
            <TextBlock Text="{Binding Path=Name,StringFormat='Name: {0}'}" />
        </Border>
    </DataTemplate>
</ComboBox.ItemTemplate>

3) Add a DataTemplate to the XAML resources. This is useful for associating a given class:

<UserControl xmlns:local="CLASS_CONTEXT_HERE">
<UserControl.Resources>
    <DataTemplate DataType="local:mSystem">
        <TextBlock Text="{Binding Name}" />
    </DataTemplate>
</UserControl.Resources>

4)If you want to display NAME and ID :

Public Overrides Function ToString() As String
    Return string.Format("{0} ({1})", Name, ID)
End Function