我在使用数据绑定时,与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代码后,我仍然完全陷入困境。
以下是该类定义的最重要部分(不是全部源代码)。
以下是绑定设置:
- 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