Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C++/CLI

Using an Enum Class in a DataGridView Combobox Column

4.30/5 (6 votes)
19 May 2011CPOL8 min read 63.4K   5.1K  
This is a working example of an enum class column on a DataGridView for a Windows Form using C++/CLI where data is drawn from multiple datasources.
Complex enum class column in action

Objective

The purpose of this article is to provide a working example of an enum class column on a DataGridView on a Windows Form using C++/CLI where data is drawn from multiple datasources via DataTables. It will also show a simplified approach to the same feature drawing data from a single source without using a DataTable and some other unrelated features:

  • Row banding
  • Cell highlighting
  • Error Icons in individual cells
  • Using an enum class as a flag set
  • Building a DataGridView from multiple data sources

Background

There are very few examples of enum class columns on the DataGridView for C++/CLI or indeed C# returned when searching the web, but plenty of requests for assistance. The examples that are present populate their grids from a single datasource and are both simple and effective in their execution. However, my attempts to broaden this approach to a DataGridView drawing its data from two or more data sources led to persistent encounters with the display errors: “Error Happened: Formatting, Display” and “Error Happened: Formatting, Preferred Size”. This is down to a conflict between the natural state of an enum class entry which is ‘Int’ and the natural state of a DataGridView cell which is ‘String’, and it’s not as simple as a quick cast.

Example Origin

The example is drawn from a system I am working on, which relies heavily on expiry dates. Items can expire by a number of time units added to the current date, e.g., two weeks from Today, or by ‘Fixed’ units where by the time unit ends on a specified day regardless of commencement, e.g., Friday of next week. For simplicity, the example has a hardcoded representation of the ExpiryMaster table as its primary datasource and an extract of the Units hardcoded as its secondary datasource. Fixed intervals have been omitted because they do not add any additional value to the example. I have chosen to hardcode the datasources because I presume I am not alone in downloading CodeProject examples only to find I do not have the $%^*!%$ (sorry, required) database installed.

Presenting Data from a Single DataSource

This is the stuff of a Tech Tip, such is its simplicity. There are essentially only two key extra lines involved.

  • Create a new Windows Form Application
  • Put a new DataGridView on the Form
  • Add two Columns to the form, a text column ‘dgName’ and a combobox column ‘dgStatus
  • Define your enum class before the definition of the Form1 class:
C++
public enum class Status_Type {Married, Single};

Add these two lines to the Form1 Constructor (or load event):

C++
dgStatus->ValueType = Status_Type::typeid;
dgStatus->DataSource = Enum::GetValues(Status_Type::typeid);

Compile and run the application, and there you have it, a working enum class column in a DataGridView.

Here's some data to load in:

C++
dataGridView1->Rows->Clear();
array<Object^>^ itemRec = gcnew array<Object^> {"Sean",Status_Type::Married};
dataGridView1->Rows->Add(itemRec);
array<Object^>^ itemRec2 = gcnew array<Object^> {"Tom",Status_Type::Single};
dataGridView1->Rows->Add(itemRec2);	

And this works well while data is added row by row through the Rows?Add method. Hardly this stuff of legend. The code for this is in the attached DataGridView1 example.

Simple enum class column in action

The fun starts when the DataGridView is mapped to a DataTable!

The Enum Class Column on a DataGridView that Draws Data from Multiple Sources using DataTables

When populating your DataGridView using a DataTable, the secret to a successful enum class based column is to map that enum class to a DataTable of its own, and treat it as any other secondary datasource.

Our Objective

This is what we are aiming for:

Complex enum class column in action

Architecturally, the primary DataTable is driven by the ExpiryMaster DataSource, and there are secondary DataTables for the Expiry Type enum class and another for the Unit DataSource. The DataTable derived from the Unit DataSource drives two combobox columns for good measure, allowing your user to choose a Unit by ID or by Description with the other value updated to correspond.

The Ingredients for a Successful Enum

The Enum class can be defined immediately before the Form class:

C++
public enum class Expiry_Type {Units, Fixed_Date}; 

(although in my working system, I keep definitions like this in a shared assembly).

Include these definitions in your Form class to enable mapping of the enum class to a DataTable:

C++
DataSet ^dsExpTyp;
BindingSource ^bsExpTyp;
DataTable ^dtExpTyp;

I have included a temporary variable to interrogate new values coming from the datagrid:

C++
Expiry_Type empExpType;

You may position them using the IDE or by referencing my attached example (DataGridEnum4).

As part of my LaunchForm function called from my form constructor, I have:

A definition for the DataTable that will hold the Enum class:

C++
dtExpTyp = gcnew DataTable("dtExpTyp");
dtExpTyp->Columns->Add("Expiry_Type", Int32::typeid);
dtExpTyp->Columns->Add("Expiry_Type_Desc", String::typeid);
bsExpTyp = gcnew BindingSource();
dsExpTyp = gcnew DataSet();
dsExpTyp->Tables->Add(dtExpTyp);

