4

Having spent a lot of yesterday searching on this one I have to give up.

I have a DataGridView linked to a Datatable. One of the columns is an enum and I want that to show as a Combobox column.

I found this link Create drop down list options from enum in a DataGridView which has an answer of using the following...

    DataGridViewComboBoxColumn col = new DataGridViewComboBoxColumn();
    col.Name = "My Enum Column";
    col.DataSource = Enum.GetValues(typeof(MyEnum));
    col.ValueType = typeof(MyEnum);
    dataGridView1.Columns.Add(col);

I've tried that but when the user creates a new record, chooses an option from the drop down (the correct options show) then moves off the field they get the message "DataGridViewComboBoxCel value is not valid". I've found some solutions in searching that talk about how to trap this error, then do nothing (thus hiding the error) but I want to solve it not just hide it. If the user OK's the message they get it repeat another two times.

I've also seen solutions that loop through the values in the enum and create a datatable containing the int and the string for each one, then using the datatable as a datasource on the combo. I've used a datatable as the source for a combobox in the past when working with a back-end MSSQL database.

Another variant of this is to loop through and write straight into the combo such as this...

    foreach (MyEnum bar in MyEnum.GetValues(typeof(MyEnum)))
    {
        string barName = MyEnum.GetName(typeof(MyEnum), bar);
        MyComboColumn.Items.Add(barName);
    }

such as in the question in this link. How can I add some Enum values to a combobox

MY QUESTION: Can it be made to work using the Enum.GetValues(typeof(MyEnum)); method? The datatable method seems long winded. The looping then using MyComboColumn.Items.Add(barName); is also somewhat long winded and will result in the string version of the enum being recorded in the datatable not the integer (and I would rather it was the integer).

I can't find examples of the Enum.GetValues(typeof(MyEnum)) method where the grid is linked to a datatable. When I search on that I just come across the other methods.

I think the problem is likely to lie in the data type on the underlying table column. I've tried this as an integer, as a string and I've tried not defining it. I can't think what else to try on that type.

Here is my simplified code. (DVG is my DataGridView on the form).

enum EngineType
{
    None = 0,
    EngineType1 = 1,
    EngineType2 = 2
}
public partial class MyClass : Form
{
    DataTable DtTbl;

    public MyClass()
    {
        InitializeComponent();
        CreateTableStructure();
    }

    private void CreateTableStructure()
    {
        DGV.AutoGenerateColumns = false;
        DGV.DataSource = DtTbl;

        DtTbl = new DataTable();

        DtTbl.Columns.Add(new DataColumn("Name", System.Type.GetType("System.String")));
        DataGridViewTextBoxColumn NameCol = new DataGridViewTextBoxColumn();
        NameCol.DataPropertyName = "Name";
        NameCol.HeaderText = "Name";
        DGV.Columns.Add(NameCol);

        DtTbl.Columns.Add(new DataColumn("Engine", System.Type.GetType("System.Int32")));
        DataGridViewComboBoxColumn EngineCol = new DataGridViewComboBoxColumn();
        EngineCol.DataPropertyName = "Engine";
        EngineCol.HeaderText = "Engine";
        //EngineCol.DataSource = EngineType.GetValues(typeof(EngineType));

        foreach (EngineType engine in EngineType.GetValues(typeof(EngineType)))
        {
            string engineName = EngineType.GetName(typeof(EngineType), engine);
            EngineCol.Items.Add(engineName);
        }


        DGV.Columns.Add(EngineCol);

    }
}
Community
  • 1
  • 1
RosieC
  • 649
  • 2
  • 11
  • 27
  • Forgot to say that I get the same error in the first solution if I try and add records to the datatable programatically, as well as when the user tries to and uses the combobox to select a valid entry. – RosieC Jul 31 '13 at 13:52

3 Answers3

2

i had this problem myself, here is how i fixed it:

 DataGridViewComboBoxColumn col = new DataGridViewComboBoxColumn();
 col.ValueType = typeof(MyEnum);
 col.ValueMember = "Value";
 col.DisplayMember = "Display";
 colElementtyp.DataSource = new MyEnum[] { MyEnum.Firstenumvalue, MyEnum.Secondenumvalue }
     .Select(value => new { Display = value.ToString(), Value = value })
     .ToList();

the DataGridViewComboBoxCell has to be bound to the enum or integer. I guess the column of your datatable is integer, then it should work.

EDIT:

this should work dynamic:

 System.Array enumarray = Enum.GetValues(typeof(MyEnum)); 
 List<MyEnum> lst = enumarray.OfType<MyEnum>().ToList();
 DataGridViewComboBoxColumn col = new DataGridViewComboBoxColumn();
     col.ValueType = typeof(MyEnum);
     col.ValueMember = "Value";
     col.DisplayMember = "Display";
     colElementtyp.DataSource = lst 
         .Select(value => new { Display = value.ToString(), Value = value })
         .ToList();

