自定义DataGridViewCell未触发数据源更改事件

5
我在使用数据绑定时,与WinForms DataGridView一起使用时遇到了一些困难。我将包装了DataSet的DataView分配给DataGridView.DataSource,到目前为止一切都运作得很好。但是,当实现自定义DataGridViewCell时问题就来了。我的目标是提供一个ComboBoxCell,用于选择Enum值,它始终是完全交互式的,不需要用户显式进入编辑模式。
以下是绑定设置:
- DataSet S仅包含一个数据表T - DataView V包装该表 - DataGridView.DataSource设置为V - 应用程序的某些部分订阅T.RowChanged事件。这是关键部分。
就功能而言,我的自定义单元格行为完全符合预期。然而,除非整个DataGridView失去焦点,否则它不会导致DataTable.RowChanged事件触发,但是所有其他非自定义单元格都会。我仍然可以得到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中的其他单元格时,数据源正确地触发事件,但我无论如何都不能在编辑自定义单元格后更新它。

我该如何实现这一点?


你尝试过在选择被接受后调用 DataGridView.EndEdit() 吗? - Stephan Zaria
@StephanZaria 是的。但无论如何,它都将被隐式地完成,因为在接受新值之后会调用HideDropDown(),这将依次调用EndEdit,如上面的代码所述。当我立即在CommitEdit(..)之后或代替它调用它时,没有改变任何东西。 - Adam
你尝试过使用DataGridViewComboBoxColumn吗? - David
@David 是的,我对它失败的可用性感到恐惧。它的视觉外观表明它是交互式的,但尝试进行交互只会创建一个ComboBox控件,然后需要单独进行交互。对于普通用户来说,这看起来像是不确定性/有缺陷的行为,这是不能接受的。手动开始编辑模式的所有尝试都未能产生令人满意的结果。尽管如此,这个自定义单元格只是我将要编写的一些其他单元格中的第一个,所以我需要理解和修复上述问题。 - Adam
1个回答

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开发者进行批准,所以...请随意评论!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接