PropertyGrid自定义控件

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了PropertyGrid自定义控件相关的知识,希望对你有一定的参考价值。

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

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

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

技术分享
 1 <UserControl
 2     x:Class="PropertyGridDemo.PropertyGridControl.CustomPropertyGrid"
 3     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
 4     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 5     xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
 6     xmlns:dxprg="http://schemas.devexpress.com/winfx/2008/xaml/propertygrid"
 7     xmlns:local="clr-namespace:PropertyGridDemo.PropertyGridControl"
 8     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
 9     d:DesignHeight="300"
10     d:DesignWidth="300"
11     mc:Ignorable="d">
12     <UserControl.Resources>
13         <ResourceDictionary>
14             <ResourceDictionary.MergedDictionaries>
15                 <!--  资源字典  -->
16                 <ResourceDictionary Source="../PropertyGridControl/DynamicallyAssignDataEditorsResources.xaml" />
17             </ResourceDictionary.MergedDictionaries>
18         </ResourceDictionary>
19     </UserControl.Resources>
20     <Grid>
21         <!--  PropertyDefinitionStyle:定义属性描述的风格模板  -->
22         <!--  PropertyDefinitionTemplateSelector:定义一个模板选择器,对应一个继承自DataTemplateSelector的类  -->
23         <!--  PropertyDefinitionsSource:定义一个获取数据属性集合的类,对应一个自定义类(本Demo中对应DataEditorsViewModel)  -->
24         <dxprg:PropertyGridControl
25             x:Name="PropertyGridControl"
26             Margin="24"
27             DataContextChanged="PropertyGridControl_DataContextChanged"
28             ExpandCategoriesWhenSelectedObjectChanged="True"
29             PropertyDefinitionStyle="{StaticResource DynamicallyAssignDataEditorsPropertyDefinitionStyle}"
30             PropertyDefinitionTemplateSelector="{StaticResource DynamicallyAssignDataEditorsTemplateSelector}"
31             PropertyDefinitionsSource="{Binding Path=Properties, Source={StaticResource DemoDataProvider}}"
32             ShowCategories="True"
33             ShowDescriptionIn="Panel" />
34     </Grid>
35 </UserControl>
CustomPropertyGrid

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

技术分享
 1 <ResourceDictionary
 2     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
 3     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 4     xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
 5     xmlns:dxe="http://schemas.devexpress.com/winfx/2008/xaml/editors"
 6     xmlns:dxg="http://schemas.devexpress.com/winfx/2008/xaml/grid"
 7     xmlns:dxprg="http://schemas.devexpress.com/winfx/2008/xaml/propertygrid"
 8     xmlns:local="clr-namespace:PropertyGridDemo.PropertyGridControl"
 9     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
