C# 向绑定到 DataGridView 的 DataTable 添加列不会更新视图

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/489425/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-04 05:22:45  来源:igfitidea点击:

Adding columns to a DataTable bound to a DataGridView does not update the view

c#winformsvisual-studio-2008datagridviewdatatable

提问by Brendan

I have a DataTable which is populated from a CSV file then, using a DataGridView the data is edited in memory. As far as I understand the programmatic editing of the data should be done on the DataTable where the user editing is done via. the DataGridView.

我有一个从 CSV 文件填充的 DataTable,然后使用 DataGridView 在内存中编辑数据。据我了解,数据的编程编辑应该在用户编辑通过的 DataTable 上完成。数据网格视图。

However when I add columns programmatically to the DataTable, it is not reflected automatically in the DataGridView and I suspect the converse is also true.

但是,当我以编程方式向 DataTable 添加列时,它不会自动反映在 DataGridView 中,我怀疑反过来也是如此。

How do you keep the two concurrent? I thought the idea of data binding was that this was automatic...

你如何保持两者同时发生?我认为数据绑定的想法是这是自动的......

Here is the relevant setup code - WorksheetGridViewsubclasses DataGridView

这是相关的设置代码 -WorksheetGridView子类DataGridView

// Can access data directly
public DataTable data = new DataTable();

public WorksheetGridView()
{
    InitializeComponent();

    // Allow copying from table to clipboard
    this.ClipboardCopyMode = DataGridViewClipboardCopyMode.EnableWithoutHeaderText;

    // TODO: how to allow both row and column selects?
    //this.SelectionMode = DataGridViewSelectionMode.ColumnHeaderSelect;

    // Load up a blank DataTable to hold user inputted data
    int i;
    const int numBlankRows = Config.Application.DefaultNumRows;
    const int numBlankCols = Config.Application.DefaultNumCols;
    // TODO: Figure out how to include this as a config variable
    DBNull dfltCellContent = DBNull.Value;
    DataRow tmpRow;
    // Add columns - i used for naming
    for (i = 0; i < numBlankCols; i++)
    {
        this.AddColumn(i);
    }

    // Add rows
    for (i = 0; i < numBlankRows; i++)
    {
       tmpRow = this.data.NewRow();
       // Fill cells with something (i.e. blank cells)
       foreach (DataColumn col in this.data.Columns)
       {
           tmpRow[col.ColumnName] = DBNull.Value;
       }
       this.data.Rows.Add(tmpRow);
    }
    // Link data to the view
    this.DataSource = this.data;
}

private void AddColumn(int colIndex)
{
    // Adds a column to the data array
    DataColumn tmpCol;
    tmpCol = new DataColumn();
    tmpCol.DataType = Type.GetType(Config.Application.DataType);
    tmpCol.ColumnName = "C" + colIndex.ToString();
    tmpCol.ReadOnly = false;
    tmpCol.Unique = false;
    tmpCol.AllowDBNull = true;
    this.data.Columns.Add(tmpCol);
}

The bit that doesn't work is later when I call AddColumnfor instance in this code to handle pasting tab-delimited data,

稍后当我AddColumn在此代码中调用例如处理粘贴制表符分隔的数据时,不起作用的位,

public void PasteToCells(string mode)
{
    // TODO: Write paste code
    int i;
    if (Clipboard.ContainsText())
    {
        string clipBoardContent = Clipboard.GetText();
        using (CsvReader pastedCsvReader = new CsvReader(
            new StringReader(clipBoardContent), false, '\t'))
        {
            // TODO: If more rows/cols than in data table then expand accordingly
            int numPastedCols = pastedCsvReader.FieldCount;
            int currRowIndex, currColumnIndex;
            GetSelectedInsertPoint(out currRowIndex, out currColumnIndex);
            // Make space for columns if needed
            if (mode == "insertcols")
            {
                int baseIndex = this.data.Columns.Count;
                for (i = 0; i < numPastedCols; i++)
                {
                    //this.Columns.Add
                    // TODO: Doesn't work yet - do you edit the DataGridView and reflect to DataTable or vise-versa?
                    this.AddColumn(baseIndex + i);
                }
            }
            while  (pastedCsvReader.ReadNextRecord()) 
            {
                // Make space for rows (row by row) if needed
                if (mode == "insertrows")
                {
                    this.data.NewRow();
                    // TODO: Add a row
                }
                // Populate the cells with valid pasted text
                for (i = 0; i < numPastedCols; i++)
                {
                    this.data.Rows[currRowIndex][currColumnIndex + i] = ConvertCellContent(pastedCsvReader[i]);
                }
                currRowIndex = currRowIndex + 1;
            }
        }
    }
    else
    {
        // TODO: Do nothing?
        Console.WriteLine("No text on clipboard");
    }
}

