前言
上文介紹瞭如何透過一個Form自定義控制元件來簡化資料的錄入,並自動實現資料校驗,自動佈局排列等功能。本文繼續介紹如何最佳化表格控制元件的使用,縮減程式碼量,實現工作效率的提升。
一、功能實現
上文中分析了DataGrid跟ListView兩種表格控制元件的優劣,在這裡我們選擇ListView來實現我們的表格功能,如有疑問請看上一篇文章,這裡不再贅述。
下面我們定義一個ListViewExtensions靜態類來實現ListView的附加屬性,並在附加屬性更改後實現ListView的最佳化功能。
public static class ListViewExtensions { #region AutoGenerateColumns public static readonly DependencyProperty AutoGenerateColumnsProperty = DependencyProperty.RegisterAttached("AutoGenerateColumns", typeof(bool), typeof(ListViewExtensions), new PropertyMetadata(false, AutoGenerateColumnsChanged)); public static bool GetAutoGenerateColumns(DependencyObject obj) { return (bool)obj.GetValue(AutoGenerateColumnsProperty); } public static void SetAutoGenerateColumns(DependencyObject obj, bool value) { obj.SetValue(AutoGenerateColumnsProperty, value); } private static void AutoGenerateColumnsChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) { ListView listView = obj as ListView; bool autoGenerateColumns = (bool)e.NewValue; if (listView != null) { if (autoGenerateColumns) { listView.Loaded += ListViewLoadedHandler; } else { listView.Loaded -= ListViewLoadedHandler; } } } private static void ListViewLoadedHandler(object sender, RoutedEventArgs e) { var listView = (ListView)sender; if (listView.ItemsSource == null) return; var itemType = listView.ItemsSource.GetType().GetGenericArguments().FirstOrDefault(); if (itemType != null) { // 獲取資料來源的屬性資訊 var properties = itemType.GetProperties(); GridView gridView = null; if (listView.View is GridView gv) { gridView = gv; } else { listView.View = gridView = new GridView(); } // 根據屬性資訊建立列並新增到 GridView 中 var optionMappingConverter = new OptionMappingConverter(); var enumDescriptionConverter = new EnumDescriptionConverter(); foreach (PropertyInfo property in properties) { //排除無用屬性 var name = property.Name; if (PropertyHelper.IgnoredProperties.Contains(property.Name)) continue; //讀取列引數 var columnAttribute = property.GetCustomAttributes().OfType<ColumnAttribute>().FirstOrDefault(); if (columnAttribute == null || !columnAttribute.IsVisible) continue; //排除重複屬性 var targetColumn = gridView.Columns.FirstOrDefault(item => GetColumnName(item) == property.Name); if (targetColumn != null) { if (targetColumn.Header == null) targetColumn.Header = columnAttribute.Name; SetColumnIndex(targetColumn, columnAttribute.Order); continue; } var column = new GridViewColumn() { Header = !string.IsNullOrEmpty(columnAttribute.Name) ? columnAttribute.Name : name }; SetColumnName(column, property.Name); //分組 if (columnAttribute is GroupColumnAttribute group) ListViewExtensions.SetGroup(column, group.Group); //寬度 if (columnAttribute.Width > 0) column.Width = columnAttribute.Width; //轉換器 if (columnAttribute.Converter != null) { column.DisplayMemberBinding = new Binding(property.Name) { Converter = Activator.CreateInstance(columnAttribute.Converter) as IValueConverter, ConverterParameter = columnAttribute.ConverterParameter, StringFormat = columnAttribute.StringFormat }; } else { //指定轉換器 var optionMappingAttribute = property.GetCustomAttributes().OfType<OptionMappingAttribute>().FirstOrDefault(); if (optionMappingAttribute != null) { column.DisplayMemberBinding = new Binding(property.Name) { Converter = optionMappingConverter, ConverterParameter = optionMappingAttribute.Mapping }; } else if (property.PropertyType.IsEnum) { column.DisplayMemberBinding = new Binding(property.Name) { Converter = enumDescriptionConverter, StringFormat = columnAttribute.StringFormat }; } else { column.DisplayMemberBinding = new Binding(property.Name) { StringFormat = columnAttribute.StringFormat }; } } SetColumnIndex(column, columnAttribute.Order); gridView.Columns.Add(column); } var cols = gridView.Columns.OrderBy(item => GetColumnIndex(item)).ToArray(); gridView.Columns.Clear(); foreach (var col in cols) { gridView.Columns.Add(col); } } } #endregion #region ColumnIndex public static int GetColumnIndex(DependencyObject obj) { return (int)obj.GetValue(ColumnIndexProperty); } public static void SetColumnIndex(DependencyObject obj, int value) { obj.SetValue(ColumnIndexProperty, value); } public static readonly DependencyProperty ColumnIndexProperty = DependencyProperty.RegisterAttached("ColumnIndex", typeof(int), typeof(ListViewExtensions), new PropertyMetadata(0)); #endregion #region ColumnName public static string GetColumnName(DependencyObject obj) { return (string)obj.GetValue(ColumnNameProperty); } public static void SetColumnName(DependencyObject obj, string value) { obj.SetValue(ColumnNameProperty, value); } public static readonly DependencyProperty ColumnNameProperty = DependencyProperty.RegisterAttached("ColumnName", typeof(string), typeof(ListViewExtensions)); #endregion #region Group public static string GetGroup(DependencyObject obj) { return (string)obj.GetValue(GroupProperty); } public static void SetGroup(DependencyObject obj, string value) { obj.SetValue(GroupProperty, value); } public static readonly DependencyProperty GroupProperty = DependencyProperty.RegisterAttached("Group", typeof(string), typeof(ListViewExtensions)); #endregion }
二、示例程式碼
View
<ListView extensions:ListViewExtensions.AutoGenerateColumns="True" ItemsSource="{Binding Collection}"/>
Model
public class Person : ValidationObjectBase { private string _name; private DateTime _dateOfBirth = DateTime.Now; private int _age; private int _gender = 1; private string _phoneNumber; private string _email; private string _address; private string _idCardNumber; private EducationInfo _education; private MaritalStatus _maritalStatus; /// <summary> /// 姓名 /// </summary> [Column(Name = "姓名", Order = 1)] public string Name { get => _name; set => this.SetProperty(ref _name, value); } /// <summary> /// 出生日期 /// </summary> [Column(Name = "出生日期", StringFormat = "{0:yyyy年MM月dd日}", Order = 2)] public DateTime DateOfBirth { get => _dateOfBirth; set => this.SetProperty(ref _dateOfBirth, value); } /// <summary> /// 年齡 /// </summary> [Column(Name = "年齡", Order = 3)] public int Age { get => _age; set => this.SetProperty(ref _age, value); } /// <summary> /// // 性別 /// </summary> [Column(Name = "性別", Order = 4)] public int Gender { get => _gender; set => this.SetProperty(ref _gender, value); } /// <summary> /// 手機號碼 /// </summary> [Column(Name = "電話號碼", Order = 5)] public string PhoneNumber { get => _phoneNumber; set => this.SetProperty(ref _phoneNumber, value); } /// <summary> /// 電子郵箱 /// </summary> [Column(Name = "電子郵箱", Order = 6)] public string Email { get => _email; set => this.SetProperty(ref _email, value); } /// <summary> /// 地址資訊 /// </summary> [Column(Name = "地址", Order = 7)] public string Address { get => _address; set => this.SetProperty(ref _address, value); } /// <summary> /// 身份證號碼 /// </summary> [Column(Name = "身份證號碼", Order = 8)] public string IdCardNumber { get => _idCardNumber; set => this.SetProperty(ref _idCardNumber, value); } /// <summary> /// 教育資訊 /// </summary> //[Column(Name = "教育資訊", IsVisible = false, Order = 9)] public EducationInfo Education { get => _education; set => this.SetProperty(ref _education, value); } /// <summary> /// 婚姻狀況 /// </summary> [Column(Name = "婚姻狀況", Order = 10)] public MaritalStatus MaritalStatus { get => _maritalStatus; set => this.SetProperty(ref _maritalStatus, value); }
}
ViewModel
public class FormDemoViewModel : BindableBase { public ObservableCollection<Person> Collection { get { return _collection; } set { this.SetProperty(ref _collection, value); } } }
三、答疑解惑
2.1 如何新增自定義列?
答:可以按常規方式新增自定義列,用ListViewExtensions.ColumnIndex來指定位的位置,以下為示例程式碼。
<ListView extensions:ListViewExtensions.AutoGenerateColumns="True" ItemsSource="{Binding Collection}"> <GridView> <GridViewColumn extensions:ListViewExtensions.ColumnIndex="0"> <GridViewColumn.Header> <CheckBox Content="全選" /> </GridViewColumn.Header> <GridViewColumn.CellTemplate> <DataTemplate> <CheckBox /> </DataTemplate> </GridViewColumn.CellTemplate> </GridViewColumn> </GridView> </ListView>
2.2 如何自動生成的列不滿足需求,可不可以自己定義列?
答:可以,使用ListViewExtensions.ColumnName屬性可以覆蓋某一個列的自動生成的操作。
<ListView extensions:ListViewExtensions.AutoGenerateColumns="True" ItemsSource="{Binding Collection}"> <GridView> <GridViewColumn extensions:ListViewExtensions.ColumnIndex="1" extensions:ListViewExtensions.ColumnName="Name"> <GridViewColumn.CellTemplate> <DataTemplate> <TextBlock Text="{Binding Name}" /> </DataTemplate> </GridViewColumn.CellTemplate> </GridViewColumn> </GridView> </ListView>
2.3 如何給列設定順序?
答:在屬性上給ColumnAttribute特性設定引數Order。
[Column(Name = "出生日期", Order = 2)] public DateTime DateOfBirth { get => _dateOfBirth; set => this.SetProperty(ref _dateOfBirth, value); }
2.4 如何設定列的寬度?
答:在屬性上給ColumnAttribute特性設定引數Width。
[Column(Name = "出生日期", Width =100)] public DateTime DateOfBirth { get => _dateOfBirth; set => this.SetProperty(ref _dateOfBirth, value); }
2.5 如何將列設為不可見?
答:在屬性上給ColumnAttribute特性設定引數IsVisible。
[Column(Name = "出生日期", IsVisible = false)] public DateTime DateOfBirth { get => _dateOfBirth; set => this.SetProperty(ref _dateOfBirth, value); }
2.6 如何給繫結設定格式化字串?
答:在屬性上給ColumnAttribute特性設定引數StringFormat。
[Column(Name = "出生日期", StringFormat = "{0:yyyy年MM月dd日}")] public DateTime DateOfBirth { get => _dateOfBirth; set => this.SetProperty(ref _dateOfBirth, value); }
2.7 如何給繫結設定轉換器及引數?
答:在屬性上給ColumnAttribute特性設定Converter和ConverterParameter引數。
[Column(Name = "出生日期", Converter = typeof(TestConverter), ConverterParameter=1)] public DateTime DateOfBirth { get => _dateOfBirth; set => this.SetProperty(ref _dateOfBirth, value); }
技術交流群
聯絡方式