0

I have this datagrid where Combobox is populated from Db.

What I'm trying to achieve is that when I select something in the column "Esercizio", the cell of "Video" column auto populate with respective value from the "link_video" column of Db.

So if I select "kickback", I need to see the link video of kickback from db in the textbox cell.

Here's the code that i use to populate the combobox on form load:

private void Myform_Load(object sender, EventArgs e) {

        con = new SqlConnection("Data Source=(LocalDB)\\etc");
        cmd = new SqlCommand();
        con.Open();
        cmd.Connection = con;
        cmd.CommandText = "SELECT * FROM Esercizi";
        dr = cmd.ExecuteReader();

        while (dr.Read())
        {
          //populate Column1 combobox with "nome" column from Esercizi db table
            Column1.Items.Add(dr["nome"]);
            
        }

        con.Close();
    }

datagridview

EDIT I've figured out with 2 new problems.

I'm trying to load a saved workout from db but when I do this, no video link populate the dgv as the grid event doesn't fire.

What I've tried is to add a foreach loop to a new selectionindexchanged function and to fire it at the end of the Load Button code like this:

private void curCombo_LoadedValues(object sender, EventArgs e)
        {

            foreach (DataGridViewRow row in dataGridView1.Rows)
            {
                foreach (DataGridViewCell cell in row.Cells)
                {
                    if (curCombo != null && curCombo.SelectedValue != null)
                    {
                        ExerciseAndVideo selectedExercise = (ExerciseAndVideo)curCombo.SelectedItem;
                        dataGridView1.CurrentRow.Cells["Video"].Value = selectedExercise.Video;

                    }
                }
            }
        }

private void button9_Click(object sender, EventArgs e){

    string connectionString = "Data Source=(LocalDB)\\etc";
    string sql = "SELECT * FROM Schede WHERE Id = 6 AND dgv = 'dataGridView1'";
    SqlConnection connection = new SqlConnection(connectionString);
    SqlDataAdapter dataadapter = new SqlDataAdapter(sql, connection);using (DataTable dt = new DataTable())
    {
        dataadapter.Fill(dt);

        //Set AutoGenerateColumns False
        dataGridView1.AutoGenerateColumns = false;

        //Set Columns Count
        dataGridView1.ColumnCount = 6;

        //Add Columns

        dataGridView1.Columns[0].Name = "Esercizio";
        dataGridView1.Columns[0].HeaderText = "Esercizio";
        dataGridView1.Columns[0].DataPropertyName = "Esercizio";

        dataGridView1.Columns[1].Name = "Serie";
        dataGridView1.Columns[1].HeaderText = "Serie";
        dataGridView1.Columns[1].DataPropertyName = "Serie";

        dataGridView1.Columns[2].HeaderText = "Ripetizioni";
        dataGridView1.Columns[2].Name = "Ripetizioni";
        dataGridView1.Columns[2].DataPropertyName = "Ripetizioni";

        dataGridView1.Columns[3].Name = "Recupero";
        dataGridView1.Columns[3].HeaderText = "Recupero";
        dataGridView1.Columns[3].DataPropertyName = "Recupero";

        dataGridView1.Columns[4].Name = "Time Under Tension";
        dataGridView1.Columns[4].HeaderText = "Time Under Tension";
        dataGridView1.Columns[4].DataPropertyName = "Time_Under_Tension";

        dataGridView1.DataSource = dt;

        connection.Close();

        
    }

    curCombo_LoadedValues();
}

But I get this error "the are no arguments for the obligatory parameter sender...

How I can call it correctly?

The second Issue is that when I populate some dgv columns like this, combos stops working correctly and I get an error exception on the combobox :