I tried the example given below and it does work. However when I try and do this the horizontal scroll bar expands and contracts briefly but the table in the subclassed DataGridViewis not updated.
The column is however in the DataTablethough - for example I cannot add a column of the same name, the column count reflects the extra columns too. Is there perhaps a designer option that might constrict the updating of the DataGridView?

我尝试了下面给出的示例,它确实有效。但是,当我尝试执行此操作时,水平滚动条会短暂​​地展开和收缩,但子类中的表格DataGridView并未更新。
然而,该列DataTable仍在 - 例如,我无法添加同名列,列数也反映了额外的列。是否有可能限制更新的设计器选项DataGridView

Also adding rows works fine.

添加行也可以正常工作。

SOLVED:

解决了:

The AutoGeneratedColumnswas set to falsein the designer code despite being truein the properties dialog (and set explicitly in the code). The initial columns were generated programmatically and so should not have appeared however this was not picked up on since the designer code also continued to generate 'designed in' columns that were originally used for debugging.

AutoGeneratedColumns被设置为false尽管是在设计器代码true在属性对话框中(和在代码中明确设置)。初始列是以编程方式生成的,因此不应该出现,但是由于设计器代码还继续生成最初用于调试的“设计中”列,因此并未采用这种方式。

Moral: Check the autogenerated code!

道德:检查自动生成的代码!

In addition to this, see this postand this post

除此之外,请参阅此帖子此帖子

采纳答案by BFree

This doesn't sound right. To test it out, I wrote a simple app, that creates a DataTable and adds some data to it.
On the button1.Clickit binds the table to the DataGridView.
Then, I added a second button, which when clicked, adds another column to the underlying DataTable.
When I tested it, and I clicked the second button, the grid immedialtey reflected the update.

这听起来不对。为了测试它,我编写了一个简单的应用程序,它创建了一个 DataTable 并向其中添加了一些数据。
button1.Click它将表绑定到 DataGridView。
然后,我添加了第二个按钮,当单击该按钮时,会将另一列添加到基础 DataTable。
当我测试它并单击第二个按钮时,网格立即反映了更新。

To test the reverse, I added a third button which pops up a dialog with a DataGridView that gets bound to the same DataTable. At runtime, I then added some values to the first DataGridView, and when I clicked the button to bring up the dialog, the changes were reflected.

为了测试相反的情况,我添加了第三个按钮,它会弹出一个对话框,其中包含绑定到同一个 DataTable 的 DataGridView。在运行时,我向第一个 DataGridView 添加了一些值,当我单击按钮打开对话框时,这些更改就被反映了。

My point is, they are supposed to stay concurrent. Mark may be right when he suggested you check if AutoGenerateColumnsis set to true. You don't need to call DataBind though, that's only for a DataGridView on the web. Maybe you can post of what you're doing, because this SHOULD work.

我的观点是,它们应该保持并发。马克可能是对的,他建议您检查是否AutoGenerateColumns设置为true。不过,您不需要调用 DataBind,这仅适用于 Web 上的 DataGridView。也许你可以发布你在做什么,因为这应该有效。

How I tested it:

我是如何测试的:

DataTable table = new DataTable();
public Form1()
{
    InitializeComponent();
}

private void button1_Click(object sender, EventArgs e)
{
    table.Columns.Add("Name");
    table.Columns.Add("Age", typeof(int));
    table.Rows.Add("Alex", 26);
    table.Rows.Add("Jim", 36);
    table.Rows.Add("Bob", 34);
    table.Rows.Add("Mike", 47);
    table.Rows.Add("Joe", 61);

    this.dataGridView1.DataSource = table;
}

private void button2_Click(object sender, EventArgs e)
{
    table.Columns.Add("Height", typeof(int));
    foreach (DataRow row in table.Rows)
    {
        row["Height"] = 100;
    }
}

private void button3_Click(object sender, EventArgs e)
{
    GridViewer g = new GridViewer { DataSource = table };
    g.ShowDialog();
}

public partial class GridViewer : Form //just has a DataGridView on it
{
    public GridViewer()
    {
    InitializeComponent();
    }

    public object DataSource
    {
        get { return this.dataGridView1.DataSource; }
        set { this.dataGridView1.DataSource = value; }
    }
}

回答by Mark Brittingham

