【问题标题】:Custom DataGridViewCell not triggering DataSource change events自定义 DataGridViewCell 不触发 DataSource 更改事件
【发布时间】:2014-03-21 14:04:51
【问题描述】:

我在将 WinForms DataGridView 与数据绑定结合使用时遇到了困难。我将包装 DataSet 的 DataView 分配给 DataGridView.DataSource,到目前为止效果很好。当实现 自定义 DataGridViewCell 时,问题就开始了。我的目标是提供一个用于选择 Enum 值的 ComboBoxCell,它始终是完全交互的,并且不需要用户显式地进入编辑模式。

这是绑定设置:

  • DataSet S 只包含一个 DataTable,T
  • DataView V 包装了该表
  • DataGridView.DataSource 设置为 V
  • 应用程序的某些部分订阅了 T.RowChanged 事件。这是关键部分。

就功能而言,我的自定义单元格的行为完全符合预期。但是,它不会导致触发 DataTable.RowChanged 事件,除非整个 DataGridView 失去焦点......但所有其他非自定义单元格都会这样做。我仍然收到一个 CellValueChanged 事件,并且 DataSet 具有新值.. 但是既没有 DataTable.RowChanged 也没有 DataGridView.DataBindingComplete,并且该行不会像通常那样自动失效。

我显然做错了什么。我可能错过了通知事件或实现了错误,但经过两天的搜索、步进和反汇编 .Net 代码,我仍然完全卡住了。

以下是类定义中最重要的部分(不是完整的源代码):

public class DataGridViewEnumCell : DataGridViewCell, IDataGridViewEditingCell
{
    private Type    enumType            = null;
    private Enum    enumValue           = default(Enum);
    private bool    enumValueChanged    = false;


    public virtual object EditingCellFormattedValue
    {
        get { return this.GetEditingCellFormattedValue(DataGridViewDataErrorContexts.Formatting); }
        set { this.enumValue = (Enum)Utility.SafeCast(value, this.enumType); }
    }
    public virtual bool EditingCellValueChanged
    {
        get { return this.enumValueChanged; }
        set { this.enumValueChanged = value; }
    }
    public override Type EditType
    {
        get { return null; }
    }
    public override Type FormattedValueType
    {
        get { return this.enumType; }
    }
    public override Type ValueType
    {
        get
        {
            if (this.OwningColumn != null && this.OwningColumn.ValueType != null)
            {
                return this.OwningColumn.ValueType;
            }
            else
            {
                return this.enumType;
            }
        }
        set
        {
            base.ValueType = value;
        }
    }
    // The kind of Enum that is edited in this cell.
    public Type EnumValueType
    {
        get { return this.enumType; }
        set { this.enumType = value; }
    }


    public virtual object GetEditingCellFormattedValue(DataGridViewDataErrorContexts context)
    {
        if (context.HasFlag(DataGridViewDataErrorContexts.ClipboardContent))
        {
            return Convert.ToString(this.enumValue);
        }
        else
        {
            return this.enumValue ?? this.enumType.GetDefaultValue();
        }
    }
    public override object ParseFormattedValue(object formattedValue, DataGridViewCellStyle cellStyle, TypeConverter formattedValueTypeConverter, TypeConverter valueTypeConverter)
    {
        // Cast the Enum value to the original cell value type
        object cellVal;
        Utility.SafeCast(formattedValue, this.ValueType, out cellVal);
        return cellVal;
    }
    protected override object GetFormattedValue(object value, int rowIndex, ref DataGridViewCellStyle cellStyle, TypeConverter valueTypeConverter, TypeConverter formattedValueTypeConverter, DataGridViewDataErrorContexts context)
    {
        if (this.DataGridView == null || value == null)
        {
            return this.enumType.GetDefaultValue();
        }

        // Cast the cell value to the appropriate Enum value type
        object enumVal;
        Utility.SafeCast(value, this.enumType, out enumVal);

        // Let the base implementation apply additional formatting
        return base.GetFormattedValue(enumVal, rowIndex, ref cellStyle, valueTypeConverter, formattedValueTypeConverter, context);
    }
    private Enum GetCurrentValue()
    {
        object unknownVal = (this.enumValueChanged ? this.enumValue : this.Value);
        object enumVal;
        Utility.SafeCast(unknownVal, this.enumType, out enumVal);

        return (Enum)enumVal;
    }