dataGridView1.Rows.Add(7);
            Random rnd = new Random();
            
            dataGridView1.Rows[0].Cells[1].Value = 3;
            dataGridView1.Rows[0].Cells[2].Value = rnd.Next(1, 13);
            dataGridView1.Rows[0].Cells[3].Value = 1;
            dataGridView1.Rows[0].Cells[4].Value = 201;
            
            dataGridView1.Rows[1].Cells[1].Value = 2;
            dataGridView1.Rows[1].Cells[2].Value = rnd.Next(1, 13);
            dataGridView1.Rows[1].Cells[3].Value = 1;
            dataGridView1.Rows[1].Cells[4].Value = 201;
            
            dataGridView1.Rows[2].Cells[1].Value = 3;
            dataGridView1.Rows[2].Cells[2].Value = rnd.Next(1, 13);
            dataGridView1.Rows[2].Cells[3].Value = 1;
            dataGridView1.Rows[2].Cells[4].Value = 201;
            
            dataGridView1.Rows[3].Cells[1].Value = 4;
            dataGridView1.Rows[3].Cells[2].Value = rnd.Next(1, 13);
            dataGridView1.Rows[3].Cells[3].Value = 1;
            dataGridView1.Rows[3].Cells[4].Value = 201;
            
            dataGridView1.Rows[4].Cells[1].Value = 5;
            dataGridView1.Rows[4].Cells[2].Value = rnd.Next(1, 13);
            dataGridView1.Rows[4].Cells[3].Value = 1;
            dataGridView1.Rows[4].Cells[4].Value = 201;
            
            dataGridView1.Rows[5].Cells[1].Value = 6;
            dataGridView1.Rows[5].Cells[2].Value = rnd.Next(1, 13);
            dataGridView1.Rows[5].Cells[3].Value = 1;
            dataGridView1.Rows[5].Cells[4].Value = 201;
            
            dataGridView1.Rows[6].Cells[1].Value = 7;
            dataGridView1.Rows[6].Cells[2].Value = rnd.Next(1, 13);
            dataGridView1.Rows[6].Cells[3].Value = 1;
            dataGridView1.Rows[6].Cells[4].Value = 201;

This is the look of the dgv now: dgv

And this is the error the i get after combos stop working correctly (I click and no dropdown appear or if I click 2-3 times, a random item get selected but no video link appear in the other column):

error

