Jim Fell
Jim Fell

Reputation: 14264

Trouble Getting DataGridView to Work with ComboBox Dropdown

I need some help getting my code to work. DataGridView is proving much more complex that I had anticipated. I need someone to please show me how to fix my code, so that both the DataGridView control can be updated and my DataGridViewComboBoxColumn dropdown and DataGridViewCheckBoxColumn controls all work. I'm okay with any strategy, provided the solution you offer is somewhat elegant and complete. I've already spent far too much time trying to figure this out on my own. Any thoughts or suggestions would be greatly appreciated.

Here is a simplifcation of what my backend looks like:

MySqlDataAdapter dataAdapter = new MySqlDataAdapter(sqlRequestString, this.dbConnection);
MySqlCommandBuilder commandBuilder = new MySqlCommandBuilder(dataAdapter);
DataTable table = new DataTable();
table.Locale = System.Globalization.CultureInfo.InvariantCulture;
dataAdapter.Fill(table);
bindingSource.DataSource = table;

If I get rid of the BindingSource, how do I connect the DataTable directly to the DataGridView, so that the ComboBox and Checkbox columns are properly populated?

UPDATE

This should be a complete overview of how I am attempting to initialize my DataGridView:

private void InitializeComponent()
{

/* ... */

this.dataGridView1.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] {
    this.DataGridViewColumn_Section,
    this.DataGridViewColumn_Indent,
    this.DataGridViewColumn_Content,
    this.DataGridViewColumn_Summary,
    this.DataGridViewColumn_Role,
    this.DataGridViewColumn_Author,
    this.DataGridViewColumn_Updated});
    this.dataGridView1.Name = "dataGridView1";
    this.dataGridView1.DataSourceChanged += new System.EventHandler(this.dataGridView1_DataSourceChanged);
    this.dataGridView1.CellEnter += new System.Windows.Forms.DataGridViewCellEventHandler(this.dataGridView1_CellEnter);
    this.dataGridView1.CellLeave += new System.Windows.Forms.DataGridViewCellEventHandler(this.dataGridView1_CellLeave);
    this.dataGridView1.ColumnAdded += new System.Windows.Forms.DataGridViewColumnEventHandler(this.dataGridView1_ColumnAdded);
    this.dataGridView1.SelectionChanged += new System.EventHandler(this.dataGridView1_SelectionChanged);
    this.dataGridView1.Leave += new System.EventHandler(this.dataGridView1_Leave);
    // 
    // DataGridViewColumn_Section
    // 
    this.DataGridViewColumn_Section.HeaderText = "Section";
    this.DataGridViewColumn_Section.Name = "DataGridViewColumn_Section";
    // 
    // DataGridViewColumn_Indent
    // 
    this.DataGridViewColumn_Indent.HeaderText = "Indent";
    this.DataGridViewColumn_Indent.Name = "DataGridViewColumn_Indent";
    // 
    // DataGridViewColumn_Content
    // 
    this.DataGridViewColumn_Content.HeaderText = "Content";
    this.DataGridViewColumn_Content.MinimumWidth = 100;
    this.DataGridViewColumn_Content.Name = "DataGridViewColumn_Content";
    // 
    // DataGridViewColumn_Summary
    // 
    this.DataGridViewColumn_Summary.HeaderText = "Summary";
    this.DataGridViewColumn_Summary.Name = "DataGridViewColumn_Summary";
    // 
    // DataGridViewColumn_Role
    // 
    this.DataGridViewColumn_Role.HeaderText = "Minimum Signoff";
    this.DataGridViewColumn_Role.Name = "DataGridViewColumn_Role";
    // 
    // DataGridViewColumn_Author
    // 
    this.DataGridViewColumn_Author.HeaderText = "Author";
    this.DataGridViewColumn_Author.Name = "DataGridViewColumn_Author";
    this.DataGridViewColumn_Author.ReadOnly = true;
    // 
    // DataGridViewColumn_Updated
    // 
    this.DataGridViewColumn_Updated.HeaderText = "Updated";
    this.DataGridViewColumn_Updated.Name = "DataGridViewColumn_Updated";
    this.DataGridViewColumn_Updated.ReadOnly = true;

/* ... */

}

public MyWinform()
{
    InitializeComponent();

    // Initialize DataGridView DataTable
    this.dgvDataTable = new DataTable();

    // Initialize DataGridView column indexes
    this.idxSection = dataGridView1.Columns["DataGridViewColumn_Section"].Index;
    this.idxIndent = dataGridView1.Columns["DataGridViewColumn_Indent"].Index;
    this.idxContent = dataGridView1.Columns["DataGridViewColumn_Content"].Index;
    this.idxSummary = dataGridView1.Columns["DataGridViewColumn_Summary"].Index;
    this.idxRole = dataGridView1.Columns["DataGridViewColumn_Role"].Index;
    this.idxAuthor = dataGridView1.Columns["DataGridViewColumn_Author"].Index;
    this.idxLastUpdate = dataGridView1.Columns["DataGridViewColumn_Updated"].Index;
}

