wpf的低调自定义属性面板PropertyGrid

引用:https://www.cnblogs.com/lsgsanxiao/p/11776421.html

当没有轮子的时候,就自己制作轮子。

前言

项目上的需求,我想需要用到这样一个跟vs属性编辑一样的东西,专业叫法,属性面板

怎么弄呢?

百度一下,wpf的PropertyGrid,如下:

WPF中实现PropertyGrid的三种方式

群上问wpf跟vs属性编辑类似的东西有人弄过吗

开始

为了要体现我的卑微,这里要做一下说明:

刚接触wpf不久(不对,以前也看过这方面的东西,就是没实际项目),刚好两个月前,项目要用wpf弄,然后就开干。

很多东西都是边研究边做的。

上面那段是我一年前写的,本来当时做出来之后就想写个博文,没完成,现在把它完成了。

这里先介绍一个wpf控件库HandyControl,我一年前用的时候控件还没那么多,现在也有PropertyGrid,具体表现如下:

是不是很酷炫,我最近更新才看到的,害,可惜了。

本来想替换掉我写的,但很麻烦:1.功能  2.现有项目布局。

我写的是这样的:

跟HandyControl样式方面差别很大,那是因为我把样式Style = null,使用的全部原生的样式,所以如果你想酷炫,完全可以自己改,这里我只讲这个控件的实现思路。

怎么来?慢慢来。

1.分析这个控件功能:显示对象属性,并对其进行分类和编辑

2.分析控件显示布局,可以参考vs的属性面板

肯定有人头大,vs属性面板那么多功能,哇,烦躁。

有人欲求不得,所以烦躁。简单的讲就是想太多

把一个东西,一件事分成n件事情来做,然后把每步做好,这件事就做好了。

如果很乱,你就写下来。vs属性面板很复杂,那简化一下,就展示一个属性,做成下面这样:

以上的分析,我们就知道了控件的两个重要的东西,逻辑和布局。

第一步:创建测试类

public class Test:ViewModelBase{private string _Name;/// <summary>/// Name 属性更改通知/// </summary>public string Name{get{return _Name;}set{_Name = value;RaisePropertyChanged(() => Name);}}}

ViewModelBase只是为了使用RaisePropertyChanged触发属性变化,引用自GalaSoft.MvvmLight

1

既然是编辑对象的属性,那肯定少不了Attribute,所以需要写一个描述对象属性的Attribute,如下:

/// <summary>/// 可对字段应用的 PropertyGrid 特征  /// </summary>[AttributeUsage(AttributeTargets.All,AllowMultiple = true, Inherited = true)]public class LsPropertyGridAttribute : Attribute{/// <summary>/// 对应的板块/// </summary>public string Plate;/// <summary>/// 显示名称/// </summary>public string ShowName;public LsPropertyGridAttribute(string plate, string showName){TypeName = type;ShowName = showName;}}

1

<br>那测试的类的name属性就可以添加上特征

public class Test:ViewModelBase{private string _Name;/// <summary>/// Name 属性更改通知/// </summary>[LsPropertyGrid("内容","名字")]public string Name{get{return _Name;}set{_Name = value;RaisePropertyChanged(() => Name);}}}

接下来写PropertyGrid控件,这里我继承StackPanel,并且你得有个展示的依赖属性,用来赋值对象,所以它的类型是object,别问我怎么知道的,问就是掐指一算。

 public class PropertyGrid : StackPanel{static PropertyGrid(){//设置该控件引用样式的键// set the key to reference the style for this controlFrameworkElement.DefaultStyleKeyProperty.OverrideMetadata(typeof(PropertyGrid), new FrameworkPropertyMetadata(typeof(PropertyGrid)));}/// <summary>/// 需要显示属性的类型/// </summary>private object _ShowProp;#region 依赖属性/// <summary>/// 显示该类的属性编辑/// </summary>public object ShowProp{get { return (object)GetValue(ShowPropProperty); }set { SetValue(ShowPropProperty, value); }}// Using a DependencyProperty as the backing store for ShowProp.  This enables animation, styling, binding, etc...public static readonly DependencyProperty ShowPropProperty =DependencyProperty.Register("ShowProp", typeof(object), typeof(PropertyGrid),new PropertyMetadata(default(object), new PropertyChangedCallback((d, e) =>{//属性更改事件OnShowPropChanged(d, e);})));#endregion/// <summary>/// ShowProp属性更改事件/// </summary>/// <param name="d"></param>/// <param name="e"></param>private static void OnShowPropChanged(DependencyObject d, DependencyPropertyChangedEventArgs e){}}

上面的简单代码,其实已经把整个属性面板的代码结构给搭好了,接下来,我们慢慢完善。因为属性面板是面对所有类型的对象,所以我们需要用反射获取这个对象的信息

获取对象的编辑属性,然后生成布局,并绑定