Gabriele
  • 57
  • 9
  • Maybe fill Combobox with two columns data . ComboBox has properties DisplayMember and ValueMember. Then you can copy selectedValue to the other column. – benuto Jul 01 '21 at 10:02
  • @benuto so, you suggest to fill the combobox with "nome" in display member and "link_video" in value member, then I can fill textbox with the link_video of value member? I'm new to c#. Can you tell me how to do that? – Gabriele Jul 01 '21 at 10:11
  • look for information about the `ComboBox.DataSource` and onChange event, it's a fairly simple topic – benuto Jul 01 '21 at 11:47
  • @benuto i'm searching about 3 days but I can't resolve it – Gabriele Jul 01 '21 at 15:13
  • 1. query the database for name and video link, store them in a list (for example) 2. assign the list to combo.datasource 3 set combo memebers 4. add method to the onSelectedValueChanged 5. voilà! – benuto Jul 02 '21 at 08:44
  • @benuto ok, I'm almost there. I only need to add the method (point 4) and pass the value to the other column but I don't find any code example to do that. – Gabriele Jul 02 '21 at 12:05
  • Really? ;) my mistake, should be an indexChanged [ms docs](https://learn.microsoft.com/pl-pl/dotnet/api/system.windows.forms.combobox.selectedindexchanged?view=net-5.0) – benuto Jul 02 '21 at 12:15
  • @benuto yes I seen that but doesn't help. When I insert: this.Column1.SelectedIndexChanged += new System.EventHandler(Column1_SelectedIndexChanged); I get an error like "datagridviewcomboboxcolumn" doesnt contain a definition of SelectedIndexChanged" etc... – Gabriele Jul 02 '21 at 12:41

2 Answers2

1

In the posted code, it shows a query to a DB to get the exercise names and adds those names as the items to display in “each” combo box cell in that column. This is fine, however, there is zero (0) information about “which video” belongs to each of the items in the combo boxes list of items. I will assume the “relation” to which video is related to which exercise would involve another query to the DB.

If this is the case, then it is easy to see that when a combo box in the grid is changed, that you could simply query the DB for which video to use for the selected exercise. This would certainly work; however, it creates a redundancy issue. Example, let’s say the user selected “kicks” in the combo box in row 1 in the gird. The code queries the DB and gets the location of the “kicks” video and sets the “video” cell to this videos path. Then later, the user selects “kicks” again for some other row. This will re-query the DB for the SAME data we previously got. You want to avoid querying the DB unnecessarily.

So given this, it appears a better approach to avoid re-querying the DB unnecessarily, is that we somehow “combine” the exercise with the particular video that the exercise uses. You could do this ONCE when the form loads. Once you have the exercises, then loop though each exercise and query the DB for that exercises video and combine this with the exercise. Once we have the video link, we “save” this info. With this approach, you will not have to re-query the DB for any given exercise since we have saved that info for all exercises.

There are a myriad number of ways to “combine” the exercise with the video. One possible solution is to create a Class that has these two properties. Let’s call this Class ExerciseAndVideo… it has two properties, a string Exercise that is the exercise text and a string Video that defines the path to the video for that Exercise. This simple class may look something like…

public class ExerciseAndVideo {
  public string Exercise { get; set; }
  public string Video { get; set; }
}

This is one approach to “combine” an Exercise with a particular Video. We could then make a list of ExerciseAndVideo objects like…

List<ExerciseAndVideo> ExerciseList;

Then we cannot only use this list as a DataSource to the combo box column, but we could also use this list as a way to easily tell “which” video belongs to “which” exercise. The example below uses this strategy.

How to implement this in the DataGridView

One thing to keep in mind is that a DataGridViewComboBoxCell is “different monster” than a “regular” ComboBox. Example; for a regular combo box, there is an event called… SelectedIndexChanged event. This event fires when the user “changes” the combo boxes currently selected index. On the other hand, a DataGridViewComboBoxCell does NOT have a SelectedIndexChanged event, which I assume you may be aware of. The grid’s absence of this event makes sense, because the grid’s combo box “column” may have MANY combo boxes in it.

Fortunately, even though the DataGridViewComboBoxCell does not have a SelectedIndexChanged event, we CAN cast an “individual” combo box cell to a ComboBox (in this case the combo box cell being edited), THEN we CAN subscribe to that ComboBoxes SelectedIndexChanged event. Then we could simply wait until the SelectedIndexChanged event fires then update the video cell data with the appropriate video link.

The DataGridView provides a special event for this called… EditingControlShowing. This event will fire when the user starts to “edit” a cell in the grid. In this particular case, this event would fire when the user clicks into a combo box cell and “starts” to change the cells value. Is what we want to do in this event is simply cast that combo box cell to a regular ComboBox…, THEN, subscribe to that ComboBoxes SelectedIndexChanged event which we will implement below.

The strategy goes like this… we will make a “global” ComboBox variable we will call curCombo. When the grids EditingControlShowing event fires and we see that the edited cell is an “Exercise” cell… Then we will cast THAT combo box cell in the grid to the global curCombo variable. Then we will subscribe to that combo boxes SelectedIndexChanged event. When the user “leaves” the cell we will unsubscribe from the global variables curCombo SelectedIndexChanged event to keep things clean.

Therefore, given this, the grids EditingControlShowing event may look something like below...

private void dataGridView1_EditingControlShowing(object sender, DataGridViewEditingControlShowingEventArgs e) {
  if (dataGridView1.Columns[dataGridView1.CurrentCell.ColumnIndex].Name == "Exercise") {
    curCombo = e.Control as ComboBox;
    if (curCombo != null) {
      curCombo.SelectedIndexChanged -= new EventHandler(curCombo_SelectedIndexChanged);
      curCombo.SelectedIndexChanged += new EventHandler(curCombo_SelectedIndexChanged);
    }
  }
}

Obviously we need to implement the event handler curCombo_SelectedIndexChanged. In this event, we would know that previously, the user selected a combo box cell and has changed the value in that cell to some other value. Since the Exercise changed, we know we need to change the “Video” cell.

Again there are numerous ways you could do this, however, if we set the grid’s “Exercise” combo box column’s DataSource as a List of ExerciseAndVideo objects, then we should be able to get that particular ExerciseAndVideo object directly from the global ComboBox curCombo. This will tell us “which” video to place in the “Video” cell. This may look something like…

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;
  }
}

