wpf - ICutomTypeDescriptor , PropertyDescriptor and its use in PropertyGrid

The type 

 

 

 

is widely used in a lots of places, for one, the PropertyGrid, where it allows you to view/sort/filter properties based on different criteria and the other is DataGrid, where it allows you to display/sort/filter tabular table without knowing the schema of the fields in advance. 

 

 

In this post, we are going to show an example on how to create classes that can be consumed by the PropertyGrid with the helpof ICustomTypePropertyDescriptor and PropertyDescriptor.

 

As in our discussion, we will also discuss/use another concrete type descriptor (which we use extensively in our code)

 

 


Also, we implementes the example with WPF, and we are going to show something on how to host some Forms Controls inside WPF . 


First, we are going to create our data class, which represents the business model that will be exposed by our custom Property Descriptor classs.

  /// <summary>
  /// Custom Property Class
  /// </summary>
  public class CustomProperty
  {
    private string sName = string.Empty;
    private bool bReadonly = false;
    private bool bVisible = true;
    private object objValue = null;


    public CustomProperty(string sName, object objValue, bool bReadonly, bool bVisible)
    {
      this.sName = sName;
      this.bReadonly = bReadonly;
      this.bVisible = bVisible;
      this.objValue = objValue;
    }

    public bool Readonly
    {
      get { return bReadonly; } 
    }

    public string Name { get { return sName; } }

    public bool Visible { get { return bVisible; } }

    public object Value { get { return objValue; } set { objValue = value; } }

  }
 
and then we wrap the Business Model in a Custom Property Descriptor, where it allows client to peek/inspect the values in contracted way.

/// <summary>
  /// Custom Property Descriptor
  /// </summary>
  /// <remarks>
  /// The Type Descriptor Provide values/descriptor for CustomProperty
  /// </remarks>
  public class CustomPropertyDescriptor : PropertyDescriptor
  {
    CustomProperty m_Property;

    // @NOTE
    //  Attribute is from System.Attribute
    public CustomPropertyDescriptor(ref CustomProperty myProperty, Attribute[] attrs)
      : base(myProperty.Name, attrs)
    {
      m_Property = myProperty;
    }

    #region PropertyDescriptor Specific 
    public override bool CanResetValue(object component)
    {
      return false; // Can NOT Reset Value
    }

    public override Type ComponentType
    {
      get { return null; } // do NOT provide information about the component information 
    }

    public override object GetValue(object component)
    {
      return m_Property.Value; // Provide value - return the value of the enclosed data object
    }

    public override bool IsReadOnly
    {
      get { return m_Property.Readonly; }
    }

    public override Type PropertyType
    {
      get { return m_Property.Value.GetType();  }
    }

    public override void ResetValue(object component)
    {
      // TODO
      //  have to implement
    }

    public override void SetValue(object component, object value)
    {
      // set the value to the real store , the m_Property.Value property 
      m_Property.Value = value;
    }

    public override bool ShouldSerializeValue(object component)
    {
      return false;  // What does the Serialize stands for ? 
                     // normaly way to implements the ShouldSerializeValue is  
                     //   if the parent shouldSerializeValue return true, return true
                     //   otherwise, if the current object should serialize, return true
                     //   else, return false;
    }


    #region PropertyDescriptor Overrides
    public override string Description
    {
      get
      {
        return m_Property.Name; // OVERRIDE the description of this PropertyDescriptor
      }
    }

    public override string Category
    {
      get
      {
        return string.Empty; // what is the use of CATEGORY ?
      }
    }


    public override string DisplayName
    {
      get
      {
        return m_Property.Name; // OVERRIDE the display name of this PropertyDescriptor, you can see that from PropertyGrid
      }
    }


    #endregion PropertyDescriptor Overrides

    #endregion PropertyDescriptor Specific


  }
 
