Custom property editor (UITypeEditor) not working properly with class
Hi,
I want to give some property of a custom control I created a custom property editor window. What I mean by that is that, in the property grid, the property is not directly editable but has a "[...]" button next to it. Clicking that button will open an editor form which allows you to edit the value.
http://i47.tinypic.com/303b8y0.jpg
In the screenshot you can see a very simple example, where 'MyProperty' is just a String property on a custom TextBox class:
vb.net Code:
Imports System.ComponentModel
Imports System.Drawing.Design
Public Class CustomTextBox
Inherits TextBox
Private _MyProperty As String
<Editor(GetType(CustomPropertyEditor), GetType(UITypeEditor))> _
Public Property MyProperty() As String
Get
Return _MyProperty
End Get
Set(ByVal value As String)
_MyProperty = value
End Set
End Property
End Class
The CustomPropertyEditor class, which is set as this property's EditorAttribute, is slightly involved, so I left out some of the irrelevant parts. It inherits from UITypeEditor and takes care of showing a modal form when the [...] button is clicked.
vb.net Code:
Imports System.Drawing
Imports System.Drawing.Design
Imports System.Windows.Forms
Imports System.Windows.Forms.Design
Imports System.ComponentModel
Public Class CustomPropertyEditor
Inherits System.Drawing.Design.UITypeEditor
Private m_editorForm As TextEditorForm
Private Function GetEditControl(ByVal PropertyName As String, ByVal CurrentValue As Object) As System.Windows.Forms.Control
Dim str As String = CStr(CurrentValue)
m_editorForm = New TextEditorForm()
m_editorForm.Value = str
Return m_editorForm
End Function
Private Function GetEditedValue(ByVal EditControl As System.Windows.Forms.Control, ByVal PropertyName As String, ByVal OldValue As Object) As Object
If m_editorForm Is Nothing _
OrElse m_editorForm.DialogResult = DialogResult.Cancel Then
Return OldValue
Else
Return m_editorForm.Value
End If
End Function
Public Overrides Function GetEditStyle(ByVal context As System.ComponentModel.ITypeDescriptorContext) As UITypeEditorEditStyle
Return UITypeEditorEditStyle.Modal
End Function
'some stuff omitted
End Class
Basically, the GetEditorControl function returns a new instance of the TextEditorForm (which is the modal form you can see on the screenshot, used to edit the property), but first sets its Value property (the text in its TextBox) to the current value of the MyProperty property.
The GetEditedValue function is used to return the result of the edit (the text in the textbox, which is the Value property) back to the property.
This works just fine. I can edit the String property and the result is stored and shown forever. Great.
Then, I wanted something more involved. Instead of just a String property, I have a property that returns an instance of some nested class, which in turn carries a whole load of other properties.
So now the CustomTextbox class becomes a little more involved. It has one nested class (called NestedClass), with two string properties (as an example). It exposes an instance of this class via a NestedClassInstance property.
vb.net Code:
Imports System.ComponentModel
Imports System.Drawing.Design
Public Class CustomTextBox
Inherits TextBox
Public Sub New()
_NestedClassInstance = New NestedClass()
End Sub
Private _NestedClassInstance As NestedClass
<DesignerSerializationVisibility(DesignerSerializationVisibility.Content)> _
<Editor(GetType(CustomPropertyEditor), GetType(UITypeEditor))> _
Public Property NestedClassInstance() As NestedClass
Get
Return _NestedClassInstance
End Get
Set(ByVal value As NestedClass)
_NestedClassInstance = value
End Set
End Property
Public Class NestedClass
Private _MyProperty1 As String
Public Property MyProperty1() As String
Get
Return _MyProperty1
End Get
Set(ByVal value As String)
_MyProperty1 = value
End Set
End Property
Private _MyProperty2 As String
Public Property MyProperty2() As String
Get
Return _MyProperty2
End Get
Set(ByVal value As String)
_MyProperty2 = value
End Set
End Property
End Class
End Class
This time, the Editor attribute is set on the NestedClassInstance property, because I want to edit that property directly via the editor form.
(Please note that, if I get rid of the Editor attribute, and replace it with an ExpandableObject TypeConverter attribute, (so that the property is expandable in the property grid), I can get and set the two string properties just fine, simply using the property grid, and they are stored like they should be.)
Now, in order for my editor form to be able to change this NestedClassInstance property, I gave it a PropertyGrid control instead of the TextBox. I also had it take an instance of the CustomTextBox.NestedClass in its constructor, so that the PropertyGrids SelectedObject can be set to that instance, so that it's editable. Finally, there is now a NestedClassInstance on the editor form as well, which simply returns the instance of the NestedClass which was passed again.
vb.net Code:
Imports System.Windows.Forms
Public Class TextEditorForm
Private _NestedClassInstance As CustomTextBox.NestedClass
Public Sub New(ByVal nc As CustomTextBox.NestedClass)
' This call is required by the Windows Form Designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
_NestedClassInstance = nc
PropertyGrid1.SelectedObject = nc
End Sub
Public ReadOnly Property NestedClassInstance() As CustomTextBox.NestedClass
Get
Return _NestedClassInstance
End Get
End Property
Private Sub OK_Button_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles OK_Button.Click
Me.DialogResult = System.Windows.Forms.DialogResult.OK
Me.Close()
End Sub
Private Sub Cancel_Button_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Cancel_Button.Click
Me.DialogResult = System.Windows.Forms.DialogResult.Cancel
Me.Close()
End Sub
End Class
Simple enough I hope.
Now finally, I also needed a slight change in the CustomPropertyEditor UITypeEditor, mainly just to pass the CurrentValue (cast to a NestedClass class) to the editor form, and to get it back again:
vb.net Code:
Imports System.Drawing
Imports System.Drawing.Design
Imports System.Windows.Forms
Imports System.Windows.Forms.Design
Imports System.ComponentModel
Public Class CustomPropertyEditor
Inherits System.Drawing.Design.UITypeEditor
Private m_editorForm As TextEditorForm
Private Function GetEditControl(ByVal PropertyName As String, ByVal CurrentValue As Object) As System.Windows.Forms.Control
Dim nestedClass = TryCast(CurrentValue, CustomTextBox.NestedClass)
If nestedClass IsNot Nothing Then
m_editorForm = New TextEditorForm(nestedClass)
Return m_editorForm
Else
Return Nothing
End If
End Function
Private Function GetEditedValue(ByVal EditControl As System.Windows.Forms.Control, ByVal PropertyName As String, ByVal OldValue As Object) As Object
If m_editorForm Is Nothing _
OrElse m_editorForm.DialogResult = DialogResult.Cancel Then
Return OldValue
Else
Return m_editorForm.NestedClassInstance
End If
End Function
Public Overrides Function GetEditStyle(ByVal context As System.ComponentModel.ITypeDescriptorContext) As UITypeEditorEditStyle
Return UITypeEditorEditStyle.Modal
End Function
End Class
Now when I click the button in the property grid, I get the new editor form with PropertyGrid control, which shows my two properties:
http://i49.tinypic.com/2vw9pu8.jpg
So far so good. I can change the properties on the editor form, press the OK button, and they seem to be stored. When I open the editor form again, the changes were persisted.
However...
When I build the solution, close the Form1 designer file, and then re-open it (so that it's reloaded), then the changes are gone! It seems they are not persisted after all, at least not in the form designer file I guess.
Also, when I make a change, Visual Studio doesn't seem to 'notice' it, as it does not tell me that the Form1.vb file has changed, so I can't even save it before closing it. (It doesn't get the asterisk * after it's name indicating an unsaved change).
As I said before: if I get rid of the whole custom UITypeEditor thingy, and just edit the properties from the property grid, it works fine. Then the changes are persisted even after building.
Am I missing something? Am I doing something fundamentally wrong? I can't really see the difference in using a String as the type to edit, or some custom class which in turn hosts some other properties. A String is just another class, right? So where's the difference?
Note also that I am already setting the DesignerSerializationVisibility attribute of the NestedClassInstance to Content, which, in my understanding, should ensure that any changes to that property are stored in the designer file. Still, it doesn't work.
Anyone? Thanks for any help!
Re: Custom property editor (UITypeEditor) not working properly with class
I came here hoping to find an answer, as it seemed the most promising for 2 reasons:
1 - This is an excellent site with superb resources.
2 - The question was an exact match to my issue. As the question yielded minimal related results.
After some thought, messing around and looking closely at the code, i had a facepalm moment.
I see the same error in this code, and all the references i looked up also. It gives the illusion the control is updated, but if you look at the myControl.designer.vb file, you will see it was never in fact updated.
You have to simply return the base implementation. So in case anyone else out there (as it is too late for op) is having trouble with this also, here is some code:
Code:
Imports System.Drawing.Design
Imports System.Windows.Forms.Design
Imports System.ComponentModel.Design
Public Class MyControlEditor
Inherits UITypeEditor
Private WithEvents propEditor As New MyControlPropertyGrid ' A Windows Form with a PropertyGrid to edit MyControl
Private oldVal As New MyControl ' So we can save a copy of the values in MyControl as they were before user went in to edit them
Public Overrides Function EditValue(context As System.ComponentModel.ITypeDescriptorContext, provider As System.IServiceProvider, value As Object) As Object
If (context IsNot Nothing) And (context.Instance IsNot Nothing) And (provider IsNot Nothing) Then
If Not IsNothing(CType(value, MyControl)) Then
oldVal = CType(CType(value, MyControl).Clone, MyControl) ' Copy the values in case user decides to cancel the property editing operation
propEditor.MyControl_PropGrid.SelectedObject = CType(value, MyControl) ' Set the PropertyGrid in our propEditor form to work with MyControl
End If
Dim result As Windows.Forms.DialogResult = propEditor.ShowDialog()
If result = Windows.Forms.DialogResult.OK Then
value = propEditor.MyControl_PropGrid.SelectedObject
Else
value = oldVal
End If
End If
Return MyBase.EditValue(context, provider, value) ' This was the DOH! moment
End Function
Public Overrides Function GetEditStyle(context As System.ComponentModel.ITypeDescriptorContext) As System.Drawing.Design.UITypeEditorEditStyle
If (Not IsNothing(context)) And (Not IsNothing(context.Instance)) Then
Return UITypeEditorEditStyle.Modal
End If
Return MyBase.GetEditStyle(context)
End Function
End Class