private void dataGridView1_CellLeave(object sender, DataGridViewCellEventArgs e) {
  if (dataGridView1.Columns[e.ColumnIndex].Name == "Exercise") {
    if (curCombo != null) {
      curCombo.SelectedIndexChanged -= new EventHandler(curCombo_SelectedIndexChanged);
    }
  }
}

To complete the example and putting all this together may produce something like below…

enter image description here

List<ExerciseAndVideo> ExerciseList;
ComboBox curCombo;

public Form2() {
  InitializeComponent();
}

private void Form2_Load(object sender, EventArgs e) {
  ExerciseList = GetExerciseVideoComboBoxListFromDB();
  dataGridView1.Columns.Add(GetExcerciseComboBoxColumn(ExerciseList));
  dataGridView1.Columns.Add(GetLinkColumn());
  dataGridView1.CellLeave += new DataGridViewCellEventHandler(dataGridView1_CellLeave);
  dataGridView1.EditingControlShowing += new DataGridViewEditingControlShowingEventHandler(dataGridView1_EditingControlShowing);
}


private DataGridViewComboBoxColumn GetExcerciseComboBoxColumn(List<ExerciseAndVideo> exerciseData) {
  DataGridViewComboBoxColumn cbCol = new DataGridViewComboBoxColumn();
  cbCol.HeaderText = "Exercise";
  cbCol.Name = "Exercise";
  cbCol.DisplayMember = "Exercise";
  cbCol.DataSource = exerciseData;
  return cbCol;
}

private DataGridViewLinkColumn GetLinkColumn() {
  DataGridViewLinkColumn col = new DataGridViewLinkColumn();
  col.HeaderText = "Video";
  col.Name = "Video";
  col.AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
  return col;
}

private List<ExerciseAndVideo> GetExerciseVideoComboBoxListFromDB() {
  List<ExerciseAndVideo> exerciseList = new List<ExerciseAndVideo>();
  exerciseList.Add(new ExerciseAndVideo { Exercise = "Crosses", Video = @"C:/somepath/Crosses.htm/" });
  exerciseList.Add(new ExerciseAndVideo { Exercise = "Kickback", Video = @"C:/somepath/Kickback.htm" });
  exerciseList.Add(new ExerciseAndVideo { Exercise = "Leg Extensions", Video = @"C:/somepath/LegExtensions.htm" });
  exerciseList.Add(new ExerciseAndVideo { Exercise = "Crunches", Video = @"C:/somepath/Crunches.htm" });
  exerciseList.Add(new ExerciseAndVideo { Exercise = "Pushups", Video = @"C:/somepath/Pushups.htm" });
  return exerciseList;
}

What if the grid has a data source?

This works as expected when the grid has no data source. However, if the grid has a data source and one of the columns/properties in the data source is bound to our “Exercise” combo box column, then, is what will happen… is that after the data is loaded, the combo boxes should display the proper exercise, however, all the video cells will remain empty. This is obviously because the grid events are not fired when the data is loaded.

So, in that case, you will want a method that loops through all the rows in the grid, checks what the exercise value is for that row, then set the video cell value to the correct video link. Since you do not say if the grid does or does not have a data source, I will assume this is all you need. If there is a data source, I recommend you check each “Exercise” to make sure the “Exercises” in the data are in the combo boxes list of items, if one or more “Exercises” are in the data that are not in the combo box columns list of items, then you will get the grids DataError when you attempt to set the grids data source.

