PropertyGrid自定义控件使用详解

PropertyGrid是一个很强大的控件,使用该控件做属性设置面板的一个好处就是你只需要专注于代码而无需关注UI的呈现,PropertyGrid会默认根据变量类型选择合适的控件显示。但是这也带来了一个问题,就是控件的使用变得不是特别灵活,主要表现在你无法根据你的需求很好的选择控件,比如当你需要用Slider控件来设置int型变量时,PropertyGrid默认的模板选择器是不支持的。网上找了许多资料基本都是介绍WinForm的实现方式,主要用到了IWindowFromService这个接口,并未找到合适的适合WPF的Demo,后来在参考了DEVExpress的官方Demo之后我做了一个基于WPF和DEV 16.2的PropertyGrid Demo,基本实现了上述功能。

为了实现这一点,需要自定义一个DataTemplateSeletor类,这也是本文的核心代码。

1.创建一个CustomPropertyGrid自定义控件:

<UserControl
  x:Class="PropertyGridDemo.PropertyGridControl.CustomPropertyGrid"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
  xmlns:dxprg="http://schemas.devexpress.com/winfx/2008/xaml/propertygrid"
  xmlns:local="clr-namespace:PropertyGridDemo.PropertyGridControl"
  xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
  d:DesignHeight="300"
  d:DesignWidth="300"
  mc:Ignorable="d">
  <UserControl.Resources>
    <ResourceDictionary>
      <ResourceDictionary.MergedDictionaries>
        <!-- 资源字典 -->
        <ResourceDictionary Source="../PropertyGridControl/DynamicallyAssignDataEditorsResources.xaml" />
      </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>
  </UserControl.Resources>
  <Grid>
    <!-- PropertyDefinitionStyle:定义属性描述的风格模板 -->
    <!-- PropertyDefinitionTemplateSelector:定义一个模板选择器,对应一个继承自DataTemplateSelector的类 -->
    <!-- PropertyDefinitionsSource:定义一个获取数据属性集合的类,对应一个自定义类(本Demo中对应DataEditorsViewModel) -->
    <dxprg:PropertyGridControl
      x:Name="PropertyGridControl"
      Margin="24"
      DataContextChanged="PropertyGridControl_DataContextChanged"
      ExpandCategoriesWhenSelectedObjectChanged="True"
      PropertyDefinitionStyle="{StaticResource DynamicallyAssignDataEditorsPropertyDefinitionStyle}"
      PropertyDefinitionTemplateSelector="{StaticResource DynamicallyAssignDataEditorsTemplateSelector}"
      PropertyDefinitionsSource="{Binding Path=Properties, Source={StaticResource DemoDataProvider}}"
      ShowCategories="True"
      ShowDescriptionIn="Panel" />
  </Grid>
</UserControl>

该控件使用的资源字典如下:

