ArtsAutosBooksBusinessEducationEntertainmentFamilyFashionFoodGamesGenderHealthHolidaysHomeHubPagesPersonal FinancePetsPoliticsReligionSportsTechnologyTravel

Cascading ComboBox in C-Sharp.Net

Updated on October 21, 2012
Source

Cascading a ComboBox in C# was a logical requirement which I got while developing a CRM application for my client. They want me to show two ComboBoxes, for example, one ComboBox will have bank names like HDFC, Citi Bank, AXIS etc and second ComboBox will have credit card type offered by respective company. This may sound easy and you can say, we can data bind “Bank” in one ComboBox and “Credit Card Types” in other ComboBox but there is one challenge. I have to display those credit cards which are provided by selected bank in ComboBox one. For example, HDFC provides platinum and titanium cards and Citi Bank provides regular and premium cards. If user selects HDFC in first ComboBox then in credit card ComboBox I have to display platinum and titanium cards only.

Let see how we can achieve this in C# based Windows application. There are two examples, one demonstrates with two simple ComboBox and other one demonstrates above logic in DataGridView control.

In my example I am using mapping table for ComboBox values which is given below.

Above mapping table will help you to understand application requirement easily. It’s very simple; still I want to add few words to explain it. In our application we will have two ComboBoxes. One ComboBox will display values from “Category” column of above table and other ComboBox will display values form “Items” columns. As you can see I have colored cells in some logical order, this is for simple mapping purpose. I mean, as a user, if I select “Media Player” in “Category” ComboBox (which is highlighted in light pink) then “Items” ComboBox should display values which are highlighted in light pink in “Items” column (like “Jet Audio, iTunes” etc.). Same logic applies for remaining categories.

MainForm Screenshot

Source

LoadDataTable method which is used in both example

private void LoadDataTable()
{
    dtCategories = new DataTable();
    dtCategories.Columns.Add("CategoryID", typeof(int));
    dtCategories.Columns.Add("CategoryName", typeof(string));

    dtCategories.Rows.Add(0, "--Select--");
    dtCategories.Rows.Add(1, "Internet Browser");
    dtCategories.Rows.Add(2, "Operating System");
    dtCategories.Rows.Add(3, "Media Player");

    dtItems = new DataTable();
    dtItems.Columns.Add("ItemID", typeof(int));
    dtItems.Columns.Add("ItemName", typeof(string));
    dtItems.Columns.Add("CategoryID", typeof(int));

    dtItems.Rows.Add(1, "Google Chrome", 1);
    dtItems.Rows.Add(2, "Firefox", 1);
    dtItems.Rows.Add(3, "Internet Explorer", 1);
    dtItems.Rows.Add(4, "Windows 7", 2);
    dtItems.Rows.Add(5, "Linux", 2);
    dtItems.Rows.Add(6, "Mac", 2);
    dtItems.Rows.Add(7, "VLC Media Player", 3);
    dtItems.Rows.Add(8, "Windows Media Player", 3);
    dtItems.Rows.Add(9, "iTunes Player", 3);
    dtItems.Rows.Add(10, "Jet Audio", 3);
}

1. ComboBox Example:

If you open the downloaded project in Visual Studio 2010 you will find one “ComboBoxCascading.cs” windows form (WinForm), open it in code view. In “LoadDataTable()” method we are populating two DataTable which will be used in our ComboBox and this method is called from “ComboBoxCascading()” constructor.

Along with above method we have two other events. On “ComboBoxCascading_Load” event fire we are data binding “cbCategory” ComboBox with “dtCategories” DataTable. We will only data bind “cbCategory” ComboBox here and we will data bind “cbItems” ComboBox in “cbCategory_SelectedIndexChanged” event of “cbCategory” ComboBox. Why? Because we want to filter unwanted values before we bind it with DataSource. I think now you got what I am trying to do. Based on value selected in “Category” ComboBox we will filter values of “Items” ComboBox DataSource and then we will bind it. So “Items” ComboBox data binding is completely dynamic and it shows value based on Category ComboBox selection.

