how to change combobox (dropdownlist ) value with arrow key? - c#

I have a combo box witch is DropDownList and i bind it to a property of a class. this Combo Box is populated with an array.
now in run time when i change selected item by mouse click every things sounds good. but when change item by arrow key any thing wont work. even textchanged event of combo box would not raise.

For ComboBoxit's really easy to use selected index changed event, instead of text changed event. It will fire by mouse or keyboard, when it changes the selection item of a ComboBox.
Example:
private void CB_Company_SelectedIndexChanged(object sender, EventArgs e)
{
if (CB_Company.SelectedItem.ToString() != "Select a company" & CB_Company.SelectedItem.ToString() != "")
{
CB_Company.BackColor = Color.White;
CB_Company.Enabled = false;
RB_Option1.Enabled = true;
RB_Option2.Enabled = true;
}
}
Populating combobox method:
private void SetDropDownItems()
{
List<DropDownModel> dropDownModel = new List<DropDownModel>();
dropDownModel.Add(new DropDownModel()
{
Name = "Select a company",
Value = ""
});
dropDownModel.Add(new DropDownModel()
{
Name = "My Company",
Value = "Comp"
});
CB_Company.DataSource = dropDownModel;
CB_Company.DisplayMember = "Name";
CB_Company.ValueMember = "Value";
}
I hope you get the idea.

Related

C# Fail to set DatagridComboboxCell value to null

I am trying to make a datagridview in which there exists a combobox column, and this column is binded a list of options. My logic is that, if the user selects the wrong option, the program could detect and deselect this wrong option(which means comboboxCell.value = null).
The psuedo code for this logic is as follows:
//use CurrentCellDirtyStateChanged and CellValueChanged as event listeners
this.DGV.CurrentCellDirtyStateChanged += DGV_CurrentCellDirtyStateChanged;
this.DGV.CellValueChanged += DGV_CellValueChanged;
//CurrentCellDirtyStateChanged event
private void DGV_CurrentCellDirtyStateChanged(object sender, EventArgs e)
{
if (DGV.IsCurrentCellDirty)
{
DGV.CommitEdit(DataGridViewDataErrorContexts.Commit);
}
}
//CellValueChanged event
private void DGV_CellValueChanged(object sender, DataGridViewCellEventArgs e)
{
if (!CheckingCorrect(DGV.CurrentCell))
{
// set selected value null
DGV.CurrentCell.Value = null
}
}
However, after the code sets currentcell value to null, the datagridview cell still kept the selected value shown.
I also tried may other ways like DGV.ClearSelection(), but none of these works.
There are two things that will make working with the DataGridViewComboBoxColumn/Cell easier...
Make a Class specifically for the combo box column
Use the proper event to help in casting the DataGridViewComboBoxCell to a
regular ComboBox.
In this example I made a simple Class called ComboValues that has two properties for the column ValueMember and DisplayMember. In this example the ValueMember is an int, however you could leave it out or use some other type that fits your needs. This class will simplify things as we will have the properties for both the ValueMember and DisplayMember for the combo box column.
This ComboValues Class may look something like…
public class ComboValues {
public int Value { get; set; }
public string Display { get; set; }
}
Then a method to get a List<ComboValues> from the DB that we will use as a DataSource for the combo box column.
private List<ComboValues> GetComboValues() {
List<ComboValues> items = new List<ComboValues>();
items.Add(new ComboValues { Value = 0, Display = null });
items.Add(new ComboValues { Value = 1, Display = "Option 1" });
items.Add(new ComboValues { Value = 2, Display = "Option 2" });
items.Add(new ComboValues { Value = 3, Display = "Option 3" });
items.Add(new ComboValues { Value = 4, Display = "Option 4" });
return items;
}
And setting up the combo box column for the grid…
private DataGridViewComboBoxColumn GetComboCol() {
DataGridViewComboBoxColumn col = new DataGridViewComboBoxColumn();
col.HeaderText = "Combo Items";
col.ValueMember = "Value";
col.DisplayMember = "Display";
col.DataSource = GetComboValues();
return col;
}
We will call the above method in the forms Load event to add the column to the Grid…
private void Form1_Load(object sender, EventArgs e) {
DGV.Columns.Add(GetComboCol());
}
Next, we want to set up the edited DataGridViewComboBoxCell to act like a REGULAR ComboBox. We can do this using the grids EditingControlShowing event. This event will fire when the user clicks into a cell and in this case if the user clicks into a combo box cell, then we want to cast that cell to a globally defined ComboBox. So, we will need a global ComboBox variable called something like… EditedComboBox.
ComboBox EditedComboBox = null;
Is what we will do is that when the grids EditingControlShowing event fires and we check to see if it is the combo box cell, then we simply cast that cell to our global EditedComboBox and wire up its SelectedIndexChanged event. Then we can capture “when” the user “changes” a value in the combo box cell. The EditingControlShowing event may look something like…
private void DGV_EditingControlShowing(object sender, DataGridViewEditingControlShowingEventArgs e) {
if (DGV.CurrentCell.ColumnIndex == 0) {
EditedComboBox = (ComboBox)e.Control;
if (EditedComboBox != null) {
EditedComboBox.SelectedIndexChanged += new EventHandler(ComboBox_SelectedIndexChanged);
}
}
}
Above, column index 0 is the target combo box column. All we need to do is implement and wire up the SelectedIndexChanged event and wait for the user to change the combo box value.
When the user does indeed change the combo boxes value, then our SelectedIndexChanged event will fire and it is in this event where you would “CHECK” if the selected value needs adjustments on some parameters. In this example, the condition is if the user selects “Option 3”, then that is an invalid value and we will set the cells value to null. This event may look something like…
private void ComboBox_SelectedIndexChanged(object sender, EventArgs e) {
//Debug.WriteLine("CB_SelectedIndexChanged");
if (EditedComboBox != null) {
ComboValues cv = (ComboValues)EditedComboBox.SelectedItem;
if (cv != null && !CheckingCorrect(cv.Display)) {
EditedComboBox.SelectedIndexChanged -= new EventHandler(ComboBox_SelectedIndexChanged);
EditedComboBox.SelectedIndex = 0;
EditedComboBox.SelectedIndexChanged += new EventHandler(ComboBox_SelectedIndexChanged);//DGV.CurrentCell.Value = 0;
}
}
}
In this event, we grab (cast) the combo boxes SelectedItem to a ComboValues object. With it we can easily check what value is selected and in turn set its value to null if needed. Note “item” 0 in our List<ComboValues> is the null value, so we set the combo boxes SelectedIndex to zero (0) to make the grid display a null value.
Also, since we are “changing” the combo boxes Index, we do NOT want this event to re-fire.
Lastly, we need one more event to wire up to TURN OFF the SelectedIndex changed event to prevent it from firing more than we want. In this case we will wire up the grids CellLeave event. In that event, the user is leaving the cell and if the EditedComboBox is not null... then we know we need to un-wire the event. It may look something like…
private void DGV_CellLeave(object sender, DataGridViewCellEventArgs e) {
if (EditedComboBox != null) {
EditedComboBox.SelectedIndexChanged -= new EventHandler(ComboBox_SelectedIndexChanged);
EditedComboBox = null;
}
}
I altered the checking code to check for a string and not necessarily a cell. The condition checker code…
private bool CheckingCorrect(string cellValue) {
if (cellValue != null && cellValue == "Option 3") {
return false;
}
else {
return true;
}
}
I hope this helps and makes sense. If you have questions, then feel free to ask.