<ResourceDictionary
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
  xmlns:dxe="http://schemas.devexpress.com/winfx/2008/xaml/editors"
  xmlns:dxg="http://schemas.devexpress.com/winfx/2008/xaml/grid"
  xmlns:dxprg="http://schemas.devexpress.com/winfx/2008/xaml/propertygrid"
  xmlns:local="clr-namespace:PropertyGridDemo.PropertyGridControl"
  xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
  mc:Ignorable="d">

  <local:DynamicallyAssignDataEditorsTemplateSelector x:Key="DynamicallyAssignDataEditorsTemplateSelector" />
  <local:DataEditorsViewModel x:Key="DemoDataProvider" />

  <DataTemplate x:Key="DescriptionTemplate">
    <RichTextBox
      x:Name="descriptionRichTextBox"
      MinWidth="150"
      HorizontalContentAlignment="Stretch"
      Background="Transparent"
      BorderThickness="0"
      Foreground="{Binding Path=(TextElement.Foreground), RelativeSource={RelativeSource TemplatedParent}}"
      IsReadOnly="True"
      IsTabStop="False" />
  </DataTemplate>
  <DataTemplate x:Key="descriptionTemplate">
    <RichTextBox
      x:Name="descriptionRichTextBox"
      MinWidth="150"
      HorizontalContentAlignment="Stretch"
      Background="Transparent"
      BorderThickness="0"
      Foreground="{Binding Path=(TextElement.Foreground), RelativeSource={RelativeSource TemplatedParent}}"
      IsReadOnly="True"
      IsTabStop="False" />
  </DataTemplate>

  <!-- 设置控件的全局样式和数据绑定 -->
  <Style x:Key="DynamicallyAssignDataEditorsPropertyDefinitionStyle" TargetType="dxprg:PropertyDefinition">
    <Setter Property="Path" Value="{Binding Name}" />
    <!--<Setter Property="Header" Value="{Binding Converter={StaticResource PropertyDescriptorToDisplayNameConverter}}"/>-->
    <Setter Property="Description" Value="{Binding}" />
    <Setter Property="DescriptionTemplate" Value="{StaticResource descriptionTemplate}" />
  </Style>
  <Style x:Key="DescriptionContainerStyle" TargetType="dxprg:PropertyDescriptionPresenterControl">
    <Setter Property="ShowSelectedRowHeader" Value="False" />
    <Setter Property="MinHeight" Value="70" />
  </Style>

  <Style TargetType="Slider">
    <Setter Property="Margin" Value="2" />
  </Style>
  <Style TargetType="dxe:ComboBoxEdit">
    <Setter Property="IsTextEditable" Value="False" />
    <Setter Property="ApplyItemTemplateToSelectedItem" Value="True" />
    <Setter Property="Margin" Value="2" />
  </Style>

  <!-- 测试直接从DataTemplate获取控件 -->
  <DataTemplate x:Key="SliderTemplate" DataType="local:SliderExtend">
    <!--<dxprg:PropertyDefinition>
      <dxprg:PropertyDefinition.CellTemplate>-->
    <!--<DataTemplate>-->
    <StackPanel x:Name="Root">
      <Slider
        Maximum="{Binding Path=Max}"
        Minimum="{Binding Path=Min}"
        Value="{Binding Path=Value}" />
      <TextBlock Text="{Binding Path=Value}" />
    </StackPanel>
    <!--</DataTemplate>-->
    <!--</dxprg:PropertyDefinition.CellTemplate>
    </dxprg:PropertyDefinition>-->
  </DataTemplate>

  <DataTemplate x:Key="ComboBoxEditItemTemplate" DataType="Tuple">
    <TextBlock
      Height="20"
      Margin="5,3,0,0"
      VerticalAlignment="Center"
      Text="{Binding Item1}" />
  </DataTemplate>
</ResourceDictionary>

2.编写对应的模板选择类 DynamicallyAssignDataEditorsTemplateSelector:

using DevExpress.Xpf.Editors;
using DevExpress.Xpf.PropertyGrid;
using System.ComponentModel;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;

namespace PropertyGridDemo.PropertyGridControl
{
  public class DynamicallyAssignDataEditorsTemplateSelector : DataTemplateSelector
  {
    private PropertyDescriptor _property = null;
    private RootPropertyDefinition _element = null;
    private PropertyDataContext _propertyDataContext => App.PropertyGridDataContext;

    /// <summary>
    /// 当重写在派生类中,返回根据自定义逻辑的 <see cref="T:System.Windows.DataTemplate" /> 。
    /// </summary>
    /// <param name="item">数据对象可以选择模板。</param>
    /// <param name="container">数据对象。</param>
    /// <returns>
    /// 返回 <see cref="T:System.Windows.DataTemplate" /> 或 null。默认值为 null。
    /// </returns>
    public override DataTemplate SelectTemplate(object item, DependencyObject container)
    {
      _element = (RootPropertyDefinition)container;
      DataTemplate resource = TryCreateResource(item);
      return resource ?? base.SelectTemplate(item, container);
    }

    /// <summary>
    /// Tries the create resource.
    /// </summary>
    /// <param name="item">The item.</param>
    /// <returns></returns>
    private DataTemplate TryCreateResource(object item)
    {
      if (!(item is PropertyDescriptor)) return null;
      PropertyDescriptor pd = (PropertyDescriptor)item;
      _property = pd;
      var customUIAttribute = (CustomUIAttribute)pd.Attributes[typeof(CustomUIAttribute)];
      if (customUIAttribute == null) return null;
      var customUIType = customUIAttribute.CustomUI;
      return CreatePropertyDefinitionTemplate(customUIAttribute);
    }

