wpf的低调自定义属性面板PropertyGrid
当没有轮子的时候,就自己制作轮子。
前言
项目上的需求,我想需要用到这样一个跟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
既然是编辑对象的属性,那肯定少不了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; } }
那测试的类的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 control FrameworkElement.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 control FrameworkElement.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); //板块的Grid Grid 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 control FrameworkElement.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); //板块的Grid Grid 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 Folder double 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第一列设置false sender.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); #endregion return; 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); } #endregion return; 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(','); //Key tempCB.SelectedValuePath = attrMV[0]; //Value tempCB.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控制显示隐藏VisibilityBool if (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
链接只有七天有效,其他时候评论要。
属性面板是不是很简单:特征,反射,绑定。应该都懂了,收工。