DataGridView ComboBox Column:从下拉列表中选择后更改单元格值?

Posted

技术标签:

【中文标题】DataGridView ComboBox Column:从下拉列表中选择后更改单元格值?【英文标题】:DataGridView ComboBox Column: Change cell value after selection from dropdown is made? 【发布时间】:2012-03-07 20:10:35 【问题描述】:

我已经为我的 DataGridView 设置了一个 ComboBoxColumn,并从枚举中设置了它的可选值。它主要按我的意愿工作,但有以下例外。

每当我单击下拉箭头,然后选择其中一个枚举值时,它都会保持某种“中间”状态,其中不会触发 CellValueChanged 事件。我需要关注另一个单元格或另一个控件才能触发事件。

我还有一个 DataGridView 的 Leaving 事件的事件处理程序,它通过确保没有单元格为空来“验证”内容。

因此,如果我创建一行并填充所有单元格并到达(当前为空白的)ComboBox 列,请将其更改为一个值,然后单击“运行”按钮;我的错误对话框弹出,因为 ComboBox 选择没有“保存”。

我该如何解决这个问题?有没有办法在我从下拉列表中选择一个值后,它会自动“设置”该值?

谢谢!

【问题讨论】:

【参考方案1】:

您应该使用CurrentCellDirtyStateChanged 事件并在网格上强制提交编辑:

    private void dataGridView1_CurrentCellDirtyStateChanged(object sender, EventArgs e)
    
        dataGridView1.CommitEdit(DataGridViewDataErrorContexts.Commit);
    

希望对你有帮助!

【讨论】:

确实做到了!谢谢!【参考方案2】:

我会通过检查单元格类型而不是列类型来扩展 Moop 的答案。

dataGridView1.CurrentCellDirtyStateChanged += dataGridView1_CurrentCellDirtyStateChanged;

void dataGridView1_CurrentCellDirtyStateChanged(object sender, EventArgs e)

    if (CurrentCell is DataGridViewComboBoxCell)
    
        dataGridView1.CommitEdit(DataGridViewDataErrorContexts.Commit);
        dataGridView1.EndEdit();
    

【讨论】:

EndEdit() 救我一命。谢谢 Bioukh @Bioukh 为了接收对DataGridViewComboBoxColumn 字段的更改,我创建了一个事件处理程序:private void Dgv_CurrentCellDirtyStateChanged (object sender, EventArgs e) dgv.CommitEdit (DataGridViewDataErrorContexts.Commit); dgv.EndEdit (); iNumerator ++; textBox1.Text = textBox1.Text.Insert (0, "*** Event_CurrentCellDirtyStateChanged_4 -" + iNumerator + "\ r \ n"); // ++ Add a line to the beginning @Bioukh In textBox1.Text 我得到结果:*** Event_CurrentCellDirtyStateChanged_4 - 2 *** Event_CurrentCellDirtyStateChanged_4 - 1 换句话说,事件触发了两次。 问题: 事件触发两次是否正确?事件可以触发一次吗?或者这不是一个根本问题? 通过提交当前cell版本,该函数会改变当前cell的脏状态(即IsCurrentCellDirty),所以确实会再次触发该事件。【参考方案3】:

我会通过在强制CommitEdit 之前检查DataGridViewColumn 是否是DataGridViewComboBoxColumn 的类型来扩展ionden 的答案。这将防止其他 DataGridViewColumn 对象过早提交。

    dataGridView1.CurrentCellDirtyStateChanged += dataGridView1_CurrentCellDirtyStateChanged;

    void dataGridView1_CurrentCellDirtyStateChanged(object sender, EventArgs e)
    
        DataGridViewColumn col = dataGridView1.Columns[dataGridView1.CurrentCell.ColumnIndex];
        if (col is DataGridViewComboBoxColumn)
        
            dataGridView1.CommitEdit(DataGridViewDataErrorContexts.Commit);
        
    

【讨论】:

【参考方案4】:

CurrentCellDirtyStateChanged 事件修复了此问题的鼠标交互,但它破坏了键盘交互 - 使用 F4 然后向上/向下箭头,每次箭头单击都会导致脏状态更改并提交编辑。我找到的解决方案是在创建“DataGridViewComboBoxEditingControl”时获取它,并将 DropDownClosed 事件附加到它。这适用于键盘和鼠标交互。在此示例中,我们扩展了 DataGridView,因此每个实例都将继承此功能:

    protected override void OnEditingControlShowing(DataGridViewEditingControlShowingEventArgs e)
    
        DataGridViewComboBoxEditingControl control = e.Control as DataGridViewComboBoxEditingControl;
        if (control != null)
        
            control.DropDownClosed -= ComboBoxDropDownClosedEvent;
            control.DropDownClosed += ComboBoxDropDownClosedEvent;
        
        base.OnEditingControlShowing(e);
    

    void ComboBoxDropDownClosedEvent(object sender, EventArgs e)
    
        DataGridViewComboBoxCell cell = CurrentCell as DataGridViewComboBoxCell;
        if ((cell != null) && cell.IsInEditMode)
        
            CommitEdit(DataGridViewDataErrorContexts.Commit);
            EndEdit();
        
    

【讨论】:

【参考方案5】:

在某些情况下,在焦点完全离开该行之前,该值不会保持不变。在这种情况下,强制当前编辑结束的唯一方法是在整个绑定上下文中结束它:

mGridView.CommitEdit(DataGridViewDataErrorContexts.Commit);
mGridView.BindingContext[mGridView.DataSource].EndCurrentEdit(); // <<===

我发现了这个提示here。

【讨论】:

【参考方案6】:

我花了大约两个小时来寻找错误,因为我没有注意到如果单元格值没有散焦,则不会保存它,或者更好地说我只是注意到单元格没有散焦,因为组合框变白了保存时(btn 事件)。 不仅如此,EditOnEnter-Mode 比上面显示的大多数其他方法更有效。使用 EditOnEnter 的原因是,当你使用 DataGridViewComboBoxColumn 时,如果没有将 EditMode 设置为 EditOnEnter,则必须单击两次才能打开下拉菜单。

this.dataGridView.EditMode = DataGridViewEditMode.EditOnKeystrokeOrF2; this.dataGridView.EndEdit(); this.dataGridView.EditMode = DataGridViewEditMode.EditOnEnter;

我希望这会有所帮助。我花了大约两个小时想知道为什么对象中的值与 GUI 上显示的值不同。

【讨论】:

【参考方案7】:

我正在添加我的答案作为已经发生的讨论的后续行动。我试图构建一个每行具有不同组合框的 DataGridView。他们还必须对单击做出响应。并且,当进行选择时,需要根据组合框选择更改行中的另一个单元格。一旦做出选择,就需要立即进行更改。与 OP 一样,我的主要问题是在组合框失去焦点之前不会发生更改。

所以,这里是这样一个 DataGridView 的完整工作的最小示例。我不得不把它降到最低限度,因为同时满足我的所有要求是很棘手的。有几篇 SO 帖子对此进行了介绍,稍后我将使用参考资料更新我的帖子。但是现在,这里是......

using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;

namespace TestDGV

