views:

258

answers:

1

I had a strange problem editing a class in the property grid whereby the property grid would refresh incorrectly.

I managed to reduce the problem down to a class with just two properties. I've included the code at the end to ease explanation.

It basically boils down to a class with two properties. The first of which is expandable (a font). The class itself is expandable and also implements the CreateInstance method in the type converter.

To see the problem, expand the font, edit, say 'Bold', and tab away. Two problems happen:

(1) The second property jumps up and ends up in the expanded font property.

(2) The '-' sign of the expanded font changes to a '+'.

The problem goes away by attaching ResfreshProperties(RefreshProperties.All) to the class.

That's great, but I'd like to understand how it fixed the problem. I've had a look in reflector and can't find any examples of RefreshProperties being attached at the class level.

/// Simple Class

<TypeConverter(GetType(Class1Converter)), _
RefreshProperties(RefreshProperties.All)> _
Public Class Class1

Public Sub New(ByVal font As Font, ByVal image As Image)
    Me.New()
    Me.Image = image
    Me.Font = font
End Sub

Public Sub New()
End Sub

Private _Font As Font = New Font("Arial", 10)
Public Property Font() As Font
    Get
        Return _Font
    End Get
    Set(ByVal value As Font)
        _Font = value
    End Set
End Property

Private _Image As Image
Public Property Image() As Image
    Get
        Return _Image
    End Get
    Set(ByVal value As Image)
        _Image = value
    End Set
End Property

End Class

/// Converter for the class

Public Class Class1Converter
Inherits ExpandableObjectConverter

Public Overrides Function GetCreateInstanceSupported(ByVal context As System.ComponentModel.ITypeDescriptorContext) As Boolean
    Return True
End Function

Public Overrides Function CreateInstance(ByVal context As System.ComponentModel.ITypeDescriptorContext, ByVal propertyValues As System.Collections.IDictionary) As Object
    Dim font As Font = TryCast(propertyValues("Font"), Font)
    Dim image As Image = CType(propertyValues("Image"), Image)
    Return New Class1(font, image)
End Function

End Class

/// A button to host the class

Public Class MyButton
Inherits Button

Private _C As Class1 = New Class1
Public Property C() As Class1
    Get
        Return _C
    End Get
    Set(ByVal value As Class1)
        _C = value
    End Set
End Property

End Class

A: 

It sounds like what's happening is a painting bug in the property grid control. Was the RefreshPropertiesAttribute applied at the property level when the bug occurred? Does the component implement INotifyPropertyChanged?

One reason the problem likely goes away is that applying RefreshPropertiesAttribute at the class level is not the way the attribute is supposed to be used. I'm guessing that by applying it at the class level you're effectively removing it. It looks like they left it set as AttributeTargets.All but clearly that's not by design.

RefreshProperties is kind of a cop-out attribute. It tells the designer that when the value of this property changes (via the property grid), go re-query every other property on this class. Obviously that's kind of wasteful, particularly if the property that changed doesn't have any impact on any other properties.

If you do have properties that cause changes in other properties, you can use the PropNameChanged event pattern. In this case, you would raise a FontChanged or ImageChanged event on the class when those properties change. The Windows Forms designer looks for events with that naming convention and uses them to invalidate the property grid.

But in the example you gave, those two properties would not invalidate each other and therefore you wouldn't need to invalidate either one in response to the other.

Josh Einstein