You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

自定义DataGridViewComboBox:EditingControlShowing时SelectedIndexChanged触发-1的问题

问题描述

我自定义了一套继承自DataGridViewComboBoxColumn的控件:TPDataGridViewComboBoxColumn列、TPDataGridViewComboBoxCell单元格,以及TPDataGridViewComboBoxEditingControl编辑控件,并为其绑定了字符串类型的数据源。
遇到两个问题:

  1. 打开下拉框时,SelectedIndex会被先设置为-1,触发SelectedIndexChanged事件;
  2. 在处理自定义的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 23:45:42