I hope this makes sense.

Edit... An example of setting the video cell after the data has been loaded into the grid.

private void SetVideoCellsAfterDataLoad() {
  foreach (DataGridViewRow row in dataGridView1.Rows) {
    if (!row.IsNewRow && row.Cells["Exercise"].Value != null) {
      foreach (ExerciseAndVideo eav in ExerciseList) {
        if (row.Cells["Exercise"].Value.ToString() == eav.Exercise) {
          row.Cells["Video"].Value = eav.Video;
          break;
        }
      }
    }
  }
}
JohnG
  • 9,259
  • 2
  • 20
  • 29
  • Thank you very much! Very nice and complete explanation. Unfortunately I did it work with the previous answer and I'm now trying to resolve the issue with iTextsharp that I describe up. – Gabriele Jul 03 '21 at 07:09
  • now I'm testing also your code. I did copy paste in a new form, but when I choose the exercise name, I see no link in the link column. I get no error in debug. I'm missing something? – Gabriele Jul 03 '21 at 12:22
  • I can only assume you did not properly subscribe to the grids two events. I added the code in the forms load event. – JohnG Jul 03 '21 at 19:31
  • Yes it's true. I resolved by setting the form load property that wasn't using the form load code, Thank you very much! Your solution solved also the issue with iTextsharp. – Gabriele Jul 04 '21 at 11:23
  • I'm using a button to save the datagrid in pdftable with itextsharp. If I want to identify the link column like "if (dataGridView1.Columns[e.ColumnIndex].Name == "Video")" how I can do it inside the button click function? – Gabriele Jul 05 '21 at 13:12
  • If you want to save the “whole” grid, then loop through the rows and columns of the grid or its data source. – JohnG Jul 05 '21 at 20:32
  • I've added a load functionality on the click of a button. This load from db an already saved workout and as you explain in your answer, this not fire the grid events, so the video columns remains blank. I've tried to call a curCombo_SelectedIndexChanged with the loop added as you suggest, in the load function but I get some errors. How I can call it correctly? I see also that if I populate the other columns with something like "dataGridView1.Rows[0].Cells[1].Value = 3;" combos stops working correctly and I get an error exception on the combobox. It'normal? – Gabriele Jul 09 '21 at 07:46
  • _”I've tried to call a curCombo_SelectedIndexChanged with the loop added as you suggest, in the load function but I get some errors. How I can call it correctly?”_ … You do NOT want to call the `curCombo_SelectedIndexChanged` in the loop to set the video cell after the data has been loaded… simply repeat the code IN the event to set each video cell. For your other issue, you are going to have to show the code you are using if there are additional columns. I do not see how adding columns would change things in my current code since all the code references cells sing the column name. – JohnG Jul 09 '21 at 18:45
  • I didn't call the function in the loop, I think my english is not so clear :) I edited the post with the description of the problem and the code I used, so You can see. – Gabriele Jul 10 '21 at 07:18
  • The `curCombo_LoadedValues` method is all wrong. For starters, when you try and call this code, you get the error you mentioned because it is missing the parameters, which are not needed. You are missing an important concept here. `curCombo` ONLY GETS SET, when the user changes one of the combo boxes in the grid… which has not happened yet. `curCombo` is not going to help in this and will be `null`. The code should be a simple loop through all the rows, check what exercise is in that row and then set the proper video for that row which may be another loop. I posted and example. – JohnG Jul 10 '21 at 07:45
  • Yeah, this works great! You are my hero!! for the other issue, do You have an idea of how it's possible since the code doesn't touch the combo column? – Gabriele Jul 10 '21 at 08:18
  • In reference to you second question. I am confused by why the code is manually adding rows to the grid. In the code above it, it shows the grids data source being set… `dataGridView1.DataSource = dt;` …? So technically the code below would throw an error along the lines of _”can’t programmatically add rows to a data bound grid”_ … So it is unclear why the code is manually adding rows to the grid as opposed to adding the rows to a `DataTable`. I am betting the combo box may not like an “empty/null” value. – JohnG Jul 10 '21 at 08:37
  • the two function are not related. So there is a Load function to load a saved workout from db, and this works fine now. The issue about combos is when I start with empty datagridview with no datasource. I have a "generate" button that use the code to manually add rows and values (to the other columns with no touching to combos or video column). When I do that, combos have problem. – Gabriele Jul 10 '21 at 09:07
  • It is difficult to say what could be the problem without seeing the exact error and the code that produces it. Even though the code is manually adding values to rows and is not setting the combo box value, the combo box cell is STILL created and will most likely be a `null` cell value which the combo box MAY complain about. I set the combo box value to an empty string and it did not throw any errors. Again, start a new question showing the code and the exact error. Good Luck. – JohnG Jul 10 '21 at 19:11