10     mc:Ignorable="d">
11 
12     <local:DynamicallyAssignDataEditorsTemplateSelector x:Key="DynamicallyAssignDataEditorsTemplateSelector" />
13     <local:DataEditorsViewModel x:Key="DemoDataProvider" />
14 
15     <DataTemplate x:Key="DescriptionTemplate">
16         <RichTextBox
17             x:Name="descriptionRichTextBox"
18             MinWidth="150"
19             HorizontalContentAlignment="Stretch"
20             Background="Transparent"
21             BorderThickness="0"
22             Foreground="{Binding Path=(TextElement.Foreground), RelativeSource={RelativeSource TemplatedParent}}"
23             IsReadOnly="True"
24             IsTabStop="False" />
25     </DataTemplate>
26     <DataTemplate x:Key="descriptionTemplate">
27         <RichTextBox
28             x:Name="descriptionRichTextBox"
29             MinWidth="150"
30             HorizontalContentAlignment="Stretch"
31             Background="Transparent"
32             BorderThickness="0"
33             Foreground="{Binding Path=(TextElement.Foreground), RelativeSource={RelativeSource TemplatedParent}}"
34             IsReadOnly="True"
35             IsTabStop="False" />
36     </DataTemplate>
37 
38     <!--  设置控件的全局样式和数据绑定  -->
39     <Style x:Key="DynamicallyAssignDataEditorsPropertyDefinitionStyle" TargetType="dxprg:PropertyDefinition">
40         <Setter Property="Path" Value="{Binding Name}" />
41         <!--<Setter Property="Header" Value="{Binding Converter={StaticResource PropertyDescriptorToDisplayNameConverter}}"/>-->
42         <Setter Property="Description" Value="{Binding}" />
43         <Setter Property="DescriptionTemplate" Value="{StaticResource descriptionTemplate}" />
44     </Style>
45     <Style x:Key="DescriptionContainerStyle" TargetType="dxprg:PropertyDescriptionPresenterControl">
46         <Setter Property="ShowSelectedRowHeader" Value="False" />
47         <Setter Property="MinHeight" Value="70" />
48     </Style>
49 
50     <Style TargetType="Slider">
51         <Setter Property="Margin" Value="2" />
52     </Style>
53     <Style TargetType="dxe:ComboBoxEdit">
54         <Setter Property="IsTextEditable" Value="False" />
55         <Setter Property="ApplyItemTemplateToSelectedItem" Value="True" />
56         <Setter Property="Margin" Value="2" />
57     </Style>
58 
59     <!--  测试直接从DataTemplate获取控件  -->
60     <DataTemplate x:Key="SliderTemplate" DataType="local:SliderExtend">
61         <!--<dxprg:PropertyDefinition>
62             <dxprg:PropertyDefinition.CellTemplate>-->
63         <!--<DataTemplate>-->
64         <StackPanel x:Name="Root">
65             <Slider
66                 Maximum="{Binding Path=Max}"
67                 Minimum="{Binding Path=Min}"
68                 Value="{Binding Path=Value}" />
69             <TextBlock Text="{Binding Path=Value}" />
70         </StackPanel>
71         <!--</DataTemplate>-->
72         <!--</dxprg:PropertyDefinition.CellTemplate>
73         </dxprg:PropertyDefinition>-->
74     </DataTemplate>
75 
76     <DataTemplate x:Key="ComboBoxEditItemTemplate" DataType="Tuple">
77         <TextBlock
78             Height="20"
79             Margin="5,3,0,0"
80             VerticalAlignment="Center"
81             Text="{Binding Item1}" />
82     </DataTemplate>
83 </ResourceDictionary>
ResourceDictionary

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

