您好,欢迎来到三六零分类信息网!老站,搜索引擎当天收录,欢迎发信息

一个很强大的控件--PropertyGrid

2025/9/12 20:38:00发布26次查看
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: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的详细内容。
该用户其它信息

VIP推荐

免费发布信息,免费发布B2B信息网站平台 - 三六零分类信息网 沪ICP备09012988号-2
企业名录 Product