Okay, so I have a dataGridView with two combobox columns.
Bank and BankBranch, the user has the option to add several banks in the combobox but the branch list depends on the bank selected.
on the first row, this works flawlessly.
On any other rows, when the bank is selected, all the branch columns on all rows are updated to the branch list of that bank.
My question is, how do I make it so that when a second or third bank is selected, the branch list for only that row is updated and not all the others.
This is what im playing with.
if (grid.CurrentCell != null)
{
if (grid.CurrentCell.ColumnIndex == 3)
{
if (grid.CurrentRow != null)
{
foreach (Bank bank in banks)
{
if (bank.Description.Trim() == grid.CurrentRow.Cells["gridBank"].Value.ToString().Trim())
{
bankID = bank.ID;
GetBankBranchList(grid.CurrentRow.Index);
}
}
}
}
}
and this is the GetBankBranchList method
bankBranches = dal.GetByCriteria<BankBranch>(bankBranchQuery);
foreach (BankBranch bankBranch in bankBranches)
{
if (bankBranch.Active)
{
gridBranch.Items.Add(bankBranch.Description);
}
}
A common misconception is that a DataGridViewComboBoxCell
works like a regular ComboBox
. And in many ways, it does. However; a regular ComboBox
is far more forgiving than a DataGridViewComboBoxCell
. Example, in code, if you try to set a regular ComboBox
es value to something that is NOT in its list of items… then… nothing happens. No error/exception is thrown and the value is simply not set. This is NOT the case for the DataGridViewComboBoxCell
. Setting a combo box cells value to something that is NOT in its list of items will cause the DataGridView
to throw its DataError
exception.
You may reach for a last resort option by simply swallowing/ignoring the grids DataError
, however, that is a poor choice for many reasons. In particular, in the case of the using the grid’s combo boxes as we want, you may not have the luxury of ignoring the grids DataError
since the constant errors may eventually overwhelm the UI.
One approach to creating Cascading Combo boxes in a DataGridView
As others have commented, one possible solution for the combo box column that will have each combo box cell containing different values… is to set the combo box “columns” data source to a list that contains “ALL” the possible combo box values. Then, individually set each combo box “cell’s” DataSource
to a “filtered/subset” list of the combo box column's DataSource
. This is the approach used in the full example below.
I used your Bank-Branch type scenario. Specifically, there are 10 different Banks and 50 different Branches. Each Bank may have zero or more Branches. In addition, a Branch may belong to more than one Bank, in other words, many banks may have the same Branch.
To test, we will need a Customer. A Customer will have a unique ID, a Name, a BankID
and possibly a BranchID
. This will be used to test the combo boxes when the grid’s DataSource
is set. However, we will break this down to two steps. The first step is to get the two combo boxes working properly FIRST and do NOT set the grids data source. After step 1 is complete, then we will move to step 2 which deals with the issues when setting the grids data source.
You can follow along by creating a new winforms solution, drop a DataGridView
, and Button
onto the form, name the Button
btnNewData
and wire up its Click event. Copy the posted code below in the steps as shown below to finish with a form that works something like below…
This shows the error message boxes when the data is loaded.
Showing the filtered combo boxes in action.
Step 1) Setting up the combo boxes without setting the grids data source.
For this example, the grid will be set up to hold Customer data and the combo box columns would contain the Bank and Branch values for that Customer.
To start, we are going to make a global “regular” ComboBox
variable called SelectedCombo
. When the user clicks on a Banks combo box cell the grids EditingControlShowing
event will fire and we will cast that DataGridViewComboBoxCell
to a “regular” ComboBox
i.e., SelectedCombo
. Then, subscribe to the SelectedIndexChanged
event. When the SelectedIndexChanged
event fires, we would then set the accompanying Branch cells data source.
We will use several grid events, and below is a brief description of the events used in this code.
EditingControlShowing event …
Fires when the user clicks into a grid cell/combo box cell and puts that cell into “edit” mode. This event fires BEFORE the user actually types/changes anything in the cell. If the edited cell is a Banks combo box cell, then, the code sets up the global SelectedCombo
variable and subscribes to its SelectedIndexChanged
event.
CellLeave event…
Fires when the user tries to “leave” a cell. This event is only used to “un-subscribe” from the global variable SelectedCombo
Combo Boxes SelectedIndexChanged
event. Otherwise, the event will improperly fire when the user clicks on one of the Branches combo box cells.
DefaultValuesNeeded event…
Fires when the user types something or selects a combo box value in the LAST “new” row in the grid. This may cause some problems if the data source for the combo boxes does NOT have an “empty/null” value. So, the idea is to go ahead and give the “new” row of combo boxes some default values, namely the first Bank in the Banks list and an empty Branch.
To help we will create three simple Classes.
BranchCB Class
Represents a Branch and has two properties… an int
BranchID
and string
BranchName
. And overriding the ToString()
method for debugging output.
In addition, there is a static BlankBranch
property that returns a BranchCB
object with a BranchID
of 0 and an empty string
as BranchName
. NOTE: One possible issue using the DataGridViewComboBoxCell
is when a cells value becomes empty/null. The grid may complain about this and throw its DataError
. To help minimize this, AND to allow the Customer to have a “no” branch option, we will add a BlankBranch
to the Branches combo box column’s data source and to each Bank’s Branch collection. Even if a Bank has “no” Branches, this BlankBranch
will be present in the Bank’s Branches collection.
public class BranchCB {
public int BranchID { get; set; }
public string BranchName { get; set; }
public static BranchCB BlankBranch {
get {
return new BranchCB { BranchID = 0, BranchName = "" };
}
}
public override string ToString() {
return "BranchID: " + BranchID + " Name: " + BranchName;
}
}
BankCB Class
The BankCB
class is straight forward, an int
BankID
, a string
BankName
and a BindingList
of BranchCB
objects. An overridden ToString()
method for debugging.
public class BankCB {
public int BankID { get; set; }
public string BankName { get; set; }
public BindingList<BranchCB> Branches { get; set; }
public override string ToString() {
StringBuilder sb = new StringBuilder();
sb.AppendLine("----------------------------------------------------------");
sb.AppendLine("BankID: " + BankID + " Name: " + BankName + " Branches:...");
if (Branches.Count > 1) {
foreach (BranchCB branch in Branches) {
if (branch.BranchID != 0) {
sb.AppendLine(branch.ToString());
}
}
}
else {
sb.AppendLine("No Branches");
}
return sb.ToString();
}
}
Customer Class
A Customer
class with an int
CustomerID
, string
CustomerName
and two int
properties for BankID
and BranchID
. This class is used for testing the combo boxes.
public class Customer {
public int CustomerID { get; set; }
public string CustomerName { get; set; }
public int BankID { get; set; }
public int BranchID { get; set; }
}
For this example, five (5) global variables are created…
Random rand = new Random();
BindingList<BankCB> Banks;
BindingList<BranchCB> Branches;
BindingList<Customer> Customers;
ComboBox SelectedCombo;
rand
is used for creating test data.
Banks
is a list of BankCB
objects and will be used as a DataDource
for the Banks
DataGridViewComboBoxColumn
.
Branches
is a list of ALL BranchCB
objects and will be used as a DataSource
for the Branches
DataGridViewComboBoxColumn
.
Customers
is a list of Customer
objects and will be used as a DataSource
for the DataGridView
.
Lastly, the SelectedCombo
is a regular ComboBox
and is used as previously described.
Creating some test random Banks and Branches data…
The code below creates and sets the global variables Banks
and Branches
variables with 50 Branches and 10 Banks. Each Bank will have 0 to max 10 Branches. Some Branches may be left out and may not be used by any Bank. Each Bank’s Branches list will not contain duplicate Branches; however, multiple Banks may have the same Branch.
private void Setup_10_BanksWithRandomNumberOfBranches() {
Branches = new BindingList<BranchCB>();
Branches.Add(BranchCB.BlankBranch);
for (int numOfBranches = 1; numOfBranches <= 50; numOfBranches++) {
Branches.Add(new BranchCB { BranchID = numOfBranches, BranchName = "Branch " + numOfBranches });
}
Banks = new BindingList<BankCB>();
BindingList<BranchCB> tempBranches;
BranchCB curBranch;
int totBranches;
for (int numOfBank = 1; numOfBank <= 10; numOfBank++) {
tempBranches = new BindingList<BranchCB>();
tempBranches.Add(BranchCB.BlankBranch);
totBranches = rand.Next(0, 11);
for (int i = 0; i < totBranches; i++) {
curBranch = Branches[rand.Next(0, 50)];
if (!tempBranches.Contains(curBranch)) {
tempBranches.Add(curBranch);
}
}
tempBranches = new BindingList<BranchCB>(tempBranches.OrderBy(x => x.BranchID).ToList());
Banks.Add(new BankCB { BankID = numOfBank, BankName = "Bank " + numOfBank, Branches = tempBranches });
}
foreach (BankCB bank in Banks) {
Debug.WriteLine(bank);
}
}
Adding the columns to the grid
Setting the grids Banks combo box column should be fairly straight forward since all the combo boxes contain the same data. We want to set the Bank combo box column’s ValueMember
property to the BankID
and set its DisplayMember
property to the BankName
. For the Branches
combo box column, the ValueMember
would be BranchID
and DisplayMember
would be BranchName
.
private void AddColumns() {
dataGridView1.Columns.Add(GetTextBoxColumn("CustomerID", "Customer ID", "CustomerID"));
dataGridView1.Columns.Add(GetTextBoxColumn("CustomerName", "Customer Name", "CustomerName"));
DataGridViewComboBoxColumn col = GetComboBoxColumn("BankID", "BankName", "BankID", "Banks", "Banks");
col.DataSource = Banks;
dataGridView1.Columns.Add(col);
col = GetComboBoxColumn("BranchID", "BranchName", "BranchID", "Branches", "Branches");
col.DataSource = Branches;
dataGridView1.Columns.Add(col);
}
private DataGridViewComboBoxColumn GetComboBoxColumn(string dataPropertyName, string displayMember, string valueMember, string headerText, string name) {
DataGridViewComboBoxColumn cbCol = new DataGridViewComboBoxColumn();
cbCol.DataPropertyName = dataPropertyName;
cbCol.DisplayMember = displayMember;
cbCol.ValueMember = valueMember;
cbCol.HeaderText = headerText;
cbCol.Name = name;
return cbCol;
}
private DataGridViewTextBoxColumn GetTextBoxColumn(string dataPropertyName, string headerText, string name) {
DataGridViewTextBoxColumn txtCol = new DataGridViewTextBoxColumn();
txtCol.DataPropertyName = dataPropertyName;
txtCol.HeaderText = headerText;
txtCol.Name = name;
return txtCol;
}
At this time, we do not want to set the grids data source and want one row in the grid with the Bank and Branches combo boxes. If we run the code from the form’s load event…
private void Form3_Load(object sender, EventArgs e) {
Setup_10_BanksWithRandomNumberOfBranches();
AddColumns();
}
We should see both Bank and Branch combo box cells and selecting the bank combo box should display 10 Banks while the Branches combo box will display all 50 Branches plus the “blank” branch.
Filtering the Branches combo box cells
The first grid event to subscribe to is the grids EditingControlShowing
event. If the edited cell “is” a Bank combo box cell, then, we want to cast that Bank combo box cell to our global SelectedCombo
ComboBox
variable. Then have the global SelectedCombo
subscribe to is SelectedIndexChanged
event.
dataGridView1.EditingControlShowing += new DataGridViewEditingControlShowingEventHandler(dataGridView1_EditingControlShowing);
private void dataGridView1_EditingControlShowing(object sender, DataGridViewEditingControlShowingEventArgs e) {
if (dataGridView1.Columns[dataGridView1.CurrentCell.ColumnIndex].Name == "Banks") {
SelectedCombo = e.Control as ComboBox;
if (SelectedCombo != null) {
SelectedCombo.SelectedIndexChanged -= new EventHandler(ComboBox_SelectedIndexChanged);
SelectedCombo.SelectedIndexChanged += new EventHandler(ComboBox_SelectedIndexChanged);
}
}
}
We need to implement the ComboBox_SelectedIndexChanged
event. When this event fires, we know the Bank selection has changed and we want to set the accompanying Branch cells data source. We can get the selected BankCB
object from the global SelectedCombo.SelectedItem
property. Next, we get the Branches combo box cell for that row and set is DataSource
to the selected BankCB
’s Branches
collection. Lastly, set the Value
of the Branches cell to the “blank” Branch which will always be the first “empty” Branch in the list.
Even though we have changed the cells data source to a different list, we can have confidence, that each Bank’s Branches list is a “subset” of ALL the Branches used in the Branches combo box column’s DataSource
. This should help in minimizing the chances of throwing the grid’s DataError
.
private void ComboBox_SelectedIndexChanged(object sender, EventArgs e) {
if (SelectedCombo.SelectedValue != null) {
BankCB selectedBank = (BankCB)SelectedCombo.SelectedItem;
DataGridViewComboBoxCell branchCell = (DataGridViewComboBoxCell)(dataGridView1.CurrentRow.Cells["Branches"]);
branchCell.DataSource = selectedBank.Branches;
branchCell.Value = selectedBank.Branches[0].BranchID;
}
}
If we run the code now… you should note that... BEFORE you click the Bank combo box cell… if you click on the Branch combo box cell then you will see “all” the Branches. However, if you select/change the Bank combo box value, then, click on the Branches combo box cell… you will get a casting error in our ComboBox_SelectedIndexChanges
code.
The problem is that the global ComboBox
es SelectedCombo
’s SelectedIndexChanged
event is still wired up and will fire when the Branches combo box is selected… which we do not want. We need to UN-subscribe the SelectedCombo
from its SelectedIndexChanged
event when the user “leaves” a Bank cell. Therefore, wiring up the grids CellLeave
event and UN-subscribing from the global SelectedCombo_SelectedIndexChanged
event should fix this.
dataGridView1.CellLeave += new DataGridViewCellEventHandler(dataGridView1_CellLeave);
private void dataGridView1_CellLeave(object sender, DataGridViewCellEventArgs e) {
if (dataGridView1.Columns[e.ColumnIndex].Name == "Banks") {
SelectedCombo.SelectedIndexChanged -= new EventHandler(ComboBox_SelectedIndexChanged);
}
}
If we run the code now… the user can change the Branch combo box without errors. However, there is one possible issue… “Before” the user selects a Bank, the user can click on the Branches combo box and since the Bank combo box has not been set, the user will be shown “all” the branches and can select any Branch. This could possibly leave the Branch in an inconsistent state with a Branch selected but no Bank selected. We do not want this to happen.
In this example, we will wire up the grids DefaultValuesNeeded
event to set the new row to a “default” state by setting the BankID to the first bank in the Banks list. And set the new rows Branch value to the “Blank” Branch. This should take care of preventing the user from selecting a Branch without “first” selecting a Bank.
dataGridView1.DefaultValuesNeeded += new DataGridViewRowEventHandler(dataGridView1_DefaultValuesNeeded);
private void dataGridView1_DefaultValuesNeeded(object sender, DataGridViewRowEventArgs e) {
int newCustID = 1;
if (Customers != null) {
newCustID = Customers.Count;
}
e.Row.Cells["CustomerID"].Value = newCustID;
DataGridViewComboBoxCell cbCell = (DataGridViewComboBoxCell)e.Row.Cells["Banks"];
cbCell.DataSource = Banks;
cbCell.Value = Banks[0].BankID;
cbCell = (DataGridViewComboBoxCell)e.Row.Cells["Branches"];
cbCell.DataSource = Banks[0].Branches;
cbCell.Value = Banks[0].Branches[0].BranchID;
}
This should now have the combo boxes working as we want without errors. If the user adds rows, the method above will help in avoiding an inconsistent Bank-Branch state. This should end the first step.
Step 2) Adding a DatSource
to the grid.
We will need some test Customer
data. For this test, Customer
data will be 18 Customers
. The first 15 will have valid Bank and Branch values. “Customer 16” will have an invalid Bank number, 17 will have an invalid Branch and lastly 18 will have a valid Bank and a valid Branch however, the Branch value will not be in the Branches collection for the Customers selected Bank.
private BindingList<Customer> GetCustomers() {
BindingList<Customer> customers = new BindingList<Customer>();
BankCB curBank;
BranchCB curBranchID;
for (int i = 1; i <= 15; i++) {
curBank = Banks[rand.Next(0, Banks.Count)];
if (curBank.Branches.Count > 0) {
curBranchID = curBank.Branches[rand.Next(0, curBank.Branches.Count)];
customers.Add(new Customer { CustomerID = i, CustomerName = "Cust " + i, BankID = curBank.BankID, BranchID = curBranchID.BranchID });
}
else {
customers.Add(new Customer { CustomerID = i, CustomerName = "Cust " + i, BankID = curBank.BankID, BranchID = BranchCB.BlankBranch.BranchID });
}
}
customers.Add(new Customer { CustomerID = 16, CustomerName = "Bad Cust 16", BankID = 22, BranchID = 1 });
customers.Add(new Customer { CustomerID = 17, CustomerName = "Bad Cust 17", BankID = 3, BranchID = 55 });
customers.Add(new Customer { CustomerID = 18, CustomerName = "Bad Cust 18", BankID = 3, BranchID = 1 });
return customers;
}
Updating the forms load event may look like…
private void Form3_Load(object sender, EventArgs e) {
dataGridView1.EditingControlShowing += new DataGridViewEditingControlShowingEventHandler(dataGridView1_EditingControlShowing);
dataGridView1.CellLeave += new DataGridViewCellEventHandler(dataGridView1_CellLeave);
dataGridView1.DefaultValuesNeeded += new DataGridViewRowEventHandler(dataGridView1_DefaultValuesNeeded);Setup_10_BanksWithRandomNumberOfBranches();
Setup_10_BanksWithRandomNumberOfBranches();
AddColumns();
Customers = GetCustomers();
dataGridView1.DataSource = Customers;
}
If you run this code, you should be getting the grid’s DataError
attention at least twice for each bad Customer test data. When the grids data source is set you may see two of the bad Customers (16, and 17) such that the Bank or Branch combo boxes values are empty. If you roll the cursor over those two combo boxes, you should see the data error continually firing and basically we need to fix this. In addition , if you look at bad Customer 18, you will note that the Branch combo box value is set to an inconsistent state... in my particular test… Branch 1 is not a Branch in Bank 3.
The reason for the errors is obvious, however, the solution not so much. In this case, we have a DataSource
to the grid with BAD data for the combo boxes and unfortunately, we can NOT ignore this. We MUST do something. In this situation there is no “good” option, the data from the DB is obviously corrupt and we cannot continue without doing something. So…, you can leave the row out by removing it… Or … you could add the bad value as a new combo box value… Or … you could “change” the bad value to a default/good value. Other options may apply, but the bottom line is… we want to continue but we have to do something about the bad values first. So… Pick your own poison.
In this example, I am going with the last option and will change the bad values to default/valid values, and popup a message box to the user to let them know what was changed then continue.
We MUST check the combo box values in the Customer’s data BEFORE we set the grid’s DataSource
. Therefore, A small method is created that loops through the Customers
list and checks for bad Bank and bad Branch values. If a bad Bank value is found, then, the Bank value will be set to the first Bank in the Banks list. If the Bank value is ok, but the Branch is bad, then we will set the Branch to the “blank” Branch.
This looks like a lot of code for this; however, most of the code is building the error string. You could simply change the values and continue and never interrupt the user. However, as a minimum a debug or log statement would be a good idea for debugging purposes.
First a check is made to see if the BankID
is valid, then the BranchID
. If either is bad, then, the bad values will be replaced with default/valid values.
Keep in mind… since each Branch combo box cell’s list of items is based on what Bank is selected, then, we need to look in the Customer’s selected Bank’s Branches collection and see if the Customers BranchID
is one of the Branches in that Bank’s Branches collection.
private void CheckDataForBadComboBoxValues() {
StringBuilder sb = new StringBuilder();
foreach (Customer cust in Customers) {
sb.Clear();
List<BankCB> targetBank = Banks.Where(x => x.BankID == cust.BankID).ToList();
if (targetBank.Count > 0) {
BankCB curBank = targetBank[0];
var targetBranch = curBank.Branches.Where(x => x.BranchID == cust.BranchID).ToList();
if (targetBranch.Count > 0) {
sb.AppendLine("Valid bank and branch");
Debug.Write(sb.ToString());
}
else {
sb.AppendLine("Invalid Branch ID ----");
sb.AppendLine("CutomerID: " + cust.CustomerID + " Name: " + cust.CustomerName);
sb.AppendLine("BankID: " + cust.BankID + " BranchID: " + cust.BranchID);
sb.AppendLine("Setting Bank to : " + cust.BankID + " setting branch to empty branch");
MessageBox.Show(sb.ToString(), "Invalid Branch ID!", MessageBoxButtons.OK, MessageBoxIcon.Warning);
Debug.WriteLine(sb.ToString());
if (curBank.Branches.Count > 0) {
cust.BranchID = curBank.Branches[0].BranchID;
}
}
}
else {
sb.AppendLine("Invalid Bank ID ----");
sb.AppendLine("CutomerID: " + cust.CustomerID + " Name: " + cust.CustomerName);
sb.AppendLine("BankID: " + cust.BankID + " BranchID: " + cust.BranchID);
sb.AppendLine("Setting Bank to first bank, setting branch to empty branch");
MessageBox.Show(sb.ToString(), "Invalid Bank ID!", MessageBoxButtons.OK, MessageBoxIcon.Warning);
Debug.WriteLine(sb.ToString());
cust.BankID = Banks[0].BankID;
if (Banks[0].Branches.Count > 0) {
cust.BranchID = Banks[0].Branches[0].BranchID;
}
else {
cust.BranchID = BranchCB.BlankBranch.BranchID;
}
}
}
}
Calling this method before we set the grids DataSource
should eliminate the previous DataError
. I highly recommend checking the grids DataSource
values before setting it as a DataSource
to the grid. Specifically, the combo box values simply to avoid a possible code crash. I have no faith in “good” data, so a check is needed just to CYA.
Now the updated forms Load
method may look something like…
private void Form3_Load(object sender, EventArgs e) {
dataGridView1.EditMode = DataGridViewEditMode.EditOnEnter;
dataGridView1.EditingControlShowing += new DataGridViewEditingControlShowingEventHandler(dataGridView1_EditingControlShowing);
dataGridView1.CellLeave += new DataGridViewCellEventHandler(dataGridView1_CellLeave);
dataGridView1.DefaultValuesNeeded += new DataGridViewRowEventHandler(dataGridView1_DefaultValuesNeeded);
Setup_10_BanksWithRandomNumberOfBranches();
AddColumns();
Customers = GetCustomers();
CheckDataForBadComboBoxValues();
dataGridView1.DataSource = Customers;
}
This should get rid of the grid’s DataError
when setting the grids data source. In addition, the bad Branch value for Customer 18 is set to a blank Branch as we want. However, there is still one small issue… when the grids data source was set, the previous actions/work we did by using the grids’ events to manage each Bank-Branch combo box cell… did not happen when the grids DataSource
was set. When each Customer row was added to the grid, the events we used earlier to set each Branch combo box cell’s DataSource
did not fire.
The Branch combo box will display the proper selected Customer’s Branch value in the combo box, however, if you click on a Branch combo box, you will see all the branches since the individual Branch combo box cells DataSource
has not been set yet.
So, we need another method to loop through the grid’s row collection, grab the rows selected Bank, then set the Branch combo box cell’s DataSource
to the selected Banks Branches collection. We only need to do this once after the grids data source has been set.
private void SetAllBranchComboCellsDataSource() {
Customer curCust;
foreach (DataGridViewRow row in dataGridView1.Rows) {
if (!row.IsNewRow) {
curCust = (Customer)row.DataBoundItem;
BankCB bank = (BankCB)Banks.Where(x => x.BankID == curCust.BankID).FirstOrDefault();
// since we already checked for valid Bank values, we know the bank id is a valid bank id
DataGridViewComboBoxCell cbCell = (DataGridViewComboBoxCell)row.Cells["Branches"];
cbCell.DataSource = bank.Branches;
}
}
}
After this change the grids FINAL updated Load
method may look something like below. Setting the grids EditMode
to EditOnEnter
will facilitate clicking on the combo box cells once to get the drop down to display. In addition a Button
is added to the form to re-set the grids data for testing.
Random rand = new Random();
BindingList<BankCB> Banks;
BindingList<BranchCB> Branches;
BindingList<Customer> Customers;
ComboBox SelectedCombo;
private void Form3_Load(object sender, EventArgs e) {
dataGridView1.EditMode = DataGridViewEditMode.EditOnEnter;
dataGridView1.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;
dataGridView1.EditingControlShowing += new DataGridViewEditingControlShowingEventHandler(dataGridView1_EditingControlShowing);
dataGridView1.CellLeave += new DataGridViewCellEventHandler(dataGridView1_CellLeave);
dataGridView1.DefaultValuesNeeded += new DataGridViewRowEventHandler(dataGridView1_DefaultValuesNeeded);
SetNewData();
}
private void SetNewData() {
dataGridView1.Columns.Clear();
Setup_10_BanksWithRandomNumberOfBranches();
//Setup_10_BanksWith5BranchesNoDuplicates();
AddColumns();
Customers = GetCustomers();
CheckDataForBadComboBoxValues();
dataGridView1.DataSource = Customers;
SetAllBranchComboCellsDataSource();
}
private void btnNewData_Click(object sender, EventArgs e) {
SetNewData();
}
This should complete the example. However, it may be a challenge to test some aspects of this when the number of branches is randomly generated along with the random branches selected. In other words, different data is produced each time the code is executed. To remove this randomness, I created a second set of data such that there are 10 Banks and 50 Branches. Each Bank has exactly five (5) Branches. In addition, each Branch belongs to one and only one bank. Bank 1 has Branches 1-5; Bank 2 has Branches 6-10, etc. and all 50 Branches are used only once. For testing, it may be easier using this data.
Call this method instead of the Setup_10_BanksWithRandomNumberOfBranches();
private void Setup_10_BanksWith5BranchesNoDuplicates() {
Branches = new BindingList<BranchCB>();
Branches.Add(BranchCB.BlankBranch);
for (int numOfBranches = 1; numOfBranches <= 50; numOfBranches++) {
Branches.Add(new BranchCB { BranchID = numOfBranches, BranchName = "Branch " + numOfBranches });
}
Banks = new BindingList<BankCB>();
BindingList<BranchCB> tempBranches;
BranchCB curBranch;
int branchIndex = 1;
for (int numOfBank = 1; numOfBank <= 10; numOfBank++) {
tempBranches = new BindingList<BranchCB>();
tempBranches.Add(BranchCB.BlankBranch);
for (int i = 0; i < 5; i++) {
if (branchIndex < Branches.Count) {
curBranch = Branches[branchIndex++];
tempBranches.Add(curBranch);
}
else {
break;
}
}
tempBranches = new BindingList<BranchCB>(tempBranches.OrderBy(x => x.BranchID).ToList());
Banks.Add(new BankCB { BankID = numOfBank, BankName = "Bank " + numOfBank, Branches = tempBranches });
}
}
Sorry for the long post. I hope this makes sense and helps.