技术分享
  1 using DevExpress.Xpf.Editors;
  2 using DevExpress.Xpf.PropertyGrid;
  3 using System.ComponentModel;
  4 using System.Reflection;
  5 using System.Windows;
  6 using System.Windows.Controls;
  7 using System.Windows.Data;
  8 
  9 namespace PropertyGridDemo.PropertyGridControl
 10 {
 11     public class DynamicallyAssignDataEditorsTemplateSelector : DataTemplateSelector
 12     {
 13         private PropertyDescriptor _property = null;
 14         private RootPropertyDefinition _element = null;
 15         private PropertyDataContext _propertyDataContext => App.PropertyGridDataContext;
 16 
 17         /// <summary>
 18         /// 当重写在派生类中,返回根据自定义逻辑的 <see cref="T:System.Windows.DataTemplate" /> 19         /// </summary>
 20         /// <param name="item">数据对象可以选择模板。</param>
 21         /// <param name="container">数据对象。</param>
 22         /// <returns>
 23         /// 返回 <see cref="T:System.Windows.DataTemplate" /> 或 null。默认值为 null。
 24         /// </returns>
 25         public override DataTemplate SelectTemplate(object item, DependencyObject container)
 26         {
 27             _element = (RootPropertyDefinition)container;
 28             DataTemplate resource = TryCreateResource(item);
 29             return resource ?? base.SelectTemplate(item, container);
 30         }
 31 
 32         /// <summary>
 33         /// Tries the create resource.
 34         /// </summary>
 35         /// <param name="item">The item.</param>
 36         /// <returns></returns>
 37         private DataTemplate TryCreateResource(object item)
 38         {
 39             if (!(item is PropertyDescriptor)) return null;
 40             PropertyDescriptor pd = (PropertyDescriptor)item;
 41             _property = pd;
 42             var customUIAttribute = (CustomUIAttribute)pd.Attributes[typeof(CustomUIAttribute)];
 43             if (customUIAttribute == null) return null;
 44             var customUIType = customUIAttribute.CustomUI;
 45             return CreatePropertyDefinitionTemplate(customUIAttribute);
 46         }
 47 
 48         /// <summary>
 49         /// Gets the data context.
 50         /// </summary>
 51         /// <param name="dataContextPropertyName">Name of the data context property.</param>
 52         /// <returns></returns>
 53         private object GetDataContext(string dataContextPropertyName)
 54         {
 55             PropertyInfo property = _propertyDataContext?.GetType().GetProperty(dataContextPropertyName);
 56             if (property == null) return null;
 57             return property.GetValue(_propertyDataContext, null);
 58         }
 59 
 60         /// <summary>
 61         /// Creates the slider data template.
 62         /// </summary>
 63         /// <param name="customUIAttribute">The custom UI attribute.</param>
 64         /// <returns></returns>
 65         private DataTemplate CreateSliderDataTemplate(CustomUIAttribute customUIAttribute)
 66         {
 67             DataTemplate ct = new DataTemplate();
 68             ct.VisualTree = new FrameworkElementFactory(typeof(StackPanel));
 69             ct.VisualTree.SetValue(StackPanel.DataContextProperty, GetDataContext(customUIAttribute.DataContextPropertyName));
 70 
 71             FrameworkElementFactory sliderFactory = new FrameworkElementFactory(typeof(Slider));
 72             sliderFactory.SetBinding(Slider.MaximumProperty, new Binding(nameof(SliderUIDataContext.Max)));
 73             sliderFactory.SetBinding(Slider.MinimumProperty, new Binding(nameof(SliderUIDataContext.Min)));
 74             sliderFactory.SetBinding(Slider.SmallChangeProperty, new Binding(nameof(SliderUIDataContext.SmallChange)));
 75             sliderFactory.SetBinding(Slider.LargeChangeProperty, new Binding(nameof(SliderUIDataContext.LargeChange)));
 76             sliderFactory.SetBinding(Slider.ValueProperty, new Binding(nameof(SliderUIDataContext.Value)));
 77             ct.VisualTree.AppendChild(sliderFactory);
 78 
 79             FrameworkElementFactory textFacotry = new FrameworkElementFactory(typeof(TextBlock), "TextBlock");
 80             textFacotry.SetValue(TextBlock.TextProperty, new Binding(nameof(SliderUIDataContext.Value)));
 81             //textBoxFactory.AddHandler(TextBox.IsVisibleChanged, new DependencyPropertyChangedEventHandler(SearchBoxVisibleChanged));
 82             ct.VisualTree.AppendChild(textFacotry);
 83             ct.Seal();
 84             return ct;
 85         }
 86 
 87         /// <summary>
 88         /// Creates the ComboBox edit template.
 89         /// </summary>
 90         /// <param name="customUIAttribute">The custom UI attribute.</param>
 91         /// <returns></returns>
 92         private DataTemplate CreateComboBoxEditTemplate(CustomUIAttribute customUIAttribute)
 93         {
 94             DataTemplate template = new DataTemplate();
 95             template.VisualTree = new FrameworkElementFactory(typeof(DockPanel));
 96             template.VisualTree.SetValue(DockPanel.DataContextProperty, GetDataContext(customUIAttribute.DataContextPropertyName));
 97 
 98             FrameworkElementFactory textFactory = new FrameworkElementFactory(typeof(TextBlock)) ;
 99             textFactory.SetValue(TextBlock.TextProperty, new Binding(nameof(ComboBoxEditDataContext.Name)));
100             template.VisualTree.AppendChild(textFactory);
101 
102             FrameworkElementFactory comboBoxEditFactory = new FrameworkElementFactory(typeof(ComboBoxEdit));
103             comboBoxEditFactory.SetBinding(ComboBoxEdit.ItemsSourceProperty, new Binding(nameof(ComboBoxEditDataContext.ItemSource)));
104             comboBoxEditFactory.SetBinding(ComboBoxEdit.EditValueProperty, new Binding(nameof(ComboBoxEditDataContext.EditValue)));
105             comboBoxEditFactory.SetBinding(ComboBoxEdit.SelectedIndexProperty, new Binding(nameof(ComboBoxEditDataContext.SelectedIndex)));
106             comboBoxEditFactory.SetValue(ComboBoxEdit.ItemTemplateProperty, (DataTemplate)_element.TryFindResource("ComboBoxEditItemTemplate"));
107             template.VisualTree.AppendChild(comboBoxEditFactory);
108             template.Seal();
109             return template;
110         }
111 
112         /// <summary>
113         /// Creates the property definition template.
114         /// </summary>
115         /// <param name="customUIAttribute">The custom UI attribute.</param>
116         /// <returns></returns>
117         private DataTemplate CreatePropertyDefinitionTemplate(CustomUIAttribute customUIAttribute)
118         {
119             DataTemplate dataTemplate = new DataTemplate();
120             DataTemplate cellTemplate = null;//单元格模板
121             FrameworkElementFactory factory = new FrameworkElementFactory(typeof(PropertyDefinition));
122             dataTemplate.VisualTree = factory;
123             switch (customUIAttribute.CustomUI)
124             {
125                 case CustomUITypes.Slider:
126                     cellTemplate = CreateSliderDataTemplate(customUIAttribute); break;
127                     //cellTemplate = (DataTemplate)_element.TryFindResource("SliderTemplate");break;
128                 case CustomUITypes.ComboBoxEit:
129                     cellTemplate = CreateComboBoxEditTemplate(customUIAttribute);break;
130                 
131             }
132 
133             if (cellTemplate != null)
134             {
135                 factory.SetValue(PropertyDefinition.CellTemplateProperty, cellTemplate);
136                 dataTemplate.Seal();
137 
138             }
139             else
140             {
141                 return null;
142             }
143             return dataTemplate;
144         }
145     }
146 }
DynamicallyAssignDataEditorsTemplateSelector
技术分享
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>(); } }
    }
}
DataEditorsViewModel

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;
        }
    }

}
CustomUIType