    /// <summary>
    /// Gets the data context.
    /// </summary>
    /// <param name="dataContextPropertyName">Name of the data context property.</param>
    /// <returns></returns>
    private object GetDataContext(string dataContextPropertyName)
    {
      PropertyInfo property = _propertyDataContext?.GetType().GetProperty(dataContextPropertyName);
      if (property == null) return null;
      return property.GetValue(_propertyDataContext, null);
    }

    /// <summary>
    /// Creates the slider data template.
    /// </summary>
    /// <param name="customUIAttribute">The custom UI attribute.</param>
    /// <returns></returns>
    private DataTemplate CreateSliderDataTemplate(CustomUIAttribute customUIAttribute)
    {
      DataTemplate ct = new DataTemplate();
      ct.VisualTree = new FrameworkElementFactory(typeof(StackPanel));
      ct.VisualTree.SetValue(StackPanel.DataContextProperty, GetDataContext(customUIAttribute.DataContextPropertyName));

      FrameworkElementFactory sliderFactory = new FrameworkElementFactory(typeof(Slider));
      sliderFactory.SetBinding(Slider.MaximumProperty, new Binding(nameof(SliderUIDataContext.Max)));
      sliderFactory.SetBinding(Slider.MinimumProperty, new Binding(nameof(SliderUIDataContext.Min)));
      sliderFactory.SetBinding(Slider.SmallChangeProperty, new Binding(nameof(SliderUIDataContext.SmallChange)));
      sliderFactory.SetBinding(Slider.LargeChangeProperty, new Binding(nameof(SliderUIDataContext.LargeChange)));
      sliderFactory.SetBinding(Slider.ValueProperty, new Binding(nameof(SliderUIDataContext.Value)));
      ct.VisualTree.AppendChild(sliderFactory);

      FrameworkElementFactory textFacotry = new FrameworkElementFactory(typeof(TextBlock), "TextBlock");
      textFacotry.SetValue(TextBlock.TextProperty, new Binding(nameof(SliderUIDataContext.Value)));
      //textBoxFactory.AddHandler(TextBox.IsVisibleChanged, new DependencyPropertyChangedEventHandler(SearchBoxVisibleChanged));
      ct.VisualTree.AppendChild(textFacotry);
      ct.Seal();
      return ct;
    }

    /// <summary>
    /// Creates the ComboBox edit template.
    /// </summary>
    /// <param name="customUIAttribute">The custom UI attribute.</param>
    /// <returns></returns>
    private DataTemplate CreateComboBoxEditTemplate(CustomUIAttribute customUIAttribute)
    {
      DataTemplate template = new DataTemplate();
      template.VisualTree = new FrameworkElementFactory(typeof(DockPanel));
      template.VisualTree.SetValue(DockPanel.DataContextProperty, GetDataContext(customUIAttribute.DataContextPropertyName));

      FrameworkElementFactory textFactory = new FrameworkElementFactory(typeof(TextBlock)) ;
      textFactory.SetValue(TextBlock.TextProperty, new Binding(nameof(ComboBoxEditDataContext.Name)));
      template.VisualTree.AppendChild(textFactory);

      FrameworkElementFactory comboBoxEditFactory = new FrameworkElementFactory(typeof(ComboBoxEdit));
      comboBoxEditFactory.SetBinding(ComboBoxEdit.ItemsSourceProperty, new Binding(nameof(ComboBoxEditDataContext.ItemSource)));
      comboBoxEditFactory.SetBinding(ComboBoxEdit.EditValueProperty, new Binding(nameof(ComboBoxEditDataContext.EditValue)));
      comboBoxEditFactory.SetBinding(ComboBoxEdit.SelectedIndexProperty, new Binding(nameof(ComboBoxEditDataContext.SelectedIndex)));
      comboBoxEditFactory.SetValue(ComboBoxEdit.ItemTemplateProperty, (DataTemplate)_element.TryFindResource("ComboBoxEditItemTemplate"));
      template.VisualTree.AppendChild(comboBoxEditFactory);
      template.Seal();
      return template;
    }

