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 

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

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

posted @ 2021-01-27 17:06  三小  阅读(6632)  评论(5编辑  收藏  举报