我终于找到了一个我整个周末都在努力解决的错误,但是我看不到真正解决它的方法 - 就是以一种干净的方式。
情况是我有一个绑定到业务对象列表的 DGV 控件。其中一列未绑定到 DataSource,我已经连接了 CellParsing 和 CellFormatting 事件来处理此单元格的数据的持久性。我基本上绕过了 .net 数据绑定并实现了我自己的糟糕版本。这不是故意的,它是真正的旧代码,具有复杂的解析和格式化要求,我错误地实现了基于未绑定列的解决方案。我现在知道处理这个问题的正确方法,并且已经修复了我的代码,但是我仍然想知道如何以另一种方式解决这个错误。
我处理 RowValidating 事件并对整个行进行最终验证,以确保一切正常。当然,如果出现问题,我会取消验证并且未提交该行。当用户通过 UI 交互编辑和添加行时,这一切都可以正常工作,但在设置 DataSource 时会产生问题。问题似乎是当 DGV 更新其内部列表并构建行时未调用 CellFormatting,或者至少在触发验证事件之前未调用它。这会导致 RowValidating 处理程序从 Unbound 列中提取一个空值(因为尚未调用 CellFormatting 并设置该值)。
我正在刷新我对 DGV 的了解,并认为处理 CellValueNeeded 事件可能是票,但设置 DataGridViewCellValueEventArgs.Value 并没有像我希望的那样触发 CellFormatting 事件。
我一直在考虑如何处理这种情况,我想出的唯一方法是检测何时从 UI 事件触发验证,而不是初始绑定或绑定列表更改。这不仅是一个 hacky 解决方案,而且看不到它是如何完成的。
我创建了一个完整的示例应用程序来说明问题。我真的很想知道你们中的一些人将如何解决这样的问题。这里很可能有主要的设计气味。
using System;
using System.Collections.Generic;
using System.Windows.Forms;
public class Form1 : Form
{
private List<DomainModel> _sampleData;
public Form1()
{
InitializeComponent();
_sampleData = new List<DomainModel>();
_sampleData.Add(new DomainModel("Widget A"));
_sampleData.Add(new DomainModel("Widget B"));
}
private void button1_Click(object sender, EventArgs e)
{
Console.WriteLine("Setting DataSource");
domainModelBindingSource.DataSource = _sampleData;
}
private void dataGridView1_CellFormatting(object sender,
DataGridViewCellFormattingEventArgs e)
{
Console.WriteLine("CellFormatting fired for {0},{1}",
e.RowIndex, e.ColumnIndex);
if (e.ColumnIndex != 0 && !dataGridView1.Rows[e.RowIndex].IsNewRow)
{
var model = domainModelBindingSource[e.RowIndex] as DomainModel;
e.Value = model.Name;
e.FormattingApplied = true;
}
}
private void dataGridView1_CellParsing(object sender, DataGridViewCellParsingEventArgs e)
{
if (e.ColumnIndex == 1)
{
e.Value = e.Value.ToString();
e.ParsingApplied = true;
}
}
private void dataGridView1_RowValidating(object sender, DataGridViewCellCancelEventArgs e)
{
if (dataGridView1.Rows[e.RowIndex].IsNewRow)
return;
object value = dataGridView1[1, e.RowIndex].Value;
if (value == null || String.IsNullOrEmpty(value.ToString()))
e.Cancel = true;
}
#region Designer stuff
private System.ComponentModel.IContainer components = null;
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
components.Dispose();
base.Dispose(disposing);
}
#region Windows Form Designer generated code
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
this.dataGridView1 = new System.Windows.Forms.DataGridView();
this.nameDataGridViewTextBoxColumn = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.Column1 = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.domainModelBindingSource = new System.Windows.Forms.BindingSource(this.components);
this.button1 = new System.Windows.Forms.Button();
((System.ComponentModel.ISupportInitialize)(this.dataGridView1)).BeginInit();
((System.ComponentModel.ISupportInitialize)(this.domainModelBindingSource)).BeginInit();
this.SuspendLayout();
//
// dataGridView1
//
this.dataGridView1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.dataGridView1.AutoGenerateColumns = false;
this.dataGridView1.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
this.dataGridView1.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] {
this.nameDataGridViewTextBoxColumn,
this.Column1});
this.dataGridView1.DataSource = this.domainModelBindingSource;
this.dataGridView1.Location = new System.Drawing.Point(12, 41);
this.dataGridView1.Name = "dataGridView1";
this.dataGridView1.Size = new System.Drawing.Size(437, 161);
this.dataGridView1.TabIndex = 0;
this.dataGridView1.CellFormatting += new System.Windows.Forms.DataGridViewCellFormattingEventHandler(this.dataGridView1_CellFormatting);
this.dataGridView1.CellParsing += new System.Windows.Forms.DataGridViewCellParsingEventHandler(this.dataGridView1_CellParsing);
this.dataGridView1.RowValidating += new System.Windows.Forms.DataGridViewCellCancelEventHandler(this.dataGridView1_RowValidating);
//
// nameDataGridViewTextBoxColumn
//
this.nameDataGridViewTextBoxColumn.DataPropertyName = "Name";
this.nameDataGridViewTextBoxColumn.HeaderText = "Name";
this.nameDataGridViewTextBoxColumn.Name = "nameDataGridViewTextBoxColumn";
//
// Column1
//
this.Column1.HeaderText = "Data (unbound)";
this.Column1.Name = "Column1";
//
// domainModelBindingSource
//
this.domainModelBindingSource.DataSource = typeof(DomainModel);
//
// button1
//
this.button1.Location = new System.Drawing.Point(12, 12);
this.button1.Name = "button1";
this.button1.Size = new System.Drawing.Size(168, 23);
this.button1.TabIndex = 1;
this.button1.Text = "Update Data Source";
this.button1.UseVisualStyleBackColor = true;
this.button1.Click += new System.EventHandler(this.button1_Click);
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(461, 214);
this.Controls.Add(this.button1);
this.Controls.Add(this.dataGridView1);
this.Name = "Form1";
this.Text = "Form1";
((System.ComponentModel.ISupportInitialize)(this.dataGridView1)).EndInit();
((System.ComponentModel.ISupportInitialize)(this.domainModelBindingSource)).EndInit();
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.DataGridView dataGridView1;
private System.Windows.Forms.BindingSource domainModelBindingSource;
private System.Windows.Forms.Button button1;
private System.Windows.Forms.DataGridViewTextBoxColumn nameDataGridViewTextBoxColumn;
private System.Windows.Forms.DataGridViewTextBoxColumn Column1;
#endregion
}
internal sealed class DomainModel
{
public DomainModel() { }
public DomainModel(string name)
{
this.Name = name;
}
public string Name { get; set; }
}
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
}