    /// <summary>
    /// Creates the property definition template.
    /// </summary>
    /// <param name="customUIAttribute">The custom UI attribute.</param>
    /// <returns></returns>
    private DataTemplate CreatePropertyDefinitionTemplate(CustomUIAttribute customUIAttribute)
    {
      DataTemplate dataTemplate = new DataTemplate();
      DataTemplate cellTemplate = null;//单元格模板
      FrameworkElementFactory factory = new FrameworkElementFactory(typeof(PropertyDefinition));
      dataTemplate.VisualTree = factory;
      switch (customUIAttribute.CustomUI)
      {
        case CustomUITypes.Slider:
          cellTemplate = CreateSliderDataTemplate(customUIAttribute); break;
          //cellTemplate = (DataTemplate)_element.TryFindResource("SliderTemplate");break;
        case CustomUITypes.ComboBoxEit:
          cellTemplate = CreateComboBoxEditTemplate(customUIAttribute);break;

      }

      if (cellTemplate != null)
      {
        factory.SetValue(PropertyDefinition.CellTemplateProperty, cellTemplate);
        dataTemplate.Seal();

      }
      else
      {
        return null;
      }
      return dataTemplate;
    }
  }
}
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;

namespace PropertyGridDemo.PropertyGridControl
{
  /// <summary>
  ///初始化所有属性并调用模板选择器进行匹配
  /// </summary>
  public class DataEditorsViewModel
  {
    public IEnumerable<PropertyDescriptor> Properties { get { return TypeDescriptor.GetProperties(typeof(TestPropertyGrid)).Cast<PropertyDescriptor>(); } }
  }
}

3.编写一个可用于构建模板的属性 CustomUIType:

using System;

namespace PropertyGridDemo.PropertyGridControl
{
  public class CustomUIType
  {

  }

  public enum CustomUITypes
  {
    Slider,
    ComboBoxEit,
    SpinEdit,
    CheckBoxEdit
  }

  [AttributeUsage(AttributeTargets.Property)]
  internal class CustomUIAttribute : Attribute
  {
    public string DataContextPropertyName { get; set; }
    public CustomUITypes CustomUI { get; set; }
    /// <summary>
    /// 自定义控件属性构造函数
    /// </summary>
    /// <param name="uiTypes">The UI types.</param>
    /// <param name="dataContextPropertyName">Name of the data context property.</param>
    internal CustomUIAttribute(CustomUITypes uiTypes, string dataContextPropertyName)
    {
      CustomUI = uiTypes;
      DataContextPropertyName = dataContextPropertyName;
    }
  }

}

4.编写对应的DataContext类 TestPropertyGrid:

using DevExpress.Mvvm.DataAnnotations;
using System;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Timers;
using System.Windows;

namespace PropertyGridDemo.PropertyGridControl
{
  [MetadataType(typeof(DynamicallyAssignDataEditorsMetadata))]
  public class TestPropertyGrid : PropertyDataContext
  {
    private double _count = 0;
    private SliderUIDataContext _countSource = null;
    private ComboBoxEditDataContext _comboSource = null;
    private double _value=1;

    public TestPropertyGrid()
    {
      Password = "1111111";
      Notes = "Hello";
      Text = "Hello hi";
    }

    [Browsable(false)]
    public SliderUIDataContext CountSource
    {
      get
      {
        if (_countSource != null)
        {

          return _countSource;
        }
        else
        {
          _countSource = new SliderUIDataContext(0, 100, Count, 0.1, 1);
          _countSource.PropertyChanged += (object o, PropertyChangedEventArgs e) =>
          {
            this.Count = _countSource.Value;
          };
          return _countSource;
        }
      }
    }

    [Browsable(false)]
    public ComboBoxEditDataContext ComboSource
    {
      get
      {
        if(_comboSource==null)
        {
          _comboSource =new ComboBoxEditDataContext(ComboBoxEditItemSource.TestItemSource,Value);
          _comboSource.PropertyChanged += (object o, PropertyChangedEventArgs e) =>
           {
             this.Value =Convert.ToDouble(_comboSource.EditValue.Item2);
           };

        }
        return _comboSource;
      }
    }

    [Display(Name = "SliderEdit", GroupName = "CustomUI")]
    [CustomUI(CustomUITypes.Slider, nameof(CountSource))]
    public double Count
    {
      get => _count;
      set
      {
        _count = value;
        CountSource.Value = value;
        RaisePropertyChanged(nameof(Count));
      }
    }

    [Display(Name = "ComboBoxEditItem", GroupName = "CustomUI")]
    [CustomUI(CustomUITypes.ComboBoxEit, nameof(ComboSource))]
    public double Value
    {
      get => _value;
      set
      {
        if (_value == value) return;
        _value = value;
        //ComboSource.Value = value;
        RaisePropertyChanged(nameof(Value));
      }
    }

