wpf的低調自定義屬性面板PropertyGrid 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 

鏈接只有七天有效,其他時候評論要。

屬性面板是不是很簡單:特徵,反射,綁定。應該都懂了,收工。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章