ComboBoxCascading form load event

private void ComboBoxCascading_Load(object sender, EventArgs e)
{
    cbCategory.DisplayMember = "CategoryName";
    cbCategory.ValueMember = "CategoryID";
    cbCategory.DataSource = dtCategories;
}

In “cbCategory_SelectedIndexChanged” event of “cbCategory” ComboBox we are filtering “dtItems” DataTable records and data binding the result to “cbItems” ComboBox.

SelectedIndexChanged event of category combobox

private void cbCategory_SelectedIndexChanged(object sender, EventArgs e)
{
    int categoryId = Convert.ToInt32(cbCategory.SelectedValue);
    if (categoryId > 0)
    {
        DataTable dtTemp = dtItems.Select(string.Format("CategoryID = {0}", categoryId)).CopyToDataTable();

        DataRow drTemp = dtTemp.NewRow();
        drTemp["ItemID"] = 0;
        drTemp["ItemName"] = "--Select--";
        drTemp["CategoryID"] = 0;
        dtTemp.Rows.InsertAt(drTemp, 0);

        cbItems.DisplayMember = "ItemName";
        cbItems.ValueMember = "ItemID";
        cbItems.DataSource = dtTemp;
    }
    else if (cbItems.DataSource != null)
    {
        cbItems.SelectedIndex = 0;
    }
}

ComboBox cascading window

Source

2. DataGridView Example:

This example is advancement to above example. Here we do ComboBox cascading in DataGridView.

So how do we achieve this in DataGridView? It’s simple; on form load event we will data bind “Category” and “Items” column of DataGridView with “dtCategories” and “dtItems” DataSource respectively. When we display “Items” ComboBox values, that time we will filter its values based on value of “Category” ComboBox.

DataGridViewComboBoxCascading_Load Event

 private void DataGridViewComboBoxCascading_Load(object sender, EventArgs e)
        {
            //Databind gridview columns
            ((DataGridViewComboBoxColumn)dataGridView1.Columns[indexCategory]).DisplayMember = "CategoryName";
            ((DataGridViewComboBoxColumn)dataGridView1.Columns[indexCategory]).ValueMember = "CategoryID";
            ((DataGridViewComboBoxColumn)dataGridView1.Columns[indexCategory]).DataSource = dtCategories;

            //We need to data bind items column when we load this form.
            ((DataGridViewComboBoxColumn)dataGridView1.Columns[indexItem]).DisplayMember = "ItemName";
            ((DataGridViewComboBoxColumn)dataGridView1.Columns[indexItem]).ValueMember = "ItemID";
            ((DataGridViewComboBoxColumn)dataGridView1.Columns[indexItem]).DataSource = dtItems;
        }

ComboBox cascading logic for DataGridView is different than ComboBox because in DataGridView ComboBoxes are part of every row. And if you try to filter values before binding to DataSource that will not work. You may ask why it won’t work. Let me tell you, as you know this is a DataGridView and it may have more than one row. For example, let’s assume that, you have selected “Media Player” as “Category” and based on this selection you filtered DataSource and assigned result to “Items” DataSource. Now in 2nd row you selected “Operating System” and you filtered Items DataSource. But it will invalidate you 1st row selection because the value you have selected is not a part of DataSource now and your application will throw an error.

So what is my logic to overcome this problem? Here I would suggest do not filter DataSource instead we will filter displaying values of ComboBox.

Did you notice “dataGridView1_EditingControlShowing” event in our program, this event will help us on this. This event fires when any cell in DataGridView comes in edit mode (actually when editable control is displayed to edit value) and we can filter ComboBox values when DropDown values are show. In this way we can achieve ComboBox cascading in DataGridView control.

dataGridView1_EditingControlShowing Event