4.编写对应的DataContext类 TestPropertyGrid:

技术分享
  1 using DevExpress.Mvvm.DataAnnotations;
  2 using System;
  3 using System.ComponentModel;
  4 using System.ComponentModel.DataAnnotations;
  5 using System.Timers;
  6 using System.Windows;
  7 
  8 namespace PropertyGridDemo.PropertyGridControl
  9 {
 10     [MetadataType(typeof(DynamicallyAssignDataEditorsMetadata))]
 11     public class TestPropertyGrid : PropertyDataContext
 12     {
 13         private double _count = 0;
 14         private SliderUIDataContext _countSource = null;
 15         private ComboBoxEditDataContext _comboSource = null;
 16         private double _value=1;
 17 
 18         public TestPropertyGrid()
 19         {
 20             Password = "1111111";
 21             Notes = "Hello";
 22             Text = "Hello hi";
 23         }
 24 
 25         [Browsable(false)]
 26         public SliderUIDataContext CountSource
 27         {
 28             get
 29             {
 30                 if (_countSource != null)
 31                 {
 32 
 33                     return _countSource;
 34                 }
 35                 else
 36                 {
 37                     _countSource = new SliderUIDataContext(0, 100, Count, 0.1, 1);
 38                     _countSource.PropertyChanged += (object o, PropertyChangedEventArgs e) =>
 39                     {
 40                         this.Count = _countSource.Value;
 41                     };
 42                     return _countSource;
 43                 }
 44             }
 45         }
 46 
 47         [Browsable(false)]
 48         public ComboBoxEditDataContext ComboSource
 49         {
 50             get
 51             {
 52                 if(_comboSource==null)
 53                 {
 54                     _comboSource =new ComboBoxEditDataContext(ComboBoxEditItemSource.TestItemSource,Value);
 55                     _comboSource.PropertyChanged += (object o, PropertyChangedEventArgs e) =>
 56                       {
 57                           this.Value =Convert.ToDouble(_comboSource.EditValue.Item2); 
 58                       };
 59                    
 60                 }
 61                 return _comboSource;
 62             }
 63         }
 64 
 65         [Display(Name = "SliderEdit", GroupName = "CustomUI")]
 66         [CustomUI(CustomUITypes.Slider, nameof(CountSource))]
 67         public double Count
 68         {
 69             get => _count;
 70             set
 71             {
 72                 _count = value;
 73                 CountSource.Value = value; 
 74                 RaisePropertyChanged(nameof(Count));
 75             }
 76         }
 77 
 78         [Display(Name = "ComboBoxEditItem", GroupName = "CustomUI")]
 79         [CustomUI(CustomUITypes.ComboBoxEit, nameof(ComboSource))]
 80         public double Value
 81         {
 82             get => _value;
 83             set
 84             {
 85                 if (_value == value) return;
 86                 _value = value;
 87                 //ComboSource.Value = value;
 88                 RaisePropertyChanged(nameof(Value));
 89             }
 90         }
 91 
 92         [Display(Name = "Password", GroupName = "DefaultUI")]
 93         public string Password { get; set; }
 94 
 95         [Display(Name = "TextEdit", GroupName = "DefaultUI")]
 96         public string Text { get; set; }
 97 
 98         [Display(Name = "Notes", GroupName = "DefaultUI")]
 99         public string Notes { get; set; }
100 
101 
102         [Display(Name = "Double", GroupName = "DefaultUI")]
103         [DefaultValue(1)]
104         public double TestDouble { get; set; }
105 
106         [Display(Name = "Items", GroupName = "DefaultUI")]
107         [DefaultValue(Visibility.Visible)]
108         public Visibility TestItems { get; set; }
109     }
110 
111     public static class DynamicallyAssignDataEditorsMetadata
112     {
113         public static void BuildMetadata(MetadataBuilder<TestPropertyGrid> builder)
114         {
115             builder.Property(x => x.Password)
116                 .PasswordDataType();
117 
118             builder.Property(x => x.Notes)
119                 .MultilineTextDataType();
120         }
121     }
122 }
TestPropertyGrid

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

