This approach works for everything but collections:
Collections are displayed like this:
So even though they are expandable, there isn't much use for them inside property grid.
Here is an example of what I am looking for (screenshot taken from here):
The linked article also contains some code, which would make this happen, but it requires modifying the original class. Between it and my previous question, I came up with some ideas, but I'm not very fluent in using System.ComponentModel
namespace.
Here is a reduced test case (custom class with one property of collection type, which contains one object of custom type, which has one string property):
Imports System.ComponentModel
Public Class Form1
Sub New()
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
Me.AssignTypeConverter(Of MyCustomClassCollection, ExpandableObjectConverter)()
End Sub
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
Dim collection As New MyCustomClassCollection
collection.Add(New MyCustomClass With {.MyCustomProperty = "Hello"})
Dim container As New MyCustomClassCollectionContainer(collection)
Me.PropertyGrid1.SelectedObject = container
End Sub
Private Sub AssignTypeConverter(Of IType, IConverterType)()
System.ComponentModel.TypeDescriptor.AddAttributes(GetType(IType),
New System.ComponentModel.TypeConverterAttribute(GetType(IConverterType)))
End Sub
End Class
Public Class MyCustomClass
Public Property MyCustomProperty As String
End Class
Public Class MyCustomClassCollection : Inherits System.Collections.ObjectModel.Collection(Of MyCustomClass)
End Class
Public Class MyCustomClassCollectionContainer
Dim _items As MyCustomClassCollection
Public ReadOnly Property Items As MyCustomClassCollection
Get
Return _items
End Get
End Property
Sub New(items As MyCustomClassCollection)
_items = items
End Sub
End Class
Proposed solution (pseudo-code, does not compile)
Imports System.ComponentModel
Public Class MyCustomClassTypeDescriptor : Inherits ExpandableObjectConverter
Public Overrides Function GetProperties(context As ITypeDescriptorContext,
value As Object, attributes() As Attribute) _
As PropertyDescriptorCollection
Dim pds As New PropertyDescriptorCollection(Nothing)
Dim lst As IList(Of Object) = DirectCast(value, IList)
For i As Integer = 0 To lst.Count - 1
Dim item As MyCustomClass = DirectCast(lst.Item(i), MyCustomClass)
'compile error - abstract class cannot be instantiated
Dim pd As New PropertyDescriptor(item)
pds.Add(pd)
Next
Return pds
End Function
End Class
And then apply this custom object converter at runtime.
Is it going to work like this? What am I missing? Any suggestions are welcome!
Note: The above is VB.NET, but if you speak C#, feel free to use it.
Too long to continue in the comments, but how about something like this - a custom ExpandableObjectConverter
that turns each collection item into a property (ItemX), and a custom property descriptor that gets the appropriate item.
Public Class MyCollectionTypeDescriptor(Of TColl As Collection(Of TItem), TItem)
Inherits ExpandableObjectConverter
Public Overrides Function GetProperties(context As ITypeDescriptorContext, value As Object, attributes() As Attribute) As PropertyDescriptorCollection
Dim coll = DirectCast(value, TColl)
Dim props(coll.Count - 1) As PropertyDescriptor
For i = 0 To coll.Count - 1
props(i) = New MyCollectionPropertyDescriptor(Of TColl, TItem)("Item" & CStr(i))
Next
Return New PropertyDescriptorCollection(props)
End Function
End Class
Public Class MyCollectionPropertyDescriptor(Of TColl, TItem)
Inherits PropertyDescriptor
Private _index As Integer = 0
Public Sub New(name As String)
MyBase.New(name, Nothing)
Dim indexStr = Regex.Match(name, "\d+$").Value
_index = CInt(indexStr)
End Sub
Public Overrides Function CanResetValue(component As Object) As Boolean
Return False
End Function
Public Overrides ReadOnly Property ComponentType As Type
Get
Return GetType(TColl)
End Get
End Property
Public Overrides Function GetValue(component As Object) As Object
Dim coll = DirectCast(component, Collection(Of TItem))
Return coll(_index)
End Function
Public Overrides ReadOnly Property IsReadOnly As Boolean
Get
Return True
End Get
End Property
Public Overrides ReadOnly Property PropertyType As Type
Get
Return GetType(TItem)
End Get
End Property
Public Overrides Sub ResetValue(component As Object)
End Sub
Public Overrides Sub SetValue(component As Object, value As Object)
End Sub
Public Overrides Function ShouldSerializeValue(component As Object) As Boolean
Return False
End Function
End Class
You can associate everything to your classes using:
Me.AssignTypeConverter(Of MyCustomClass, ExpandableObjectConverter)()
Me.AssignTypeConverter(Of MyCustomClassCollection, MyCollectionTypeDescriptor(Of MyCustomClassCollection, MyCustomClass))()
That should list each item in the main property grid, and each item will be expandable inline. Is that what you are looking for?