private void MyWinform_Load(object sender, EventArgs e)
{
    DataGridView dgv = this.dataGridView1;
    DataGridViewComboBoxColumn comboCol;

    // Load System Menu
    SystemMenu.Load(this.Handle);

    // Insert selection prompt
    ProcTemplateRecord selectProcPrompt = new ProcTemplateRecord();
    selectProcPrompt.PrimaryKey = 0;
    selectProcPrompt.ProcName = "Select from the list...";
    this.procList.Insert(0, selectProcPrompt);

    // Add new procedure prompt
    ProcTemplateRecord newProcPrompt = new ProcTemplateRecord();
    newProcPrompt.PrimaryKey = -1;
    newProcPrompt.ProcName = "Start a new Safe Job Procedure";
    this.procList.Add(newProcPrompt);

    // Finish initializing the ComboBox dropdown list
    this.comboBox1.DataSource = this.procList;
    this.comboBox1.DisplayMember = "ProcName";
    this.comboBox1.ValueMember = "PrimaryKey";

    // Finish initializing DataGridView and bind to BindingSource
    this.dataGridView1.AutoGenerateColumns = false;

/*
    // Finish initializing the DataGridView ComboBox columns...
    comboCol = (DataGridViewComboBoxColumn)dgv.Columns[this.idxSection];
    comboCol.DataSource = Enum.GetValues(typeof(SectionType));
    comboCol.ValueType = typeof(SectionType);
    comboCol.DropDownWidth = ComboBoxMaxLabelWidth(comboCol);

    comboCol = (DataGridViewComboBoxColumn)dgv.Columns[this.idxRole];
    comboCol.DataSource = Enum.GetValues(typeof(RoleType));
    comboCol.ValueType = typeof(RoleType);
    comboCol.DropDownWidth = ComboBoxMaxLabelWidth(comboCol);

    this.dataGridView1.DataSource = this.dgvDataTable;
*/
    this.RefreshDataGridViewColumnWidths();

    // Setup post-initialization DataGridViewEvent handlers
    this.dataGridView1.CellValueChanged += new System.Windows.Forms.DataGridViewCellEventHandler(this.dataGridView1_CellValueChanged);
    this.dataGridView1.CurrentCellDirtyStateChanged += new System.EventHandler(this.dataGridView1_CurrentCellDirtyStateChanged);
}

private void dataGridView1_DataSourceChanged(object sender, EventArgs e)
{
    this.RefreshDataGridViewColumnWidths();
}

private void dataGridView1_CellEnter(object sender, DataGridViewCellEventArgs e)
{
    DataGridView dgv = (DataGridView)sender;

    this.RefreshButtons();

    if (e.ColumnIndex == this.idxSection)
    {
        this.label_dgvToolTip.Visible = false;
    }
    else if (e.ColumnIndex == this.idxIndent)
    {
        this.label_dgvToolTip.Visible = false;
    }
    else if (e.ColumnIndex == this.idxContent)
    {
        this.label_dgvToolTip.Visible = false;
    }
    else if (e.ColumnIndex == this.idxSummary)
    {
        this.label_dgvToolTip.Visible = false;
    }
    else if (e.ColumnIndex == this.idxRole)
    {
        this.label_dgvToolTip.Visible = false;
    }
    else if (e.ColumnIndex == this.idxAuthor)
    {
        this.label_dgvToolTip.Visible = false;
        this.label_dgvToolTip.Text = "Author column values are read only.";
        this.label_dgvToolTip.Visible = true;
    }
    else if (e.ColumnIndex == this.idxLastUpdate)
    {
        this.label_dgvToolTip.Visible = false;
        this.label_dgvToolTip.Text = "Updated column values are read only.";
        this.label_dgvToolTip.Visible = true;
    }
    else
    {
        this.label_dgvToolTip.Visible = false;
    }

    this.idxActiveColumn = e.ColumnIndex;
    this.idxActiveRow = e.RowIndex;
}

private void dataGridView1_CellLeave(object sender, DataGridViewCellEventArgs e)
{
    this.label_dgvToolTip.Visible = false;
    this.RefreshButtons();
}