    [Display(Name = "Password", GroupName = "DefaultUI")]
    public string Password { get; set; }

    [Display(Name = "TextEdit", GroupName = "DefaultUI")]
    public string Text { get; set; }

    [Display(Name = "Notes", GroupName = "DefaultUI")]
    public string Notes { get; set; }

    [Display(Name = "Double", GroupName = "DefaultUI")]
    [DefaultValue(1)]
    public double TestDouble { get; set; }

    [Display(Name = "Items", GroupName = "DefaultUI")]
    [DefaultValue(Visibility.Visible)]
    public Visibility TestItems { get; set; }
  }

  public static class DynamicallyAssignDataEditorsMetadata
  {
    public static void BuildMetadata(MetadataBuilder<TestPropertyGrid> builder)
    {
      builder.Property(x => x.Password)
        .PasswordDataType();

      builder.Property(x => x.Notes)
        .MultilineTextDataType();
    }
  }
}

该类中用到的其他类主要有以下几个,以下几个类主要用于数据绑定:

 namespace PropertyGridDemo.PropertyGridControl
{
  public class SliderUIDataContext:PropertyDataContext
  {
    private double _value = 0;
    private double _max = 0;
    private double _min = 0;
    private double _smallChange = 1;
    private double _largeChange=1;

    public SliderUIDataContext()
    {

    }

    /// <summary>
    /// Initializes a new instance of the <see cref="SliderUIDataContext"/> class.
    /// </summary>
    /// <param name="min">The minimum.</param>
    /// <param name="max">The maximum.</param>
    /// <param name="value">The value.</param>
    /// <param name="smallChange">The small change.</param>
    /// <param name="largeChange">The large change.</param>
    public SliderUIDataContext(double min, double max, double value,double smallChange=0.01,double largeChange=0.1)
    {
      SmallChange = smallChange;
      LargeChange = largeChange;
      Max = max;
      Min = min;
      Value = value;
    }

    /// <summary>
    /// Gets or sets the small change.
    /// </summary>
    /// <value>
    /// The small change.
    /// </value>
    public double SmallChange
    {
      get => _smallChange;
      set
      {
        if (value == _min) return;
        _min = value;
        RaisePropertyChanged(nameof(SmallChange));
      }
    }

    /// <summary>
    /// Gets or sets the large change.
    /// </summary>
    /// <value>
    /// The large change.
    /// </value>
    public double LargeChange
    {
      get => _largeChange;
      set
      {
        if (Value == _largeChange) return;
        _largeChange = value;
        RaisePropertyChanged(nameof(LargeChange));
      }
    }

    /// <summary>
    /// Gets or sets the maximum.
    /// </summary>
    /// <value>
    /// The maximum.
    /// </value>
    public double Max
    {
      get => _max;
      set
      {
        if (value == _max) return;
        _max = value;
        RaisePropertyChanged(nameof(Max));
      }
    }

    /// <summary>
    /// Gets or sets the minimum.
    /// </summary>
    /// <value>
    /// The minimum.
    /// </value>
    public double Min
    {
      get => _min;
      set
      {
        if (value == _min) return;
        _min = value;
        RaisePropertyChanged(nameof(Min));
      }
    }

    /// <summary>
    /// Gets or sets the value.
    /// </summary>
    /// <value>
    /// The value.
    /// </value>
    public double Value
    {
      get => _value;
      set
      {
        if (value == _value) return;
        _value = value;
        RaisePropertyChanged(nameof(Value));
      }
    }
  }
}
using System;
using System.Linq;

namespace PropertyGridDemo.PropertyGridControl
{
  public class ComboBoxEditDataContext:PropertyDataContext
  {
    private Tuple<string, object>[] _itemSource;
    private Tuple<string, object> _editValue;
    private int _selectedIndex;

