`

wpf - ICutomTypeDescriptor , PropertyDescriptor and its use in PropertyGrid

    博客分类:
  • WPF
wpf 
阅读更多

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


分享到:
评论

相关推荐

    gong-wpf-dragdrop, GongSolutions.WPF.DragDrop 库是WPF的拖动'n'拖放框架.zip

    gong-wpf-dragdrop, GongSolutions.WPF.DragDrop 库是WPF的拖动'n'拖放框架 简介GongSolutions.WPF.DragDrop 库是一个易于使用的拖拉'n'拖放框架。特性使用 MVVM: 拖放逻辑可以放在ViewModel中。 代码不需要放在in中...

    wpf-mvvm-DeskTop-Sample-master_C#_WPF_wpf客户端zfs_

    标题中的“wpf-mvvm-DeskTop-Sample-master”表明这是一个关于WPF(Windows Presentation Foundation)桌面应用程序的示例项目,使用了MVVM(Model-View-ViewModel)设计模式。这个项目是用C#编程语言编写的,面向的...

    WPF PropertyGrid的资料合集

    在Windows Presentation Foundation(WPF)中,`PropertyGrid`是一个非常有用的控件,它允许开发者以网格的形式展示和编辑对象的属性。这个控件通常用于创建用户友好的配置界面或编辑器,使得用户可以直观地修改对象...

    C#开发WPF-Silverlight动画及游戏系列教程-深蓝色右手 4

    C#开发WPF-Silverlight动画及游戏系列教程-深蓝色右手 C#开发WPF-Silverlight动画及游戏系列教程-深蓝色右手 C#开发WPF-Silverlight动画及游戏系列教程-深蓝色右手

    WPF-Samples-master_WPF基本sample_

    WPF的基本空间历程,使用.net core3.0.1版本

    bootstrap-wpf-style-master 样式

    Bootstrap-WPF 样式是一种将流行的前端框架 Bootstrap 的设计风格应用于 WPF(Windows Presentation Foundation)应用程序的方法。Bootstrap 是一个广泛使用的开源工具包,主要用于构建响应式、移动设备优先的网页...

    Navigation-Drawer-Sidebar-Menu-in-WPF-master.zip

    标题"Navigation-Drawer-Sidebar-Menu-in-WPF-master.zip"提示我们这是一个关于WPF应用的项目,它包含了一个可展开和收缩的左侧导航菜单。描述中的“WPF左侧展开收缩图标导航菜单”进一步确认了这一功能。通过分析...

    WPF PropertyGrid

    **WPF PropertyGrid详解** WPF (Windows Presentation Foundation) 是微软.NET Framework的一部分,它提供了一个强大的用户界面框架,用于构建美观且高效的桌面应用程序。在WPF中,`PropertyGrid`是一个常用的控件...

    WPF-Diagram-Designer:WPF图表设计器源代码

    通过深入研究WPF-Diagram-Designer的源代码(如WPF-Diagram-Designer-master文件夹中的内容),开发者不仅可以学习到如何在WPF中构建复杂的图形界面,还可以了解到图形编辑器的设计原理和实现细节,对于提升图形应用...

    wpf---StatusBar

    “wpf---StatusBar”这个标题表明我们将探讨的是WPF(Windows Presentation Foundation)框架中的StatusBar组件。WPF是.NET Framework的一部分,用于构建桌面应用程序,它提供了丰富的用户界面(UI)功能。StatusBar...

    WPF-强大的图表.zip

    **WPF - 强大的图表技术** Windows Presentation Foundation (WPF) 是Microsoft开发的一个用于构建桌面应用程序的框架,它提供了丰富的图形系统,包括对2D和3D图形的强大支持。在WPF中,开发人员可以利用各种图表...

    wpf-datagrid-access DB

    在这个“wpf-datagrid-access DB”主题中,我们将深入探讨如何利用WPF Datagrid与Microsoft Access数据库进行交互,实现数据的读取、更新和保存。 1. **WPF Datagrid简介** - Datagrid是WPF中的一个数据展示控件,...

    practical-wpf-charts-graphics-master.rar

    该资源"practical-wpf-charts-graphics-master.rar"包含了这本书的源代码,为读者提供了丰富的实践案例和深入理解WPF图表及图形编程的宝贵材料。 WPF(Windows Presentation Foundation)是.NET Framework的一部分...

    C#开发WPF-Silverlight动画及游戏系列教程-深蓝色右手1

    C#开发WPF-Silverlight动画及游戏系列教程-深蓝色右手 C#开发WPF-Silverlight动画及游戏系列教程-深蓝色右手 C#开发WPF-Silverlight动画及游戏系列教程-深蓝色右手

    WPFPropertyGrid源码

    `WPFPropertyGrid`是一个强大的用户界面组件,它在Windows Presentation Foundation(WPF)环境中模拟了.NET Framework中的`System.Windows.Forms.PropertyGrid`控件。这个控件允许用户以网格的形式查看和编辑对象的...

    WPF-ScreenData.zip

    超酷WPF数据可视化,全套源程序,非常适合做数据可视化的程序员使用,WPF,XAML。 找了好久的资源,附有很多很详细的插图,是大数据时代不可缺少的可视化学习数据,仅供大家学习使用请勿用做商业用途。与大家一起...

    PropertyGrid中的枚举显示为中文

    然后,创建一个自定义的`PropertyGrid`类,如`LocalizedPropertyGrid`,并在其中重写`CreateProperty`方法,使用`LocalizedPropertyDescriptor`替代默认的`PropertyDescriptor`: ```csharp public class ...

    给propertyGrid动态添加属性

    总结来说,通过继承`PropertyDescriptor`并自定义行为,我们可以灵活地在运行时向`PropertyGrid`动态添加属性,从而增强用户界面的交互性和灵活性。这种技术在需要动态配置对象属性的场景中尤其有用,例如配置文件、...

    手动动态添加 PropertyGrid 的数据行并显示 C# (非属性绑定方式)

    创建好`PropertyDescriptor`和`TypeDescriptor`后,可以将`PropertyGrid`的`DataSource`设置为自定义对象,然后`PropertyGrid`会根据`TypeDescriptor`中的信息显示属性。 ```csharp propertyGrid1.DataSource = my...

    gong-wpf-dragdrop-develop.rar

    gong-wpf-dragdrop-develop.rar

Global site tag (gtag.js) - Google Analytics