private void dataGridView1_ColumnAdded(object sender, DataGridViewColumnEventArgs e)
{
    DataGridView dgv = this.dataGridView1;

    if ((e.Column.DataPropertyName == "PageSection") &&
        (e.Column.CellType != typeof(DataGridViewComboBoxCell)))
    {
        var cbo = GetComboBoxColumn(e.Column);
        cbo.DataSource = Enum.GetValues(typeof(SectionType));
        cbo.ValueType = typeof(SectionType);
        dgv.Columns.Remove(e.Column);
        dgv.Columns.Add(cbo);
    }
    else if ((e.Column.DataPropertyName == "UserRole") &&
        (e.Column.CellType != typeof(DataGridViewComboBoxCell)))
    {
        var cbo = GetComboBoxColumn(e.Column);
        cbo.DataSource = Enum.GetValues(typeof(RoleType));
        cbo.ValueType = typeof(RoleType);
        dgv.Columns.Remove(e.Column);
        dgv.Columns.Add(cbo);
    }
}

private void dataGridView1_SelectionChanged(object sender, EventArgs e)
{
    this.RefreshButtons();
}

private void dataGridView1_Leave(object sender, EventArgs e)
{
    DataGridView dgv = this.dataGridView1;

    if (dgv.SelectedCells.Count == 0)
    {
        this.idxActiveColumn = -1;
        this.idxActiveRow = -1;
    }
}

private void dataGridView1_CellValueChanged(object sender, DataGridViewCellEventArgs e)
{
    DataGridView dgv = (DataGridView)sender;
    DataGridViewComboBoxCell sectionCell = (DataGridViewComboBoxCell)dgv.Rows[e.RowIndex].Cells[this.idxSection];
    DataGridViewTextBoxCell indentCell = (DataGridViewTextBoxCell)dgv.Rows[e.RowIndex].Cells[this.idxIndent];
    DataGridViewComboBoxCell roleCell = (DataGridViewComboBoxCell)dgv.Rows[e.RowIndex].Cells[this.idxRole];
    Int32 colIndex = e.ColumnIndex;

    try
    {
        if (colIndex == this.idxIndent)
        {
            int number;
            string cellValue = indentCell.Value.ToString();
            bool isNumeric = int.TryParse(cellValue, out number);

            if (!isNumeric)
            {
                cellValue = cellValue.Substring(0, cellValue.Length - 1);
                indentCell.Value = cellValue;
            }
        }

        // Column resizing code goes last
        this.RefreshDataGridViewColumnWidths();

        this.dgvIsDirty = true;
    }
    catch (Exception ex)
    {
        throw new Exception("Failed to refresh DataGridView on cell change.", ex);
    }
}

private void dataGridView1_CurrentCellDirtyStateChanged(object sender, EventArgs e)
{
    DataGridView dgv = (DataGridView)sender;

    try
    {
        if (dgv.IsCurrentCellDirty)
        {
            // This fires the cell value changed handler below
            dgv.CommitEdit(DataGridViewDataErrorContexts.Commit);
            this.RefreshDataGridViewColumnWidths();
        }
    }
    catch (Exception ex)
    {
        throw new Exception("Failed to commit edit of DataGridView cell.", ex);
    }
}

This is the code I am using to update my DataGridView contorl:

// Load the data from the database into the DataGridView
this.dbif.GetProcedure(this.procList.ElementAt(selectedIndex).PrimaryKey, ref this.dgvDataTable);

DataRow[] rows = this.dgvDataTable.Select();
//Object dgvDataSource = dgv.DataSource;
//dgv.DataSource = null;

foreach (DataRow dataRow in rows)
{
    DataGridViewRow dgvRow = new DataGridViewRow();
    dgvRow.CreateCells(dgv);
    dgvRow.Cells[idxSection].Value = dataRow.Field<string>(0);
    dgvRow.Cells[idxIndent].Value = dataRow.Field<byte>(1);
    dgvRow.Cells[idxContent].Value = dataRow.Field<string>(3);
    dgvRow.Cells[idxSummary].Value = dataRow.Field<UInt32>(4) != 0;
    dgvRow.Cells[idxRole].Value = dataRow.Field<string>(5);
    dgvRow.Cells[idxAuthor].Value = dataRow.Field<string>(6) + dataRow.Field<string>(7);
    dgvRow.Cells[idxLastUpdate].Value = dataRow.Field<DateTime>(8).ToString();
    dgv.Rows.Add(dgvRow);
}

//dgv.DataSource = dgvDataSource;

This is how my enumerations are defined:

public enum SectionType
{
    ESJP_SECTION_HEADER = 1,        // start with 1 for database compatibility
    ESJP_SECTION_FOOTER,
    ESJP_SECTION_BODY
}

public enum RoleType
{
    ESJP_ROLE_NONE = 1,             // start with 1 for database compatibility
    ESJP_ROLE_TEST_ENG,
    ESJP_ROLE_FEATURE_LEAD,
    ESJP_ROLE_TEAM_LEAD
}

Upvotes: 2

Views: 1215

Answers (1)

There are a number of issues/improvements. There are too many unknowns about the data to fix everything, but some techniques shown here may help.