 public class PropertyGrid : StackPanel{static PropertyGrid(){//设置该控件引用样式的键// set the key to reference the style for this controlFrameworkElement.DefaultStyleKeyProperty.OverrideMetadata(typeof(PropertyGrid), new FrameworkPropertyMetadata(typeof(PropertyGrid)));}/// <summary>/// 需要显示属性的类型/// </summary>private object _ShowProp;#region 依赖属性/// <summary>/// 显示该类的属性编辑/// </summary>public object ShowProp{get { return (object)GetValue(ShowPropProperty); }set { SetValue(ShowPropProperty, value); }}// Using a DependencyProperty as the backing store for ShowProp.  This enables animation, styling, binding, etc...public static readonly DependencyProperty ShowPropProperty =DependencyProperty.Register("ShowProp", typeof(object), typeof(PropertyGrid),new PropertyMetadata(default(object), new PropertyChangedCallback((d, e) =>{//属性更改事件OnShowPropChanged(d, e);})));#endregion/// <summary>/// ShowProp属性更改事件/// </summary>/// <param name="d"></param>/// <param name="e"></param>private static void OnShowPropChanged(DependencyObject d, DependencyPropertyChangedEventArgs e){var sender = d as PropertyGrid;var newValue = e.NewValue;if (newValue != null){Type t = newValue.GetType();sender._ShowProp = newValue;Object[] obj = t.GetProperties();//取属性上的自定义特性foreach (PropertyInfo propInfo in obj){object[] objAttrs = propInfo.GetCustomAttributes(typeof(LsPropertyGridAttribute), true);if (objAttrs.Length > 0){//获取编辑的属性特征LsPropertyGridAttribute attr = objAttrs[0] as LsPropertyGridAttribute;if (attr != null){double positionLeft = 10;//距离左边double positionTop = 15;//距离上//Console.WriteLine("Type : {0}", attr.TypeName);//板块不存在创建TextBlock label = new TextBlock();label.Text = attr.Plate;label.HorizontalAlignment = HorizontalAlignment.Left;label.Margin = new Thickness(positionLeft, positionTop, 0, 2);label.FontSize = 16;//超过400才有粗效果label.FontWeight = FontWeight.FromOpenTypeWeight(600);sender.Children.Add(label);//板块的GridGrid grid = new Grid();//grid.Width = 200;grid.Margin = new Thickness(positionLeft, 0, 0, 2);grid.HorizontalAlignment = HorizontalAlignment.Left;grid.Background = Brushes.White;//添加列var column = new ColumnDefinition();column.Width = new GridLength(80);column.MinWidth = 80;column.MaxWidth = 100;grid.ColumnDefinitions.Add(column);var column2 = new ColumnDefinition();//column.Width = new GridLength(1.0, GridUnitType.Star);column2.Width = new GridLength(1.0, GridUnitType.Auto);column2.MinWidth = 250;column2.MaxWidth = 250;grid.ColumnDefinitions.Add(column2);sender.Children.Add(grid);var row = new RowDefinition();row.MinHeight = 22;grid.RowDefinitions.Add(row); //添加行//左边显示名称TextBlock tb = new TextBlock();tb.Text = attr.ShowName;tb.HorizontalAlignment = HorizontalAlignment.Left;tb.VerticalAlignment = VerticalAlignment.Center;tb.Margin = new Thickness(0, 0, 0, 0);//通过代码修改控件的Grid.Row属性Grid.SetRow(tb, grid.RowDefinitions.Count - 1);Grid.SetColumn(tb, 0);grid.Children.Add(tb);//根据执行属性的名称绑定到控件Binding binding = new Binding(propInfo.Name);binding.Source = newValue;binding.Mode = BindingMode.TwoWay;var control = new TextBox();control.Style = null;//回车触发绑定control.PreviewKeyDown += Control_PreviewKeyDown;//binding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;binding.UpdateSourceTrigger = UpdateSourceTrigger.Explicit;control.SetBinding(System.Windows.Controls.TextBox.TextProperty, binding);control.VerticalAlignment = VerticalAlignment.Center;//通过代码修改控件的Grid.Row属性Grid.SetRow(control, grid.RowDefinitions.Count - 1);Grid.SetColumn(control, 1);grid.Children.Add(control);}}}}}/// <summary>/// 回车触发数据改变/// </summary>/// <param name="sender"></param>/// <param name="e"></param>private static void Control_PreviewKeyDown(object sender, System.Windows.Input.KeyEventArgs e){if (e.Key == Key.Enter){var temp = sender as WControls.TextBox;BindingExpression binding = temp.GetBindingExpression(WControls.TextBox.TextProperty);binding.UpdateSource();}}}

一个最简单的属性面板就诞生了,只有一个属性,生成后,即可使用

窗体xaml中引用控件路径:xmlns:Data="clr-namespace:属性面板Demo.Data"

<Data:PropertyGrid x:Name="pg" HorizontalAlignment="Left" Height="100" Margin="215,107,0,0" Grid.Row="1" VerticalAlignment="Top" Width="400"/>
 var test = new Test();test.Name = "wc";pg.ShowProp = test;

如下展示:

其他的就一点一点的添加,同理可得了。

那下面我直接就上完整的代码,嗯嗯,你们都同意了(- -,你是有多懒)

控件里面有下拉框,选中,按钮(用来绑定触发方法)等,可以控制绑定控件的任何可绑定的属性,比如:隐藏显示

完整的PropertyGrid

