2

I would like to auto create all the columns for my DataGridView based on my custom class. Every thing works like it should, but what I need is to format and align the cell values.

So is there an attribute that I can add to my field (HeightMeter) so that it can align and format as required. To do this in a manual column create code, You will use the following:

DataGridViewColumn.DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleRight;
DataGridViewColumn.DefaultCellStyle.Format = "N2";

How to I specify the DefaultCellStyle properties when using the AutoGenerateColumns solution - Note I am limited to using .net 2 :(

Here is a sample of what I need and what I get:

public partial class Form1 : Form
{
  private List<Person> people = new List<Person>();
  private DataGridView dataGridView1 = new DataGridView();
  private DataGridView dataGridView2 = new DataGridView();
  public Form1()
  {
    InitializeComponent();
    dataGridView1.Dock = DockStyle.Top;
    dataGridView1.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCells;

    dataGridView2.Dock = DockStyle.Top;
    dataGridView2.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCells;

    Controls.Add(dataGridView2);
    Controls.Add(dataGridView1);

    Load += new EventHandler(Form1_Load);
    Text = "";
  }

  private void Form1_Load(object sender, EventArgs e)
  {
    PopulateLists();
    dataGridView1.AutoGenerateColumns = true;
    dataGridView1.DataSource = people;

    CreateAndPopulateGrid2();
  }

  public void CreateAndPopulateGrid2()
  {
    DataGridViewColumn columnName = new DataGridViewTextBoxColumn();
    columnName.HeaderText = "Name";

    DataGridViewColumn columnHeight = new DataGridViewTextBoxColumn();
    columnHeight.HeaderText = "Height [m]";
    columnHeight.ValueType = typeof(double);

    columnHeight.DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleRight;
    columnHeight.DefaultCellStyle.Format = "N2";
    dataGridView2.Rows.Clear();
    dataGridView2.Columns.Clear();
    dataGridView2.Columns.Add(columnName);
    dataGridView2.Columns.Add(columnHeight);

    DataGridViewRow row;
    foreach (Person p in people)
    {
      row = new DataGridViewRow();
      row.CreateCells(dataGridView2);
      row.Cells[0].Value = p.Name;
      row.Cells[1].Value = p.HeightMeter;
      dataGridView2.Rows.Add(row);
    }
  }

  private void PopulateLists()
  {
    people.Clear();
    people.Add(new Person("Harry", 1.7523));
    people.Add(new Person("Sally", 1.658));
    people.Add(new Person("Roy", 2.158));
    people.Add(new Person("Pris", 1.2584));
  }
}

class Person
{
  [System.ComponentModel.DisplayName("Name")]
  public string Name { get; set; }
  [System.ComponentModel.DisplayName("Height [m]")]
  public double HeightMeter { get; set; }

  public Person(string name, double heightMeter)
  {
    Name = name;
    HeightMeter = heightMeter;
  }
}
Reza Aghaei
  • 120,393
  • 18
  • 203
  • 398
Darrel K.
  • 1,611
  • 18
  • 28
  • I want to generate my DataGridView columns from a class. What i need is to apply formatting on certain columns. This can be done when manually creating columns with the DefaultCellStyle property. But i need to define this like I define the name of the column with an attribute or any way that does not force me to create each column by hand. – Darrel K. Jun 15 '18 at 06:24
  • 1
    You can create some custom attributes and decorate properties with those attributes, wherever you are setting up the grid, after auto-generating columns, you can write some code to extract metadata from those attributes and apply on grid. – Reza Aghaei Jun 15 '18 at 06:28
  • As another option, you can let the datagridview auto-generate columns. Then write some code to manually setup columns. For example `dgv.Columns["Id"].Width = 200;` and so on. – Reza Aghaei Jun 15 '18 at 06:30
  • Thanks, This is definintly an option, but is there a built in way so i dont have to create my own attribute? Like using [System.ComponentModel.DisplayName("Name")] sets the HeaderText of the column, Is there no way to access the DefaultCellStyle in the same way(bult in)? If there is no way that any one knows of, I will create my own attribute to access that properties. – Darrel K. Jun 15 '18 at 06:31
  • There is no built-in way. – Reza Aghaei Jun 15 '18 at 06:32
  • That sucks, plan B then. Create my own attribute... I like your other option, but the class I am using contains multiple fields that require formatting so to do the following (works 100%) "dataGridView1.Columns["HeightMeter"].DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleRight; dataGridView1.Columns["HeightMeter"].DefaultCellStyle.Format = "N2";" for each field is a bit much. Would of loved it if there were a built in function. Thanks for the assist. :) – Darrel K. Jun 15 '18 at 06:38
  • I think you will like these posts: [DataAnnotations attributes for DataGridView in Windows Forms](https://stackoverflow.com/a/59885956/3110834) and [DataAnnotations Validation attributes for Windows Forms](http://www.reza-aghaei.com/dataannotations-validation-attributes-in-windows-forms/). – Reza Aghaei Feb 09 '20 at 02:14
  • If you can upgrade .NET version ;) Otherwise you can implement the attributes yourself and use the approach described in the linked posts. – Reza Aghaei Feb 09 '20 at 02:21
  • I wish I could. 10 years of code to check when upgrading to latest .NET – Darrel K. Feb 10 '20 at 03:52