Next, we are going to create a domain class, which internally manage the Property Descriptor collection, designated by the class CustomClass (the class implements ICustomTypeDescriptor);


  /// <summary>
  /// The Custom Class
  /// </summary>
  /// <remarks>
  /// CustomClass provides the data that you can bind to Controls such as PropertyGrid, 
  /// It implements the ICustomTypeDescriptor and internally manages instances of class that is derived from ProeprtyDescriptor
  /// </remarks>
  public  class CustomClass : CollectionBase, ICustomTypeDescriptor
  {

    #region PropertyDescriptors Management
    /// <summary>
    /// Add CustomProperty to CollectionBase List
    /// </summary>
    /// <param name="value"></param>
    public void Add(CustomProperty value)
    {
      base.List.Add(value);
    }


    /// <summary>
    /// Remove a CustomProeprty which has <paramref name="Name"/>
    /// </summary>
    /// <param name="value"></param>
    public void Remove(string Name)
    {
      foreach (CustomProperty prop in base.List)
      {
        if (prop.Name == Name)
        {
          base.List.Remove(prop);
          return;
        }
      }
    }

    public CustomProperty this[int index]
    {
      get
      {
        return (CustomProperty)base.List[index];
      }
      set
      {
        base.List[index] = (CustomProperty) value;
      }
    }

    #endregion PropertyDescriptors Management

    #region ICustomTypeDescriptor

    public AttributeCollection GetAttributes()
    {
      return TypeDescriptor.GetAttributes(this, true); // Default way to get the Attribute that pertaining/associated to
                                                       // the class (the ICustomPropertyDescriptor class)
    }

    public string GetClassName()
    {
      return TypeDescriptor.GetClassName(this, true); // true to consider Custom Type Descriptor 
    }

    public string GetComponentName()
    {
      return TypeDescriptor.GetComponentName(this, true); // There is a serie of TypeDescriptor method where you can get the
                                                          // most of common impl of ICustomTypeDescriptor
    }

    public TypeConverter GetConverter()
    {
      return TypeDescriptor.GetConverter(this, true);
    }

    public EventDescriptor GetDefaultEvent()
    {
      return TypeDescriptor.GetDefaultEvent(this, true);
    }

    public PropertyDescriptor GetDefaultProperty()
    {
      return TypeDescriptor.GetDefaultProperty(this, true); 
    }

    /// <summary>
    /// GetEditor
    /// </summary>
    /// <param name="editorBaseType"></param>
    /// <returns></returns>
    /// <remarks>
    /// Is the Editor something that can change the value of the CustomClass in an interactive way?</remarks>
    public object GetEditor(Type editorBaseType)
    {
      return TypeDescriptor.GetEditor(this, editorBaseType, true);
    }

    public EventDescriptorCollection GetEvents(Attribute[] attributes)
    {
      //  is the Event drive what you can see from 
      // property Grid under the Event Category?
      //
      return TypeDescriptor.GetEvents(this, attributes, true);
    }

    public EventDescriptorCollection GetEvents()
    {
      return TypeDescriptor.GetEvents(this, true);
    }

    public PropertyDescriptorCollection GetProperties(Attribute[] attributes)
    {
      PropertyDescriptor[] newProps = new PropertyDescriptor[this.Count];
      for (int i = 0; i < this.Count; i++)
      {
        CustomProperty prop = (CustomProperty)this[i];
        newProps[i] = new CustomPropertyDescriptor(ref prop, attributes);
      }
      return new PropertyDescriptorCollection(newProps);
    }

    public PropertyDescriptorCollection GetProperties()
    {
      return TypeDescriptor.GetProperties(this, true);
    }

    public object GetPropertyOwner(PropertyDescriptor pd)
    {
      return this; // normally it is 'this' who is the owner of <paramref Name="pd" />
    }

    #endregion ICustomTypeDescriptor
  }

 

We are using WPF page, so we are going to create a data model for the window. Here is the Data model behind the ViewModel 


  public class DataObject 
  {
    public CustomClass MyProperties { get; set; }
    public string AddName { get; set; }
    public string AddValue { get; set; }
    public string RemoveName { get; set; }
    public bool AddIsReadonly { get; set; } 
  }
 
And then let's take a look at the Xaml that has the view. 

// MainWindow.xaml

