當沒有輪子的時候,就自己製作輪子。
前言
項目上的需求,我想需要用到這樣一個跟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
鏈接只有七天有效,其他時候評論要。
屬性面板是不是很簡單:特徵,反射,綁定。應該都懂了,收工。