The DataTable that maps to the DataGridView needs this entry to map to the above datastructure:

C++
dtViewExpiryData->Columns->Add("Expiry_Type", Int32::typeid);

The datasource for the column is defined as follows:

C++
gridExpiry->Columns[dgExpiry_Type->Index]->DataPropertyName = "Expiry_Type";	

Note: In this example, the DataGridView is called ‘gridExpiry’.

And a call to a function that will do the mapping:

C++
Load_Expiry_Enum();

You can see their placement in the example.

The Load_Expiry_Enum() source code is:

C++
{
	DataRow ^row;
	for each (Expiry_Type^ tmpEx in Enum::GetValues(Expiry_Type::typeid))
	{
		row = dsExpTyp->Tables["dtExpTyp"]->NewRow();
		row["Expiry_Type"] = tmpEx;
		row["Expiry_Type_Desc"] = tmpEx->ToString();
		dsExpTyp->Tables["dtExpTyp"]->Rows->Add(row);
	}
	// Set up the UnitDesc binding source
	bsExpTyp->DataSource = dsExpTyp;
	bsExpTyp->DataMember = "dtExpTyp";

	// bind Name
	dgExpiry_Type->DataSource = bsExpTyp;
	dgExpiry_Type->DisplayMember = "Expiry_Type_Desc";
	dgExpiry_Type->ValueMember = "Expiry_Type";
}

The section that follows this one will show you how this fits in to the primary datasource in the same manner any other secondary datasource. In the meantime, we will conclude this section by looking at the code to fetch, display, and interrogate the data on the enum class column on the DataGridView.

Now, we will look at two ways of getting the data from the enum column.

C++
try
{
	if (gridExpiry->Rows[e->RowIndex]->Cells[dgExpiry_Type->Index]->Value != nullptr
		&&gridExpiry->Rows[e->RowIndex]->Cells[dgExpiry_Type->Index]->
			Value->ToString() != "")
	{
		String^ IntExpType = gridExpiry->Rows[e->RowIndex]->
		Cells[dgExpiry_Type->Index]->Value->ToString();
		empExpType = safe_cast<Expiry_Type>
			(System::Convert::ToInt32(IntExpType));
		lblExpiryType->Text = empExpType.ToString();
	}
}
catch (...)
{
	lblExpiryType->Text = nullptr;
}

And:

C++
array<DataRow^>^ row;
try
{
	if (gridExpiry->Rows[e->RowIndex]->Cells[dgExpiry_Type->Index]->Value != nullptr
		&&gridExpiry->Rows[e->RowIndex]->Cells[dgExpiry_Type->Index]->
			Value->ToString() != "")
	{
		String^ IntExpType = gridExpiry->Rows[e->RowIndex]->
		Cells[dgExpiry_Type->Index]->Value->ToString();
		row =dtExpTyp->Select(String::Format("Expiry_Type={0}", IntExpType));
		if (row->Length > 0)
		{
			// ItemArray[0] holds the integer representation of the enum, 
			// alternately
			lblExpiryType->Text = row[0]->ItemArray[1]->ToString();
		}
	}
}
catch (Exception ^e)
{
	String ^MessageString = 
	" Error reading internal enum table for Expiry Type: " + e->Message;
	MessageBox::Show(MessageString);
	lblExpiryType->Text = nullptr;
}

Either one of these methods allows you to use your enum class at will.

Walking Through the Attached Code

The attached DataGridEnum4 example is a complete Visual Studio 2008 project, cleaned before zipping. First a note on my style. I don’t like much more than the definitions in my .h files, so where the IDE adds a Windows Form function to the .h, my practice is to call a user defined function in the .cpp passing on the parameters. It’s an overhead, because I cannot force the IDE to insert Windows Form functions directly into the .cpp, and when I relocate them there, the IDE gets confused – but I prefer the order that it gives things.

This sample is a standard Windows Forms application with a DataGridView and several labels dropped on from the toolbox as illustrated in the second screenshot. The Expiry Type column is a combobox column as are Unit ID and Unit Description, the other three columns are textbox columns. All this is achieved using the form designer.

In here, we will take a further look at the Form1.h and DataGridEnum4.cpp modules.

Form1.h

This is the complete list of assemblies I am referencing:

C++
using namespace System;
using namespace System::ComponentModel;
using namespace System::Collections;
using namespace System::Windows::Forms;
using namespace System::Data;
using namespace System::Drawing;
using namespace System::IO;
using namespace System::Collections::Generic;

I have defined the Expiry_Type enum class inside the application namespace, but before the form class.

In addition to the Launchform function call, I have one other line in the constructor. This is for the error icon.

C++
m_CellInError = gcnew Point(-2, -2); 

