【发布时间】: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