0

I mixed ComboBox with DatagridViewComboboxColumn. It's partly your fault :).

Here you have a form with events. Since CellValueChanged fires on cell exit, I added a Dirty StateEvent to update the Video column. From the designer, just put the datagrid in the form and make sure the name is the same. IMHO, these 3 events are crucial

public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();

            Init();
        }

        private void Init()
        {
            var list = new List<Exercise>() {
            new Exercise (){Name="Name1", Link= "Link1" },
            new Exercise (){Name="Name3", Link= "Link3" },
            new Exercise (){Name="Name4", Link= "Link4" },

            };

            var comboColumn = new DataGridViewComboBoxColumn() { Name = "ExerciseName", CellTemplate = new DataGridViewComboBoxCell() };
            comboColumn.DisplayMember = nameof(Exercise.Name);
            comboColumn.ValueMember = nameof(Exercise.Link);
            comboColumn.DataSource = list;
            
            dataGridView1.Columns.Add(comboColumn);
            dataGridView1.Columns.Add(new DataGridViewTextBoxColumn() { Name = "Video" });
            dataGridView1.CellContentClick += DataGridView1_CellContentClick;
            dataGridView1.CellValueChanged += DataGridView1_CellValueChanged;
            dataGridView1.CurrentCellDirtyStateChanged += DataGridView1_CurrentCellDirtyStateChanged;
        }

        private void DataGridView1_CurrentCellDirtyStateChanged(object sender, EventArgs e)
        {
            var currentCell = (sender as DataGridView).CurrentCell;
            if(currentCell.ColumnIndex == 0)
                dataGridView1.CommitEdit(DataGridViewDataErrorContexts.Commit);
            
        }

        private void DataGridView1_CellValueChanged(object sender, DataGridViewCellEventArgs e)
        {
            if (e.ColumnIndex != 0)
                return;
            var comboCell = (dataGridView1.Rows[e.RowIndex].Cells[0] as DataGridViewComboBoxCell);
            var value = comboCell.Value;
            dataGridView1.Rows[e.RowIndex].Cells["Video"].Value = value;

        }

        private void DataGridView1_CellContentClick(object sender, DataGridViewCellEventArgs e)
        {
            
        }
    }

    public class Exercise
    {
        public string Name { get; set; }
        public string Link { get; set; }
    }
Dharman
  • 30,962
  • 25
  • 85
  • 135
benuto
  • 292
  • 1
  • 6
  • Yes yes yes! With some changes it works fine. Now I have another issue.. LOL :) After filling the datagridview I create a pdf with iTextsharp pdf. Before using display and value member in the combo, it was saving the name of exercise in the pdf table. Now instead is saving the value member of the combo. The code that populate the pdf table is this: foreach (DataGridViewRow row in dataGridView1.Rows) {foreach (DataGridViewCell cell in row.Cells) {pdfTable.AddCell(Convert.ToString(cell.Value));}} Do you have some idea? – Gabriele Jul 03 '21 at 07:05