After the...

C++
#pragma endregion

...I define my internal variables and functions. Included here is another enum class, this one prefixed [Flags]. You will see examples of a flag variable, m_ElementList, being set and reset throughout the code. This is particularly useful when you need to write an update statement to commit your changes to a database. The flag settings determine the columns that need updating.

There are two variables defined for the error icon handling followed by the definitions for the DataTables, BindingSources and DataSets.

The final segment of the .h file has the function definitions added through the form designer on the IDE for the DataGridView and the Exit button. They are:

  • UserAddedRow (not used in this example)
  • CellValueChanged
  • CellValidating (not used in this example)
  • ColumnHeaderMouseClick – used to eliminate double click on the dropdowns
  • RowEnter
  • RowValidating
  • CellBeginEdit – Stores colours – doesn’t have anything in the .CPP
  • CellEndEdit – Restores Colour, then calls .CPP function
  • Exit_Click – Shuts down the example
  • CellClick
  • DataError

DataGridEnum4.cpp

LaunchForm

There are a few interesting ‘bells and whistles’ on show in this function called from the constructor.

This code customized the grid header:

C++
DataGridViewCellStyle^ headerStyle = gcnew DataGridViewCellStyle;
headerStyle->Font = gcnew System::Drawing::Font("Times New Roman", 12,FontStyle::Bold);
gridExpiry->ColumnHeadersDefaultCellStyle = headerStyle;

Personalizing the selection colours:

C++
gridExpiry->DefaultCellStyle->SelectionBackColor=Color::FromArgb(255,255,128);
gridExpiry->DefaultCellStyle->SelectionForeColor=Color::Black;

Tooltip text on the column headers:

C++
for each(DataGridViewColumn^ column in gridExpiry->Columns)
		column->ToolTipText = L"Click to\nsort rows";

Apply Colour banding to the grid:

C++
gridExpiry->AlternatingRowsDefaultCellStyle->BackColor = Color::LightGray;

Datatable for the Units datasource and the Expiry Type Enum class are defined as seen already, followed by the datatable definition for the expiry datasource.

Next up, the grid columns are mapped to data properties:

C++
gridExpiry->Columns[dgExpiry_ID->Index]->DataPropertyName = "Expiry_ID";
gridExpiry->Columns[dgExpiry_Type->Index]->DataPropertyName = "Expiry_Type";
gridExpiry->Columns[dgDescription->Index]->DataPropertyName = "Description";
gridExpiry->Columns[dgUnitIDNum->Index]->DataPropertyName = "UnitIDNum";
gridExpiry->Columns[dgUnitDesc->Index]->DataPropertyName = "Unit_ID";
gridExpiry->Columns[Number_Interval_Units->Index]->
	DataPropertyName = "Number_Interval_Units";

The function concludes with calls to dedicated functions which will populate each data table and conclude the grid definition.

List_Setup

The key thing here is the binding of the primary datatable to the DataGridView:

C++
gridExpiry->DataSource = dtViewExpiryData;

RowEntered

This function has little of note except the initialization of the flags, and reading the data from the combo box columns. I have already given two examples of how to read the comb box columns, here are the Enum Flags being initialized:

C++
m_ElementList = m_ElementList & ~ m_FlagBits::EXPIRY_ID;
m_ElementList = m_ElementList & ~ m_FlagBits::EXPIRY_TYPE;
m_ElementList = m_ElementList & ~ m_FlagBits::DESCRIPTION;
m_ElementList = m_ElementList & ~ m_FlagBits::UNIT_ID;
m_ElementList = m_ElementList & ~ m_FlagBits::NUMBER_OF_INTERVAL_UNITS;

CellEndEdit

Use this function to display an icon in any cells that you want to enforce population of. In this example, I am applying it at row level to produce this effect:

Error icons in action

CellValueChanged

Again, you have already seen how a combobox column may be read. The other feature of note here is the setting of a flag when a cell value change is detected.

C++
m_ElementList = m_ElementList | m_FlagBits::EXPIRY_ID;

CellClick

ComboBox columns on DataGridViews have an annoying habit of needing to be clicked twice in order to open the dropdown. This function has the effect of opening the dropdown as soon as the cell is clicked.

RowValidating

This function places the error icon on any cell that should have a value when your user attempts to leave a row.

DataError

This section is included to handle unforeseen display failures on the DataGridView. These errors occur when the DataGridView doesn’t know how to display a particular value. For example, in this solution where we are using datatables, if we used the simpler enum method above, we would see plenty of “Error Happened: Formatting Display” etc., because the DataGridView is looking for a string value, but the enum class is supplying an integer.

That said, it is good practice to include this function regardless of whether or not you are using enum classes because it nicely catches unexpected display issues with the DataGridView.

History

  • 2011-05-16 - V1.0 - Initial submission

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)