Tutorial: Create and Manage Data in Code and Apply Data Annotation Attributes
- 17 minutes to read
This walkthrough is a transcript of the Create and Manage Data in Code and Apply Data Annotation Attributes video available on the DevExpress YouTube Channel.
In this tutorial, you will learn how to create a data source for your grid control in code. You will also see how to apply data attributes to make the grid apply appropriate editing modes, cell editors and input validation rules.
Creating a Simple Data Source Using DevExpress Project Wizard
Start by creating a project with the DevExpress Project Wizard available at the DevExpress Templates collection as you create a new solution.
Launch the app to see that there’s sample data displayed by the grid.
Switch to code to locate the source of this data. There’s code automatically generated by the wizard, including the InitGrid method call in the form constructor. This method populates a BindingList with 5 instances of the Person class. The BindingList object is then assigned to the gird control’s GridControl.DataSource property.
class Person {
string firstName;
string secondName;
string comments;
public Person(string firstName, string secondName) {
this.firstName = firstName;
this.secondName = secondName;
comments = String.Empty;
}
public Person(string firstName, string secondName, string comments)
: this(firstName, secondName) {
this.comments = comments;
}
public string FirstName {
get { return firstName; }
set { firstName = value; }
}
public string SecondName {
get { return secondName; }
set { secondName = value; }
}
public string Comments {
get { return comments; }
set { comments = value; }
}
}
public Form1() {
// ...
InitGrid();
}
BindingList<Person> gridDataList = new BindingList<Person>();
void InitGrid() {
gridDataList.Add(new Person("John", "Smith"));
gridDataList.Add(new Person("Gabriel", "Smith"));
gridDataList.Add(new Person("Ashley", "Smith", "some comment"));
gridDataList.Add(new Person("Adrian", "Smith", "some comment"));
gridDataList.Add(new Person("Gabriella", "Smith", "some comment"));
gridControl.DataSource = gridDataList;
}
This is how you can create data in code in the simplest case. Define an object representing a record, then create a collection of such objects and assign the collection to the grid’s GridControl.DataSource property.
Creating More Complex Data Source
Now see a few more examples of creating data in code and then binding it to the grid.
You can define your custom classes that would serve as data records. The sample code file in this tutorial contains definitions of 3 classes: CompanyPublicInfo, CompanyPrivateInfo and Product, each including its own properties that will serve as data fields.
// ...
public class CompanyPublicInfo {
public string CompanyName { get; set; }
public string Country { get; set; }
public string City { get; set; }
public string Url { get; set; }
public string Email { get; set; }
public string Phone { get; set; }
public string AdditionalInfo { get; set; }
}
// ...
The GridSampleDataList
class defined later in this file provides 3 methods: GetCompanyPrivateInfo
, GetCompanyPublicInfo
, and GetProductSample
. Each of these methods returns a BindingList
populated with objects of corresponding classes:
public class GridSampleDataList {
static public List<CompanyPublicInfo> GetCompanyPublicInfo() {
return new List<CompanyPublicInfo> {
new CompanyPublicInfo() {
AdditionalInfo = "Some Info",
City = "Glendale",
CompanyName = "Developer Express",
Country = "USA",
Email = "info@devexpress.com",
Phone = "1234567890",
Url = "www.devexpress.com",
},
// ...
};
}
// ...
}
Now create the UI allowing application users to switch between these three data sources. Return to the main form design where you can see the editor added to the Ribbon Control. The dropdown list will have three items corresponding to data source types defined earlier.
Now handle the BarEditItem.EditValueChanged event to assign different BindingList objects to the grid’s GridControl.DataSource depending on the currently selected dropdown list item.
private void barEditItem1_EditValueChanged(object sender, EventArgs e) {
DevExpress.XtraBars.BarEditItem item = sender as DevExpress.XtraBars.BarEditItem;
if(item == null) return;
switch (item.EditValue as string) {
case "Company public info":
gridControl.DataSource = GridSampleDataList.GetCompanyPublicInfo();
break;
case "Company private info":
gridControl.DataSource = GridSampleDataList.GetCompanyPrivateInfo();
break;
case "Product info":
gridControl.DataSource = GridSampleDataList.GetProductSample();
break;
}
}
To make sure that grid columns are re-created based on the currently available data fields, handle the GridControl.DataSourceChanged event that fires each time the grid receives a new data source. In the event handler, simply call the ColumnView.PopulateColumns method that does exactly what is needed.
void gridControl_DataSourceChanged(object sender, EventArgs e) {
DevExpress.XtraGrid.GridControl grid = sender as DevExpress.XtraGrid.GridControl;
if(grid == null) return;
grid.MainView.PopulateColumns();
(grid.MainView as GridView).BestFitColumns();
}
Run the application to see how this works. The application still starts with auto-generated sample data. If you select an item from the dropdown list in the Ribbon, the grid will display data from the corresponding data source.
Applying Data Annotation Attributes
Notice that all grid columns are displayed with their default editors and have default formatting applied. For instance, the Product Info data contains multi-line text that cannot be viewed entirely since the default grid cell editor allows only single-line text. Company Public Info data includes URLs and e-mails that are displayed as simple text strings, as well as phone numbers that should ideally use a phone mask format. Finally, the Private Company Info displays passwords that should not be immediately visible.
One way to change this is by accessing column objects and updating their settings. But that would mean that you should do this every time you bind your data source to a data-aware control. Another way to do this is by using Data Annotation Attributes provided by Microsoft and supported by most DevExpress data-aware controls. To be able to use these attributes, make sure your application references the System.ComponentModel.DataAnnotations namespace.
There are two ways to use these attributes. The first and simplest method is to define required attributes before each data field. This is what is done for the Product class. Some attributes indicate data types so that an appropriate cell editor can be assigned. The ReadOnly attribute allows you to disable data editing for a specific field. You can also apply data input validation rules, as it is done by the Range attribute.
using System.ComponentModel.DataAnnotations;
public class Product {
[ReadOnly(true)]
public double UnitPrice { get; set; }
[EnumDataType(typeof(ProductCategory))]
public int Category { get; set; }
[Display(Description = "The amount of currently available product")]
public int Quantity { get; set; }
[DataType(DataType.Text), Display(Order = -1)]
public string Text { get; set; }
[DataType(DataType.MultilineText)]
public string MultilineText { get; set; }
[DataType(DataType.Currency), Range(200, 5000)]
public int Currency { get; set; }
[DataType(DataType.Date)]
public DateTime Date { get; set; }
[DataType(DataType.Time)]
public DateTime Time { get; set; }
}
Launch the app and switch to the Product Info data to see how it looks now. The multiline text uses the MemoEdit cell editor that allows us to view the text entirely, and the Currency column allows only data in the specified range.
This approach is most useful when you have unique data fields that are not used in multiple classes. Another way to accomplish the same task is by using the MetadataType class attribute. Using this approach, you can define a data field attribute once and then use the definition for multiple classes. It can also improve code readability, since data attributes don’t have to precede every property definition.
Both Private and Public Company Info classes will use metadata defined by the CompanyProductMetadata class.
[MetadataType(typeof(CompanyProductMetadata))]
public class CompanyPublicInfo {
// ...
}
// ...
public class CompanyProductMetadata {
[Display(ShortName = "Company", Name = "Company Name", AutoGenerateFilter = false)]
public object CompanyName;
[Display(Order = 2)]
public object Country;
[Display(Order = 1), Editable(false)]
public object City;
[DataType(DataType.Url)]
public object Url;
[DataType(DataType.EmailAddress)]
public object Email;
[DataType(DataType.PhoneNumber), Required]
public object Phone;
[DataType(DataType.Text), Display(Order = -1)]
public object Text;
[Display(AutoGenerateField = false, Description = "This column isn't created")]
public object AdditionalInfo;
[DataType(DataType.Password), StringLength(20, MinimumLength = 3)]
public object Password;
// ...
}
Run the application to see the result. Switch to the Public Company Info data source to see that URLs are now displayed as actual hyperlinks and phone numbers use masked input.
Complete Code
The solution in this example includes a DataSource file that has three classes that provide grid data - CompanyPublicInfo, CompanyPrivateInfo and Product. Properties for all three of them derive Data Annotation Attributes from the CompanyProductMetadata class by using the MetadataType attribute. End-users can use the editor at the top of the form to call one of the Get… methods that will populate the grid with sample data.
Important
To work with Data Annotation Attributes, you need to reference the System.ComponentModel.DataAnnotations library in your solution.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace GridDataAttributes {
[MetadataType(typeof(CompanyProductMetadata))]
public class CompanyPublicInfo {
public string CompanyName { get; set; }
public string Country { get; set; }
public string City { get; set; }
public string Url { get; set; }
public string Email { get; set; }
public string Phone { get; set; }
public string AdditionalInfo { get; set; }
}
[MetadataType(typeof(CompanyProductMetadata))]
public class CompanyPrivateInfo {
public string Password { get; set; }
public DateTime Date2 { get; set; }
public double Sales { get; set; }
public double Profit { get; set; }
public double SalesVsTarget { get; set; }
public double MarketShare { get; set; }
public double CustomersSatisfaction { get; set; }
}
public class Product {
[ReadOnly(true)]
public double UnitPrice { get; set; }
[EnumDataType(typeof(ProductCategory))]
public int Category { get; set; }
[Display(Description = "The amount of currently available product")]
public int Quantity { get; set; }
[DataType(DataType.Text), Display(Order = -1)]
public string Text { get; set; }
[DataType(DataType.MultilineText)]
public string MultilineText { get; set; }
[DataType(DataType.Currency), Range(200, 5000)]
public int Currency { get; set; }
[DataType(DataType.Date)]
public DateTime Date { get; set; }
[DataType(DataType.Time)]
public DateTime Time { get; set; }
}
public class CompanyProductMetadata {
[Display(ShortName = "Company", Name = "Company Name", AutoGenerateFilter = false)]
public object CompanyName;
[Display(Order = 2)]
public object Country;
[Display(Order = 1), Editable(false)]
public object City;
[DataType(DataType.Url)]
public object Url;
[DataType(DataType.EmailAddress)]
public object Email;
[DataType(DataType.PhoneNumber), Required]
public object Phone;
[DataType(DataType.Text), Display(Order = -1)]
public object Text;
[Display(AutoGenerateField = false, Description = "This column isn't created")]
public object AdditionalInfo;
[DataType(DataType.Password), StringLength(20, MinimumLength = 3)]
public object Password;
[DisplayFormat(DataFormatString = "MMMM/yyyy"), Display(Name = "Date 2")]
public object Date2;
[DisplayFormat(DataFormatString = "#,##0,,M")]
public object Sales;
[DisplayFormat(DataFormatString = "#,##0,,M")]
public object Profit;
[DisplayFormat(DataFormatString = "p", ApplyFormatInEditMode = true), Display(Name = "Sales vs Target")]
public object SalesVsTarget;
[DisplayFormat(DataFormatString = "p0", ApplyFormatInEditMode = false)]
public object MarketShare;
[Display(Name = "Cust Satisfaction")]
public object CustomersSatisfaction;
}
public enum ProductCategory {
Beverages = 1,
Fruit = 2,
Vegetables = 3,
Meat = 4,
Condiments = 5,
Confections = 6,
DairyProducts = 7,
GrainsCereals = 8,
Seafood = 9
}
public class GridSampleDataList {
static public List<CompanyPrivateInfo> GetCompanyPrivateInfo() {
return new List<CompanyPrivateInfo> {
new CompanyPrivateInfo() {
CustomersSatisfaction = 3.1,
Date2 = DateTime.Now,
MarketShare = 42,
Password = "123qwerty",
Profit = 4951515,
Sales = 311414134,
SalesVsTarget = 0.0277,
}
};
}
static public List<CompanyPublicInfo> GetCompanyPublicInfo() {
return new List<CompanyPublicInfo> {
new CompanyPublicInfo() {
AdditionalInfo = "Some Info",
City = "Glendale",
CompanyName = "Developer Express",
Country = "USA",
Email = "info@devexpress.com",
Phone = "1234567890",
Url = "www.devexpress.com",
}
};
}
static public List<Product> GetProductSample() {
return new List<Product> {
new Product() {
Currency = 1000,
Category = 2,
Date = DateTime.Now,
MultilineText = "Line1\r\nLine2\r\nLine3",
Quantity = 321,
Text = "Sample Text",
Time = DateTime.Now,
UnitPrice = 1800,
}
};
}
}
}