public partial class Form1 : Form

    public Form1()
    
        InitializeComponent();
    

    private Panel panel2;
    private DataGridView TestGrid;

    private void InitializeComponent()
    
        this.panel2 = new System.Windows.Forms.Panel();
        this.SuspendLayout();
        // 
        // panel2
        // 
        this.panel2.Dock = DockStyle.Fill;
        this.panel2.Name = "panel2";
        this.panel2.TabIndex = 1;
        // 
        // Form1
        // 
        this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
        this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
        this.ClientSize = new System.Drawing.Size(661, 407);
        this.Controls.Add(this.panel2);
        this.Name = "Form1";
        this.Text = "Form1";
        this.Load += new System.EventHandler(this.Form1_Load);
        this.ResumeLayout(false);
    

    private void Form1_Load(object sender, EventArgs e)
    
        //basic grid properties
        TestGrid = new DataGridView();
        TestGrid.Dock = DockStyle.Fill;
        TestGrid.AutoGenerateColumns = false;
        TestGrid.Name = "TestGrid";
        TestGrid.ReadOnly = false;
        TestGrid.EditMode = DataGridViewEditMode.EditOnEnter;

        //Event handlers
        TestGrid.DataBindingComplete += TestGrid_DataBindingComplete;
        TestGrid.CurrentCellDirtyStateChanged += TestGrid_CurrentCellDirtyStateChanged;
        TestGrid.CellValueChanged += TestGrid_CellValueChanged;

        //columns
        var textCol = new DataGridViewTextBoxColumn();
        textCol.HeaderText = "Text";
        textCol.Name = "Text";
        textCol.DataPropertyName = "Text";
        textCol.AutoSizeMode = DataGridViewAutoSizeColumnMode.AllCells;
        TestGrid.Columns.Add(textCol);

        var comboCol = new DataGridViewComboBoxColumn();
        comboCol.HeaderText = "ComboBox";
        comboCol.Name = "ComboBox";
        comboCol.AutoComplete = true;
        comboCol.AutoSizeMode = DataGridViewAutoSizeColumnMode.AllCells;
        TestGrid.Columns.Add(comboCol);

        var resultCol = new DataGridViewTextBoxColumn();
        resultCol.HeaderText = "Result";
        resultCol.Name = "Result";
        resultCol.DataPropertyName = "Result";
        resultCol.AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
        TestGrid.Columns.Add(resultCol);

        //Bind the data
        Datum.TestLoad();
        TestGrid.DataSource = Datum.Data;

        panel2.Controls.Add(TestGrid);
    

    void TestGrid_CellValueChanged(object sender, DataGridViewCellEventArgs e)
    
        if (e.RowIndex < 0 || e.ColumnIndex < 0)
            return;

        var row = TestGrid.Rows[e.RowIndex];
        var cell = row.Cells[e.ColumnIndex];
        if (cell is DataGridViewComboBoxCell)
        
            var val = cell.Value as string;
            var datum = row.DataBoundItem as Datum;
            datum.Current = val;
            row.Cells["Result"].Value = datum.Result;
            TestGrid.InvalidateRow(e.RowIndex);
        
    


    void TestGrid_CurrentCellDirtyStateChanged(object sender, EventArgs e)
    
        if(TestGrid.CurrentCell is DataGridViewComboBoxCell)
        
            TestGrid.CommitEdit(DataGridViewDataErrorContexts.Commit);
            TestGrid.EndEdit();
        
    

    void TestGrid_DataBindingComplete(object sender, DataGridViewBindingCompleteEventArgs e)
    
        foreach (DataGridViewRow row in TestGrid.Rows)
        
            var datum = row.DataBoundItem as Datum;
            if (datum == null)
                return;

            var cell = row.Cells["ComboBox"] as DataGridViewComboBoxCell;
            if (cell.DataSource == null)
            
                cell.DisplayMember = "KeyDisplayValue";
                cell.ValueMember = "KeyValue";
                cell.DataSource = (row.DataBoundItem as Datum).Combo;
                cell.Value = (row.DataBoundItem as Datum).Current;
            
        
        TestGrid.DataBindingComplete -= TestGrid_DataBindingComplete;
    

    public class Datum
    
        public static void TestLoad()
        
            var t1 = new Triplet[] 
                     new Triplet("1", "World", "Everyone" ),
                     new Triplet("2", "Charlie", "Friend of Algernon" ),
                     new Triplet("3", "Lester", "Phenomenal programmer" ),
            ;
            var t2 = new Triplet[] 
                     new Triplet("1", "World", "Everyone" ),
                     new Triplet("4", "Mary", "Wife of George Bailey" ),
                     new Triplet("3", "Lester", "Phenomenal programmer" ),
            ;
            Data.Add(new Datum("hello, ", t1.ToList()));
            Data.Add(new Datum("g'bye, ", t2.ToList()));
        
        public static List<Datum> Data = new List<Datum>();

        public Datum(string text, List<Triplet> combo)
        
            this._text = text;
            this._combo = combo.ToDictionary<Triplet,string>(o => o.KeyValue);
            this.Current = combo[0].KeyValue;
        

        private string _text;
        public string Text
        
            get
            
                return _text;
            
        

        private Dictionary<string, Triplet> _combo;
        public List<Triplet> Combo
        
            get
            
                return _combo.Values.ToList();
            
        

        private string _result;
        public string Result
        
            get
            
                return _result;
            
        

        private string _current;
        public string Current
        
            get
            
                return _current;
            
            set
            
                if (value != null && _combo.ContainsKey(value))
                
                    _current = value;
                    _result = _combo[value].Description;
                
            
        
    

    public class Triplet
    
        public string KeyValue  get; set; 
        public string KeyDisplayValue  get; set; 
        public string Description  get; set; 
        public Triplet(string keyValue, string keyDisplayValue, string description)
        
            KeyValue = keyValue;
            KeyDisplayValue = keyDisplayValue;
            Description = description;
        
    


