如何检测 DataGridView.RowValidating 事件何时来自用户交互与绑定源列表更改



我终于找到了我整个周末一直在研究的一个错误,但是我没有看到真正解决它的方法 - 以一种干净的方式,也就是说。

情况是我有一个绑定到业务对象列表的 DGV 控件。 其中一列未绑定到数据源,我已经连接了单元格分析和单元格格式化事件来处理此单元格的数据持久性。 我基本上绕过了 .net 数据绑定并实现了我自己的糟糕版本。 这不是故意的,它确实是具有复杂解析和格式化要求的旧代码,并且我错误地实现了基于未绑定列的解决方案。 我现在知道处理此问题的正确方法,并且已经修复了我的代码,但是我仍然想知道如何以另一种方式解决该错误。

我处理 RowValidating 事件,并对整个行进行最终验证,以确保一切都很酷。 当然,如果有问题,我会取消验证并且该行未提交。 当用户通过 UI 交互编辑和添加行时,这一切都可以正常工作,但在设置数据源时会产生问题。 问题似乎是,当 DGV 更新其内部列表并生成行时,不会调用 CellFormating,或者至少在触发验证事件之前不会调用它。 这会导致 RowValidating 处理程序从"未绑定"列中提取空值(因为尚未调用 CellFormat 并设置该值)。

我正在更新我对DGV的知识,并认为处理CellValueNeed事件可能是票证,但是设置DataGridViewCellValueEventArgs.Value并没有像我希望的那样触发CellFormating事件。

我一直在考虑如何处理这种情况,我唯一想到的是检测何时从 UI 事件触发验证,而不是初始绑定或绑定列表更改。 这不仅是一个黑客解决方案,而且看不到如何做到这一点。

我创建了一个完整的示例应用程序来说明这个问题。 我真的很想知道你们中的一些人将如何解决这样的问题。 这里很可能有明显的设计气味。

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());
    }
}

我提到我已经"修复了问题",这并不完全准确。 我在测试应用程序中修复了它,有效地验证了我计划使用的解决方案,但实际上并没有实现它。 一旦我尝试这样做,我就意识到为什么我以最初的方式做事:未绑定列中使用的数据是对象的只读集合,为了设置它们,我需要在对象上调用一个单独的方法。 最佳实践和所有...

好吧,只读属性当然不能在 DGV 中编辑,所以我死在了水里。 这让我回到了我最初的设计,在研究时,我偶然发现了布莱恩·诺伊斯的这个专栏/博客。

他提到了事件 行添加 这就是我所需要的!这是我问题的解决方案:

private void dataGridView1_RowsAdded(object sender, 
    DataGridViewRowsAddedEventArgs e)
{
    if (dataGridView1.Rows[e.RowIndex].IsNewRow)
    {
        return;
    }
    dataGridView1[1, e.RowIndex].Value = 
        (dataGridView1.Rows[e.RowIndex].DataBoundItem as DomainModel).Name;
}

我喜欢这个解决方案,因为它将数据放置在 Cell 上的方式与绑定时相同,然后将格式化责任交给 CellFormating,我可以做我的工作。 同样,我可以解析数据并构造所需的对象以传递给我的绑定对象"SetListData"方法。

都很好!

最新更新