PropertyGrid是⼀个很强⼤的控件,使⽤该控件做属性设置⾯板的⼀个好处就是你只需要专注于代码⽽⽆需关注UI的呈
现,PropertyGrid会默认根据变量类型选择合适的控件显⽰。但是这也带来了⼀个问题,就是控件的使⽤变得不是特别灵活,主要表现在你⽆法根据你的需求很好的选择控件,⽐如当你需要⽤Slider控件来设置int型变量时,PropertyGrid默认的模板选择器是不⽀持的。⽹上找了许多资料基本都是介绍WinForm的实现⽅式,主要⽤到了IWindowFromService这个接⼝,并未找到合适的适合WPF的Demo,后来在参考了DEVExpress的官⽅Demo之后我做了⼀个基于WPF和DEV 16.2的PropertyGrid Demo,基本实现了上述功能。
为了实现这⼀点,需要⾃定义⼀个DataTemplateSeletor类,这也是本⽂的核⼼代码。1.创建⼀个CustomPropertyGrid⾃定义控件:
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\"> DataContextChanged=\"PropertyGridControl_DataContextChanged\" ExpandCategoriesWhenSelectedObjectChanged=\"True\" PropertyDefinitionStyle=\"{StaticResource DynamicallyAssignDataEditorsPropertyDefinitionStyle}\" PropertyDefinitionTemplateSelector=\"{StaticResource DynamicallyAssignDataEditorsTemplateSelector}\" PropertyDefinitionsSource=\"{Binding Path=Properties, Source={StaticResource DemoDataProvider}}\" ShowCategories=\"True\" ShowDescriptionIn=\"Panel\" />
该控件使⽤的资源字典如下:
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\"> HorizontalContentAlignment=\"Stretch\" Background=\"Transparent\" BorderThickness=\"0\" Foreground=\"{Binding Path=(TextElement.Foreground), RelativeSource={RelativeSource TemplatedParent}}\" IsReadOnly=\"True\" IsTabStop=\"False\" /> HorizontalContentAlignment=\"Stretch\" Background=\"Transparent\" BorderThickness=\"0\" Foreground=\"{Binding Path=(TextElement.Foreground), RelativeSource={RelativeSource TemplatedParent}}\" IsReadOnly=\"True\" IsTabStop=\"False\" /> VerticalAlignment=\"Center\" Text=\"{Binding Item1}\" />
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;
/// /// 当重写在派⽣类中,返回根据⾃定义逻辑的
/// 数据对象可以选择模板。 /// 数据对象。 /// /// 返回
public override DataTemplate SelectTemplate(object item, DependencyObject container) {
_element = (RootPropertyDefinition)container;
DataTemplate resource = TryCreateResource(item);
return resource ?? base.SelectTemplate(item, container); }
/// /// Tries the create resource. ///
/// The item. ///
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); }
/// /// Gets the data context. ///
/// Name of the data context property. ///
private object GetDataContext(string dataContextPropertyName) {
PropertyInfo property = _propertyDataContext?.GetType().GetProperty(dataContextPropertyName); if (property == null) return null;
return property.GetValue(_propertyDataContext, null); }
/// /// Creates the slider data template. ///
/// The custom UI attribute. ///
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; }
/// /// Creates the ComboBox edit template. ///
/// The custom UI attribute. ///
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; }
/// /// Creates the property definition template. ///
/// The custom UI attribute. ///
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{
/// ///初始化所有属性并调⽤模板选择器进⾏匹配 ///
public class DataEditorsViewModel {
public IEnumerable 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; } /// /// ⾃定义控件属性构造函数 /// /// The UI types. /// Name of the data context property. 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\ [CustomUI(CustomUITypes.Slider, nameof(CountSource))] public double Count { get => _count; set { _count = value; CountSource.Value = value; RaisePropertyChanged(nameof(Count)); } } [Display(Name = \"ComboBoxEditItem\ [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\ public string Password { get; set; } [Display(Name = \"TextEdit\ public string Text { get; set; } [Display(Name = \"Notes\ public string Notes { get; set; } [Display(Name = \"Double\ [DefaultValue(1)] public double TestDouble { get; set; } [Display(Name = \"Items\ [DefaultValue(Visibility.Visible)] public Visibility TestItems { get; set; } } public static class DynamicallyAssignDataEditorsMetadata { public static void BuildMetadata(MetadataBuilder 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() { } /// /// Initializes a new instance of the /// The minimum. /// The maximum. /// The value. /// The small change. /// The large change. 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; } /// /// Gets or sets the small change. /// /// The small change. /// public double SmallChange { get => _smallChange; set { if (value == _min) return; _min = value; RaisePropertyChanged(nameof(SmallChange)); } } /// /// Gets or sets the large change. /// /// The large change. /// public double LargeChange { get => _largeChange; set { if (Value == _largeChange) return; _largeChange = value; RaisePropertyChanged(nameof(LargeChange)); } } /// /// Gets or sets the maximum. /// /// The maximum. /// public double Max { get => _max; set { if (value == _max) return; _max = value; RaisePropertyChanged(nameof(Max)); } } /// /// Gets or sets the minimum. /// /// The minimum. /// public double Min { get => _min; set { if (value == _min) return; _min = value; RaisePropertyChanged(nameof(Min)); } } /// /// Gets or sets the 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 /// /// Initializes a new instance of the /// The item source. /// The edit value. public ComboBoxEditDataContext(Tuple _itemSource = itemSource; _editValue = _itemSource.FirstOrDefault(x => x?.Item1.ToString() == editValue?.Item1.ToString() && x?.Item2?.ToString() == x?.Item2?.ToString()); } /// /// Initializes a new instance of the /// The item source. /// The value. public ComboBoxEditDataContext(Tuple _itemSource = itemSource; _editValue = _itemSource.FirstOrDefault(x => x?.Item2.ToString() == value.ToString() ); } public string Name { get;set; } /// /// Gets or sets the item source. /// /// The item source. /// public Tuple get => _itemSource; set { //if (_itemSource == value) return; _itemSource = value; RaisePropertyChanged(nameof(ItemSource)); } } /// /// Gets or sets the edit value. /// /// The edit value. /// public Tuple get => _editValue; set { if (_editValue == value) return; _editValue = value; RaisePropertyChanged(nameof(EditValue)); } } public object Value { set { EditValue = ItemSource.FirstOrDefault(x => x.Item2.Equals(value)); } } /// /// Gets or sets the index of the selected. /// /// The index of the selected. /// 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 { /// /// 在更改属性值时发⽣。 /// public event PropertyChangedEventHandler PropertyChanged; /// /// public virtual void RaisePropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } }} using System; namespace PropertyGridDemo.PropertyGridControl{ internal static class ComboBoxEditItemSource { internal static Tuple 5.将以上的CustomPropertyGrid丢进容器中即可,这⾥我直接⽤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\"> HorizontalScrollBarVisibility=\"Auto\" ScrollViewer.CanContentScroll=\"True\" /> 运⾏⽰意图: 以上就是⾃定义PropertyGrid控件的实现代码,本⼈只实现了简单的Slider和ComboBoxEdit控件,实际上可以根据⾃⼰的需要仿照以上的⽅法扩展到其他控件,这个就看需求了。 个⼈感觉以上⽅案还是有所⽋缺,主要是⾃定义控件的模板是由代码⽣成的,如果可以直接从资源⽂件中读取将会更加⽅便,不过本⼈尝试了⼏次并不能成功的实现数据的绑定,如果⼤家有什么好的解决⽅案欢迎在评论区留⾔,也欢迎⼤家在评论区进⾏讨论。 以上就是本⽂的全部内容,希望对⼤家的学习有所帮助,也希望⼤家多多⽀持。 因篇幅问题不能全部显示,请点此查看更多更全内容
Copyright © 2019- igbc.cn 版权所有 湘ICP备2023023988号-5
违法及侵权请联系:TEL:199 1889 7713 E-MAIL:2724546146@qq.com
本站由北京市万商天勤律师事务所王兴未律师提供法律服务