private void dataGridView1_EditingControlShowing(object sender, DataGridViewEditingControlShowingEventArgs e)
        {
            //register a event to filter displaying value of items column.
            if (dataGridView1.CurrentRow != null && dataGridView1.CurrentCell.ColumnIndex == indexItem)
            {
                cmbItem = e.Control as ComboBox;
                if (cmbItem != null)
                {
                    cmbItem.DropDown += new EventHandler(cmbItem_DropDown);
                }
            }

            //Register SelectedValueChanged event and reset item comboBox to default if category changes
            if (dataGridView1.CurrentRow != null && dataGridView1.CurrentCell.ColumnIndex == indexCategory)
            {
                cmbCategory = e.Control as ComboBox;
                if (cmbCategory != null)
                {
                    cmbCategory.SelectedValueChanged += new EventHandler(cmbCategory_SelectedValueChanged);
                }
            }
        }

        void cmbCategory_SelectedValueChanged(object sender, EventArgs e)
        {
            //If category value changed then reset item to default.
            dataGridView1.CurrentRow.Cells[indexItem].Value = 0;
        }

        void cmbItem_DropDown(object sender, EventArgs e)
        {
            int categoryID = Convert.ToInt32(dataGridView1.CurrentRow.Cells[indexCategory].Value);

            if (categoryID > 0)
            {
                DataRow[] drTempRows = dtItems.Select(string.Format("CategoryID = {0}", categoryID));

                if (drTempRows != null && drTempRows.Length > 0)
                {
                    DataTable dtTemp = drTempRows.CopyToDataTable();
                    DataRow drTemp = dtTemp.NewRow();
                    drTemp["ItemID"] = 0;
                    drTemp["ItemName"] = "--Select--";
                    drTemp["CategoryID"] = 0;
                    dtTemp.Rows.InsertAt(drTemp, 0);

                    cmbItem.DisplayMember = "ItemName";
                    cmbItem.ValueMember = "ItemID";
                    cmbItem.DataSource = dtTemp;
                }
            }
            else
            {
                DataTable dtTemp = dtItems.Clone();
                DataRow drTemp = dtTemp.NewRow();
                drTemp["ItemID"] = 0;
                drTemp["ItemName"] = "--Select--";
                drTemp["CategoryID"] = 0;
                dtTemp.Rows.InsertAt(drTemp, 0);

                cmbItem.DisplayMember = "ItemName";
                cmbItem.ValueMember = "ItemID";
                cmbItem.DataSource = dtTemp;
            }
        }
Source

I tried to keep it simple and explained it as much as possible. Still if you face any difficulty then please share that with me and I will try to solve it. Thank you very much.

Comments

    0 of 8192 characters used
    Post Comment

    • profile image

      osama aftab 3 years ago

      is it possible to fill the category column by sql datasource?what if user wants to update the category column later on?? i think its not a good idea to open the source and add categories if user wants??

    • Bytes Of Code profile image
      Author

      RAJKISHOR SAHU 3 years ago from Bangalore

      My friend both columns are dataBound, for demo purpose I have added values in program. In my real life project its loading from SQL Server DB.

    • profile image

      Jim Kiely 3 years ago

      I tried your solution in Windows Form and it works somewhat. If you add a row and then try to add another row the comboboxcolumn datasources override the previous row. Any ideas? I did add the columns to the datagridview in design mode and Im populating the rows on load with data from the database.

    • Bytes Of Code profile image
      Author

      RAJKISHOR SAHU 3 years ago from Bangalore

      @Jim : Can you please share your code? It would be easy for me to understand.

    • profile image

      Jim Kiely 3 years ago

      I created a small simplified form and you will see what I'm up against. Thanks in advance.

      http://stackoverflow.com/questions/17865097/state-...

    • profile image

      Jim Kiely 3 years ago

      I posted my code on stackoverflow, here is the link.

      http://stackoverflow.com/questions/17865097/state-...

    • profile image

      revathy 3 years ago

      Thank you So much sir..I have tried for 3 days..none works..but your work is great.....Run at first time itself...

    • profile image

      Dennis 3 years ago

      Thanks for share C# article

    • profile image

      Wendys Art 2 years ago

      nice article,... :) so how to use in vb.net...

      thanks

    • Bytes Of Code profile image
      Author

      RAJKISHOR SAHU 2 years ago from Bangalore

      Sorry, I am not a VB dev.

    Click to Rate This Article