Populate datagridview combo box based on other dgv combobox selection

I'm currently populating combobox with list from db query like this:
private List<ExerciseAndVideo> GetExerciseVideoComboBoxListFromDB()
{
List<ExerciseAndVideo> exerciseList = new List<ExerciseAndVideo>();
con = new SqlConnection("my db path etc");
cmd = new SqlCommand();
con.Open();
cmd.Connection = con;
cmd.CommandText = "SELECT nome, link_video, gruppo_muscolare FROM Esercizi ORDER BY nome ASC";
dr = cmd.ExecuteReader();
while (dr.Read())
{
ExerciseAndVideo item = new ExerciseAndVideo();
item.Esercizio = dr.GetString(0);
item.Video = dr.GetString(1);
item.Gruppo = dr.GetString(2);
exerciseList.Add(item);
}
con.Close();
return exerciseList;
}
public class ExerciseAndVideo
{
public string Esercizio { get; set; }
public string Video { get; set; }
public string Gruppo { get; set; }
}
What I'm trying to do now is to add another combobox in the dgv for filter the results of the exercise list.
I've added the combo like this:
dataGridView1.Columns.Add(GetGroupComboBoxColumn(GrList));
private List<GroupList> GetGroupComboBoxListFromDB()
{
List<GroupList> GrList = new List<GroupList>();
GrList.Add(new GroupList { Gruppo = "quads" });
GrList.Add(new GroupList { Gruppo = "hamstring" });
GrList.Add(new GroupList { Gruppo = "gluteus" });
return GrList;
}
How can I do this?
I want that when in the combo1 is selected "quads", in the combo 2 I see only the exercises for quads.
EDIT
Here there are the events that get fire on the dgv combos:
private void dataGridView1_EditingControlShowing(object sender, DataGridViewEditingControlShowingEventArgs e)
{
if (dataGridView1.Columns[dataGridView1.CurrentCell.ColumnIndex].Name == "Esercizio")
{
curCombo = e.Control as ComboBox;
if (curCombo != null)
{
curCombo.SelectedIndexChanged -= new EventHandler(curCombo_SelectedIndexChanged);
curCombo.SelectedIndexChanged += new EventHandler(curCombo_SelectedIndexChanged);
}
}
}
private void curCombo_SelectedIndexChanged(object sender, EventArgs e)
{
if (curCombo != null && curCombo.SelectedValue != null)
{
ExerciseAndVideo selectedExercise = (ExerciseAndVideo)curCombo.SelectedItem;
dataGridView1.CurrentRow.Cells["Video"].Value = selectedExercise.Video;
dataGridView1.CurrentRow.Cells["Gruppo"].Value = selectedExercise.Gruppo;
}
}
private void dataGridView1_CellLeave(object sender, DataGridViewCellEventArgs e)
{
if (dataGridView1.Columns[e.ColumnIndex].Name == "Esercizio")
{
if (curCombo != null)
{
curCombo.SelectedIndexChanged -= new EventHandler(curCombo_SelectedIndexChanged);
}
}
}
And Here how the combos get datasource:
private DataGridViewComboBoxColumn GetGroupComboBoxColumn(List<GroupList> GrList)
{
DataGridViewComboBoxColumn cbCol = new DataGridViewComboBoxColumn();
cbCol.HeaderText = "Zona";
cbCol.Name = "Zona";
cbCol.DisplayMember = "Zona";
cbCol.DataSource = GrList;
cbCol.Width = 100;
return cbCol;
}
private DataGridViewComboBoxColumn GetExcerciseComboBoxColumn(List<ExerciseAndVideo> exerciseList)
{
DataGridViewComboBoxColumn cbCol = new DataGridViewComboBoxColumn();
cbCol.HeaderText = "Esercizio";
cbCol.Name = "Esercizio";
cbCol.DisplayMember = "Esercizio";
// if I use this string works fine
string selectedGroup = "quadricipiti";
var filteredList = exerciseList.Where(x => x.Gruppo == selectedGroup).ToList();
cbCol.DataSource = filteredList;
cbCol.Width = 140;
return cbCol;
}
EDIT 2
I edit the curCombo_SelectedIndexChanged event as suggest but I'm getting error on exerciseList because not exist in the function:
private void curCombo_SelectedIndexChanged(object sender, EventArgs e)
{
if (curCombo != null && curCombo.SelectedValue != null)
{
ExerciseAndVideo selectedExercise = (ExerciseAndVideo)curCombo.SelectedItem;
dataGridView1.CurrentRow.Cells["Video"].Value = selectedExercise.Video;
dataGridView1.CurrentRow.Cells["Gruppo"].Value = selectedExercise.Gruppo;
string selectedGroup = Convert.ToString((GroupList)SelectedCombo.SelectedItem);
DataGridViewComboBoxCell Esercizi = (DataGridViewComboBoxCell)(dataGridView1.CurrentRow.Cells["Esercizi"]);
var filteredList = exerciseList.Where(x => x.Gruppo == selectedGroup).ToList();
Esercizi.DataSource = filteredList;
}
}
EDIT 3
After correcting the list code to make it work as global, I get no error but I'm not be able to pick the selected value from combo 1 as selected group.
If I use a string like "quads" or other everything works fine, else if I try to pick the value from combo1, I get a blank list on combo 2.
Here some code I tried for get the value from combo 1:
string selectedGroup = Convert.ToString(dataGridView1.CurrentRow.Cells["Zona"]);
string selectedGroup = SelectedCombo.SelectedItem.ToString();
string selectedGroup = Convert.ToString((GroupList)SelectedCombo.SelectedItem);
LAST EDIT
Here what I found is working to get the value from combo 1:
string selectedGroup = Convert.ToString(dataGridView1.CurrentRow.Cells[0].FormattedValue.ToString());
The problem now is that to make working the filter, I must select 2 time the value in the combo 1.
So for example, I select "quads" in combo 1 and I get a blank list in combo 2. I select "quads" another time and I get the filtered result.
I think the problem can be in the event but I don't see errors:
private void dataGridView1_EditingControlShowing(object sender, DataGridViewEditingControlShowingEventArgs e)
{
if (dataGridView1.Columns[dataGridView1.CurrentCell.ColumnIndex].Name == "Esercizio")
{
curCombo = e.Control as ComboBox;
if (curCombo != null)
{
curCombo.SelectedIndexChanged -= new EventHandler(curCombo_SelectedIndexChanged);
curCombo.SelectedIndexChanged += new EventHandler(curCombo_SelectedIndexChanged);
}
}
if (dataGridView1.Columns[dataGridView1.CurrentCell.ColumnIndex].Name == "Zona")
{
curCombo = e.Control as ComboBox;
if (curCombo != null)
{
curCombo.SelectedIndexChanged -= new EventHandler(ComboBox_SelectedIndexChanged);
curCombo.SelectedIndexChanged += new EventHandler(ComboBox_SelectedIndexChanged);
}
}
}
private void curCombo_SelectedIndexChanged(object sender, EventArgs e)
{
if (curCombo != null && curCombo.SelectedValue != null)
{
ExerciseAndVideo selectedExercise = (ExerciseAndVideo)curCombo.SelectedItem;
dataGridView1.CurrentRow.Cells["Video"].Value = selectedExercise.Video;
dataGridView1.CurrentRow.Cells["Gruppo"].Value = selectedExercise.Gruppo;
}
}
private void ComboBox_SelectedIndexChanged(object sender, EventArgs e)
{
if (curCombo.SelectedValue != null)
{
string selectedGroup = Convert.ToString(dataGridView1.CurrentRow.Cells[0].FormattedValue.ToString());
DataGridViewComboBoxCell Esercizi = (DataGridViewComboBoxCell)(dataGridView1.CurrentRow.Cells["Esercizio"]);
// string selectedGroup = "femorali";
var filteredList = exerciseList.Where(x => x.Gruppo == selectedGroup).ToList();
Esercizi.DataSource = filteredList;
}
}
private void dataGridView1_CellLeave(object sender, DataGridViewCellEventArgs e)
{
if (dataGridView1.Columns[e.ColumnIndex].Name == "Esercizio")
{
if (curCombo != null)
{
curCombo.SelectedIndexChanged -= new EventHandler(curCombo_SelectedIndexChanged);
}
}
if (dataGridView1.Columns[e.ColumnIndex].Name == "Zona")
{
if (curCombo != null)
{
curCombo.SelectedIndexChanged -= new EventHandler(ComboBox_SelectedIndexChanged);
}
}
}
From the long back and forth in comments. I have a solution below that should work as you describe. Before I start I would like to suggest you take more care when you post a question. Initially when you posted this question, there were many-many details left out that would have avoided the unnecessary extended comments that you see. The more details you leave out and the more questions others have to ask to understand your question, the less chance you have of getting your question answered.
In the future, I suggest you provide all the needed info and certainly edit your question to add any pertinent info if it becomes obvious that it is needed. The more work others have to do to help you, the less chance you have of getting any help at all much less an answer. Do yourself a favor and take that extra step to make sure your question is fully understandable and better yet reproducible… I promise you will not regret it.
Moving on, given the picture below, I assume this is similar to what you have posted.
The user can select and change a “Zone” combo box item. When this happens, is what we want to do is “filter” the “Exercise” columns combo box cell to contain ONLY exercises that are in the selected Zone. THEN, if the user selects an exercise from the Exercise combo box cell, THEN, the matching “Video” link for that exercise is displayed in the “Video” cell on that row. Hopefully this is correct.
Observations lead to some other assumed behaviors.
IF a row has a selected Zone and a selected Exercise and the Video cell displays the video link for the exercise… then… IF the user changes the “ZONE” value for that row, THEN the code will CLEAR the Exercise and Video cells since those values MAY NOT necessarily belong to the newly selected Zone.
Assuming the Zone combo box cell is “empty”… IF the user attempts to select an Exercise from the Exercise combo box BEFORE the Zone value has been set for that row… THEN… we want the Exercise combo box to contain NO values since the Zone has not been selected yet. In other words, the Exercise combo box will not fill with values until the user selects a Zone.
The code below uses the above assumptions and clears the Exercise and Video values when the Zone changes and prevents the user from selecting an Exercise before a Zone has been selected.
If you create a new winform solution and drop a DataGridView onto the form, you should be able to follow along to complete this example step by step. Below is some code and classes to help with test data and building the grid.
First the two classes we will use for the combo box columns. Both are simple and fairly straight forward.
public class ExerciseAndVideo {
public string Exercise { get; set; }
public string Video { get; set; }
public string ZoneName { get; set; }
}
public class Zone {
public string ZoneName { get; set; }
}
Some additional code to fill the global variables ExerciseList and ZoneList with some test data for the combo boxes…
private void SetExerciseListFromDB() {
ExerciseList = new List<ExerciseAndVideo>();
ExerciseList.Add(new ExerciseAndVideo { Exercise = "-", ZoneName = "-", Video = "" });
ExerciseList.Add(new ExerciseAndVideo { Exercise = "Exercise 1", ZoneName = "quads", Video = "Video 1" });
ExerciseList.Add(new ExerciseAndVideo { Exercise = "Exercise 2", ZoneName = "gluteus", Video = "Video 2" });
ExerciseList.Add(new ExerciseAndVideo { Exercise = "Exercise 3", ZoneName = "quads", Video = "Video 3" });
ExerciseList.Add(new ExerciseAndVideo { Exercise = "Exercise 4", ZoneName = "hamstring", Video = "Video 4" });
ExerciseList.Add(new ExerciseAndVideo { Exercise = "Exercise 5", ZoneName = "quads", Video = "Video 5" });
ExerciseList.Add(new ExerciseAndVideo { Exercise = "Exercise 6", ZoneName = "hamstring", Video = "Video 6" });
ExerciseList.Add(new ExerciseAndVideo { Exercise = "Exercise 7", ZoneName = "quads", Video = "Video 7" });
ExerciseList.Add(new ExerciseAndVideo { Exercise = "Exercise 8", ZoneName = "gluteus", Video = "Video 8" });
}
private void SetZoneListFromDB() {
ZoneList = new List<Zone>();
ZoneList.Add(new Zone { ZoneName = "-" });
ZoneList.Add(new Zone { ZoneName = "quads" });
ZoneList.Add(new Zone { ZoneName = "hamstring" });
ZoneList.Add(new Zone { ZoneName = "gluteus" });
}
NOTE: In the above code you may notice that each list of exercises and zones has an additional item added that is used to represent a NO CHOICE by the user….
ExerciseList.Add(new ExerciseAndVideo { Exercise = "-", ZoneName = "-", Video = "" });
and
ZoneList.Add(new Zone { ZoneName = "-" });
We could do without these “blank” (NO choice) values; however, we will need a way to set the Exercise combo box to some “blank” value when a different Zone is selected. You could set a default value like the first exercise in the list and this would work. The main point here is that if we DO NOT set the Exercise value to a value that BELONGS to the “filtered” version of the “filtered” exercises… you will get the grid’s DataError complaining about the value in the cell is not a valid value in the combo boxes list of items.
This applies when the user “changes” a Zone combo box value and the Exercise cell already contains a value from the previously set Zone value. If we do not change the Exercise value to “something” that is in the newly filtered version and simply leave the exercise value with its old value… then we will get the grid’s DataError.
This is the logic behind adding a “Blank” No-choice value to the Exercise list. When we filter the list, we will ADD this “Blank” value to the list and then set the Exercise value to that “blank” value when the Zone changes. This way we can almost guarantee that we will avoid the grid’s DataError because of an invalid value in the exercise cell. There are other ways around this, however, I have found this “blank” non-choice value makes things much easier when dealing with combo boxes that are related to each other. I hope that makes sense.
Next the code that manually sets up the grid’s columns including the Zone and Exercise combo box columns. You may want to consider giving the grid a DataSource… more on this at the end.
private void AddColumnsToGrid() {
DataGridViewComboBoxColumn cbCol = GetComboBoxColumn("Zone", "Zone", "ZoneName", 100);
cbCol.DataSource = ZoneList;
dataGridView1.Columns.Add(cbCol);
cbCol = GetComboBoxColumn("Exercise", "Exercise", "Exercise", 140);
cbCol.DataSource = ExerciseList;
dataGridView1.Columns.Add(cbCol);
dataGridView1.Columns.Add(GetTextBoxColumn("Serie", "serie"));
dataGridView1.Columns.Add(GetTextBoxColumn("Ripetizioni", "ripetizioni"));
dataGridView1.Columns.Add(GetTextBoxColumn("Recupero", "recupero"));
dataGridView1.Columns.Add(GetTextBoxColumn("Time under tension", "tut"));
dataGridView1.Columns.Add(GetLinkColumn("Video", "Video"));
dataGridView1.Columns.Add(GetTextBoxColumn("Note", "note"));
}
private DataGridViewComboBoxColumn GetComboBoxColumn(string headertext, string name, string displayMember, int width) {
DataGridViewComboBoxColumn cbCol = new DataGridViewComboBoxColumn();
cbCol.HeaderText = headertext;
cbCol.Name = name;
cbCol.DisplayMember = displayMember;
cbCol.Width = width;
return cbCol;
}
private DataGridViewTextBoxColumn GetTextBoxColumn(string headerText, string name) {
DataGridViewTextBoxColumn col = new DataGridViewTextBoxColumn();
col.HeaderText = headerText;
col.Name = name;
return col;
}
private DataGridViewLinkColumn GetLinkColumn(string headerText, string name) {
DataGridViewLinkColumn col = new DataGridViewLinkColumn();
col.HeaderText = headerText;
col.Name = name;
return col;
}
If we run the code now, it will look like the picture above. The user can select any Zone and any Exercise. The filtering for the Exercise combo box has not been implemented nor has the setting of the Video cell. So you should see all the Zones and all the Exercises in the combo boxes regardless which Zone is selected.
Next we will apply the “filtering” to the Exercise combo box when the user changes the Zone combo box value. The overall approach goes something like this… First we want to create a global regular ComboBox variable we will call CurCombo. Then we will wire up the grid’s EditingControlShowing event. This event fires when the user clicks into a grid cell and starts editing the cell. Bear in mind… this event fires BEFORE the user actually makes any changes to the cell. In this event we will look to see if the edited cell is a Zone cell. IF the edited cell IS a Zone cell THEN… all we want to do is cast the grid’s combo box cell to our global CurCombo variable. This will look something like…
CurCombo = e.Control as ComboBox;
Where e is a passed in DataGridViewEditingControlShowingEventArgs variable. It will allow us to cast the cell to a regular combo box. Then, we will wire up the CurCombo’s SelectedIndexChanged event then exit and wait for the user to change the combo box’s index.
When the user does indeed change the Zone combo boxes index, and the combo boxes SelectedIndexChanged event fires… then we will know that we need to “filter” the Exercise combo box based on the selected Zone.
To further complicate things… IF the user changes one of the Exercise combo boxes, then we want to update the Video cell to display the link to the selected Video. We could create another ComboBox and do the same thing as we did with the Zone combo box, however, we know that the user can NOT select BOTH combo boxes at the same time. So we can use the same CurCombo variable for both combo boxes. HOWEVER… we need to make sure and UN-WIRE the CurCombo’s SelectedIndexChanged event when the user leaves the cell. Otherwise, BOTH events will start firing when either combo box is changed.
Considering that BOTH combo boxes are going to fire the grid’s EditingControlShowingEvent event, we need to take an extra step if the combo box is an Exercise combo box. Before we wire up the CurCombo’s SelectedIndexChanged event, we want to check and see if the Zone combo box has a value. If the Zone combo box has no value, then we do NOT want the Exercise combo box to contain any values other than the blank no-choice value. We want the user to select a Zone first.
Therefore if the Exercise combo box is edited and the Zone combo box cell is not empty then we simply wire up the CurCombo’s SelectedIndexChanged event to point to a different event method that will update the video cell, then exit and wait for the user to change an Exercise value. If the Zone cell IS EMPTY, then we will filter the Exercise combo box to contain ONLY our BLANK Non-choice value AND we will NOT wire up the CurCombo event since the user cannot select an exercise anyway.
It looks like a lot of code; however I peppered the code with some debug statements so we can see in the Output window when the event is entered and when it exits along with showing which event gets wired up. This may help in debugging and I would remove the debugging statements once you get it working fully.
private void dataGridView1_EditingControlShowing(object sender, DataGridViewEditingControlShowingEventArgs e) {
Debug.WriteLine("DGV_EditingControlShowing <- Enter");
if (dataGridView1.Columns[dataGridView1.CurrentCell.ColumnIndex].Name == "Exercise") {
if (dataGridView1.CurrentRow.Cells["Zone"].Value != null && // <- Zone is not null
!string.IsNullOrEmpty(dataGridView1.CurrentRow.Cells["Zone"].Value.ToString()) && // <- Zone is not an empty string
dataGridView1.CurrentRow.Cells["Zone"].Value.ToString() != "-") { // <= Zone is not "-" blank value
CurCombo = e.Control as ComboBox;
if (CurCombo != null) {
Debug.WriteLine("----------Wiring up Exercise combo box");
CurCombo.SelectedIndexChanged -= new EventHandler(ExerciseComboBox_SelectedIndexChanged);
CurCombo.SelectedIndexChanged += new EventHandler(ExerciseComboBox_SelectedIndexChanged);
}
}
else {
// NO Zone is selected - set the Exercise combo box item list to our single blank value
DataGridViewComboBoxCell ExerciseCell = (DataGridViewComboBoxCell)(dataGridView1.CurrentRow.Cells["Exercise"]);
var filteredList = ExerciseList.Where(x => x.ZoneName == "-").ToList();
Debug.WriteLine("----------Zone is empty filtering Exercise combo box to single blank value");
ExerciseCell.DataSource = filteredList;
}
}
else {
if (dataGridView1.Columns[dataGridView1.CurrentCell.ColumnIndex].Name == "Zone") {
CurCombo = e.Control as ComboBox;
if (CurCombo != null) {
Debug.WriteLine("----------Wiring up Zone combo box");
CurCombo.SelectedIndexChanged -= new EventHandler(ZoneComboBox_SelectedIndexChanged);
CurCombo.SelectedIndexChanged += new EventHandler(ZoneComboBox_SelectedIndexChanged);
}
}
}
Debug.WriteLine("DGV_EditingControlShowing -> Leave");
}
Next we need to implement the ExerciseComboBox_SelectedIndexChanged and the ZoneComboBox_SelectedIndexChanged event methods… Again debugging statements are added to help in debugging. It should be noted, that if we use a List<ExerciseAndVideo> as a DataSource to the combo box column, then instead of trying to get the combo boxes value directly from the grids cell… I suggest you get the ExerciseAndVideo object directly from the combo boxes SelectedItem property. We should be able to cast it like…
ExerciseAndVideo EandV = (ExerciseAndVideo)CurCombo.SelectedItem;
And
Zone selectedZone = (Zone)CurCombo.SelectedItem;
This should make it easier to get the newly selected value in the grids cell.
private void ExerciseComboBox_SelectedIndexChanged(object sender, EventArgs e) {
Debug.WriteLine("ExerciseComboBox_SelectedIndexChanged <- Enter");
if (CurCombo != null) {
ExerciseAndVideo EandV = (ExerciseAndVideo)CurCombo.SelectedItem;
dataGridView1.CurrentRow.Cells["Video"].Value = EandV.Video;
}
Debug.WriteLine("ExerciseComboBox_SelectedIndexChanged -> Leave");
}
private void ZoneComboBox_SelectedIndexChanged(object sender, EventArgs e) {
Debug.WriteLine("ZoneComboBox_SelectedIndexChanged <- Enter");
if (CurCombo.SelectedValue != null) {
Zone selectedZone = (Zone)CurCombo.SelectedItem;
DataGridViewComboBoxCell ExerciseCell = (DataGridViewComboBoxCell)(dataGridView1.CurrentRow.Cells["Exercise"]);
var filteredList = ExerciseList.Where(x => x.ZoneName == selectedZone.ZoneName || x.ZoneName == "-").ToList();
Debug.WriteLine("----------Exercises combo filtered");
ExerciseCell.DataSource = filteredList;
dataGridView1.CurrentRow.Cells["Exercise"].Value = "-";
dataGridView1.CurrentRow.Cells["Video"].Value = "";
}
Debug.WriteLine("ZoneComboBox_SelectedIndexChanged -> Leave");
}
Almost done… If we run the code now, you will be able to change the Zone combo box, however, if we change a Zone combo box value then click on one of the Exercise combo boxes you will get a casting error in the ZoneComboBox_SelectedIndexChanged method. The reason for the error is that when we clicked on the Zone combo box, the global CurCombo’s SelectedIndexChanged gets wired up to point to the method that filters the Exercise combo box. Therefore, when the Exercise combo is selected, the code will fail and throw a casting exception when it tries to cast the combo box to an Zone object and it is really an ExerciseAndVideo object.
This relates to the comment earlier where we MUST make sure and UN-WIRE the CurCombo’s SelectedIndexChanged event when the user LEAVES the cell. The approach used in this example is to simply wire up the grid’s CellLeave event, and in that event we will check to see which cell is ending its edit (Zone or ExerciseAndVideo), then UN-WIRE the CurCombo’s SelectedIndexChanged event. The grid’s CellLeave event may look something like.
private void dataGridView1_CellLeave(object sender, DataGridViewCellEventArgs e) {
Debug.WriteLine("DGV_CellLeave <- Enter");
if (CurCombo != null) {
string colName = dataGridView1.Columns[e.ColumnIndex].Name;
switch (colName) {
case "Exercise":
Debug.WriteLine("----------UN-Wiring Exercise combo box");
CurCombo.SelectedIndexChanged -= new EventHandler(ExerciseComboBox_SelectedIndexChanged);
break;
case "Zone":
Debug.WriteLine("----------UN-Wiring Zone combo box");
CurCombo.SelectedIndexChanged -= new EventHandler(ZoneComboBox_SelectedIndexChanged);
break;
default:
break;
}
}
Debug.WriteLine("DGV_CellLeave -> Leave");
}
And finally, the grid’s DataError event is wired up to help in debugging.
private void dataGridView1_DataError(object sender, DataGridViewDataErrorEventArgs e) {
MessageBox.Show("Error: From row: " + e.RowIndex + "Col: " + e.ColumnIndex + " Message: " + e.Exception.Message);
}
Putting all this together in the forms Load event may look something like below. That should do it. Below shows this in action.
using System.Diagnostics;
using System.Linq;
List<ExerciseAndVideo> ExerciseList;
List<Zone> ZoneList;
ComboBox CurCombo;
public Form1() {
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e) {
SetZoneListFromDB();
SetExerciseListFromDB();
AddColumnsToGrid();
dataGridView1.EditMode = DataGridViewEditMode.EditOnEnter;
}
Lastly, as previously commented, this example uses a grid that is NOT bound to a data source. If you intend to use a data source for the grid that contains existing data… Then it would be wise to add two additional steps.
BEFORE the gird’s data source is set… It is highly recommended that you check each Zone and Exercise values in the data source. If there is a Zone or Exercise that is NOT is the Zone or Exercise list, then the grid will throw its DataError when the grid’s data source is set. In addition, IF an Exercise does not belong to the given Zone, then the grid will throw its DataError. In other words, if the Exercise on a row of data does NOT BELONG to the Zone it has, then you will need to fix this before setting the grid’s DataSource.
Once you have checked to makes sure the Zone and Exercise data is good, then AFTER the grid’s data source is set you will need to loop through all the rows in the grid and “filter” each Exercise combo box based on the selected Zone. When the data is loaded into the grid, obviously the events we previously used are NOT fired. Fortunately you will only need to do this ONCE after the data is loaded.
I hope this makes sense and helps.