 /// <summary>/// 自定义属性显示控件/// </summary>public class PropertyGrid : StackPanel{static PropertyGrid(){//设置该控件引用样式的键// set the key to reference the style for this controlFrameworkElement.DefaultStyleKeyProperty.OverrideMetadata(typeof(PropertyGrid), new FrameworkPropertyMetadata(typeof(PropertyGrid)));}#region 字段/// <summary>/// 记录一个板块对应的Grid/// </summary>private Dictionary<string, Grid> _KeyValuePairs = new Dictionary<string, Grid>();/// <summary>/// 需要显示属性的类型/// </summary>private object _ShowProp;#endregion#region 依赖属性/// <summary>/// 显示该类的属性编辑/// </summary>public object ShowProp{get { return (object)GetValue(ShowPropProperty); }set { SetValue(ShowPropProperty, value); }}// Using a DependencyProperty as the backing store for ShowProp.  This enables animation, styling, binding, etc...public static readonly DependencyProperty ShowPropProperty =DependencyProperty.Register("ShowProp", typeof(object), typeof(PropertyGrid),new PropertyMetadata(default(object), new PropertyChangedCallback((d, e) =>{//属性更改事件OnShowPropChanged(d, e);})));#endregion#region private方法/// <summary>/// ShowProp属性更改事件/// </summary>/// <param name="d"></param>/// <param name="e"></param>private static void OnShowPropChanged(DependencyObject d, DependencyPropertyChangedEventArgs e){var sender = d as PropertyGrid;sender.Children.Clear();sender._KeyValuePairs.Clear();var newValue = e.NewValue;if (newValue != null){Type t = newValue.GetType();sender._ShowProp = newValue;Object[] obj = t.GetProperties();//取属性上的自定义特性foreach (PropertyInfo propInfo in obj){CreateControlByAttribute(sender, newValue, propInfo);}Object[] objFields = t.GetFields();//取公有字段上的自定义特性foreach (FieldInfo propInfo in objFields){CreateControlByAttribute(sender, newValue, propInfo);}Object[] objMethods = t.GetMethods();//取公有方法上的自定义特性foreach (MethodInfo propInfo in objMethods){CreateControlByAttribute(sender, newValue, propInfo);}}}/// <summary>/// 根据属性特征创建控件/// </summary>/// <param name="objAttrs"></param>/// <param name="sender"></param>/// <param name="Source"></param>/// <param name="path"></param>private static void CreateControlByAttribute(PropertyGrid sender, object Source, MemberInfo memberInfo){object[] objAttrs = memberInfo.GetCustomAttributes(typeof(LsPropertyGridAttribute), true);if (objAttrs.Length > 0){//获取编辑的属性特征LsPropertyGridAttribute attr = objAttrs[0] as LsPropertyGridAttribute;if (attr != null){//Console.WriteLine("Type : {0}", attr.TypeName);Create(sender, attr, Source, memberInfo);}}}/// <summary>/// 创建/// </summary>/// <param name="sender">PropertyGrid</param>/// <param name="attr"></param>/// <param name="Source">绑定的对象</param>/// <param name="path">对象的属性</param>public static void Create(PropertyGrid sender, LsPropertyGridAttribute attr, object Source, MemberInfo memberInfo){double positionLeft = 10;//距离左边double positionTop = 15;//距离上//判断板块是否已存在if (sender._KeyValuePairs.ContainsKey(attr.Plate)){var grid = sender._KeyValuePairs[attr.Plate];//存在直接在Grid后面添加控件CreateControl(sender,grid, attr, Source, memberInfo);}else{//板块不存在创建TextBlock label = new TextBlock();label.Text = attr.Plate;label.HorizontalAlignment = HorizontalAlignment.Left;label.Margin = new Thickness(positionLeft, positionTop, 0, 2);label.FontSize = 16;//超过400才有粗效果label.FontWeight = FontWeight.FromOpenTypeWeight(600);sender.Children.Add(label);//板块的GridGrid grid = new Grid();//grid.Width = 200;grid.Margin = new Thickness(positionLeft, 0, 0, 2);grid.HorizontalAlignment = HorizontalAlignment.Left;grid.Background = Brushes.White;//添加列var column = new ColumnDefinition();column.Width = new GridLength(80);column.MinWidth = 80;column.MaxWidth = 100;grid.ColumnDefinitions.Add(column);var column2 = new ColumnDefinition();//column.Width = new GridLength(1.0, GridUnitType.Star);column2.Width = new GridLength(1.0, GridUnitType.Auto);column2.MinWidth = 250;column2.MaxWidth = 250;grid.ColumnDefinitions.Add(column2);//添加记录模板sender._KeyValuePairs[attr.Plate] = grid;sender.Children.Add(grid);CreateControl(sender,grid, attr, Source, memberInfo);}}/// <summary>/// 创建并绑定控件/// </summary>/// <param name="pROPERTYType"></param>/// <param name="path"></param>/// <returns></returns>private static void CreateControl(PropertyGrid sender, Grid grid, LsPropertyGridAttribute attr, object Source, MemberInfo memberInfo){Control control = new Control();if (attr.TypeName != PROPERTYType.Size){var row = new RowDefinition();row.MinHeight = 22;grid.RowDefinitions.Add(row); //添加行//左边显示名称TextBlock tb = new TextBlock();tb.Text = attr.ShowName;tb.HorizontalAlignment = HorizontalAlignment.Left;tb.VerticalAlignment = VerticalAlignment.Center;tb.Margin = new Thickness(0, 0, 0, 0);//通过代码修改控件的Grid.Row属性Grid.SetRow(tb, grid.RowDefinitions.Count - 1);Grid.SetColumn(tb, 0);grid.Children.Add(tb);}//根据执行属性的名称绑定到控件Binding binding = new Binding(memberInfo.Name);binding.Source = Source;binding.Mode = BindingMode.TwoWay;//if ((attr.TypeName & PROPERTYType.TextBox) == PROPERTYType.TextBox)//{//    control = new WControls.TextBox();//    control.Style = null;//    binding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;//    control.SetBinding(System.Windows.Controls.TextBox.TextProperty, binding);//}switch (attr.TypeName){case PROPERTYType.Folder:#region Folderdouble tbFolderWidth = 210;var btnFolder = new WControls.Button();btnFolder.Content = "...";btnFolder.Width = 40;btnFolder.HorizontalAlignment = HorizontalAlignment.Left;btnFolder.Margin = new Thickness(tbFolderWidth, 0, 0, 0);//通过代码修改控件的Grid.Row属性Grid.SetRow(btnFolder, grid.RowDefinitions.Count - 1);Grid.SetColumn(btnFolder, 1);btnFolder.Style = null;var tbFolder = new WControls.TextBox();tbFolder.Width = tbFolderWidth;tbFolder.HorizontalAlignment = HorizontalAlignment.Left;Grid.SetRow(tbFolder, grid.RowDefinitions.Count - 1);Grid.SetColumn(tbFolder, 1);tbFolder.Style = null;//方法绑定在Button控件sender.MethodSeBinding(btnFolder, memberInfo);//属性两个都绑定 所有绑定必须要绑定两个都有的属性sender.RelationSeBinding(tbFolder, memberInfo, grid);//再次绑定就不需要绑定grid第一列设置falsesender.RelationSeBinding(btnFolder, memberInfo, grid,false);//回车触发绑定tbFolder.PreviewKeyDown += Control_PreviewKeyDown;//binding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;binding.UpdateSourceTrigger = UpdateSourceTrigger.Explicit;tbFolder.SetBinding(System.Windows.Controls.TextBox.TextProperty, binding);grid.Children.Add(btnFolder);grid.Children.Add(tbFolder);#endregionreturn;case PROPERTYType.BoldItalic://grid.Children.RemoveAt(grid.Children.Count - 1);string[] vsBoldItalic = attr.Tag.Split(',');#region 粗体//粗体string[] vsBold = vsBoldItalic[0].Split(':');var controlBold = new WControls.Button();controlBold.Width = 40;controlBold.Content = vsBold[1];controlBold.HorizontalAlignment = HorizontalAlignment.Left;//通过代码修改控件的Grid.Row属性Grid.SetRow(controlBold, grid.RowDefinitions.Count - 1);Grid.SetColumn(controlBold, 1);controlBold.Style = null;grid.Children.Add(controlBold);//根据执行属性的名称绑定到控件Binding bindingBold = new Binding(vsBold[0]);bindingBold.Source = Source;bindingBold.Mode = BindingMode.TwoWay;//绑定到tag根据绑定的数据变化颜色controlBold.SetBinding(TagProperty, bindingBold);controlBold.Click += ControlBold_Click;#endregion#region 斜体//斜体string[] vsItalic = vsBoldItalic[1].Split(':');var controlItalic = new WControls.Button();controlItalic.Style = null;controlItalic.Width = 40;controlItalic.Content = vsItalic[1];controlItalic.Margin = new Thickness(40, 0, 0, 0);controlItalic.HorizontalAlignment = HorizontalAlignment.Left;//通过代码修改控件的Grid.Row属性Grid.SetRow(controlItalic, grid.RowDefinitions.Count - 1);Grid.SetColumn(controlItalic, 1);grid.Children.Add(controlItalic);//根据执行属性的名称绑定到控件Binding bindingItalic = new Binding(vsItalic[0]);bindingItalic.Source = Source;bindingItalic.Mode = BindingMode.TwoWay;//绑定到tag根据绑定的数据变化颜色controlItalic.SetBinding(TagProperty, bindingItalic);controlItalic.Click += ControlBold_Click;#endregion//这样两个按钮都绑定了同一个事件,所有需要判断sender.MethodSeBinding(controlBold, memberInfo);sender.RelationSeBinding(controlBold, memberInfo,grid);sender.MethodSeBinding(controlItalic, memberInfo);sender.RelationSeBinding(controlItalic, memberInfo, grid);return;case PROPERTYType.Button:control = new WControls.Button();var tempbtn = control as Button;tempbtn.Width = 40;tempbtn.Content = attr.Content;tempbtn.HorizontalAlignment = HorizontalAlignment.Left;sender.MethodSeBinding(control, memberInfo);sender.RelationSeBinding(control, memberInfo, grid);control.Style = null;break;case PROPERTYType.TextBox:control = new WControls.TextBox();sender.MethodSeBinding(control, memberInfo);sender.RelationSeBinding(control, memberInfo, grid);control.Style = null;//回车触发绑定control.PreviewKeyDown += Control_PreviewKeyDown;//binding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;binding.UpdateSourceTrigger = UpdateSourceTrigger.Explicit;control.SetBinding(System.Windows.Controls.TextBox.TextProperty, binding);break;case PROPERTYType.Size:#region 大小,可回调该函数string[] vs = attr.ShowName.Split(',');if (vs.Length == 2){attr.TypeName = PROPERTYType.TextBox;attr.ShowName = vs[0];//宽度CreateControl(sender,grid, attr, Source, memberInfo);//高度attr.ShowName = vs[1];CreateControl(sender,grid, attr, Source, memberInfo);}#endregionreturn;case PROPERTYType.Color:control = new Button();control.MinHeight = 18;sender.MethodSeBinding(control, memberInfo);sender.RelationSeBinding(control, memberInfo, grid);control.Style = null;var temp = control as Button;temp.Click += Color_Click;temp.SetBinding(Button.BackgroundProperty, binding);break;case PROPERTYType.CheckBox:control = new CheckBox();sender.MethodSeBinding(control, memberInfo);sender.RelationSeBinding(control, memberInfo, grid);control.Style = null;control.SetBinding(CheckBox.IsCheckedProperty, binding);break;case PROPERTYType.Label:control = new WControls.Label();sender.MethodSeBinding(control, memberInfo);sender.RelationSeBinding(control, memberInfo, grid);control.Style = null;var templb = control as Label;control.SetBinding(ContentControl.ContentProperty, binding);break;case PROPERTYType.ComboBox:control = new WControls.ComboBox();control.Style = null;var tempCB = control as WControls.ComboBox;//这个必须放在前面设置if (!attr.Tag.Equals("")){string[] attrMV = attr.Tag.Split(',');//KeytempCB.SelectedValuePath = attrMV[0];//ValuetempCB.DisplayMemberPath = attrMV[1];}#region 绑定关联sender.MethodSeBinding(control, memberInfo);sender.RelationSeBinding(control, memberInfo, grid);#endregion//考虑到该属性可能绑定SelectedValue或者SelectedItem,所有这里不直接硬性绑定//tempCB.SetBinding(WControls.ComboBox.SelectedValueProperty, binding);break;}control.VerticalAlignment = VerticalAlignment.Center;//通过代码修改控件的Grid.Row属性Grid.SetRow(control, grid.RowDefinitions.Count - 1);Grid.SetColumn(control, 1);grid.Children.Add(control);}#region 控件事件/// <summary>/// 回车触发数据改变/// </summary>/// <param name="sender"></param>/// <param name="e"></param>private static void Control_PreviewKeyDown(object sender, System.Windows.Input.KeyEventArgs e){if (e.Key == Key.Enter){var temp = sender as WControls.TextBox;BindingExpression binding = temp.GetBindingExpression(WControls.TextBox.TextProperty);binding.UpdateSource();}}private static void ControlBold_Click(object sender, RoutedEventArgs e){var btn = sender as Button;bool tag = (bool)btn.Tag;//  粗体 和斜体if (tag){btn.Tag = false;btn.Background = Brushes.LightGray;}else{btn.Tag = true;btn.Background = Brushes.Gold;}}#endregion/// <summary>/// 设置关联事件/// </summary>/// <param name="control"></param>public void MethodSeBinding(Control control, MemberInfo memberInfo){//Type t = _ShowProp.GetType();//Object[] obj = t.GetProperties();//取属性上的自定义特性object[] objAttrs = memberInfo.GetCustomAttributes(typeof(RelationMethodAttribute), true);if (objAttrs.Length > 0){//获取编辑的属性特征for (int i = 0; i < objAttrs.Length; i++){RelationMethodAttribute attrTemp = objAttrs[i] as RelationMethodAttribute;//反射为控件事件,添加指定方法var click = control.GetType().GetEvents().FirstOrDefault(ei => ei.Name.ToLower() == attrTemp.CrEventName.ToLower());if (click != null){//根据名称查找方法var method = _ShowProp.GetType().GetMethod(attrTemp.ClMethodName);//创造委托var handler = Delegate.CreateDelegate(click.EventHandlerType, _ShowProp, method);click.AddEventHandler(control, handler);}}}}/// <summary>/// 设置关联属性/// </summary>/// <param name="control"></param>public void RelationSeBinding(Control control, MemberInfo memberInfo,Grid grid, bool IsVisibility = true){//取属性上的自定义特性object[] objAttrs = memberInfo.GetCustomAttributes(typeof(RelationAttribute), true);if (objAttrs.Length > 0){//获取编辑的属性特征for (int i = 0; i < objAttrs.Length; i++){RelationAttribute attrTemp = objAttrs[i] as RelationAttribute;RelationSeBinding(control, attrTemp, grid);}}}/// <summary>/// Visibility转换器/// </summary>private VisibilityBoolConverter _VisibilityBool = new VisibilityBoolConverter();private VisibilityValueConverter _VisibilityValue = new VisibilityValueConverter();/// <summary>/// 设置关联属性/// </summary>/// <param name="control"></param>/// <param name="IsVisibility">如果绑定Visibility属性,这个可以true设置需不需要隐藏grid第一列的控件///true则隐藏/// </param>public void RelationSeBinding(Control control, RelationAttribute attr, Grid grid,bool IsVisibility = true){if (attr != null){//获取类的关联属性  和  控件的关联属性string[] crName = attr.CrPropName.Split(',');string[] clName = attr.ClPropName.Split(',');for (int i = 0; i < crName.Length; i++){//根据执行属性的名称绑定到控件Binding binding = new Binding(clName[i]);binding.Source = _ShowProp;binding.Mode = BindingMode.TwoWay;#region 显示隐藏的属性处理//如果是使用bool控制显示隐藏VisibilityBoolif (crName[i] == "VisibilityBool"){//使用转换器crName[i] = "Visibility";binding.Converter = _VisibilityBool;}else if (crName[i] == "VisibilityValue"){//使用转换器crName[i] = "Visibility";binding.Converter = _VisibilityValue;binding.ConverterParameter = attr.VisibilityValue;}//把gird这行的也绑定隐藏显示属性if (crName[i] == "Visibility" && IsVisibility){grid.RowDefinitions[grid.RowDefinitions.Count - 1].MinHeight = 0;var cr = grid.Children[grid.Children.Count - 1] as TextBlock;cr.SetBinding(Control.VisibilityProperty, binding);}#endregion//获取依赖属性BindingFlags mPropertyFlags = BindingFlags.Instance | BindingFlags.Public| BindingFlags.FlattenHierarchy| BindingFlags.Static | BindingFlags.NonPublic;//筛选//获取控件关联属性var fieldInfo = control.GetType().GetField(crName[i] + "Property", mPropertyFlags);if (fieldInfo != null){control.SetBinding((DependencyProperty)fieldInfo.GetValue(control), binding);}}}}/// <summary>/// 选择颜色/// </summary>/// <param name="sender"></param>/// <param name="e"></param>private static void Color_Click(object sender, RoutedEventArgs e){var tempBtn = sender as Button;//var picker = SingleOpenHelper.CreateControl<ColorPicker>();//var window = new PopupWindow//{//    PopupElement = picker,//    WindowStartupLocation = WindowStartupLocation.CenterScreen,//    AllowsTransparency = true,//    WindowStyle = WindowStyle.None,//    MinWidth = 0,//    MinHeight = 0,//    Title = "颜色选择器"//};//picker.SelectedColorChanged += delegate//{//    window.Close();//};//picker.Canceled += delegate { window.Close(); };//window.Show();var picker = SingleOpenHelper.CreateControl<ColorPicker>();var window = new PopupWindow{PopupElement = picker};picker.SelectedColorChanged += delegate{tempBtn.Background = picker.SelectedBrush;window.Close();};picker.Canceled += delegate { window.Close(); };window.ShowDialog(tempBtn, false);}#endregion#region public方法#endregion}

/// <summary>/// 生成控件类型 按位数计算控件类型/// </summary>public enum PROPERTYType{Label = 1, TextBox = 2,/// <summary>/// 大小,控件宽高/// </summary>Size = 4,/// <summary>/// 可选择颜色/// </summary>Color = 8,/// <summary>/// 下拉框/// 考虑到两种情况,使用该类型的属性,并不绑定该属性,具体绑定使用关联特征进行绑定/// 就是说,赋值了这个下拉框类型,在任何属性下都可以,但如果不使用RelationAttribute绑定的话,它跟控件是没有任何关系的/// </summary>ComboBox = 16,/// <summary>/// 可选择颜色/// </summary>CheckBox = 32,/// <summary>/// 文件夹类型/// </summary>Folder = 64,/// <summary>/// 按钮 /// </summary>Button = 128,/// <summary>/// 粗斜体 该类型不能使用VisibilityValue来显示隐藏控件(因为两个地方都用tag来保存数据),可用VisibilityBool/// </summary>BoldItalic = 256,/// <summary>/// 可绑定的控件类型,需要与其他控件类型一起赋值/// </summary>Relation = 2048}

/// <summary>/// 可对字段应用的 PropertyGrid 特征  /// </summary>[AttributeUsage(AttributeTargets.All|AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Method,AllowMultiple = true, Inherited =true)]public class LsPropertyGridAttribute : Attribute{/// <summary>/// 生成的控件类型/// </summary>public PROPERTYType TypeName; /// <summary>/// 对应的板块/// </summary>public string Plate;/// <summary>/// 显示名称/// </summary>public string ShowName;/// <summary>/// 生成控件的显示内容,不同控件可以使用的不一样,目前用与button/// </summary>public string Content;/// <summary>/// 预留Tag 携带数据对象/// </summary>public string Tag;public LsPropertyGridAttribute(PROPERTYType type,string plate,string showName){TypeName = type;#region 语言切换,查找动态资源var tempStr = ResourceHelper.GetResource<string>(plate);Plate = tempStr != null && tempStr != "" ? tempStr : plate;tempStr = ResourceHelper.GetResource<string>(showName);ShowName = tempStr != null && tempStr != "" ? tempStr : showName;#endregion}}

上面语言切换用了hc控件库的工具类,其实就是赋值,没有引用的可以去掉,如果没有引用PropertyGrid类中,要把颜色选择给去掉,引用到了hc的颜色控件。

推荐使用hc控件库

下面介绍两个特别的特征类

关于多个属性,绑定同一个属性面版的显示隐藏或者可用与否

/// <summary>/// 有一种情况/// 1.自身属性绑定其他属性的控件的属性/// 可对字段属性应用的 PropertyGrid 关联特征/// 关联特征作用:可使用修饰的字段或者属性的值,和其他属性生成控件的值进行绑定/// 多用于,属性编辑控件中勾选框,控制其他控件的显示(或者其他值),通过绑定实现/// </summary>作用范围枚举,inherited=是否继承,AllowMultiple=是否允许多次描述。[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property| AttributeTargets.Method, AllowMultiple = true,Inherited = true)]public class RelationAttribute : Attribute{/// <summary>/// 1.同一控件需要关联的属性名称,使用英文逗号隔开   不同的写多个 RelationAttribute/// eg:Text,Size/// </summary>public string CrPropName ;  /// <summary>/// 1.控件属性名称关联的类属性名称,使用英文逗号隔开,与CrPropName想对应/// eg:Name,Size/// </summary>public string ClPropName;/// <summary>/// 使用绑定显示隐藏的时候 CrPropName=VisibilityValue/// 必须设置该字段值,也就是控件显示的值/// </summary>public object VisibilityValue;public string Tag;public RelationAttribute(string clPropName, string crPropName){CrPropName = crPropName;ClPropName = clPropName;}}

另一个是绑定方法的特征类

/// <summary>/// 类的方法和控件事件绑定/// </summary>[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Method, AllowMultiple = true)]public class RelationMethodAttribute : Attribute{/// <summary>/// 1.同一控件需要关联的事件名称,使用英文逗号隔开   不同的写多个 RelationMethodAttribute/// eg:Click,Click/// </summary>public string CrEventName;/// <summary>/// 1.控件事件关联的类方法,使用英文逗号隔开,与CrPropName想对应/// eg:ControlSelect_Click,ControlSelect_Click/// </summary>public string ClMethodName;public string Tag;public RelationMethodAttribute(string clEventName, string crMethodName){CrEventName = crMethodName;ClMethodName = clEventName;}}

/// <summary>/// 用于描述属性面板的绑定属性字符串/// </summary>public class DependencyPropertyToken{/// <summary>/// /// </summary>public const string ItemsSource = nameof(ItemsSource);public const string Visibility = nameof(Visibility);/// <summary>/// 使用bool绑定控制显示/// </summary>public const string VisibilityBool = nameof(VisibilityBool);/// <summary>/// 使用某值绑定控制显示,只要出现这个值就会显示,其他值就隐藏/// </summary>public const string VisibilityValue = nameof(VisibilityValue);public const string IsEnabled = nameof(IsEnabled);public const string SelectedItem = nameof(SelectedItem);public const string SelectedValue = nameof(SelectedValue);public const string SelectedText = nameof(SelectedText);public const string Tag = nameof(Tag);}

public class EventToken{public const string Click = nameof(Click);}
DependencyPropertyToken和EventToken类是字符串类,只是为了避免写错而创建的
转换器

/// <summary>/// 使用bool控制隐藏显示控件 /// </summary>public class VisibilityBoolConverter : IValueConverter{/// <summary>/// 当值从绑定源传播给绑定目标时,调用方法Convert/// </summary>/// <param name="value"></param>/// <param name="targetType"></param>/// <param name="parameter"></param>/// <param name="culture"></param>/// <returns></returns>public object Convert(object value, Type targetType, object parameter, CultureInfo culture){if (value is bool boolValue){if (boolValue){return Visibility.Visible;}else{return Visibility.Collapsed;}}return Visibility.Visible;}/// <summary>/// 当值从绑定目标传播给绑定源时,调用此方法ConvertBack,方法ConvertBack的实现必须是方法Convert的反向实现。/// </summary>/// <param name="value"></param>/// <param name="targetType"></param>/// <param name="parameter"></param>/// <param name="culture"></param>/// <returns></returns>public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture){throw new NotImplementedException();}}