shorter:

 DataGridViewComboBoxColumn col = new DataGridViewComboBoxColumn();
 col.ValueType = typeof(MyEnum);
 col.ValueMember = "Value";
 col.DisplayMember = "Display";
 colElementtyp.DataSource = Enum.GetValues(typeof(MyEnum)).OfType<MyEnum>().ToList() 
         .Select(value => new { Display = value.ToString(), Value = value })
         .ToList();
Koryu
  • 1,371
  • 1
  • 11
  • 21
  • Thanks, I thought I might have to populate ValueMember and DisplayMember but had no idea how. My problem with this solution though is that I might want to add further values to the enum in the future and don't want to have to remember to change it here as well. I know I'll forget :( – RosieC Jul 31 '13 at 14:14
  • i see, its ofc cooler if it works dynamically. check my edit :) – Koryu Jul 31 '13 at 14:27
  • I thought that had solved it (using your last solution) but while it now works when a user enters data, if I try and programmatically add a row, with a valid value for the enum, it still gives the error. Based on my above code, with your change above, I'm adding a row like this. private void CreateDefaultRows() { DataRow Row = DtTbl.NewRow(); Row["Name"] = "FIN"; Row["Engine"] = EngineType.EngineType1; DtTbl.Rows.Add(Row); DGV.DataSource = DtTbl; } – RosieC Jul 31 '13 at 14:52
  • Sounds like the value is null, then it can't find the enum value and throws the exception. Would be a good idea to debug what value it is and when it occurs. To fix it you could add a default value for the data column or add an null item to the box. – Koryu Jul 31 '13 at 19:24
  • Thanks Koryu but I've done that and it has a valid value. Before it hits that message it's been updated to one of the enum options and the debug confirms that. – RosieC Aug 18 '13 at 17:58
  • not sure what error you get. i use the code above in my application and it works fine. maybe you have to add `DataColumn.DefaultValue = EngineType.EngineType1;` for your enum Column of your datatable. if you call `DataTable.NewRow()` it will directly set the default value. so its 100% sure that the value is never null on new rows. – Koryu Aug 25 '13 at 13:34
  • Thanks Koryu, that sounds like a good solution and could well be the answer. I'll try and get back to that and see if it works. – RosieC Nov 17 '13 at 22:59
1

I gave up on doing this linking the enum directly to the combox. I thought that Koryu had solved it but while it then worked when a user entered data, if I tried to programmatically add a row, with a valid value for the enum, it still gave the error.

Based on my above code, with Koryu's change, I added a row like this.

private void CreateDefaultRows() 
{
  DataRow Row = DtTbl.NewRow();
  Row["Name"] = "FIN";
  Row["Engine"] = EngineType.EngineType1;
  DtTbl.Rows.Add(Row);
  DGV.DataSource = DtTbl;
}

But despite debugging to ensure a valid value I still got the error.

I solved it by using the method of using a datatable. I wrote a generic method in my helper class which returns a datatable for any enum.

    public static DataTable Enum2DataTable<T>()
    {
        DataTable EnumTable = new DataTable();
        EnumTable.Columns.Add(new DataColumn("Value", System.Type.GetType("System.Int32")));
        EnumTable.Columns.Add(new DataColumn("Display", System.Type.GetType("System.String")));
        DataRow EnumRow;
        foreach (T E in Enum.GetValues(typeof(T)))
        {
            EnumRow = EnumTable.NewRow();
            EnumRow["Value"] = E;
            EnumRow["Display"] = E.ToString();
            EnumTable.Rows.Add(EnumRow);
        }

        return EnumTable;
    }

If I then add the following line when defining the combo box column in my code in the question everything works fine with no other changes and with no errors.

EngineCol.DataSource = Enum2DataTable<EngineType>();

While this works, and I have a reusable method to do this again, it still feels like it should have been possible assigning the enum directly to the combo box as that 'almost' works.

Would love to know why it doesn't work but this solution works at least.

RosieC
  • 649
  • 2
  • 11
  • 27
  • just a hint, its more secure and the recommended way to set Datatable default values: `YourDataTable.Columns["Engine"].DefaultValue = EngineType.EngineType1;` – Koryu Aug 25 '13 at 13:37
  • The reason why assigning the enum directly to the combo box with a datatable gives the 'value is not valid' error is the type difference between the enum in the DataGridViewComboBoxColumn and the integral value in the DataRow (Enums are always stored as their underlying type in a DataRow regardless if the column type is set to the enum type). If a DataTable wasn't being used it would work fine. – Jeremy Thomas Apr 04 '16 at 11:59
1

Using RosieC's code I still get the exception when the underlying type of an enum is not an Int32. This can be fixed with a minor modification:

public static DataTable Enum2DataTable<T>()
{
    DataTable EnumTable = new DataTable();
    EnumTable.Columns.Add(new DataColumn("Value", Enum.GetUnderlyingType(typeof(T))));
    EnumTable.Columns.Add(new DataColumn("Display", System.Type.GetType("System.String")));
    DataRow EnumRow;
    foreach (T E in Enum.GetValues(typeof(T)))
    {
        EnumRow = EnumTable.NewRow();
        EnumRow["Value"] = E;
        EnumRow["Display"] = E.ToString();
        EnumTable.Rows.Add(EnumRow);
    }

    return EnumTable;
}