PropertyGrid是一個很強大的控制項,使用該控制項做屬性設置面板的一個好處就是你只需要專註於代碼而無需關註UI的呈現,PropertyGrid會預設根據變數類型選擇合適的控制項顯示。但是這也帶來了一個問題,就是控制項的使用變得不是特別靈活,主要表現在你無法根據你的需求很好的選擇控制項,比如當你需要用S... ...
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