 /// <summary>/// 使用bool控制隐藏显示控件 /// </summary>public class VisibilityValueConverter : IValueConverter{/// <summary>/// 当值从绑定源传播给绑定目标时,调用方法Convert/// </summary>/// <param name="value"></param>/// <param name="targetType"></param>/// <param name="parameter"></param>/// <param name="culture"></param>/// <returns></returns>public object Convert(object value, Type targetType, object parameter, CultureInfo culture){if (value != null){if (parameter != null){string tempStr = parameter.ToString();string valueStr = value.ToString();if (valueStr == tempStr){return Visibility.Visible;}else{return Visibility.Collapsed;}}}return Visibility.Collapsed;}/// <summary>/// 当值从绑定目标传播给绑定源时,调用此方法ConvertBack,方法ConvertBack的实现必须是方法Convert的反向实现。/// </summary>/// <param name="value"></param>/// <param name="targetType"></param>/// <param name="parameter"></param>/// <param name="culture"></param>/// <returns></returns>public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture){throw new NotImplementedException();}}

一个使用bool控制隐藏显示,一个使用任意值控制

上面就是全部代码,自从完成后,都没有修改过,用的很舒坦,样式方面如果有心改也可以改的,看具体需求。

简单简单的Demo

链接: https://pan.baidu.com/s/1jRxi-u3ORyETwRoh8VLp9Q 提取码: fsb3

顺便给你们一个看小说的程序:

可以爬任意(大部分)网站的小说下来看,为什么这么做呢,因为现在的小说网站除了起点,大部分都有一堆广告弹窗,我就是无聊弄爬虫的时候顺便弄个看诡秘,咳咳。

点击自定义获取,设置完成后,返回主界面,继续点击获取,如果设置对了,就会自动下载小说。单纯娱乐自用,不可用于盈利。

链接: https://pan.baidu.com/s/1vWWntkqukBMva3N-b3WSTA 提取码: ruqr

链接只有七天有效,其他时候评论要。

属性面板是不是很简单:特征,反射,绑定。应该都懂了,收工。

wpf的低调自定义属性面板PropertyGrid相关推荐

