PropertyGrid自定義控件使用詳解。本站提示廣大學習愛好者:(PropertyGrid自定義控件使用詳解)文章只能為提供參考,不一定能成為您想要的結果。以下是PropertyGrid自定義控件使用詳解正文
作者:WCode
這篇文章主要為大家詳細介紹了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" PropertyDefinition 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控件,實際上可以根據自己的需要仿照以上的方法擴展到其他控件,這個就看需求了。
個人感覺以上方案還是有所欠缺,主要是自定義控件的模板是由代碼生成的,如果可以直接從資源文件中讀取將會更加方便,不過本人嘗試了幾次並不能成功的實現數據的綁定,如果大家有什麼好的解決方案歡迎在評論區留言,也歡迎大家在評論區進行討論。
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持。