Windows 表单中的 DataAnnotation 属性 - 如何在 WinForms 中使用 UIHint 属性
DataAnnotation Attribute in Windows Forms - How to use UIHint Attribute with WinForms
我有一个 DataGrid
和一个 List<Foo>
作为数据源。我希望数据网格使用我的自定义控件作为 Foo.Value
属性 的编辑器。我相信是UIHintAttribute的目的,但是没有效果。我知道我可以明确地制作列,并分配 ValueColumn.ColumnEdit = new FooValueEditor();
,但我试图避开 很多 的 UI 代码并依赖于网格从 public 属性推断列。
class Foo
{
public string Name { get; set; }
[UIHint("FooValueEditor", "WinForms")]
public int Value { get; set; }
}
public class FooValueEditor : System.Windows.Forms.TextBox
{
public FooValueEditor() : base()
{
...
}
}
我试过为我的自定义编辑器提供完整的命名空间。我发现许多 Asp.NET 中使用的属性示例,但属性构造函数采用 presentationLayer
参数,其中:
Can be set to "HTML", "Silverlight", "WPF", or "WinForms".
希望它支持WinForms。难道我做错了什么?这不可能吗?
编辑:
关于评论“这里的 DataGrid 是什么”。我在同一个命名空间中使用了一组 third party controls which I incorrectly assumed inherited from DataGrid. It supports the validation attributes(这是他们针对 Aps.Net 的文档,但它似乎至少部分适用于 WinForms)所以我希望 UIHint
可能得到支持.看起来我应该直接与第三方提供商开票,但与此同时,如果我选择的话,下面的答案将帮助我自己实施。
built-in 不支持 Windows 表单中的数据注释属性(包括 UI提示),但考虑到这些属性的工作方式,您可以扩展框架和控件以在 Windows 表单中使用这些属性。
在此 post 我将扩展我在其他 post 中分享的关于 DataAnnotations attributes for DataGridView in Windows Forms 的答案,以便您可以拥有以下功能:
- 列的可见性:由
[Browsable]
attribute. You can also rely on AutoGenerateField
property of the [Display]
属性控制。
- Header 列的文本:由
[Display]
属性的 Name
控制。
- 列的顺序:由
[Display]
属性的 Order
控制。
- 列的工具提示:由
[DisplayFormat]
属性控制。
- 列的类型:由
[UIHint]
属性控制。
所以在模型上设置数据注释属性后,如果你像这样设置 datagridveiw this.dataGridView1.Bind(list, true);
你会看到:
以下是示例的构建块:
有一个 UIHintMappings
class 负责将 UI 提示映射到不同的 DataGridViewColumn
类型。每个 UI 提示都将映射到一个 Func
(工厂方法),它创建所需 DataGridViewColumn
的一个实例。例如 Text
将映射到 ()=>new DataGridViewTextBoxColumn()
。您可以根据需要添加或删除映射。
有一个 Bind<T>
扩展方法负责通过应用数据注释属性使用列表生成 DataGridview 的列。您可以在此处更改列创建的逻辑;例如添加对新属性的支持。
对于每个 non-standard 列类型,您需要按照此处的 instruction/example 创建自己的列类型:How to: Host Controls in Windows Forms DataGridView Cells,然后添加映射。
例子
创建 Windows 表单申请。
在 Form1 上放置一个 DataGridView
的实例(并将其设置为停靠在 parent 容器中)
在项目中添加一个Person.cs
文件,将以下代码粘贴到文件中:
using System;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
public class Person
{
[Display(Name = "Id")]
[Browsable(false)]
public int? Id { get; set; }
[Display(Name = "First Name", Description = "First name.", Order = 1)]
[UIHint("TextBox")]
public string FirstName { get; set; }
[Display(Name = "Last Name", Description = "Last name", Order = 2)]
[UIHint("TextBox")]
public string LastName { get; set; }
[Display(Name = "Birth Date", Description = "Date of birth.", Order = 4)]
[DisplayFormat(DataFormatString = "yyyy-MM-dd")]
[UIHint("Calendar")]
public DateTime BirthDate { get; set; }
[Display(Name = "Homepage", Description = "Url of homepage.", Order = 5)]
[UIHint("Link")]
public string Url { get; set; }
[Display(Name = "Member", Description = "Is member?", Order = 3)]
[UIHint("CheckBox")]
public bool IsMember { get; set; }
}
创建一个名为 DataGridViewCalendarColumn.cs 的代码文件,并将以下代码粘贴到文件中(这是基于 MS Docs example,只是为了尊重格式做了一点改动):
using System;
using System.Windows.Forms;
public class DataGridViewCalendarColumn : DataGridViewColumn
{
public DataGridViewCalendarColumn() : base(new DataGridViewCalendarCell())
{
}
public override DataGridViewCell CellTemplate
{
get
{
return base.CellTemplate;
}
set
{
// Ensure that the cell used for the template is a CalendarCell.
if (value != null &&
!value.GetType().IsAssignableFrom(typeof(DataGridViewCalendarCell)))
{
throw new InvalidCastException("Must be a CalendarCell");
}
base.CellTemplate = value;
}
}
}
public class DataGridViewCalendarCell : DataGridViewTextBoxCell
{
public DataGridViewCalendarCell()
: base()
{
// Use the short date format.
// this.Style.Format = "d";
}
public override void InitializeEditingControl(int rowIndex, object
initialFormattedValue, DataGridViewCellStyle dataGridViewCellStyle)
{
// Set the value of the editing control to the current cell value.
base.InitializeEditingControl(rowIndex, initialFormattedValue,
dataGridViewCellStyle);
DataGridViewCalendarEditingControl ctl =
DataGridView.EditingControl as DataGridViewCalendarEditingControl;
// Use the default row value when Value property is null.
if (this.Value == null)
{
ctl.Value = (DateTime)this.DefaultNewRowValue;
}
else
{
ctl.Value = (DateTime)this.Value;
}
}
public override Type EditType
{
get
{
// Return the type of the editing control that CalendarCell uses.
return typeof(DataGridViewCalendarEditingControl);
}
}
public override Type ValueType
{
get
{
// Return the type of the value that CalendarCell contains.
return typeof(DateTime);
}
}
public override object DefaultNewRowValue
{
get
{
// Use the current date and time as the default value.
return DateTime.Now;
}
}
}
class DataGridViewCalendarEditingControl : DateTimePicker,
IDataGridViewEditingControl
{
DataGridView dataGridView;
private bool valueChanged = false;
int rowIndex;
public DataGridViewCalendarEditingControl()
{
//this.Format = DateTimePickerFormat.Short;
}
// Implements the IDataGridViewEditingControl.EditingControlFormattedValue
// property.
public object EditingControlFormattedValue
{
get
{
return this.Value.ToShortDateString();
}
set
{
if (value is String)
{
try
{
// This will throw an exception of the string is
// null, empty, or not in the format of a date.
this.Value = DateTime.Parse((String)value);
}
catch
{
// In the case of an exception, just use the
// default value so we're not left with a null
// value.
this.Value = DateTime.Now;
}
}
}
}
// Implements the
// IDataGridViewEditingControl.GetEditingControlFormattedValue method.
public object GetEditingControlFormattedValue(
DataGridViewDataErrorContexts context)
{
return EditingControlFormattedValue;
}
// Implements the
// IDataGridViewEditingControl.ApplyCellStyleToEditingControl method.
public void ApplyCellStyleToEditingControl(
DataGridViewCellStyle dataGridViewCellStyle)
{
this.Font = dataGridViewCellStyle.Font;
this.CalendarForeColor = dataGridViewCellStyle.ForeColor;
this.CalendarMonthBackground = dataGridViewCellStyle.BackColor;
if (!string.IsNullOrEmpty(dataGridViewCellStyle.Format))
{
this.Format = DateTimePickerFormat.Custom;
this.CustomFormat = dataGridViewCellStyle.Format;
}
else
{
this.Format = DateTimePickerFormat.Short;
}
}
// Implements the IDataGridViewEditingControl.EditingControlRowIndex
// property.
public int EditingControlRowIndex
{
get
{
return rowIndex;
}
set
{
rowIndex = value;
}
}
// Implements the IDataGridViewEditingControl.EditingControlWantsInputKey
// method.
public bool EditingControlWantsInputKey(
Keys key, bool dataGridViewWantsInputKey)
{
// Let the DateTimePicker handle the keys listed.
switch (key & Keys.KeyCode)
{
case Keys.Left:
case Keys.Up:
case Keys.Down:
case Keys.Right:
case Keys.Home:
case Keys.End:
case Keys.PageDown:
case Keys.PageUp:
return true;
default:
return !dataGridViewWantsInputKey;
}
}
// Implements the IDataGridViewEditingControl.PrepareEditingControlForEdit
// method.
public void PrepareEditingControlForEdit(bool selectAll)
{
// No preparation needs to be done.
}
// Implements the IDataGridViewEditingControl
// .RepositionEditingControlOnValueChange property.
public bool RepositionEditingControlOnValueChange
{
get
{
return false;
}
}
// Implements the IDataGridViewEditingControl
// .EditingControlDataGridView property.
public DataGridView EditingControlDataGridView
{
get
{
return dataGridView;
}
set
{
dataGridView = value;
}
}
// Implements the IDataGridViewEditingControl
// .EditingControlValueChanged property.
public bool EditingControlValueChanged
{
get
{
return valueChanged;
}
set
{
valueChanged = value;
}
}
// Implements the IDataGridViewEditingControl
// .EditingPanelCursor property.
public Cursor EditingPanelCursor
{
get
{
return base.Cursor;
}
}
protected override void OnValueChanged(EventArgs eventargs)
{
// Notify the DataGridView that the contents of the cell
// have changed.
valueChanged = true;
this.EditingControlDataGridView.NotifyCurrentCellDirty(true);
base.OnValueChanged(eventargs);
}
}
在项目中添加一个UIHintMappings.cs
文件,将以下代码粘贴到文件中:
using System;
using System.Collections.Generic;
using System.Windows.Forms;
public class UIHintMappings
{
public static Dictionary<string, Func<DataGridViewColumn>> DataGridViewColumns
{
get;
}
static UIHintMappings()
{
DataGridViewColumns = new Dictionary<string, Func<DataGridViewColumn>>();
DataGridViewColumns.Add("TextBox",
() => new DataGridViewTextBoxColumn());
DataGridViewColumns.Add("CheckBox",
() => new DataGridViewCheckBoxColumn(false));
DataGridViewColumns.Add("TreeStateCheckBox",
() => new DataGridViewCheckBoxColumn(true));
DataGridViewColumns.Add("Link",
() => new DataGridViewLinkColumn());
DataGridViewColumns.Add("Calendar",
() => new DataGridViewCalendarColumn());
}
}
将DataGridViewExtensions.cs
文件添加到项目中并将以下代码粘贴到文件中:
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Windows.Forms;
public static class DataGridViewExtensions
{
public static void Bind<T>(this DataGridView grid, IList<T> data,
bool autoGenerateColumns = true)
{
if (autoGenerateColumns)
{
var properties = TypeDescriptor.GetProperties(typeof(T));
var metedata = properties.Cast<PropertyDescriptor>().Select(p => new
{
Name = p.Name,
HeaderText = p.Attributes.OfType<DisplayAttribute>()
.FirstOrDefault()?.Name ?? p.DisplayName,
ToolTipText = p.Attributes.OfType<DisplayAttribute>()
.FirstOrDefault()?.GetDescription() ?? p.Description,
Order = p.Attributes.OfType<DisplayAttribute>()
.FirstOrDefault()?.GetOrder() ?? int.MaxValue,
Visible = p.IsBrowsable,
ReadOnly = p.IsReadOnly,
Format = p.Attributes.OfType<DisplayFormatAttribute>()
.FirstOrDefault()?.DataFormatString,
Type = p.PropertyType,
UIHint = p.Attributes.OfType<UIHintAttribute>()
.FirstOrDefault()?.UIHint
});
var columns = metedata.OrderBy(m => m.Order).Select(m =>
{
DataGridViewColumn c;
if(!string.IsNullOrEmpty( m.UIHint) &&
UIHintMappings.DataGridViewColumns.ContainsKey(m.UIHint))
{
c = UIHintMappings.DataGridViewColumns[m.UIHint].Invoke();
}
else
{
c = new DataGridViewTextBoxColumn();
}
c.DataPropertyName = m.Name;
c.Name = m.Name;
c.HeaderText = m.HeaderText;
c.ToolTipText = m.ToolTipText;
c.DefaultCellStyle.Format = m.Format;
c.ReadOnly = m.ReadOnly;
c.Visible = m.Visible;
return c;
});
grid.Columns.Clear();
grid.Columns.AddRange(columns.ToArray());
}
grid.DataSource = data;
}
}
在设计模式中双击 Form1
并使用以下代码处理 Load
事件:
private void Form1_Load(object sender, EventArgs e)
{
var list = new List<Person>()
{
new Person()
{
Id= 1, FirstName= "Mario", LastName= "Speedwagon",
BirthDate = DateTime.Now.AddYears(-30).AddMonths(2).AddDays(5),
IsMember = true, Url ="https://Mario.example.com"
},
new Person()
{
Id= 1, FirstName= "Petey", LastName= "Cruiser",
BirthDate = DateTime.Now.AddYears(-20).AddMonths(5).AddDays(1),
IsMember = false, Url ="https://Petey.example.com"
},
new Person()
{
Id= 1, FirstName= "Anna", LastName= "Sthesia",
BirthDate = DateTime.Now.AddYears(-40).AddMonths(3).AddDays(8),
IsMember = true, Url ="https://Anna.example.com"
},
};
this.dataGridView1.Bind(list, true);
}
运行 项目,好了!您可以看到属性如何帮助生成列。
未来读者的改进点
您还可以添加对数据注释验证的支持。为此,您可以使用 Validator
class 实现 IDataErrorInfo
接口,就像我在 DataAnnotations Validation attributes for Windows Forms.
中所做的一样
您可以使用 DataGridViewComboBoxColumn
轻松添加对枚举列的支持,例如 this post。
您可以通过以下方式改进映射,如果没有为 UIHint 定义映射,那么作为后备方法查看列的类型,对于例如使用 DataGridViewCheckBoxColumn
作为 bool
属性。
我有一个 DataGrid
和一个 List<Foo>
作为数据源。我希望数据网格使用我的自定义控件作为 Foo.Value
属性 的编辑器。我相信是UIHintAttribute的目的,但是没有效果。我知道我可以明确地制作列,并分配 ValueColumn.ColumnEdit = new FooValueEditor();
,但我试图避开 很多 的 UI 代码并依赖于网格从 public 属性推断列。
class Foo
{
public string Name { get; set; }
[UIHint("FooValueEditor", "WinForms")]
public int Value { get; set; }
}
public class FooValueEditor : System.Windows.Forms.TextBox
{
public FooValueEditor() : base()
{
...
}
}
我试过为我的自定义编辑器提供完整的命名空间。我发现许多 Asp.NET 中使用的属性示例,但属性构造函数采用 presentationLayer
参数,其中:
Can be set to "HTML", "Silverlight", "WPF", or "WinForms".
希望它支持WinForms。难道我做错了什么?这不可能吗?
编辑:
关于评论“这里的 DataGrid 是什么”。我在同一个命名空间中使用了一组 third party controls which I incorrectly assumed inherited from DataGrid. It supports the validation attributes(这是他们针对 Aps.Net 的文档,但它似乎至少部分适用于 WinForms)所以我希望 UIHint
可能得到支持.看起来我应该直接与第三方提供商开票,但与此同时,如果我选择的话,下面的答案将帮助我自己实施。
built-in 不支持 Windows 表单中的数据注释属性(包括 UI提示),但考虑到这些属性的工作方式,您可以扩展框架和控件以在 Windows 表单中使用这些属性。
在此 post 我将扩展我在其他 post 中分享的关于 DataAnnotations attributes for DataGridView in Windows Forms 的答案,以便您可以拥有以下功能:
- 列的可见性:由
[Browsable]
attribute. You can also rely onAutoGenerateField
property of the[Display]
属性控制。 - Header 列的文本:由
[Display]
属性的Name
控制。 - 列的顺序:由
[Display]
属性的Order
控制。 - 列的工具提示:由
[DisplayFormat]
属性控制。 - 列的类型:由
[UIHint]
属性控制。
所以在模型上设置数据注释属性后,如果你像这样设置 datagridveiw this.dataGridView1.Bind(list, true);
你会看到:
以下是示例的构建块:
有一个
UIHintMappings
class 负责将 UI 提示映射到不同的DataGridViewColumn
类型。每个 UI 提示都将映射到一个Func
(工厂方法),它创建所需DataGridViewColumn
的一个实例。例如Text
将映射到()=>new DataGridViewTextBoxColumn()
。您可以根据需要添加或删除映射。有一个
Bind<T>
扩展方法负责通过应用数据注释属性使用列表生成 DataGridview 的列。您可以在此处更改列创建的逻辑;例如添加对新属性的支持。对于每个 non-standard 列类型,您需要按照此处的 instruction/example 创建自己的列类型:How to: Host Controls in Windows Forms DataGridView Cells,然后添加映射。
例子
创建 Windows 表单申请。
在 Form1 上放置一个
DataGridView
的实例(并将其设置为停靠在 parent 容器中)在项目中添加一个
Person.cs
文件,将以下代码粘贴到文件中:using System; using System.ComponentModel; using System.ComponentModel.DataAnnotations; public class Person { [Display(Name = "Id")] [Browsable(false)] public int? Id { get; set; } [Display(Name = "First Name", Description = "First name.", Order = 1)] [UIHint("TextBox")] public string FirstName { get; set; } [Display(Name = "Last Name", Description = "Last name", Order = 2)] [UIHint("TextBox")] public string LastName { get; set; } [Display(Name = "Birth Date", Description = "Date of birth.", Order = 4)] [DisplayFormat(DataFormatString = "yyyy-MM-dd")] [UIHint("Calendar")] public DateTime BirthDate { get; set; } [Display(Name = "Homepage", Description = "Url of homepage.", Order = 5)] [UIHint("Link")] public string Url { get; set; } [Display(Name = "Member", Description = "Is member?", Order = 3)] [UIHint("CheckBox")] public bool IsMember { get; set; } }
创建一个名为 DataGridViewCalendarColumn.cs 的代码文件,并将以下代码粘贴到文件中(这是基于 MS Docs example,只是为了尊重格式做了一点改动):
using System; using System.Windows.Forms; public class DataGridViewCalendarColumn : DataGridViewColumn { public DataGridViewCalendarColumn() : base(new DataGridViewCalendarCell()) { } public override DataGridViewCell CellTemplate { get { return base.CellTemplate; } set { // Ensure that the cell used for the template is a CalendarCell. if (value != null && !value.GetType().IsAssignableFrom(typeof(DataGridViewCalendarCell))) { throw new InvalidCastException("Must be a CalendarCell"); } base.CellTemplate = value; } } } public class DataGridViewCalendarCell : DataGridViewTextBoxCell { public DataGridViewCalendarCell() : base() { // Use the short date format. // this.Style.Format = "d"; } public override void InitializeEditingControl(int rowIndex, object initialFormattedValue, DataGridViewCellStyle dataGridViewCellStyle) { // Set the value of the editing control to the current cell value. base.InitializeEditingControl(rowIndex, initialFormattedValue, dataGridViewCellStyle); DataGridViewCalendarEditingControl ctl = DataGridView.EditingControl as DataGridViewCalendarEditingControl; // Use the default row value when Value property is null. if (this.Value == null) { ctl.Value = (DateTime)this.DefaultNewRowValue; } else { ctl.Value = (DateTime)this.Value; } } public override Type EditType { get { // Return the type of the editing control that CalendarCell uses. return typeof(DataGridViewCalendarEditingControl); } } public override Type ValueType { get { // Return the type of the value that CalendarCell contains. return typeof(DateTime); } } public override object DefaultNewRowValue { get { // Use the current date and time as the default value. return DateTime.Now; } } } class DataGridViewCalendarEditingControl : DateTimePicker, IDataGridViewEditingControl { DataGridView dataGridView; private bool valueChanged = false; int rowIndex; public DataGridViewCalendarEditingControl() { //this.Format = DateTimePickerFormat.Short; } // Implements the IDataGridViewEditingControl.EditingControlFormattedValue // property. public object EditingControlFormattedValue { get { return this.Value.ToShortDateString(); } set { if (value is String) { try { // This will throw an exception of the string is // null, empty, or not in the format of a date. this.Value = DateTime.Parse((String)value); } catch { // In the case of an exception, just use the // default value so we're not left with a null // value. this.Value = DateTime.Now; } } } } // Implements the // IDataGridViewEditingControl.GetEditingControlFormattedValue method. public object GetEditingControlFormattedValue( DataGridViewDataErrorContexts context) { return EditingControlFormattedValue; } // Implements the // IDataGridViewEditingControl.ApplyCellStyleToEditingControl method. public void ApplyCellStyleToEditingControl( DataGridViewCellStyle dataGridViewCellStyle) { this.Font = dataGridViewCellStyle.Font; this.CalendarForeColor = dataGridViewCellStyle.ForeColor; this.CalendarMonthBackground = dataGridViewCellStyle.BackColor; if (!string.IsNullOrEmpty(dataGridViewCellStyle.Format)) { this.Format = DateTimePickerFormat.Custom; this.CustomFormat = dataGridViewCellStyle.Format; } else { this.Format = DateTimePickerFormat.Short; } } // Implements the IDataGridViewEditingControl.EditingControlRowIndex // property. public int EditingControlRowIndex { get { return rowIndex; } set { rowIndex = value; } } // Implements the IDataGridViewEditingControl.EditingControlWantsInputKey // method. public bool EditingControlWantsInputKey( Keys key, bool dataGridViewWantsInputKey) { // Let the DateTimePicker handle the keys listed. switch (key & Keys.KeyCode) { case Keys.Left: case Keys.Up: case Keys.Down: case Keys.Right: case Keys.Home: case Keys.End: case Keys.PageDown: case Keys.PageUp: return true; default: return !dataGridViewWantsInputKey; } } // Implements the IDataGridViewEditingControl.PrepareEditingControlForEdit // method. public void PrepareEditingControlForEdit(bool selectAll) { // No preparation needs to be done. } // Implements the IDataGridViewEditingControl // .RepositionEditingControlOnValueChange property. public bool RepositionEditingControlOnValueChange { get { return false; } } // Implements the IDataGridViewEditingControl // .EditingControlDataGridView property. public DataGridView EditingControlDataGridView { get { return dataGridView; } set { dataGridView = value; } } // Implements the IDataGridViewEditingControl // .EditingControlValueChanged property. public bool EditingControlValueChanged { get { return valueChanged; } set { valueChanged = value; } } // Implements the IDataGridViewEditingControl // .EditingPanelCursor property. public Cursor EditingPanelCursor { get { return base.Cursor; } } protected override void OnValueChanged(EventArgs eventargs) { // Notify the DataGridView that the contents of the cell // have changed. valueChanged = true; this.EditingControlDataGridView.NotifyCurrentCellDirty(true); base.OnValueChanged(eventargs); } }
在项目中添加一个
UIHintMappings.cs
文件,将以下代码粘贴到文件中:using System; using System.Collections.Generic; using System.Windows.Forms; public class UIHintMappings { public static Dictionary<string, Func<DataGridViewColumn>> DataGridViewColumns { get; } static UIHintMappings() { DataGridViewColumns = new Dictionary<string, Func<DataGridViewColumn>>(); DataGridViewColumns.Add("TextBox", () => new DataGridViewTextBoxColumn()); DataGridViewColumns.Add("CheckBox", () => new DataGridViewCheckBoxColumn(false)); DataGridViewColumns.Add("TreeStateCheckBox", () => new DataGridViewCheckBoxColumn(true)); DataGridViewColumns.Add("Link", () => new DataGridViewLinkColumn()); DataGridViewColumns.Add("Calendar", () => new DataGridViewCalendarColumn()); } }
将
DataGridViewExtensions.cs
文件添加到项目中并将以下代码粘贴到文件中:using System.Collections.Generic; using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Windows.Forms; public static class DataGridViewExtensions { public static void Bind<T>(this DataGridView grid, IList<T> data, bool autoGenerateColumns = true) { if (autoGenerateColumns) { var properties = TypeDescriptor.GetProperties(typeof(T)); var metedata = properties.Cast<PropertyDescriptor>().Select(p => new { Name = p.Name, HeaderText = p.Attributes.OfType<DisplayAttribute>() .FirstOrDefault()?.Name ?? p.DisplayName, ToolTipText = p.Attributes.OfType<DisplayAttribute>() .FirstOrDefault()?.GetDescription() ?? p.Description, Order = p.Attributes.OfType<DisplayAttribute>() .FirstOrDefault()?.GetOrder() ?? int.MaxValue, Visible = p.IsBrowsable, ReadOnly = p.IsReadOnly, Format = p.Attributes.OfType<DisplayFormatAttribute>() .FirstOrDefault()?.DataFormatString, Type = p.PropertyType, UIHint = p.Attributes.OfType<UIHintAttribute>() .FirstOrDefault()?.UIHint }); var columns = metedata.OrderBy(m => m.Order).Select(m => { DataGridViewColumn c; if(!string.IsNullOrEmpty( m.UIHint) && UIHintMappings.DataGridViewColumns.ContainsKey(m.UIHint)) { c = UIHintMappings.DataGridViewColumns[m.UIHint].Invoke(); } else { c = new DataGridViewTextBoxColumn(); } c.DataPropertyName = m.Name; c.Name = m.Name; c.HeaderText = m.HeaderText; c.ToolTipText = m.ToolTipText; c.DefaultCellStyle.Format = m.Format; c.ReadOnly = m.ReadOnly; c.Visible = m.Visible; return c; }); grid.Columns.Clear(); grid.Columns.AddRange(columns.ToArray()); } grid.DataSource = data; } }
在设计模式中双击
Form1
并使用以下代码处理Load
事件:private void Form1_Load(object sender, EventArgs e) { var list = new List<Person>() { new Person() { Id= 1, FirstName= "Mario", LastName= "Speedwagon", BirthDate = DateTime.Now.AddYears(-30).AddMonths(2).AddDays(5), IsMember = true, Url ="https://Mario.example.com" }, new Person() { Id= 1, FirstName= "Petey", LastName= "Cruiser", BirthDate = DateTime.Now.AddYears(-20).AddMonths(5).AddDays(1), IsMember = false, Url ="https://Petey.example.com" }, new Person() { Id= 1, FirstName= "Anna", LastName= "Sthesia", BirthDate = DateTime.Now.AddYears(-40).AddMonths(3).AddDays(8), IsMember = true, Url ="https://Anna.example.com" }, }; this.dataGridView1.Bind(list, true); }
运行 项目,好了!您可以看到属性如何帮助生成列。
未来读者的改进点
您还可以添加对数据注释验证的支持。为此,您可以使用
中所做的一样Validator
class 实现IDataErrorInfo
接口,就像我在 DataAnnotations Validation attributes for Windows Forms.您可以使用
DataGridViewComboBoxColumn
轻松添加对枚举列的支持,例如 this post。您可以通过以下方式改进映射,如果没有为 UIHint 定义映射,那么作为后备方法查看列的类型,对于例如使用
DataGridViewCheckBoxColumn
作为bool
属性。