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)
/// <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; } } }
/// <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 }
/// <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 }
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; } }
<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>
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
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; } } }