【问题标题】:New rows missing from DataTable upon ColumnChangedColumnChanged 后 DataTable 中缺少新行
【发布时间】:2018-09-01 02:41:39
【问题描述】:

我正在向用户显示一个 DataGridView。此 DataGridView 将其 DataSource 设置为带有标识符(“ID”)的 DataTable,以及其他数据。每次更改 ID 列中的值时,我都想遍历所有行以检查 ID 是否真正唯一;但是,当任何其他列发生更改时,我想避免这样做,因为对 ID 列执行的任务非常繁重。因此,我认为使用 ColumnChanged 事件是一个不错的选择,我可以从中访问已更改的列,并在需要时重置值。

当现有行在长列表中发生更改时,这种方法效果很好,但是,我注意到一个主要缺陷:当我通过 DataGridView 添加新行时,会触发 ColumnChanged 事件,而行本身实际上并未包含在DataTable,因此遍历 DataTable 会缺少一行,导致许多任务无法正确处理 DataTable。

我注意到,一旦调用 RowChanged,DataTable 确实 包含该行,但我无法访问更改了哪一列,正如上面指出的那样,我需要这样做。有没有办法解决这个问题或者我可以使用什么解决方法?

为了形象化我的问题,我做了下面的例子:每次更改 ID 列时,都会通知用户更改,而程序会检查是否有重复的 ID,如果有则通知用户。但是,您会发现,当您添加新行并且在其中做的第一件事是将 ID 列更改为已存在的值时,用户将收到更改通知,但不会通知重复。

(请注意,此示例只是一个示例,我实际检查 DataTable 的方式要复杂得多,因此我要求在调用事件时该行存在于 DataTable 中。)

Program.cs(这是从 Visual Studio 默认生成的):

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsFormsApp2
{
    static class Program
    {
        /// <summary>
        /// Der Haupteinstiegspunkt für die Anwendung.
        /// </summary>
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Form1());
        }
    }
}

Form1.Designer.cs(标准生成的Form类加DataGridView):

namespace WindowsFormsApp2
{
    partial class Form1
    {
        /// <summary>
        /// Erforderliche Designervariable.
        /// </summary>
        private System.ComponentModel.IContainer components = null;

        /// <summary>
        /// Verwendete Ressourcen bereinigen.
        /// </summary>
        /// <param name="disposing">True, wenn verwaltete Ressourcen gelöscht werden sollen; andernfalls False.</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        #region Vom Windows Form-Designer generierter Code

        /// <summary>
        /// Erforderliche Methode für die Designerunterstützung.
        /// Der Inhalt der Methode darf nicht mit dem Code-Editor geändert werden.
        /// </summary>
        private void InitializeComponent()
        {
            this.dataGridView1 = new System.Windows.Forms.DataGridView();
            ((System.ComponentModel.ISupportInitialize)(this.dataGridView1)).BeginInit();
            this.SuspendLayout();
            // 
            // dataGridView1
            // 
            this.dataGridView1.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
            this.dataGridView1.Dock = System.Windows.Forms.DockStyle.Fill;
            this.dataGridView1.Location = new System.Drawing.Point(0, 0);
            this.dataGridView1.Name = "dataGridView1";
            this.dataGridView1.RowTemplate.Height = 24;
            this.dataGridView1.Size = new System.Drawing.Size(800, 450);
            this.dataGridView1.TabIndex = 0;
            // 
            // Form1
            // 
            this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(800, 450);
            this.Controls.Add(this.dataGridView1);
            this.Name = "Form1";
            this.Text = "Form1";
            ((System.ComponentModel.ISupportInitialize)(this.dataGridView1)).EndInit();
            this.ResumeLayout(false);

        }

        #endregion

        private System.Windows.Forms.DataGridView dataGridView1;
    }
}

Form1.cs(重要的东西):

using System.Collections.Generic;
using System.Data;
using System.Windows.Forms;

namespace WindowsFormsApp2
{
    public partial class Form1 : Form
    {
        private DataTable dt;