<Window x:Class="PropertyGridAndCustomPropertyDescriptor.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:swf="clr-namespace:System.Windows.Forms;assembly=System.Windows.Forms"
        Title="MainWindow" Height="350" Width="525"
        Loaded="WindowLoaded"
        >
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="180"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <Grid Grid.Column="0"
            >
            <!-- You will need to add references to WindowsFormsIntegration.dll in order to use 
            WindowsFormsHost 
            See page: http://msdn.microsoft.com/en-us/library/system.windows.forms.integration.windowsformshost.aspx
            -->
            <WindowsFormsHost>
                <swf:PropertyGrid x:Name="propertyGrid" />
            </WindowsFormsHost>
            
        </Grid>
        <Grid Grid.Column="1">
            <Grid.RowDefinitions>
                <RowDefinition Height="5*"/>
                <RowDefinition Height="3*"/>
            </Grid.RowDefinitions>

                <GroupBox x:Name="addGroupBox" Grid.Row="0" Header="Add Property" >
                    <Grid>
                        <Grid.RowDefinitions>
                            <RowDefinition></RowDefinition>
                            <RowDefinition></RowDefinition>
                            <RowDefinition></RowDefinition>
                            <RowDefinition></RowDefinition>
                        </Grid.RowDefinitions>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition />
                            <ColumnDefinition />
                        </Grid.ColumnDefinitions>
                        <TextBlock Text="Name" Grid.Row="0" Grid.Column="0"/>
                        <TextBox Grid.Row="0" Grid.Column="1" x:Name="txtName" Text="{Binding Path=AddName}"></TextBox>
                        <TextBlock Text="Value" Grid.Row="1" Grid.Column="0"/>
                        <TextBox Grid.Row="1" Grid.Column="1" x:Name="txtValue" Text="{Binding Path=AddValue}"></TextBox>
                        <CheckBox Content="Readonly" IsChecked="{Binding Path=AddIsReadonly}" Grid.Row="2" Grid.ColumnSpan="2" />
                        <Button Content="Add" Grid.Row="3" x:Name="btnAdd" Grid.Column="1" Grid.ColumnSpan="2" Click="BtnAddClick"/>
                    </Grid>
                </GroupBox>
                <GroupBox x:Name="removeGropuBox" Grid.Row="1" Header="Remove Property">
                    <Grid>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition />
                            <ColumnDefinition />
                        </Grid.ColumnDefinitions>
                        <Grid.RowDefinitions>
                            <RowDefinition />
                            <RowDefinition />
                        </Grid.RowDefinitions>
                        <TextBlock Text="Name" Grid.Row="0" Grid.Column="0"/>
                        <TextBox Text="{Binding Path=RemoveName}" x:Name="txtRemoveName" Grid.Row="0" Grid.Column="1"/>
                        <Button x:Name="btnRemove" Content="Remove" Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="2" Click="BtnRemoveClick"/>
                    </Grid>
                </GroupBox>
            </Grid>
    </Grid>
</Window>
 
NOTE: to use the Forms Control wpf integration, you will need to add references to "WindowsFormsIntegration.dll" and you will need to use the Standard namespace 

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 
Last is the Code behind that wires up the View and the Model (still the ViewModel parts), as well as the Event handlers...


namespace PropertyGridAndCustomPropertyDescriptor
{
  /// <summary>
  /// Interaction logic for MainWindow.xaml
  /// </summary>
  public partial class MainWindow : Window
  {
    public MainWindow()
    {
      InitializeComponent();
      m_DataObject = new DataObject()
      {
        MyProperties = new CustomClass(),
        AddName  = @"Name",
        AddValue = @"Value",
        RemoveName= @"Name",
      };
      this.DataContext = m_DataObject;
    }

    private DataObject m_DataObject; 

    private void BtnAddClick(object sender, RoutedEventArgs e)
    {
      var myProp = new CustomProperty(this.m_DataObject.AddName, this.m_DataObject.AddValue, this.m_DataObject.AddIsReadonly, true);
      this.m_DataObject.MyProperties.Add(myProp);
      propertyGrid.Refresh(); // if this is necessary?
    }

    private void BtnRemoveClick(object sender, RoutedEventArgs e)
    {
      this.m_DataObject.MyProperties.Remove(this.m_DataObject.RemoveName);
      propertyGrid.Refresh();
    }

    private void WindowLoaded(object sender, RoutedEventArgs e)
    {
      propertyGrid.SelectedObject = m_DataObject.MyProperties;
    }
  }
}
 


References and speical Notes:


You may reference the following materials


1. PropertyGrid with Custom PropertyDescriptor
     http://stackoverflow.com/questions/11892064/propertygrid-with-custom-propertydescriptor
2. Add(remove) Items to(from) PropertyDescriptor at Runtime : 
     http://www.codeproject.com/KB/tabs/Dynamic_Propertygrid/Dynamic_Propertygrid_src.zip
3. Using PropertyGrid with a dictionary object
     http://www.differentpla.net/content/2005/02/using-propertygrid-with-dictionary


And there are some open source project to provide a WPF PropertyGrid;


it seems there are some good open soruce alternatives for Property Grid
  http://wpg.codeplex.com/
Currently we are going to host the Winform Property Grid in a WPF containers - WindowsFormsHost


你可能感兴趣的:(WPF)