    public virtual void PrepareEditingCellForEdit(bool selectAll)
    {
        this.enumValue = this.GetCurrentValue();
    }

    protected override void OnClick(DataGridViewCellEventArgs e)
    {
        base.OnClick(e);
        if (this.DataGridView.CurrentCell == this && (DateTime.Now - this.mouseClosed).TotalMilliseconds > 200)
        {
            // Due to some reason I don't understand sometimes EditMode is already active.
            // Don't do it twice in these cases.
            if (!this.IsInEditMode)
            {
                // Begin editing
                this.DataGridView.BeginEdit(true);
            }
            this.ShowDropDown();
        }
    }

    public void HideDropDown()
    {
        // ... snip ...

        // Revert value to original state, if not accepted explicitly
        // It will also run into this code after the new selection 
        // has been accepted (see below)
        if (this.DataGridView != null)
        {
            this.enumValue = this.GetCurrentValue();
            this.enumValueChanged = false;
            this.DataGridView.EndEdit();
        }
    }

    // Called when a value has been selected. All calue changes run through this method!
    private void dropdown_AcceptSelection(object sender, EventArgs e)
    {
        Enum selectedEnum = (Enum)this.dropdown.SelectedItem;
        if (!this.enumValue.Equals(selectedEnum))
        {
            this.enumValue = selectedEnum;
            this.enumValueChanged = true;
            this.DataGridView.NotifyCurrentCellDirty(true);
            this.DataGridView.CommitEdit(DataGridViewDataErrorContexts.Commit);
        }
    }
}

再次,当 DataGridView 失去焦点或编辑 DataGridView 中的任何其他单元格时,DataSource 正确触发事件,但我在编辑自定义单元格后不知何故无法让它更新它.

我怎样才能做到这一点?

【问题讨论】:

  • 您是否尝试在选择被接受后致电DataGridView.EndEdit()
  • @StephanZaria 是的。但无论如何它都会隐式完成,因为在接受新值后会调用 HideDropDown(),而后者又会调用 EndEdit,如上面代码中所述。当我在 CommitEdit(..) 之后立即调用它或代替 CommitEdit(..) 时没有改变任何东西
  • 您尝试过使用 DataGridViewComboBoxColumn 吗?
  • @David 是的,并且被它失败的可用性吓坏了。它的视觉外观表明是交互的,但尝试交互只会创建一个ComboBox控件,然后需要单独与之交互。对于普通用户来说,这看起来像是不确定/错误的行为,这是不行的。通过手动启动编辑模式来修补此行为的所有尝试都未能产生令人满意的结果。尽管如此,这个自定义单元格只是我将要编写的其他一些单元格中的第一个,因此无论如何我都需要理解并解决上述问题。

标签: c# .net data-binding datagridview datatable


【解决方案1】:

我终于能够解决这个问题。

事实证明,整个问题与我的自定义 IDataGridViewEditingCell 没有任何关系。没有收到 RowChanged 事件仅仅是因为 DataGridView 行在离开当前选定的行之前没有得到验证 - 我没有注意到,因为我的测试中只有一行,所以我不得不聚焦不同的控件来实现这一点。

在取消选择/散焦之前不验证当前行似乎是预期的并且 DataGridView 中的正常行为。虽然这不是我想要的,所以我派生了我自己的 DataGridView,并做了以下事情:

protected override void OnCellEndEdit(DataGridViewCellEventArgs e)
{
    base.OnCellEndEdit(e);
    // Force validation after each cell edit, making sure that 
    // all row changes are validated in the DataSource immediately.
    this.OnValidating(new System.ComponentModel.CancelEventArgs());
}

到目前为止,它似乎完美无缺,但我可能只是幸运。任何有经验的 DataGridView 开发人员的批准都将受到高度赞赏,所以..随时发表评论!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-09-23
    • 2019-09-09
    • 1970-01-01
    • 1970-01-01
    • 2021-09-26
    • 2015-07-06
    相关资源
    最近更新 更多