        public Form1()
        {
            InitializeComponent();

            dt = new DataTable();
            dt.Columns.Add("ID");
            dt.Columns.Add("First Name");
            dt.Columns.Add("Last Name");

            DataRow dr = dt.NewRow();
            dr["ID"] = "1";
            dr["First Name"] = "John";
            dr["Last Name"] = "Smith";
            dt.Rows.Add(dr);

            dataGridView1.DataSource = dt;

            dt.ColumnChanged += Dt_ColumnChanged;
        }

        private void Dt_ColumnChanged(object sender, DataColumnChangeEventArgs e)
        {
            if (e.Column.ColumnName == "ID")
            {
                MessageBox.Show("The ID column has been changed, it will now be checked.");

                List<string> names = new List<string>();

                foreach (DataRow dr in dt.Rows)
                {
                    string id = (string)dr["ID"];

                    if (names.Contains(id))
                    {
                        MessageBox.Show("The ID " + id + " is already present.");
                    }
                    else
                    {
                        names.Add(id);
                    }
                }
            }
        }
    }
}

【问题讨论】:

  • 尝试以下操作:找到布尔值 = dt.AsEnumerable().Any(x => x.Field("ID") == "abcd");
  • 如果我理解正确,这段代码会检查字符串是否已经存在并将其作为布尔值返回。可悲的是,这不太适合我的问题,因为相应的行不会突然出现在 DataTable 中。我提供的示例只是用于可视化问题,我在实际程序中检查 DataTable 的方式要复杂得多,并且需要通过多个文件中的 DataTable 呈现和引用整行。

标签: c# .net winforms datagridview datatable


【解决方案1】:

我要求在调用事件时该行出现在 DataTable 中

DataGridView 中的行不能插入到 DataTable 中,除非它通过移动到 DataGridView 中的下一行来提交,因此您不能在此事件Dt_ColumnChanged 中将行添加到 DataTable。

有没有办法解决这个问题或者我可以使用什么解决方法?

您可以这样处理 ID 列中的重复值:

1) 删除事件Dt_ColumnChanged

2) 设置DataTable的列ID唯一:

     dt.PrimaryKey = new[] { dt.Columns["ID"] };

3)处理DataGridView的DataError事件:

            private void dataGridView1_DataError(object sender, DataGridViewDataErrorEventArgs e)
            {

                // If the data source raises an exception when a cell value is commited, display an error message.
                if (e.Exception != null && e.Context == DataGridViewDataErrorContexts.Commit)
                {
                    var data = dataGridView1[e.ColumnIndex, e.RowIndex].EditedFormattedValue;

                    MessageBox.Show($"The ID {data} is already present.");
                    System.Diagnostics.Trace.WriteLine(e.Exception.Message); //log error
                    //The Exception.Message will be e.g.: Column 'ID' is constrained to be unique.  Value 'xx' is already present
                    e.ThrowException = false;

                }
            }

所以用户不能添加/修改ID列的重复值,DataGridView/DataTable中的数据会保持一致。

更新

对于评论:

我明确检查具有相同值的多行并分配颜色

你可以像这样搜索DataGridView:

//搜索“名字”列以匹配值“约翰”并设置背景颜色

            private void button1_Click(object sender, EventArgs e)
            {          

                foreach (DataGridViewRow row in dataGridView1.Rows)
                {               
                    row.DefaultCellStyle.BackColor = Color.White;
                    if (row?.Cells["First Name"]?.Value?.ToString().Trim() == "john")
                    {                   
                        row.DefaultCellStyle.BackColor = Color.Red;
                    }
                }
            }

【讨论】:

  • 这是有道理的,并且可能是一个好方法,但是并非我检查某些值的所有列都一定是唯一的,有时恰恰相反(我显式检查具有相同值的多行并且将颜色分配给单独的工作表等)
  • 我更新了我的答案以显示“如何明确检查具有相同值的多行并分配颜色”
猜你喜欢
  • 2014-04-03
  • 2011-06-22
  • 1970-01-01
  • 1970-01-01
  • 2015-09-16
  • 1970-01-01
  • 1970-01-01
  • 2019-06-24
  • 1970-01-01
相关资源
最近更新 更多