自定义DataGridViewComboBox:EditingControlShowing时SelectedIndexChanged触发-1的问题
问题描述
我自定义了一套继承自DataGridViewComboBoxColumn的控件:TPDataGridViewComboBoxColumn列、TPDataGridViewComboBoxCell单元格,以及TPDataGridViewComboBoxEditingControl编辑控件,并为其绑定了字符串类型的数据源。
遇到两个问题:
- 打开下拉框时,
SelectedIndex会被先设置为-1,触发SelectedIndexChanged事件; - 在处理自定义的
SelectedValueChanging事件时,如果调用MessageBox.Show(),该事件会频繁触发。
排查后发现,问题根源在于InitializeEditingControl方法中,控件的DataSource会先被设为NULL,再重新设置为我的数据源。请问如何阻止这种不必要的数据源重置行为?
现有代码
public class TPDataGridViewComboBoxColumn : DataGridViewComboBoxColumn { public TPDataGridViewComboBoxColumn() { CellTemplate = new TPDataGridViewComboBoxCell(); } } public class TPDataGridViewComboBoxCell : DataGridViewComboBoxCell { public override Type EditType => typeof(TPDataGridViewComboBoxEditingControl); } public class TPDataGridViewComboBoxEditingControl : DataGridViewComboBoxEditingControl { #region Events public event EventHandler<OldNewCancelEventArgs> SelectedIndexChanging; public event EventHandler<OldNewCancelEventArgs> SelectedValueChanging; #endregion Events #region Internal Properties private int _previousSelectedIndex = -1; private bool _isRevertingSelectedIndex = false; private object _previousSelectedValue = null; private bool _isRevertingSelectedValue = false; #endregion Internal Properties #region Konstruktor public TPDataGridViewComboBoxEditingControl() { } #endregion Konstruktor #region Functions protected virtual void OnSelectedValueChanging(OldNewCancelEventArgs e) { SelectedValueChanging?.Invoke(this, e); } protected override void OnSelectedValueChanged(EventArgs e) { if (_isRevertingSelectedValue) { _isRevertingSelectedValue = false; return; } var cea = new OldNewCancelEventArgs(_previousSelectedValue, SelectedValue); OnSelectedValueChanging(cea); if (cea.Cancel) { _isRevertingSelectedValue = true; SelectedValue = _previousSelectedValue; return; } _previousSelectedValue = SelectedValue; base.OnSelectedValueChanged(e); } protected virtual void OnSelectedIndexChanging(OldNewCancelEventArgs e) { SelectedIndexChanging?.Invoke(this, e); } protected override void OnSelectedIndexChanged(EventArgs e) { if (_isRevertingSelectedIndex) { _isRevertingSelectedIndex = false; return; } var cea = new OldNewCancelEventArgs(_previousSelectedIndex, SelectedIndex); OnSelectedIndexChanging(cea); if (cea.Cancel) { _isRevertingSelectedIndex = true; SelectedIndex = _previousSelectedIndex; return; } _previousSelectedIndex = SelectedIndex; base.OnSelectedIndexChanged(e); } #endregion Functions }
解决方案
方案1:重写单元格的InitializeEditingControl方法,避免不必要的数据源重置
默认的DataGridViewComboBoxCell在初始化编辑控件时,会强制先将DataSource设为null再重新赋值。我们可以重写这个方法,仅当数据源不一致时才更新,跳过无效的null赋值步骤:
public class TPDataGridViewComboBoxCell : DataGridViewComboBoxCell { public override Type EditType => typeof(TPDataGridViewComboBoxEditingControl); public override void InitializeEditingControl(int rowIndex, object initialFormattedValue, DataGridViewCellStyle dataGridViewCellStyle) { base.InitializeEditingControl(rowIndex, initialFormattedValue, dataGridViewCellStyle); var editingControl = DataGridView.EditingControl as TPDataGridViewComboBoxEditingControl; if (editingControl != null) { // 仅当数据源不一致时才更新,避免先设为null的操作 if (!ReferenceEquals(editingControl.DataSource, this.DataSource)) { editingControl.DataSource = this.DataSource; } // 同步其他必要属性 editingControl.DisplayMember = this.DisplayMember; editingControl.ValueMember = this.ValueMember; editingControl.SelectedValue = this.Value; } } }
方案2:在编辑控件中添加数据源更新标志,临时跳过事件触发
如果无法修改单元格的初始化逻辑,可以在编辑控件中添加一个标志位,当数据源更新时暂时屏蔽事件处理,避免不必要的SelectedIndex/SelectedValue事件触发:
public class TPDataGridViewComboBoxEditingControl : DataGridViewComboBoxEditingControl { // 添加数据源更新标志 private bool _isUpdatingDataSource = false; // 重写DataSource属性,设置标志位 public override object DataSource { get => base.DataSource; set { _isUpdatingDataSource = true; try { base.DataSource = value; // 数据源更新后,恢复之前的选中状态 if (_previousSelectedValue != null && value != null) { SelectedValue = _previousSelectedValue; } } finally { _isUpdatingDataSource = false; } } } // 修改SelectedIndexChanged事件处理 protected override void OnSelectedIndexChanged(EventArgs e) { // 如果正在更新数据源,直接跳过事件处理 if (_isUpdatingDataSource) { // 更新缓存的选中索引 _previousSelectedIndex = SelectedIndex; return; } if (_isRevertingSelectedIndex) { _isRevertingSelectedIndex = false; return; } var cea = new OldNewCancelEventArgs(_previousSelectedIndex, SelectedIndex); OnSelectedIndexChanging(cea); if (cea.Cancel) { _isRevertingSelectedIndex = true; SelectedIndex = _previousSelectedIndex; return; } _previousSelectedIndex = SelectedIndex; base.OnSelectedIndexChanged(e); } // 修改SelectedValueChanged事件处理 protected override void OnSelectedValueChanged(EventArgs e) { if (_isUpdatingDataSource) { _previousSelectedValue = SelectedValue; return; } if (_isRevertingSelectedValue) { _isRevertingSelectedValue = false; return; } var cea = new OldNewCancelEventArgs(_previousSelectedValue, SelectedValue); OnSelectedValueChanging(cea); if (cea.Cancel) { _isRevertingSelectedValue = true; SelectedValue = _previousSelectedValue; return; } _previousSelectedValue = SelectedValue; base.OnSelectedValueChanged(e); } // 保留原有其他代码... }
补充说明
- 方案1从根源上避免了数据源的无效重置,是更推荐的解决方案;
- 方案2通过屏蔽事件的方式减少无效触发,适合无法修改单元格逻辑的场景;
- 两种方案都能解决
SelectedIndex被强制设为-1的问题,同时避免因MessageBox.Show()导致的事件频繁触发(因为弹窗会导致控件失去焦点并重新初始化,多次触发数据源变更)。
内容的提问来源于stack exchange,提问作者Anton Schrage
相关产品推荐
相关产品推荐

