3

I have a DataGridView, which I populate data for automatically, as per the following:

MySqlDataAdapter adapter = new MySqlDataAdapter(query, connString);
DataTable table = new DataTable();
table.Locale = System.Globalization.CultureInfo.InvariantCulture;
adapter.Fill(table);
e.Result = table;

So no explicit dataGridView1.Columns.Add() calls at all.

I want to make columns of a specific type host a determined control, for example, a column of DateTime type must host a DateTimePicker. Imaginary code example:

private void dataGridView1_ColumnCreating(object sender, DataGridViewColumnEventArgs e)
{
    if(e.Column.ValueType is DateTime) 
    {
        e.Column = new MyCalendarColumn();
    }
}

Or create class that inherit from DataGridView and add my own column type support.

How can I do that? I have searched for an event fired at column's creation time or how to add my own column type by inheriting from DataGridView or so. But I could find only examples where the columns are add manually and not filling date using a DataTable.

Salah Akbari
  • 39,330
  • 10
  • 79
  • 109
Jack
  • 16,276
  • 55
  • 159
  • 284
  • *a column of `DateTime` type must host a `DateTimePicker`*, this column is one that is in your `DataTable` currently or you want to create a new column and add to your `DataGridView` then host a `DateTimePicker` for it? – Salah Akbari Jun 01 '16 at 03:57
  • I have a column of `DateTime` type but I want to instead of show current TextBox to edit the date, show a `DateTimePicker`. I don't know do that since the values aren't added manually but rahter, using the code I show in question. I hope it's clear now. – Jack Jun 01 '16 at 04:18
  • Ok. Check my answer it is based on the `MSDN` link that you are looking for. – Salah Akbari Jun 01 '16 at 09:06

3 Answers3

2

If you are looking for a native approach as suggested by MSDN here, first of all you should set the AutoGenerateColumns property of your DataGridView to false:

dataGridView1.AutoGenerateColumns = false;

Then you need to add columns that you want to appear in the DataGridView like this. (Please note that Id and Name and BirthDate are the fictional names of the columns in your table, so change them to your actual columns names):

SMySqlDataAdapter adapter = new MySqlDataAdapter(query, connString);
DataTable table = new DataTable();
table.Locale = System.Globalization.CultureInfo.InvariantCulture;
adapter.Fill(table);
e.Result = table;
dataGridView1.AutoGenerateColumns = false;
dataGridView1.DataSource = table;

DataGridViewColumn(table.Columns[0].ColumnName, new DataGridViewTextBoxColumn());
DataGridViewColumn(table.Columns[1].ColumnName, new DataGridViewTextBoxColumn());
DataGridViewColumn(table.Columns[2].ColumnName, new DataGridViewTextBoxColumn());
DataGridViewColumn(table.Columns[3].ColumnName, new CalendarColumn());//Create the CalendarColumn class as MSDN suggested


private void DataGridViewColumn(string colName, DataGridViewColumn colType)
{
    colType.DataPropertyName = colName;
    colType.HeaderText = colName;
    colType.Name = colName;
    dataGridView1.Columns.Add(colType);
}

Please note that CalendarColumn is a class that is in the MSDN link.

Salah Akbari
  • 39,330
  • 10
  • 79
  • 109
1

As I understand the question, you have a DataGridView with AutoGenerateColumns=true, just set the DataSource property to a DataTable and want to be able to customize the auto generated columns.

Let me start with the fact that this is not possible. DataGridView does not provide any way of customizing the auto generated columns - no event, virtual method, attribute etc. It's all or nothing. And by default it creates only 3 types of columns - check box, image and text, with something like this:

private static DataGridViewColumn DefaultColumnFactory(Type type)
{
    if (type == typeof(bool)) return new DataGridViewCheckBoxColumn(false);
    if (type == typeof(CheckState)) return new DataGridViewCheckBoxColumn(true);
    if (typeof(Image).IsAssignableFrom(type)) return new DataGridViewImageColumn();
    var imageConverter = TypeDescriptor.GetConverter(typeof(Image));
    if (imageConverter.CanConvertFrom(type)) return new DataGridViewImageColumn();
    if (!typeof(System.Collections.IList).IsAssignableFrom(type)) return new DataGridViewTextBoxColumn();
    return null;
}

So far so good. Fortunately it's not hard to build that functionality ourselves. First, we will add another "default" column type creation based on the property type inside the above method. And second, we will allow the caller to "override" the default behavior for each data source property, thus enable creaing combo boxes and other type of columns that need additional initialization.

In order to that, we will encapsulate it in a custom extension method with the following signature:

public static void Bind(
    this DataGridView view, 
    object dataSource,
    string dataMember = "", 
    Func<PropertyDescriptor, DataGridViewColumn> columnFactory = null)

The dataSource and dataMember represent the corresponding DataGridView properties, while the columnFactory delegate is the extension point.

Here is the full implementation:

public static class DataGridViewExtensions
{
    public static void Bind(this DataGridView view, object dataSource, string dataMember = "", Func<PropertyDescriptor, DataGridViewColumn> columnFactory = null)
    {
        var columns = new List<DataGridViewColumn>();
        var properties = ListBindingHelper.GetListItemProperties(dataSource, dataMember, null);
        for (int i = 0; i < properties.Count; i++)
        {
            var property = properties[i];
            if (!property.IsBrowsable) continue;
            var column = (columnFactory != null ? columnFactory(property) : null) ?? DefaultColumnFactory(property.PropertyType);
            if (column == null) continue;
            column.DataPropertyName = property.Name;
            column.Name = property.Name;
            column.HeaderText = !string.IsNullOrEmpty(property.DisplayName) ? property.DisplayName : property.Name;
            column.ValueType = property.PropertyType;
            column.ReadOnly = property.IsReadOnly;
            columns.Add(column);
        }
        view.DataSource = null;
        view.Columns.Clear();
        view.AutoGenerateColumns = false;
        view.Columns.AddRange(columns.ToArray());
        view.DataMember = dataMember;
        view.DataSource = dataSource;
    }

    private static DataGridViewColumn DefaultColumnFactory(Type type)
    {
        if (type == typeof(bool)) return new DataGridViewCheckBoxColumn(false);
        if (type == typeof(CheckState)) return new DataGridViewCheckBoxColumn(true);
        if (typeof(Image).IsAssignableFrom(type)) return new DataGridViewImageColumn();
        var imageConverter = TypeDescriptor.GetConverter(typeof(Image));
        if (imageConverter.CanConvertFrom(type)) return new DataGridViewImageColumn();
        // Begin custom default factory
        if (type == typeof(DateTime)) return new CalendarColumn();
        // End custom default factory
        if (!typeof(System.Collections.IList).IsAssignableFrom(type)) return new DataGridViewTextBoxColumn();
        return null;
    }
}

Note: The CalendarColumn used is from How to: Host Controls in Windows Forms DataGridView Cells MSDN example.

The above method works with DataTable, DataSet as well as every data source type (IList, IBindingList, IListSource etc.) supported by the DataGridView control.

The usage with DataTable is simply as that:

dataGridView.Bind(dataTable);

Demo::

static class Program
{
    [STAThread]
    static void Main()
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        var form = new Form();
        var dg = new DataGridView { Dock = DockStyle.Fill, Parent = form };
        dg.Bind(GetData());
        Application.Run(form);
    }

    static DataTable GetData()
    {
        var dt = new DataTable();
        dt.Columns.AddRange(new[]
        {
            new DataColumn("Id", typeof(int)),
            new DataColumn("Name"),
            new DataColumn("Description"),
            new DataColumn("StartDate", typeof(DateTime)),
            new DataColumn("EndDate", typeof(DateTime)),
        });
        dt.Rows.Add(1, "Foo", "Bar", DateTime.Today, DateTime.Today.AddDays(7));
        return dt;
    }
}

enter image description here

Ivan Stoev
  • 195,425
  • 15
  • 312
  • 343
0

Try the ColumnAdded event, have a look at the definition following:

public event DataGridViewColumnEventHandler ColumnAdded

This event occurs when a column is added to the DataGridView control.

Here is an example:

private void DataGridView1_ColumnAdded(Object sender, DataGridViewColumnEventArgs e) {

if(e.Column.ValueType is DateTime) {
          e.Column = new MyCalendarColumn();
       }
}

Let me know if this helps.

Sizons
  • 640
  • 2
  • 8
  • 24
  • Have you tried to change that state? GridView1.columns[1].ReadOnly= false; – Sizons May 27 '16 at 05:29
  • Here's another example: protected void GridView1_RowCreated(object sender, GridViewRowEventArgs e) { foreach (TableCell cell in e.Row.Cells) { if (!string.IsNullOrEmpty(cell.Text) && cell.Text != " ") { BoundField field = (BoundField)((DataControlFieldCell)cell).ContainingField; if (field.DataField == "ID") field.ReadOnly = true; } } } – Sizons May 27 '16 at 05:32
  • the property itself is readonly, not the column. You code in the answer doesn't work – Jack May 27 '16 at 05:43
  • Ok, sorry I couldn't help then :( – Sizons May 27 '16 at 06:32