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 

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

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

作者:三小 声明:原创博客请在转载时保留原文链接或者在文章开头加上本人博客地址,如发现错误,欢迎批评指正。凡是转载于本人的文章,不能设置打赏功能,如有特殊需求请与本人联系!
原文地址:https://www.cnblogs.com/lsgsanxiao/p/11776421.html