Why does setting comboBox.SelectedIndex fail to change what appears in the editable portion of my ComboBox?

I have a C# Winform app. It has a ComboBox. My goal is to have the item selected in the ComboBox drop down appear in the editable portion of the ComboBox when the delete key is typed. For example, if the ComboBox has items A, B. and C, then item A is displayed when the Form is loaded. If I click the drop down, hover over item C, and then type the delete key, I want the drop down list to be dismissed and C to appear in the editable portion of the ComboBox.
As it is, I've verified that I am getting the selected item text, but the line of code comboBox.SelectedIndex = comboBox.FindStringExact(selectedItemText); does not change what appears in the editable portion of the ComboBox
MCVE:
Note: form has a combobox named combobox and a textbox named textbox
using System.Collections;
using System.Collections.Specialized;
using System.Windows.Forms;
namespace Winforms_Scratch
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
//using string collection because I need to simulate what is returned from an Application Settings list
StringCollection computerList = new StringCollection { "C", "B", "A" };
ArrayList.Adapter(computerList).Sort();
comboBox.DataSource = computerList;
comboBox.KeyDown += ComboBox_KeyDown;
computerList = null;
}
private void ComboBox_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Delete && comboBox.DroppedDown)
{
ComboBox comboBox = (ComboBox)sender;
//get the text of the item in the dropdown that was selected when the Delete key was pressed
string selectedItemText = comboBox.GetItemText(comboBox.SelectedItem);
//take focus away from the combobox to force it to dismiss the dropdown
this.Focus();
//load selectedItemText into the textbox just so we can verify what it is
textBox.Text = selectedItemText;
//set the comboBox SelectedIndex to the index of the item that matches the
//text of the item in the dropdown that was selected when the Delete key was pressed
comboBox.SelectedIndex = comboBox.FindStringExact(selectedItemText);
comboBox.Refresh();
//Stop all further processing
e.Handled = true;
}
else if (e.KeyCode == Keys.Down && !comboBox.DroppedDown)
{
//If the down arrow is pressed show the dropdown list from the combobox
comboBox.DroppedDown = true;
}
}
}
}
My guess is that the combobox behaves differently after it loses focus. In any case, I made the following changes and it works, according to my understanding of your requirements. You can call this.Focus() after setting the selected item, if there's a separate requirement to return focus to the window. Your SelectedIndex/FindStringExact approach works identically to setting SelectedItem to the string.
I got rid of the text box, since as I understand it that's just for debugging purposes.
private void ComboBox_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Delete && comboBox.DroppedDown)
{
ComboBox comboBox = (ComboBox)sender;
// Get the text of the item in the dropdown that was selected when the
// Delete key was pressed
string selectedItemText = comboBox.GetItemText(comboBox.SelectedItem);
comboBox.DroppedDown = false;
comboBox.SelectedItem = selectedItemText;
//Stop all further processing
e.Handled = true;
}
else if (e.KeyCode == Keys.Down && !comboBox.DroppedDown)
{
// If the down arrow is pressed show the dropdown list from the combobox
comboBox.DroppedDown = true;
}
}