    /// <summary>
    /// Initializes a new instance of the <see cref="ComboBoxEditDataContext"/> class.
    /// </summary>
    /// <param name="itemSource">The item source.</param>
    /// <param name="editValue">The edit value.</param>
    public ComboBoxEditDataContext(Tuple<string,object>[] itemSource,Tuple<string,object> editValue)
    {
      _itemSource = itemSource;
      _editValue = _itemSource.FirstOrDefault(x => x?.Item1.ToString() == editValue?.Item1.ToString() && x?.Item2?.ToString() == x?.Item2?.ToString());
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="ComboBoxEditDataContext" /> class.
    /// </summary>
    /// <param name="itemSource">The item source.</param>
    /// <param name="value">The value.</param>
    public ComboBoxEditDataContext(Tuple<string, object>[] itemSource, object value)
    {
      _itemSource = itemSource;
      _editValue = _itemSource.FirstOrDefault(x => x?.Item2.ToString() == value.ToString() );
    }

    public string Name
    {
      get;set;
    }

    /// <summary>
    /// Gets or sets the item source.
    /// </summary>
    /// <value>
    /// The item source.
    /// </value>
    public Tuple<string,object>[] ItemSource
    {
      get => _itemSource;
      set
      {
        //if (_itemSource == value) return;
        _itemSource = value;
        RaisePropertyChanged(nameof(ItemSource));
      }
    }

    /// <summary>
    /// Gets or sets the edit value.
    /// </summary>
    /// <value>
    /// The edit value.
    /// </value>
    public Tuple<string,object> EditValue
    {
      get => _editValue;
      set
      {
        if (_editValue == value) return;
        _editValue = value;
        RaisePropertyChanged(nameof(EditValue));
      }
    }

    public object Value
    {
      set
      {
        EditValue = ItemSource.FirstOrDefault(x => x.Item2.Equals(value));
      }
    }

    /// <summary>
    /// Gets or sets the index of the selected.
    /// </summary>
    /// <value>
    /// The index of the selected.
    /// </value>
    public int SelectedIndex
    {
      get => _selectedIndex;
      set
      {
        if (_selectedIndex == value || value==-1) return;
        _selectedIndex = value;
        EditValue = ItemSource[value];
        RaisePropertyChanged(nameof(SelectedIndex));
      }
    }
  }
}
using System.ComponentModel;

namespace PropertyGridDemo.PropertyGridControl
{
  public class PropertyDataContext:INotifyPropertyChanged
  {
    /// <summary>
    /// 在更改属性值时发生。
    /// </summary>
    public event PropertyChangedEventHandler PropertyChanged;

    /// <summary>
    /// 触发属性变化
    /// </summary>
    /// <param name="propertyName"></param>
    public virtual void RaisePropertyChanged(string propertyName)
    {
      PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
  }
}
using System;

namespace PropertyGridDemo.PropertyGridControl
{
  internal static class ComboBoxEditItemSource
  {
    internal static Tuple<string, object>[] TestItemSource = new Tuple<string, object>[] {
      new Tuple<string, object>("1",1),
      new Tuple<string, object>("2",2),
      new Tuple<string, object>("3",3)
    };
  }
}

5.将以上的CustomPropertyGrid丢进容器中即可,这里我直接用Mainwindow来演示:

<Window
  x:Class="PropertyGridDemo.MainWindow"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:PropertyGridControl="clr-namespace:PropertyGridDemo.PropertyGridControl"
  xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
  xmlns:local="clr-namespace:PropertyGridDemo"
  xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
  Title="MainWindow"
  Width="525"
  Height="350"
  WindowState="Maximized"
  mc:Ignorable="d">
  <Grid Margin="10">
    <Grid.ColumnDefinitions>
      <ColumnDefinition Width="259*" />
      <ColumnDefinition Width="259*" />
    </Grid.ColumnDefinitions>

    <TextBox
      x:Name="OutputBox"
      Grid.ColumnSpan="1"
      HorizontalScrollBarVisibility="Auto"
      ScrollViewer.CanContentScroll="True" />
    <PropertyGridControl:CustomPropertyGrid x:Name="PropertyGrid" Grid.Column="1" />
  </Grid>
</Window>

运行示意图:

以上就是自定义PropertyGrid控件的实现代码,本人只实现了简单的Slider和ComboBoxEdit控件,实际上可以根据自己的需要仿照以上的方法扩展到其他控件,这个就看需求了。

个人感觉以上方案还是有所欠缺,主要是自定义控件的模板是由代码生成的,如果可以直接从资源文件中读取将会更加方便,不过本人尝试了几次并不能成功的实现数据的绑定,如果大家有什么好的解决方案欢迎在评论区留言,也欢迎大家在评论区进行讨论。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • ExtJS PropertyGrid中使用Combobox选择值问题

