为了实现这一点,需要自定义一个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:propertygridcontrol25 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 <richtextbox17 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 <richtextbox28 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 <slider66 maximum="{binding path=max}"67 minimum="{binding path=min}"68 value="{binding path=value}" />69 <textblock text="{binding path=value}" />70 </stackpanel>71 72 <!--</dxprg:propertydefinition.celltemplate>73 </dxprg:propertydefinition>-->74 </datatemplate>75 76 <datatemplate x:key="comboboxedititemtemplate" datatype="tuple">77 <textblock78 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 else140 {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 dynamicallyassigndataeditorsmetadata112 {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 <textbox21 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的详细内容。