ListBox deletion

I have a listbox, with 2 buttons, new and delete. new adds an item into the list box, and the delete button should delete the item out of the list box. The list box items are tied to a class that stores user entered data from text boxes below.
private void AddListBox()
{
lstCondition.BeginUpdate();
Condition cond = new Condition("");
cond.Name = string.Format("Condition {0}", _selection.NetConditions.Count + 1);
_selection.NetConditions.Add(cond);
lstCondition.EndUpdate();
lstCondition.SelectedItem = cond;
cboNetCondition.Properties.Items.Clear();
cboNetCondition.Properties.Items.AddRange(NetCondition);
cboControlType.Properties.Items.Clear();
cboControlType.Properties.Items.AddRange(ControlType);
cboFlowRate.Properties.Items.Clear();
cboFlowRate.Properties.Items.AddRange(FlowRate);
}
private void btnNew_Click(object sender, EventArgs e)
{
AddListBox();
}
the cbo items are comboboxes, whose data gets tied in the condition class to each instance of the list box.
public frmNetConditions(Condition condo, Selection selection)
{
InitializeComponent();
_selection = selection;
lstCondition.DataSource = _selection.NetConditions;
condition = _selection.NetConditions.Count;
}
private void btnDelete_Click(object sender, EventArgs e)
{
selectedCondition = (Condition)lstCondition.SelectedItem;
cboControlType.SelectedIndex = -1;
cboNetCondition.SelectedIndex = -1;
cboFlowRate.SelectedIndex = -1;
txtFlowRate.Text = string.Empty;
txtStatPressure.Text = string.Empty;
txtDampOpening.Text = string.Empty;
txtDensity.Text = string.Empty;
cboDensity.SelectedIndex = -1;
lstCondition.Items.Remove(lstCondition.SelectedItem);
lstCondition.Refresh();
}
After pressing this delete button, the listbox, still contains the item i wish to delete, im unsure why thats the case?
Update with datasource
public List<Condition> NetConditions { get { return _netconditions; } }
As already suggested, you should bind to a BindingList<Condition> instead of a List<Condition>. This allows you to change the datasource and the control (ListBox) to get notified by your changes. The code should look like this:
lstCondition.ValueMember = "ConditionId";
lstCondition.DisplayMember = "Name";
lstCondition.DataSource = NetConditions;
After defining the binding, the correct way of operating on the ListBox items is to remove from the datasource, not the ListBox itself:
// SelectedItem should be checked for null (no selection is an option)
NetCondition.Remove((Condition)lstCondition.SelectedItem);
However, if you plan to change properties from an element (so, not the list itself), the control is notified only if your element (Condition) implements INotifyPropertyChanged interface.