  1. WPF自定义控件的自定义属性绑定后不更新问题

    WPF自定义控件的自定义属性绑定后不更新问题 原文:WPF自定义控件的自定义属性绑定后不更新问题 需要在绑定时设置属性变更触发 UpdateSourceTrigger=PropertyChanged ...

  2. Editor类---自定义属性面板

    一.Editor类属性及函数 首先提供官方参考文档:API-Reference-Editor以及用户手册案例: 概述: Editor属于UnityEngine.Editor命名空间,继承与Script ...

  3. C# 自定义属性在propertyGrid控件中显示

    在上篇文章(地址: C# 设计时动态改变实体在PropertyGrid中显示出来的属性)中可以看到: 自定义属性的显示是有问题的,那么如何修改呢? 代码如下: public class Propert ...

  4. WPF -- Xceed PropertyGrid应用详解

    目录 1. Nuget安装 2. 调用 3. 常用属性 4. Model层设置 4.1 测试类 4.2  为对象添加相关特性      4.2.1 添加描述特性      4.2.2 添加分组     ...

  5. 分享Silverlight/WPF/Windows Phone一周学习导读(8月15日-8月19日)

    分享Silverlight/WPF/Windows Phone一周学习导读(8月15日-8月19日) 本周Silverlight学习资源更新: Silverlight Tools 4安装时的错误提示 ...

  6. 基于InkCanvas实现的桌面涂鸦工具-[ WPF开发 ]

    首先简单的介绍下InkCanvas,简单的来说,InkCanvas就是在WPF中实现允许使用墨迹的布局控件.实际上,InkCanvas有着更多层面上的应用,它的主要目的是(通过鼠标或者和指示笔)捕捉笔 ...

  7. WinForm中使用WPF的控件

    在WinForm中可以使用WPF中的控件,或者由WPF创建的自定义控件: 步骤1:创建WinForm工程: 步骤2:在WinForm工程的解决方案资源管理器中,在刚刚创建的WinForm解决方案中新建 ...

  8. WinForm如何使用WPF的控件

    在WinForm中可以使用WPF中的控件,或者由WPF创建的自定义控件: 步骤1:创建WinForm工程: 步骤2:在WinForm工程的解决方案资源管理器中,在刚刚创建的WinForm解决方案中新建 ...

  9. [WPF实践之路] 目录导航

    <script language='javascript' src='http://www.shiqiaotou.com/donetk/Header.js'></script> ...

最新文章

  1. poj 1679 次小生成树
  2. 进程间通信--命名管道
  3. cli3 px转rem适配移动端_Vue:将px转化为rem,适配移动端
  4. Linux Shell脚本_禁止定时任务发送邮件
  5. 找出不是两个数组共有的元素
  6. 智课雅思短语---二、exert positive/ negative effects on…
  7. 中国公有云 Top10
  8. I.MX6 linux kernel编译错误处理
  9. 数据结构:堆栈的区别
  10. 计算机桌面运维问题分类,桌面运维工程师常见面试问题汇总
  11. python获取英文字母、英文标点符号、中文标点符号
  12. App如何生成下载二维码?扫码即可下载App
  13. Hive提取身份证号中年龄和性别
  14. 日常使用计算机如何进行病毒防范,电脑日常生活中怎么防范电脑病毒
  15. Andriod+SpringBoot 图书馆管理系统
  16. Excel怎么转换成PDF?这两种转换方法看到就是赚到
  17. 《Google SRE》读后感
  18. 制作一个浪漫温馨的生日礼物送她~html+css+javascript蓝色梦幻海洋3D相册(含音乐)...
  19. excel2016 mysql_Excel2016新功能尝鲜-MySQL连接
  20. 时间应该如何“挤”出来

热门文章

  1. 批处理的几个简单命令,一学就会
  2. hnu小学期软件实训-拳王阿里
  3. Qt QTreeWidget的行间距设置
  4. zookeeper查看注册服务器,zookeeper服务器注册
  5. 微信小程序入门项目-做一个马马虎虎的计算器
  6. FindWindowEx的用法
  7. eplan接触器主触点怎么成一组_EPLAN 符号库中为何没有接触器的三相主触点
  8. 多线程基础知识(上)
  9. java 模板方法设计模式
  10. 05J5-2 坡屋面 免费下载