Changing one property of many for a PropertyGrid - .NET -

248 views Asked by At

How can I override just one EditValue function of a control while keeping all the other properties of that control available? When I assign a single control to the SelectedObject property of a PropertyGrid I get all the properties of the control. In order to override one property I would like to create a property wrapper class that will show me all the controls as before, including the custom EditValue function. But I find that I must define all properties in the wrapper class, else I only get to see the property with the custom EditValue function. This seems very impractical, there must be an easier way. I need to do this because I want to catch the file name of a BackgroundImage that the user can specify. The code to catch the name works fine, here it is for good measure:

    Friend Class CatchFileName : Inherits UITypeEditor

        Public Overrides Function GetEditStyle(context As ITypeDescriptorContext) As UITypeEditorEditStyle
            Return UITypeEditorEditStyle.Modal
        End Function

        Public Overrides Function EditValue(context As ITypeDescriptorContext,
                                provider As IServiceProvider, value As Object) As Object
            Dim ofd As New OpenFileDialog() With {.Filter = "Image Files|*.bmp;*.jpg;*.jpeg,*.gif;*.png;*.ewf;*.wmf;*.ico"}
            If (ofd.ShowDialog() = DialogResult.OK) Then
                DirectCast(context.Instance, FormPropertiesWrapper)._BackgroundImageName =
                   Path.GetFileName(ofd.FileName) ' Strip path
                Return Image.FromFile(ofd.FileName)
            End If
            Return MyBase.EditValue(context, provider, value)
        End Function
    End Class

    <Description("Defines a form's background image."), Category("Display")>
    <Editor(GetType(CatchFileName), GetType(System.Drawing.Design.UITypeEditor))>
    Public Property BackgroundImage() As Image
    Get
      Return _Form.BackgroundImage
    End Get
    Set(ByVal Value As Image)
      _Form.BackgroundImage = Value
    End Set
    End Property

_Form is declared as Form in the wrapper class FormPropertiesWrapper

1

There are 1 answers

5
TnTinMn On

The PropertyGrid uses a component's TypeDescriptor to determine what is shown in the grid via the TypeDescriptor.GetProperties Method. You can create a wrapper class that derives from CustomTypeDescriptor Class and override the GetProperties method to provide a new PropertyDescriptor that includes the EditorAttribute that points to your CatchFileName class.

I modified your CatchFileName class to work against an interface called IBackgroundImageName that needs to implement in the wrapped control. This wrapper will work with any control that implements BackgroundImageProxyWrapper.IBackgroundImageName. This way the the image name is stored in the control instead of the wrapper.

Imports System.ComponentModel
Imports System.Drawing.Design
Imports System.IO

Friend Class BackgroundImageProxyWrapper : Inherits CustomTypeDescriptor
  Private Sub New(source As IBackgroundImageName)
    MyBase.New(TypeDescriptor.GetProvider(source).GetTypeDescriptor(source))
    SourceControl = source
  End Sub

  Public Shared Function Wrap(Of T As {Control, IBackgroundImageName})(source As T) As BackgroundImageProxyWrapper
    Return New BackgroundImageProxyWrapper(source)
  End Function

  Public ReadOnly Property SourceControl As IBackgroundImageName

  Public Overrides Function GetProperties() As PropertyDescriptorCollection
    Return GetProperties(Nothing)
  End Function

  Public Overrides Function GetProperties(attributes() As Attribute) As PropertyDescriptorCollection
    Dim ret As PropertyDescriptorCollection
    ret = New PropertyDescriptorCollection(New PropertyDescriptor() {}, False)
    For Each pd As PropertyDescriptor In MyBase.GetProperties(attributes)
      If pd.Name.Equals("BackgroundImage") Then
        ' substitute a PropertyDescriptor that includes the EditorAttribute
        Dim attribs As New List(Of Attribute)(pd.Attributes.Count + 1)
        Dim editorType As Type = GetType(EditorAttribute)
        ' 1st remove any previous editor attribute
        For Each attrib As Attribute In pd.Attributes
          If attrib.GetType IsNot editorType Then
            attribs.Add(attrib)
          End If
        Next
        attribs.Add(New EditorAttribute(GetType(CatchFileName), GetType(UITypeEditor)))
        pd = TypeDescriptor.CreateProperty(pd.ComponentType, pd, attribs.ToArray())
      End If
      ret.Add(pd)
    Next
    Return ret
  End Function

  Private Class CatchFileName : Inherits UITypeEditor

    Public Overrides Function GetEditStyle(context As ITypeDescriptorContext) As UITypeEditorEditStyle
      Return UITypeEditorEditStyle.Modal
    End Function

    Public Overrides Function EditValue(context As ITypeDescriptorContext,
                            provider As IServiceProvider, value As Object) As Object
      Dim ofd As New OpenFileDialog() With {.Filter = "Image Files|*.bmp;*.jpg;*.jpeg,*.gif;*.png;*.ewf;*.wmf;*.ico"}
      If (ofd.ShowDialog() = DialogResult.OK) Then
        DirectCast(context.Instance, BackgroundImageProxyWrapper).SourceControl.BackgroundImageName =
           Path.GetFileName(ofd.FileName) ' Strip path
        Return Image.FromFile(ofd.FileName)
      End If
      Return MyBase.EditValue(context, provider, value)
    End Function
  End Class

  Public Interface IBackgroundImageName
    Property BackgroundImageName As String
  End Interface