技术分享
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));
            }
        }
    }
}
SliderUIDataContext
技术分享
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));
            }
        }
    }
}
ComboBoxEditDataContext
技术分享
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));
        }
    }
}
PropertyDataContext
技术分享
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)
        };
    }
}
ComboBoxEditItemSource

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

技术分享
 1 <Window
 2     x:Class="PropertyGridDemo.MainWindow"
 3     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
 4     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 5     xmlns:PropertyGridControl="clr-namespace:PropertyGridDemo.PropertyGridControl"
 6     xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
 7     xmlns:local="clr-namespace:PropertyGridDemo"
 8     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
 9     Title="MainWindow"
10     Width="525"
11     Height="350"
12     WindowState="Maximized"
13     mc:Ignorable="d">
14     <Grid Margin="10">
15         <Grid.ColumnDefinitions>
16             <ColumnDefinition Width="259*" />
17             <ColumnDefinition Width="259*" />
18         </Grid.ColumnDefinitions>
19 
20         <TextBox
21             x:Name="OutputBox"
22             Grid.ColumnSpan="1"
23             HorizontalScrollBarVisibility="Auto"
24             ScrollViewer.CanContentScroll="True" />
25         <PropertyGridControl:CustomPropertyGrid x:Name="PropertyGrid" Grid.Column="1" />
26     </Grid>
27 </Window>
MainWindow

 

运行示意图:

技术分享

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

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

以上内容均为原创,转发请注明出处,谢谢!

 

以上是关于PropertyGrid自定义控件的主要内容,如果未能解决你的问题,请参考以下文章

C# 如何定义让PropertyGrid控件显示[...]按钮,并且点击后以下拉框形式显示自定义控件编辑属性值

C# 自定义PropertyGrid中显示控件的大小,要求限定PropertyGrid中大小的值。

C#自定义PropertyGrid属性

C#自定义PropertyGrid属性

4-PropertyGrid绑定自定义弹窗

关于C# PropertyGrid控件的属性只读