I had the same problem: the editor control appeared to work, but the values wouldn't persist. Building on Marc's answer and some further research I've now got it running.
Although I needed this feature for editing my own controls at DesignTime, I tested it using a demo project with a PropertyGrid control after reading the following quote from Microsoft:
When you develop your custom UITypeEditor, it is recommended that you
set the build number to increment with each build. This prevents
older, cached versions of your UITypeEditor from being created in the
design environment.
I think that was actually an issue that caused problems while trying to implement Marc's solution. Testing in a separate project is also helpful because you can use Step-By-Step-Debugging to see what happens in the control, editor and converter.
The basic steps are:
- Create the type (here: MyFlags) and the property (here: MyProperty), adding some extra attributes to the
latter.
- Implement the control (here: EnumEditorControl) to be used for editing.
- Implement an
UITypeEditor
class (here: EnumEditor); this is connected to the property using the Editor
attribute. It is also creates and manages the instance of our EnumEditorControl.
- Implement a
TypeConverter
class (here: EnumConverter); this is also connected to the property using an attribute (TypeConverter
) and handles converting the value into a string for display in the property grid.
And now a walkthrough with the relevant code snippets:
Define an Enum
using the Flags
attribute.
<Flags>
Public Enum MyFlags
Flag1 = 2 ^ 0
Flag2 = 2 ^ 1
Flag3 = 2 ^ 2
End Enum
Define the property for the custom control.
Public Property MyProperty() As MyFlags
...
End Property
This property will later have attributes added to it once we've created the other components we need (see #6).
Create the desired control that is to be used for editing the
property.
Imports System.Windows.Forms.Design
Public Class EnumEditorControl(Of T As Structure)
Public Sub New(value As Long, editorService As IWindowsFormsEditorService)
'This call is required by the Windows.Forms Form Designer.
InitializeComponent()
_value = value
_editorService = editorService
For Each item As Long In [Enum].GetValues(GetType(T))
Me.CheckedListBox1.Items.Add([Enum].GetName(GetType(T), item), (_value And item) = item)
Next
End Sub
Private _value As Long
Public Property Value As Long
Get
Return _value
End Get
Set(value As Long)
_value = value
End Set
End Property
Private _editorService As IWindowsFormsEditorService
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim v As Long = 0
For Each item As String In Me.CheckedListBox1.CheckedItems
v = (v Or [Enum].Parse(GetType(T), item))
Next
_value = v
Me._editorService.CloseDropDown()
End Sub
End Class
The above is the class' code without the designer.vb portions. The important parts are: a constructor with value
and editorService
parameters, filling the control with the currently selected values (in the constructor) and implementing a "commit action" where the Value
property of the control is updated (depending on the selection made) and finally calling Me._editorService.CloseDropDown()
.
Define a generic UITypeEditor.
Imports System.ComponentModel
Imports System.Drawing.Design
Imports System.Windows.Forms.Design
Public Class EnumEditor(Of T As Structure)
Inherits UITypeEditor
Public Overrides Function GetEditStyle(context As ITypeDescriptorContext) As UITypeEditorEditStyle
Return UITypeEditorEditStyle.DropDown
End Function
Public Overrides ReadOnly Property IsDropDownResizable() As Boolean
Get
Return True
End Get
End Property
Public Overrides Function EditValue(context As ITypeDescriptorContext, provider As IServiceProvider, value As Object) As Object
Dim svc As IWindowsFormsEditorService = DirectCast(provider.GetService(GetType(IWindowsFormsEditorService)), IWindowsFormsEditorService)
Dim items As Long = CLng(value)
If svc IsNot Nothing Then
Dim c As New EnumEditorControl(Of T)(value, svc)
svc.DropDownControl(c)
value = c.Value
End If
Return CType(value, T)
End Function
End Class
The core part here is the override of EditValue
where an instance of our editor control (see #3) is created and shown using svc.DropDownControl(c)
. And, finally, retrieving the selected value from our control's property and returning it.
Define a generic TypeConverter used for converting the actual value into a string representation to be used in the property explorer.
Imports System.ComponentModel
Imports System.Drawing.Design
Imports System.Text
Imports System.Windows.Forms.Design
Class EnumConverter(Of T As Structure)
Inherits TypeConverter
Public Overrides Function CanConvertTo(context As ITypeDescriptorContext, destinationType As Type) As Boolean
Return destinationType = GetType(String) OrElse MyBase.CanConvertTo(context, destinationType)
End Function
Public Overrides Function ConvertTo(context As ITypeDescriptorContext, culture As System.Globalization.CultureInfo, value As Object, destinationType As Type) As Object
If destinationType = GetType(String) Then
Dim items As Integer = CLng(value)
If items = 0 Then
Return ""
End If
Dim values As New List(Of String)
For Each item As Integer In [Enum].GetValues(GetType(T))
If (items And item) = item Then
values.Add([Enum].GetName(GetType(T), item))
End If
Next
Return String.Join(", ", values)
End If
Return MyBase.ConvertTo(context, culture, value, destinationType)
End Function
End Class
Tie it all together by adding the following attributes to the defined property (see #2).
<Browsable(True),
DefaultValue(MyFlags.Flag1),
Editor(GetType(EnumEditor(Of MyFlags)),
GetType(System.Drawing.Design.UITypeEditor)),
TypeConverter(GetType(EnumConverter(Of MyFlags)))
>
Public Property MyProperty() As MyFlags
...
End Property
Note: Referring to the "zero value" mentioned in the question: this solution does not take that into account, but it could easily be modified to do so.