End Class

Example Usage:

Public Class Form1
  Implements BackgroundImageProxyWrapper.IBackgroundImageName
  Private Property BackgroundImageName As String Implements BackgroundImageProxyWrapper.IBackgroundImageName.BackgroundImageName

  Private Sub BtnSetForm_Click(sender As Object, e As EventArgs) Handles BtnSetForm.Click
    PropertyGrid1.SelectedObject = BackgroundImageProxyWrapper.Wrap(Me)
  End Sub
End Class

Edit: A more generic version that will work on any control. The file name is stored in the control's Tag property.

Friend Class BackgroundImageProxyWrapper : Inherits CustomTypeDescriptor
  Public Sub New(source As Control)
    MyBase.New(TypeDescriptor.GetProvider(source).GetTypeDescriptor(source))
    SourceControl = source
  End Sub

  Public ReadOnly Property SourceControl As Control

  Public Overrides Function GetProperties() As PropertyDescriptorCollection
    Return GetProperties(Nothing)
  End Function

  Public Overrides Function GetProperties(attributes() As Attribute) As PropertyDescriptorCollection
    Dim ret As PropertyDescriptorCollection
    ret = New PropertyDescriptorCollection(New PropertyDescriptor() {}, False)
    For Each pd As PropertyDescriptor In MyBase.GetProperties(attributes)
      If pd.Name.Equals("BackgroundImage") Then
        ' substitute a PropertyDescriptor that includes the EditorAttribute
        Dim attribs As New List(Of Attribute)(pd.Attributes.Count + 1)
        Dim editorType As Type = GetType(EditorAttribute)
        ' 1st remove any previous editor attribute
        For Each attrib As Attribute In pd.Attributes
          If attrib.GetType IsNot editorType Then
            attribs.Add(attrib)
          End If
        Next
        attribs.Add(New EditorAttribute(GetType(CatchFileName), GetType(UITypeEditor)))
        pd = TypeDescriptor.CreateProperty(pd.ComponentType, pd, attribs.ToArray())
      End If
      ret.Add(pd)
    Next
    Return ret
  End Function

  Private Class CatchFileName : Inherits UITypeEditor

    Public Overrides Function GetEditStyle(context As ITypeDescriptorContext) As UITypeEditorEditStyle
      Return UITypeEditorEditStyle.Modal
    End Function

    Public Overrides Function EditValue(context As ITypeDescriptorContext,
                            provider As IServiceProvider, value As Object) As Object
      Dim ofd As New OpenFileDialog() With {.Filter = "Image Files|*.bmp;*.jpg;*.jpeg,*.gif;*.png;*.ewf;*.wmf;*.ico"}
      If (ofd.ShowDialog() = DialogResult.OK) Then
        DirectCast(context.Instance, BackgroundImageProxyWrapper).SourceControl.Tag =
           Path.GetFileName(ofd.FileName) ' Strip path
        Return Image.FromFile(ofd.FileName)
      End If
      Return MyBase.EditValue(context, provider, value)
    End Function
  End Class
End Class