【讨论】:

【参考方案8】:

感谢 Droj 提供有关 EndCurrentEdit 的提示,我需要它来为我工作。 这就是我最终立即提交 DataGridViewComboBoxColumns 和 DataGridViewCheckBoxColumns 所做的:

private void dataGridViewEnumerable_CurrentCellDirtyStateChanged(object sender, EventArgs e)

  var dataGridView = sender as DataGridView;
  if (dataGridView == null || dataGridView.CurrentCell == null)
    return;
  var isComboBox = dataGridView.CurrentCell is DataGridViewComboBoxCell;
  if ((isComboBox || dataGridView.CurrentCell is DataGridViewCheckBoxCell) 
    && dataGridView.CommitEdit(DataGridViewDataErrorContexts.Commit) 
    && isComboBox && dataGridView.EndEdit())
    dataGridView.BindingContext[dataGridView.DataSource].EndCurrentEdit();

【讨论】:

【参考方案9】:

这是我解决问题的方法

Private Sub dgvEcheancier_CurrentCellDirtyStateChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles dgvEcheancier.CurrentCellDirtyStateChanged
        nbreClick += 1
            With dgvEcheancier
                Select Case .CurrentCell.ColumnIndex
                Case 9
                    Dim col As DataGridViewComboBoxColumn = .Columns(9)
                    If TypeOf (col) Is DataGridViewComboBoxColumn Then
                        dgvEcheancier.CommitEdit(DataGridViewDataErrorContexts.Commit)
                        If nbreClick = 2 Then
                            MessageBox.Show("y" & "val=" & .CurrentCell.Value)
                            nbreClick = 0
                        End If
                    End If

            End Select
            End With

【讨论】:

请看c#标签。【参考方案10】:
void dataGridView1_CurrentCellDirtyStateChanged(object sender, EventArgs e)

    dataGridView1.BeginEdit(true);
    ComboBox cmbMiCtrl=(ComboBox)dataGridView1.EditingControl;
    string Valor= cmbMiCtrl.Text;
    dataGridView1.EndEdit();

【讨论】:

【参考方案11】:

我看到的一个问题:如果您选择,它将不起作用: GridView.EditMode = System.Windows.Forms.DataGridViewEditMode.EditOnEnter;

【讨论】:

【参考方案12】:

您应该使用 CellValueChanged 来触发网格上的更改事件,并且在事件内部您应该提交更改并离开控件以便在选择项目后保存它。

    private void FilterdataGrid_CellValueChanged(object sender, DataGridViewCellEventArgs e)

    FilterdataGrid.CommitEdit(DataGridViewDataErrorContexts.Commit);      

    FilterdataGrid.EndEdit(DataGridViewDataErrorContexts.LeaveControl);

希望对你有帮助!

【讨论】:

您能否尝试在您的答案中添加一些上下文和解释,以便未来的用户可以从中受益?用纯代码回答不太可能成为未来用户更通用的答案。请查看How to Answer 了解更多信息。 这会导致 CommitEdit 上的堆栈溢出(对于未绑定的数据)。

以上是关于DataGridView ComboBox Column:从下拉列表中选择后更改单元格值?的主要内容,如果未能解决你的问题,请参考以下文章

清除 DataGridView 中的 ComboBox 文本

在运行时创建 ComboBox 时,为 DataGridView 内的 ComboBox 创建一个 EventHandler

Windows 窗体 DataGridView 将 SelectedIndexChanged 事件附加到 ComboBox

C#datagridview中点击combobox后怎么获取点击的combobox所在的单元格?

如何使用ComboBox过滤datagridview

双击datagridview后,Combobox中的SelectedValue