ColumnChanged

时间:2018-03-22 15:21:15

标签: c# .net winforms datagridview datatable

我正在向用户显示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);
                    }
                }
            }
        }
    }
}

1 个答案:

答案 0 :(得分:0)

I require the row to be present in the DataTable at the time the event is called

The row in DataGridView can't be inserted in the DataTable Except it's committed by moving to the next row in DataGridView, so you can't add row to the DataTable within this event Dt_ColumnChanged.

Is there a way to fix this or there a workaround I can use?

You can handle the duplicated values in ID column in this way:

1) Remove The event Dt_ColumnChanged

2) Set the column ID of the DataTable unique:

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

3) handle the DataError Event of the DataGridView:

            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;

                }
            }

So the user can't add /modify duplicated values Of ID column and data in DataGridView / DataTable will be consistent.

Update

For the comment:

where I explicitly check for multiple rows with the same value and assign colors

You can search DataGridView like:

//search the column "First Name" to match a value "john" and set back color

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