    问题描述: 在PropertyGrid中使用Combobox来选择值时,得到的应该是displayField的值,但是在确认选择的时候却显示了valueField的值,例如,下拉选择性别,displayField分别为'男','女',对应的valueField分别为'0','1',本来选择应该显示中文描述,但是却显示成了0或者1这样的标识数据,这对用户来说应该不能接受的. 解决: 拦截Grid的beforepropertychange事件,设置好显示的值,之后返回false,阻止修改事件中的验证

  • jQuery EasyUI API 中文文档 - PropertyGrid属性表格

    扩展自 $.fn.datagrid.defaults,用 $.fn.propertygrid.defaults 重写了 defaults. 依赖 datagrid 用法 复制代码 代码如下: <table id="pg"></table> 复制代码 代码如下: $('#pg').propertygrid({ url:'propertygrid_data.json', showGroup:true }); 特性 其特性扩展自 datagrid,下列是为 prope

  • ExtJs扩展之GroupPropertyGrid代码

    ExtJs本身就提供了丰富的空间和良好的界面开发,就如同WinForm的开发一样.但是ExtJs的空间也有不完美的地方,但是有缺点也有他自己的弥补方法.ExtJs的良好的扩展性就是ExtJs自己控件不能实现的最好的方法. 这几个中使用最多的当属ExtJs的PropertyGrid,ExtJs的PropertyGrid使用起来时相当简单的,在ExtJs的官方网站上也有相应的例子,简单的就不在叙述了.但是ExtJs本身的PropertyGrid不能支持分组,在显示的不能将属性进行分组,这是相当郁闷的

  • C++ 关于 CMFCPropertyGridCtrl 的使用方法

    题外话: 最近在写一个重要的程序,想做的更灵活一些,于是想采用属于对话框的形式,如图所示 但查了好几本大部门的C++及MFC的书,还有很多的网上的资料,这方面的介绍实在是少之又少.不过,好在VS2013是半开源的.哈哈,里抽的代码看不到,但是函数声明还是都能看到的.这为我解决问题提供了一条好的方法 ,另外在线的 MSDN 也是一个很好的学习途径,不过,汉语翻译实在是不敢恭维,那叫一个烂,基本上看不懂,他说的是什么,只能啃英文. 所以说,学东西不容易,学会了,一定不要忘记总结,要不然,过段时间就忘

  • C#实现ProperTyGrid自定义属性的方法

    本文实例讲解了C#实现ProperTyGrid自定义属性的方法,分享给大家供大家参考.具体方法如下: 一般来说,C#如果要实现自定义属性必须要需要实现接口ICustomTypeDescriptor,具体实现方法如下: // 摘要: // 提供为对象提供动态自定义类型信息的接口. public interface ICustomTypeDescriptor 示例如下: /// <summary> /// 自定义属性对象 /// </summary> public class MyAt

  • PropertyGrid自定义控件使用详解

    PropertyGrid是一个很强大的控件,使用该控件做属性设置面板的一个好处就是你只需要专注于代码而无需关注UI的呈现,PropertyGrid会默认根据变量类型选择合适的控件显示.但是这也带来了一个问题,就是控件的使用变得不是特别灵活,主要表现在你无法根据你的需求很好的选择控件,比如当你需要用Slider控件来设置int型变量时,PropertyGrid默认的模板选择器是不支持的.网上找了许多资料基本都是介绍WinForm的实现方式,主要用到了IWindowFromService这个接口,并

  • C# PropertyGrid使用案例详解

    1. 只有public的property能显示出来,可以通过BrowsableAttribute来控制是否显示,通过CategoryAttribute设置分类,通过DescriptionAttribute设置描述,Attribute可以加在Class上,也可以加在属性上,属性上的Attribute优先级更高: 2. enum会自动使用列表框表示: 3. 自带输入有效性检查,如int类型输入double数值,会弹出提示对话框: 4. 基本类型Array:增加删除只能通过弹出的集合编辑器,修改可以直

  • AngularJS自定义控件实例详解

    本文实例讲述了AngularJS自定义控件.分享给大家供大家参考,具体如下: 自定义指令介绍 AngularJS 指令作用是在 AngulaJS 应用中操作 Html 渲染.比如说,内插指令 ( {{ }} ), ng-repeat 指令以及 ng-if 指令. 当然你也可以实现自己的.这就是 AngularJS 所谓的"教会 HTML 玩新姿势".本文将告诉你如何做到. 指令类型 可以自定义的指令类型如下: 元素 属性 CSS class Comment directives 这里面