ComboBox item doesn't update even if case is changed

I have a basic form with a combobox, a textbox, and a button on it. The combobox has an unalterable number of items within it, but the items themselves can be changed by inputting a new value for the selected item.
From the example in the picture, if I input a string such as "identifier", the selected item in the combobox changes from "ID" to "identifier", as expected. However, if I input "id", the logic (below) executes normally, the item updates, but visually, the item does not change from "ID" to "id".
Here is the code for the event handler of the button
private void btnApply_Click(object sender, EventArgs e) {
string newValue = txtNewName.Text;
if(string.IsNullOrWhiteSpace(newValue)) {
MessageBox.Show("Please input a new column name");
return;
}
if(cmbHeaderNames.Items.Contains(newValue)) {
MessageBox.Show("A column with that name already exists");
return;
}
cmbHeaderNames.Items[cmbHeaderNames.SelectedIndex] = newValue;
txtNewName.Text = "";
}
I believe that the ComboBox is doing some string comparison, because the following code sample works.
if (comboBox1.SelectedItem.ToString().ToUpper() == textBox1.Text.ToUpper())
{
comboBox1.Items[comboBox1.SelectedIndex] = string.Empty;
comboBox1.Items[comboBox1.SelectedIndex] = textBox1.Text;
}
Apparently, the update successfully applies if the two string values are not identical when applying ToUpper() or ToLower().

Categories