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 ComboBoxes 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 ComboBoxes 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.
I have a table with closing codes, for example:
Code Description
CL1 Reason 1
CL2 Reason 2
AF1 After 1 period
AF2 After 2 periods
Also a table with orders. Orders will be imported once a week and the table has, besides the other columns, a column holding the batch date and one holding the closing code. This last one can be NULL when the order isn’t closed or has one of the codes from the first given table.
I want to have a gridview which list per batch the total number of orders, the number of closed orders and per reason the number of orders involved.
Already made a class like this:
public class BatchSales
{
public DateTime BatchDate { get; set; }
public int BatchCount { get; set; }
public int TotalClosed { get; set; }
public Dictionary<string, int> Counters { get; set; }
}
And a method which returns a List of BatchSales.
Defined an asp:gridview in my aspx-page, with AutoGenerateColumns=false. I like to get this filled with 5 columns (batch date, count, closed, CL1, CL2, AF1 and AF2) and per row the relevant data.
In my code behind already added the first 3. My problem is adding the columns based on the dictionary. I’ve the closing codes read in a list and doing this:
foreach (var item in reasons)
{
BoundField bf = new BoundField();
bf.HeaderText = item.Description;
bf.DataField = "xxxx";
bf.DataFormatString = "{0:n0}";
bf.HeaderStyle.VerticalAlign = VerticalAlign.Top;
bf.ItemStyle.VerticalAlign = VerticalAlign.Top;
bf.ItemStyle.Wrap = false;
gvBatch.Columns.Add(bf);
}
What do I need to define for xxxx to get the value from the dictionary? Or isn’t this the right approach?
What you're wanting is more of a pivot grid. That aside, you need either an item template in the dictionary column that shows your dictionary data as rows (not ideal) or what I would do in this case is just have a button in that column that said something like "Show Details" and either have a modal window pop up with the list of counts or send it to a details page with the list of counts.
How to filter a combobox based on another combobox? ... again :)
I'm writing an web app to learn. I'm using Visual Studio 2012, Silverlight 5, C#, and SQl Server for the data source.
I have one table loading into a datagrid and comboboxes to filter the datagrid. Up to this point everything is working just right.
The comboboxes are "FilterState" and "FilterWaterWay". Note they are not in the datagrid.
I want to select a state and re-populate the FilterWaterWay with only those waterways in the state.
I've seen a lot of ways to do this but none of them seem to match my setup. I could be wrong and just not know it.
From a learning standpoint, I would like to know how to implement this in all 3 of the following query data examples but I'll settle for just one. The last one is my favorite.
Thanks for any and all help.
I would not mind using the following to load comboboxes, filtered or not, but I can't firgure out how to
Restirct the GetQuery to only one field
Make that field distinct
This loads all data from the GetQuery to the datagrid.
LoadOperation<MASTER_DOCKS> loadOp = this._DocksContext.Load(this._DocksContext.GetMASTER_DOCKSQuery());
DocksGrid.ItemsSource = loadOp.Entities;
This loads all data from the GetQuery to the datagrid after it's been filtered
EntityQuery<MASTER_DOCKS> query = _DocksContext.GetMASTER_DOCKSQuery();
query = query.Where(s => s.WTWY_NAME == WaterwaytoFilterBy && s.STATE == StateToFilterBy);
LoadOperation<MASTER_DOCKS> loadOp = this._DocksContext.Load(query);
DocksGrid.ItemsSource = loadOp.Entities;
This is how I am currently loading the comboboxes. This works fine for the load but I don't see how to filter.
The DomainService.cs does not know my other combobox (FilterState) that I want to use as the filter for this combobox (FilterWaterway).
If I could query the ObservableCollection in the xaml I might be able to get it to work but it seems kind of chunky.
Adapted from http://www.jonathanwax.com/2010/10/wcf-ria-services-datagrid-filters-no-domaindatasource-2/
XAML =
private ObservableCollection<string> waterWayFilterList;
public ObservableCollection<string> WaterWayFilterList
{
get { return waterWayFilterList; }
set { waterWayFilterList = value; }
}
private void DoPopulateFilter()
{
//Call Invoke Method to get a list of distinct WaterWays
InvokeOperation<IEnumerable<string>> invokeOp = _DocksContext.FillWaterWayList();
invokeOp.Completed += (s, e) =>
{
if (invokeOp.HasError)
{
MessageBox.Show("Failed to Load Category Filter");
}
else
{
//Populate Filter DataSource
WaterWayFilterList = new ObservableCollection<string>(invokeOp.Value);
//Add a Default "[Select]" value
WaterWayFilterList.Insert(0, "[Select WaterWay]");
FilterWaterWay.ItemsSource = WaterWayFilterList;
FilterWaterWay.SelectedItem = "[Select WaterWay]";
}
};
}
DomainService.cs =
[Invoke]
public List<string> FillWaterWayList()
{
return (from r in ObjectContext.MASTER_DOCKS
select r.WTWY_NAME).Distinct().ToList();
}
Here's the closest I've gotten so far and it seems straight forward.
It returns no errors but the displayed result reads System.Collections.Generic.List'1[System.Char]
The record count in the dropdown is correct which leads me to think it's on the right track.
Only what is displayed is wrong. A casting problem perhaps?
I would still have to get the result from the FilterState Combo box in where "TX" is.
var filter = from r in _DocksContext.MASTER_DOCKS
where r.STATE.Equals("TX")
select r.WTWY_NAME.Distinct().ToList();
MyComboBox.ItemsSource = filter;
Without parentheses, you're doing the .Distinct().ToList() on the string (which implements IEnumerable<char>, which is why those operations work), which results in a List<char> (which isn't what you're looking for). You need to add parentheses so you get the distinct waterways:
var filter = (from r in _DocksContext.MASTER_DOCKS
where r.STATE.Equals("TX")
select r.WTWY_NAME).Distinct().ToList();
Note that if two waterways might have the same name, but actually be distinct, you'll need to instead select distinct r, and then differentiate them in the dropdown somehow, e.g.
var filter = (from r in _DocksContext.MASTER_DOCKS
where r.STATE.Equals("TX")
select r).Distinct().ToList();
// generated classes are partial, so you can extend them in a separate file
public partial class MASTER_DOCKS
{
// the dropdown uses the ToString method to show the object
public override string ToString()
{
return string.Format("{0} ({1})", WTWY_NAME, ID);
}
}
I've created some drop down lists using JavaScript, ASP.NET.
A user can add as many drop down lists as he wants by clicking a "+" button and removing them by clicking a "-" button.
If it's hard to understand what I mean pls see " How to implement a list of dropboxes in C# ".
And now I'd like to implement the code behind and want to define the order of the drop down lists, but I don't know which one is my first drop down list, etc.
We assume that all <asp:DropDownList> contain the following for list elements: method1, method2, method3 and method4. If a user selects an element, a method in the codebehind is implemented.
Example:
dropboxlist1: select list item method2,
dropboxlist2: select list item method1,
dropboxlist3: select list item method3,
string txt= "";
if (dropboxlistID.Text == "method1"){
txt = method1Imp();
} else if (dropboxlistID.Text == "method2") {
txt = method2Imp();
} else if (dropboxlistID.Text == "method3") {
txt = method3Imp();
} else {
}
But at this moment I don't have any idea which drop down lists came first and which method should be performed on my string first.
Try enqueueing each method into a queue as a delegate, then draining (invoking each delegate) the queue once you're ready from a single thread. This will ensure that the order of execution matches the order of user choices.
Sorry I didn't initally include code. Here's a basic example to get you started:
Queue<Func<string>> actions = new Queue<Func<string>>();
if(dropboxListID.Text =="m1")
{
actions.Enqueue(method1Imp);
}
if(dropboxListID.Text = "m2")
{
action.Enqueue(method2Imp);
}
...
Sometime Later when you're ready to process these
...
string txt = "";
while(actions.Count >0)
{
var method = actions.Dequeue();
txt = method();
}
Here's a blog post that delves further into the concept of a work/task queue:
http://yacsharpblog.blogspot.com/2008/09/simple-task-queue.html
IMO your drop down lists will be contained in a parent.
Let us say (acc to your link) your parent is DropDownPlaceholder.
<div id="DropDownPlaceholder">
Use linq to get all children of it. Cast them as drop down lists and then your can loop on them to find your matter.
To get the order of dropdownlists:
First set the IDs/ClientIDs of hard-coded dropdownlists in aspx page and
count them (say 2 dropdownlists are present)
While creating dropdownlists dynamically, append a count integer at
the end of their IDs/ClientIDs like ddl3, ddl4 (start the count from 3)
Then in your code, you can find the dropdownlist of selected element:
if (ddl.ClientID.EndsWith("1")){
// 1st ddl
} else if (ddl.ClientID.EndsWith("2")) {
// 2nd ddl
} else if (ddl.ClientID.EndsWith("3")) {
// 3rd ddl
}
...
Is there a way of set some "PageSize" property according the number of "Group Headers" in a RadGrid?
Regards!
The code snippet is bellow:
protected void PageResults(DataTable AnyDataTable) {
//Textbox where user inserts the number of registers that will be showed per page.
if (txt_register_per_page.Value.HasValue)
{
int RegistersPerPage = 0, EveryItens = 0;
string OldData = "";
//The loop runs over all the table's rows.
for (int Index = 0; Index <= AnyDataTable.Rows.Count; Index++)
{
//The "ColumName" is the one that all the others will be grouped.
//If no matches with the current data, means that is another "group".
if (!(String.Equals(AnyDataTable.Rows[Index]["ColumnName"].ToString(), OldData)))
{
RegistersPerPage++;
if (RegistersPerPage == txt_register_per_page.Value)
{
EveryItens = Index;
break;
}
OldData = AnyDataTable.Rows[Index]["ColumnName"].ToString();
}
}
MyRadGrid.PageSize = EveryItens;
}
}
As I see, the PageSize property allows the grid to show pages based in ALL the registers, then I tried to begin writing something that converts the total data for the respective number of groups that the user inputs on the textbox.
There is a pagesize property, but it doesn't take into affect the row type to do some specialized function. You'd have to examine the data (and do the grouping yourself) and manually calculate the groups... I don't know if that is an efficient solution.
What type of grouping calculation do you want to do?