I would like to use data annotations on my classes and entities in a Windows Forms application (WinForms). I use windows DataGridViews and Infragistics UltraGrids. I have previously successfully used the [DisplayName("Name to Display")]
attribute to set the column header text on a DataGridView/UltraGrid.
This is very beneficial, because I may have several grids displaying this class, and instead of configuring each grid to display the appropriate header text, I can simply set one data annotation.
I'd like to make use of the following Data Annotations as well:
Example
Given the following annotated data classes:
public class Item
{
//Specifies that the column should not be displayed
[Display(AutoGenerateField = false)]
public int ItemID { get; set; }
//Specifies that the column should be the 1st column in the datagridview
[Display(Order = 1)]
public int Name { get; set; }
//Specifies that the column should be the 3rd column in the datagridview
//Specifies that the column header text should display "Cost" instead of "Price"
[Display(Order = 3, Name="Cost")]
//Specifies that the column should be rendered using the default local currency format string
[DataType(DataType.Currency)]
public int Price { get; set; }
//Specifies that the column should be the 4th column in the datagridview
[Display(Order = 4)]
//specifies that the column should be rendered using the datetime format string "M/d/yy h:mm tt"
[DisplayFormat(DataFormatString = "{0:M/d/yy h:mm tt")]
public DateTime ExpirationDate { get; set; }
//Specifies that the column should be the 2nd column in the datagridview
[Display(Order = 2)]
public ItemCategory Category { get; set; }
}
//Specifies that the Name column should be displayed, if referenced in a containing object
[DisplayColumn("Name")]
public class ItemCategory
{
public int CategoryID { get; set; }
public string Name { get; set; }
}
I would like the DataGridView to render like this:
+-------+---------------+--------+-----------------+
| Name | Category | Cost | ExpirationDate |
+-------+---------------+--------+-----------------+
| Item1 | Category1Name | $30.45 | 7/23/17 5:22 PM |
+-------+---------------+--------+-----------------+
| Item2 | Category1Name | $45.05 | 8/24/17 6:22 PM |
+-------+---------------+--------+-----------------+
| Item3 | Category2Name | $35.50 | 9/25/17 7:22 PM |
+-------+---------------+--------+-----------------+
However in practice with DataGridViews in .Net 4.5.2 WinForms, the datagrid is actually displayed as follows:
+----+-------+-------+----------------+--------------------+
| ID | Name | Price | ExpirationDate | Category |
+----+-------+-------+----------------+--------------------+
| 1 | Item1 | 30.45 | 7/23/17 | Namespace.Category |
+----+-------+-------+----------------+--------------------+
| 2 | Item2 | 45.05 | 8/24/17 | Namespace.Category |
+----+-------+-------+----------------+--------------------+
| 3 | Item3 | 35.50 | 9/25/17 | Namespace.Category |
+----+-------+-------+----------------+--------------------+
Documentation
The documentation declares that it is supported in ASP.NET and ASP.NET MVC.
System.ComponentModel.DataAnnotations Namespace
The System.ComponentModel.DataAnnotations namespace provides attribute classes that are used to define metadata for ASP.NET MVC and ASP.NET data controls.
Questions
It seems that these classes have not been adopted/supported in the windows forms environment. Is this true?
Is there a simple way to implement support for the data annotations in WinForms?
Is there a simple way to annotate the classes/entities that can be used to format the display of a DataGridView and/or UltraGrid?
ComponentModel. DataAnnotations namespace includes the following validator attributes: Range – Enables you to validate whether the value of a property falls between a specified range of values. RegularExpression – Enables you to validate whether the value of a property matches a specified regular expression pattern.
Data Annotations attributes are . NET attributes which can be applied on an entity class or properties to override default conventions in EF 6 and EF Core. Data annotation attributes are included in the System. ComponentModel.
Data annotations (available as part of the System. ComponentModel. DataAnnotations namespace) are attributes that can be applied to classes or class members to specify the relationship between classes, describe how the data is to be displayed in the UI, and specify validation rules.
There is no built-in support for Data Annotation in Windows Forms, but knowing how the attributes work and how windows forms work, we can use them in windows forms.
Here in this post, I'll show an extension method for DataGridView
which binds an IList<T>
to DataGridView
and auto-generate columns based on the data annotations attributes, so you can get the following DataGridView
, by calling dataGridView1.Bind(list);
:
Look at the following items which are coming from data annotations attributes:
And many more things still you can have using attributes.
While the model is like this:
using System;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
[TypeDescriptionProvider(typeof(MetadataTypeTypeDescriptionProvider))]
public class Person
{
[Display(Name = "Id")]
[Browsable(false)]
public int? Id { get; set; }
[Display(Name = "First Name", Description = "First name.", Order = 1)]
public string FirstName { get; set; }
[Display(Name = "Last Name", Description = "Last name", Order = 2)]
public string LastName { get; set; }
[Display(Name = "Birth Date", Description = "Date of birth.", Order = 4)]
[DisplayFormat(DataFormatString = "yyyy-MM-dd")]
public DateTime BirthDate { get; set; }
[Display(Name = "Homepage", Description = "Url of homepage.", Order = 5)]
public string Url { get; set; }
[Display(Name = "Member", Description = "Is member?", Order = 3)]
public bool IsMember { get; set; }
}
Bind<T> Extension Method
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
});
var columns = metedata.OrderBy(m => m.Order).Select(m =>
{
DataGridViewColumn c;
if (m.Type == typeof(bool)) {
c = new DataGridViewCheckBoxColumn(false); }
else if (m.Type == typeof(bool?)) {
c = new DataGridViewCheckBoxColumn(true); }
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;
}
}
Also for supporting data annotations validations you can implement IDataErrorInfo
interface using Validator
class, the same way that I've done it in DataAnnotations Validation attributes for Windows Forms.
Note
To enhance the answer, you may want to creating a type descriptor which cares about metadata attributes and then decorate the models with that type descriptor. You can start by using the code of AssociatedMetadataTypeTypeDescriptor
, MetadataPropertyDescriptorWrapper
, AssociatedMetadataTypeTypeDescriptionProvider
.
Also you can create a Metadata class and apply the effect of some attributes like Url
or DataType
on the metadata. Looking into this post will give you some idea: Combining multiple Attributes to a single Attribute - Merge Attributes.
Neither Infragistics UltraGrid nor the Windows DataGridView support data annotations in this manner. DevExpress as of 15.1 has some limited WinForms data annotation support within its Data Layout Control https://community.devexpress.com/blogs/thinking/archive/2015/06/08/winforms-data-layout-control-data-annotation-attributes.aspx.
I found this link where someone is asking similar questions about how to suppress columns within Infragistics Grid by looking at metadata: http://www.infragistics.com/community/forums/t/91864.aspx
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With