  • Android自定义控件基本原理详解(一)

    前言: 在日常的Android开发中会经常和控件打交道,有时Android提供的控件未必能满足业务的需求,这个时候就需要我们实现自定义一些控件,今天先大致了解一下自定义控件的要求和实现的基本原理. 自定义控件要求: 1. 应当遵守Android标准的规范(命名,可配置,事件处理等).      2. 在XML布局中科配置控件的属性.      3. 对交互应当有合适的反馈,比如按下,点击等.      4. 具有兼容性, Android版本很多,应该具有广泛的适用性. 自定义控件学习步骤: 1

  • Android开发之自定义控件用法详解

    本文实例讲述了Android开发之自定义控件用法.分享给大家供大家参考,具体如下: 今天和大家分享下组合控件的使用.很多时候android自定义控件并不能满足需求,如何做呢?很多方法,可以自己绘制一个,可以通过继承基础控件来重写某些环节,当然也可以将控件组合成一个新控件,这也是最方便的一个方法.今天就来介绍下如何使用组合控件,将通过两个实例来介绍. 第一个实现一个带图片和文字的按钮,如图所示: 整个过程可以分四步走.第一步,定义一个layout,实现按钮内部的布局.代码如下: custom_bu

  • Android表格自定义控件使用详解

    近期公司要做报表功能,在网上搜索下表格的样式后便自己写了一个自定义的表格控件,该表格控件能根据设置的数据中数据的最大值自动设置左侧信息栏显示的值,使得条形图能尽量的充满控件,条形图部分支持左右滑动,数据的长度可能超过控件本身所能容纳的长度,所以在绘制的时候做了判断,当需要绘制的部分不再控件范围内则不进行绘制,具体请阅读代码,目前只支持一个名称对应一条数据,如有不足之处,大家提出帮忙修改 使用方法如下: 在xml文件中定义控件属性 <RelativeLayout     xmlns:android

  • Android自定义控件EditText使用详解

    本文实例为大家分享了Android自定义控件EditText的具体代码,供大家参考,具体内容如下 自定义控件分三种: 1. 自绘控件 2. 组合控件 3. 继承控件 代码已上传到 github 以后的自定义控件就都放这个仓库 需求 这里由于项目的需要实现一个自定义EditText,主要实现的为两点,一个是工具图标toolIcon,例如点击清除EditText内容.一个为EditText左边的提示图标hintIcon, 例如输入账号密码时前面的图标. 为了让这个控件的拓展性更高,设置了两个点击事件

  • Android自定义View中attrs.xml的实例详解

    Android自定义View中attrs.xml的实例详解 我们在自定义View的时候通常需要先完成attrs.xml文件 在values中定义一个attrs.xml 然后添加相关属性 这一篇先详细介绍一下attrs.xml的属性. <?xml version="1.0" encoding="utf-8"?> <resources> //自定义属性名,定义公共属性 <attr name="titleText" for

  • Android LayoutInflater加载布局详解及实例代码

    Android  LayoutInflater加载布局详解 对于有一定Android开发经验的同学来说,一定使用过LayoutInflater.inflater()来加载布局文件,但并不一定去深究过它的原理,比如 1.LayoutInflater为什么可以加载layout文件? 2.加载layout文件之后,又是怎么变成供我们使用的View的? 3.我们定义View的时候,如果需要在布局中使用,则必须实现带AttributeSet参数的构造方法,这又是为什么呢? 既然在这篇文章提出来,那说明这三

  • Android仿京东淘宝自动无限循环轮播控件思路详解

    在App的开发中,很多的时候都需要实现类似京东淘宝一样的自动无限轮播的广告栏,所以就自己写了一个,下面是我自定义控件的思路和过程. 一.自定义控件属性 新建自定义控件SliderLayout继承于RelativeLayout,首先要考虑的就是自定义的控件需要扩展那些属性,把这些属性列出来.在这里是要实现类似于京东淘宝的无限轮播广告栏,那么首先想到的就是轮播的时长.轮播指示器的样式等等.我在这里列举了一些并且结合到了代码中. 1.扩展属性 (1)是否开启自动轮播的功能. (2)指示器的图形样式,一

随机推荐