1 Answers1

5

Using custom attributes to control appearance of DataGridView columns

When auto-generating columns in DataGridView, there is built-in support for a few attributes including ReadOnly, DisplayName and Browsable attribute. For example, if you mark a property using Browsable(false) it will not be added as a column to DataGridView.

But for Format, there is no such built-in support. You can create a custom DisplayFormat attribute and write some code to use it in DataGridView after auto generating columns.

For example, let's say you have a class like this:

using System;
using System.ComponentModel;
public class Product
{
    [DisplayName("Code")]
    [Description("Unique code of the product")]
    public int Id { get; set; }

    [DisplayName("Product Name")]
    [Description("Name of the product")]
    public string Name { get; set; }

    [DisplayName("Unit Price")]
    [Description("Unit price of the product")]
    [DisplayFormat("C2")]
    public double Price { get; set; }
}

And as a result, we are going to have a DataGridView like the screenshot, which can see we used the value of Description attribute as tooltip text for columns and also we used DisplayFormat to show the price in currency format:

enter image description here

First we should create the custom attribute for format, DisplayFormat:

using System;
using System.ComponentModel;
public class DisplayFormatAttribute : Attribute
{
    public DisplayFormatAttribute(string format)
    {
        Format = format;
    }
    public string Format { get; set; }
}

Then load data and auto generate columns, for example:

var list = new List<Product>() {
    new Product(){ Id=1, Name="Product 1", Price= 321.1234},
    new Product(){ Id=2, Name="Product 2", Price= 987.5678},
};
this.dataGridView1.DataSource = list;

Then to take advantage of attributes, you can write such code which is not dependent to the model type:

var type = ListBindingHelper.GetListItemType(dataGridView1.DataSource);
var properties = TypeDescriptor.GetProperties(type);
foreach (DataGridViewColumn column in dataGridView1.Columns)
{
    var p = properties[column.DataPropertyName];
    if (p != null)
    {
        var format = (DisplayFormatAttribute)p.Attributes[typeof(DisplayFormatAttribute)];
        column.ToolTipText = p.Description;
        column.DefaultCellStyle.Format = format == null ? null : format.Format;
    }
}

You can simply encapsulate above code in a method like void SetupColumn(DataGridView dgv) or if you have a derived DataGridView, you can create a DataBind(object data) method and in the method, assign data to DataSource and then use above code as rest of body of the method.

Note

I also read in the comments under your question that you have told '...for each field is a bit much.' If for any reason you don't like the attribute approach, you can simply stick to a for loop like this:

foreach (DataGridViewColumn c in dataGridView1.Columns)
{
    if (c.ValueType == typeof(double))
    {
        c.DefaultCellStyle.Format = "C2";
        c.DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleRight;
    }
}

DataAnnotations attributes for in Windows Forms

To see how can you use data annotations attribute in Windows Forms for DataGridView and also for Validation, take a look at these posts:

Reza Aghaei
  • 120,393
  • 18
  • 203
  • 398
  • Make sure read the edited version of the answer. I change the code to .NET 2. The first version of the code was written i higher versions of the .NET. – Reza Aghaei Jun 18 '18 at 15:19
  • Thanks a lot. I like the attribute approach yes. This will work greate because not all of my double fields will have the same format. For some fields I will need more decimal points then for others. Will Implement above solution. Thank you very much. – Darrel K. Jun 20 '18 at 06:21