Is AutoGenerateColumns set to true? If you are making changes after the initial Binding you'll also have to call DataBind() to rebind the changed datasource. I know this is true for an AJAX callback, I thinkit is true for a WinForms control PostBack.

AutoGenerateColumns 是否设置为 true?如果您在初始绑定后进行更改,您还必须调用 DataBind() 来重新绑定更改后的数据源。我知道这对于 AJAX 回调是正确的,我认为对于 WinForms 控件 PostBack 也是如此。

回答by SnapJag

I had the same issue and I issued a DataBind(). It's not the silver bullet for everything, but it's what helped me in a few cases. I had to put it in before capturing information through a DataView, after the EditCommand and UpdateCommand events immediately after the EditItemIndex statement,

我有同样的问题,我发出了一个 DataBind()。这不是万能的灵丹妙药,但它在某些情况下对我有所帮助。我必须在通过 DataView 捕获信息之前,在 EditCommand 和 UpdateCommand 事件之后立即将其放入 EditItemIndex 语句之后,

protected void datalistUWSolutions_EditCommand(object source, DataListCommandEventArgs e)
{
  datalistUWSolutions.EditItemIndex = e.Item.ItemIndex;
  datalistUWSolutions.DataBind(); // refresh the grid.
}

and

protected void datalistUWSolutions_UpdateCommand(object source, DataListCommandEventArgs e)
{
  objDSSolutions.UpdateParameters["Name"].DefaultValue = ((Label)e.Item.FindControl("lblSolutionName")).Text;
  objDSSolutions.UpdateParameters["PriorityOrder"].DefaultValue = ((Label)e.Item.FindControl("lblOrder")).Text;
  objDSSolutions.UpdateParameters["Value"].DefaultValue = ((TextBox)e.Item.FindControl("txtSolutionValue")).Text;
  objDSSolutions.Update();
  datalistUWSolutions.EditItemIndex = -1; // Release the edited record
  datalistUWSolutions.DataBind();         // Redind the records for refesh the control
}

回答by LennonLam

Here is another solution which you dont need to assign a "name" to the data grid, so that the data grid can be a template element.

这是另一个不需要为数据网格分配“名称”的解决方案,因此数据网格可以是模板元素。

(In my case, data grid is a template element inside TabControl.ContentTemplate)

(就我而言,数据网格是 TabControl.ContentTemplate 中的模板元素)

The key to show the new column (added programmatically after the initial binding) is forcing the datagrid to refresh. From the answer in Force WPF DataGrid to regenerate itself, Andres suggested setting AutoGenerateColumnsfrom false to true will force datagrid to refresh.

显示新列(在初始绑定后以编程方式添加)的关键是强制刷新数据网格。从Force WPF DataGrid to regenerate self 中的答案来看,Andres 建议将AutoGenerateColumnsfalse设置为 true 将强制刷新 datagrid。

Which means I simply need to:

这意味着我只需要:

  1. Bind the AutoGenerateColumnsto a property of my object
  2. Set the propery to false before adding column
  3. Set the propery to true after added column.
  1. 将 绑定AutoGenerateColumns到我的对象的属性
  2. 在添加列之前将属性设置为 false
  3. 添加列后将属性设置为true。

Here is the code:

这是代码:

XAML:

XAML:

<DataGrid AutoGenerateColumns="{Binding toggleToRefresh}"
          ItemsSource="{Binding dataTable}"
          />

C#:

C#:

 public class MyTabItem : ObservableObject 
 {
        private DataTable _dataTable = new DataTable();
        public DataTable dataTable
        {
            get { return _dataTable; }
        }

        private bool _toggleToRefresh = true;
        public bool toggleToRefresh
        {
            get { return _toggleToRefresh; }
            set
            {
                if (_toggleToRefresh != value)
                {
                    _toggleToRefresh = value;
                    RaisePropertyChanged("toggleToRefresh");
                }
            }
        }

        public void addDTColumn()
        {
            toggleToRefresh = false;
            string newColumnName = "x" + dataTable.Columns.Count.ToString();
            dataTable.Columns.Add(newColumnName, typeof(double));
            foreach (DataRow row in dataTable.Rows)
            {
                row[newColumnName] = 0.0;
            }
            toggleToRefresh = true;
        }

        public void addDTRow()
        {
            var row = dataTable.NewRow();
            foreach (DataColumn col in dataTable.Columns)
            {
                row[col.ColumnName] = 0.0;
            }
            dataTable.Rows.Add(row);
        }
 }

Hope this help :)

希望这有帮助:)