1. Manually populating the DGV

You ought not have to do that. Just setting the DataTable as the DataSource will work most of the columns.

2. Expressions

You have 2 expressions where you populate the dgv:

dgvRow.Cells[idxSummary].Value = dataRow.Field<UInt32>(4) != 0;
dgvRow.Cells[idxAuthor].Value = dataRow.Field<string>(6) + dataRow.Field<string>(7);

This leads me to believe the dgv is ReadOnly. Otherwise you will have trouble with those. The Summary for instance: if the user Unchecks the column, you can set that value to 0, but what if they check it? How will you know what value to set??

Indent as Byte seems odd too - almost like it is a boolean.

3. Combos

In a previous question, the dgv was populated from a List<Class>. With that as the `DataSource, an Enum works well because the 2 properties were of that Type. With an enum, the following works:

cbo.ValueType = typeof(RoleType);

It's less likely to work with a DataTable as in version 1, 2 or 3 of this question because there is no db/Datatable type of SectionType or RoleType. In other cases where there is a translation - show the user "ESJP_SECTION_HEADER" but store 2 in the DataTable and ultimately the DB - a small NameValue pairs list will work. A different data model means a different approach to the DGV cbo.

It now looks like those are text columns (I've asked 3 times). If so, you really just need to constrain the selection to the enum names. In the IDE, paste in the text for the Items property:

enter image description here

Alternatively, you can do so in code:

private string[] secList = {"ESJP_SECTION_HEADER","ESJP_SECTION_FOOTER",
                "ESJP_SECTION_BODY"};
...
((DataGridViewComboBoxColumn)dgv1.Columns["PageSection"]).Items.AddRange(secList);

AutoGenerateColumns

When the DataSource for a DGV is set, by default it will automatically create a DGVcolumn for each DTColumn, and this works well for the most part. Sometimes you'll want some small tweak like hide an Id column, or change a TextColumn to a CBOColumn. Code in the ColumnAddedEvent to make these changes rather than manually laying out columns can work well.

But since there are quite a few such such changes and since you have already layed out columns in the IDE, you want to be sure to set AutoGenerateColumns to false somewhere in code. Otherwise it will add still more columns.

Setup

Things you may or may not have done in the DGV designer:

  • Add the columns in whatever order you wish, but be sure to assign the DataPropertyName to the name used in the SQL query. Leave it blank for Author.
    • Expression columns like Author means either changing the query or doing some formatting in the DGV. For this, add First and Last name columns to the DGV as well as the Author column. Make the first two invisible. The code below shows the formatting. (Make sure that the compound column (Author) appears after the parts!).
    • Normally, I would try to do that in SQL: SELECT (First + Last) AS Author, but if you do not want to mess with that query, you can concat in DGV events.
  • Be sure to add the names for the Page and Role columns to the Items collection.

Then, the rest is fairly simple:

private DataTable dtF;
...
string SQL = "SELECT PageSection, Indent, Content, SummaryId, "
        + "UserRole, AuthorFirstN, AuthorLastN, LastUpdated FROM FellPage";

using (var dbCon = new MySqlConnection(MySQLConnStr))
using (var cmd = new MySqlCommand(SQL, dbCon))
{
    dbCon.Open();
    dtF = new DataTable();
    dtF.Load(cmd.ExecuteReader());
}
// IMPORTANT!!!
dgv1.AutoGenerateColumns = false;
dgv1.DataSource = dtF;

The formatting event:

private void dgv1_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
{
    if (dgv1.Rows[e.RowIndex].IsNewRow)
        return;

    if (e.ColumnIndex == 7)      // ie "Author"
    {
        e.Value = dgv1.Rows[e.RowIndex].Cells[6].Value.ToString() + ", " +
            dgv1.Rows[e.RowIndex].Cells[5].Value.ToString();
        e.FormattingApplied = true;
    }
}

Result:

enter image description here

It is very important to turn off AutoGenerateColumns. By default, the dgv will add columns for everything in the source. Since you added them, set that to false in the code (there is no IDE property for it).

By virtue of Summary and Indent being defined as check columns, it translates non zero as true. I have no idea how you will edit any SummaryId value. That should probably be a text column so they can enter a value, if that is allowed (that col could be read only too?).

My query and such arent as complex as yours, and surely there are some details I/we are unaware of which have been omitted. But there is certainly a much less gyrations and code to get it working...whatever this is.

Being bound to a DataTable, when the user edits anything, the changes flow thru to the underlying DataTable (use the various Validating events to check their work). The DataTable in turn tracks the state of each row - new, changed, deleted - so later you can:

var changes = dtF.GetChanges();

This will return all the rows which have been changed since the last AcceptChanges().

Upvotes: 1

Related Questions