首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >C# Winforms DataGridView:通过用户输入操作数据并以编程方式导致第一次机会错误

C# Winforms DataGridView:通过用户输入操作数据并以编程方式导致第一次机会错误
EN

Stack Overflow用户
提问于 2021-07-12 11:27:32
回答 1查看 358关注 0票数 0

我有一个启用了输入的DataGridView绑定到一个BindingSource,该BindingSource有一个SortableBindingList作为DataSource。

没有与数据库的直接连接。该列表事先从数据库中读取一次。在用户完成编辑之后,他可以选择将更改的数据保存到数据库中或不保存。

该列表有两个字段:

  • "Type“(enum)
  • "Path”(string)

由于我希望用户使用ComboBox来选择“类型”,所以我添加了一个附加列"TypeCbx“,该列绑定到枚举值。最初,列表中的所有"Type“值都被复制到列"TypeCbx”中,对于更改,"TypeCbx“的值在...CellEndEdit()事件中被复制回"Type”。

此外,我还有两个按钮栏:

  • “浏览”按钮:为用户打开一个FolderDialog,以便调整实际行并设置列"Path“,并(如果是新行/条目)一个默认值为FolderDialog按钮:从DGV

中删除该行/条目

只要我使用了一个未绑定的DataTable,在该DataTable中,按钮“Browse”的代码如下:

代码语言:javascript
运行
复制
private void dgvPaths_OpenFolderClick(DataGridView sender, DataGridViewCellEventArgs e) {
    string newSelectedPath = Helper.FileBrowserDialog("Select folder", LastSelectedPath);
    if (newSelectedPath != null) {
        LastSelectedPath = Helper.CleanPath(newSelectedPath);

        if (dgvPaths.Rows[e.RowIndex].IsNewRow) {
            // --- variante old: unbound datatable ----------------------------------------
            DataGridViewRow row = (DataGridViewRow)dgvPaths.Rows[0].Clone();
            row.Cells[dgvPaths.Columns["Path"].Index].Value = LastSelectedPath;
            row.Cells[dgvPaths.Columns["Type"].Index].Value = LibraryPathType.Movies;
            row.Cells[dgvPaths.Columns["TypeCbx"].Index].Value = LibraryPathType.Movies;
            row.Cells[dgvPaths.Columns["TypeCbx"].Index].ReadOnly = false;
            dgvPaths.Rows.Add(row);
            dgvPaths_CellValueChanged(sender, e);
        } else if (dgvPaths.Rows[e.RowIndex].Cells["Path"].Value == null || LastSelectedPath != dgvPaths.Rows[e.RowIndex].Cells["Path"].Value.ToString()) {
            dgvPaths.Rows[e.RowIndex].Cells["Path"].Value = LastSelectedPath;
            dgvPaths.Rows[e.RowIndex].Cells["TypeCbx"].ReadOnly = false;
            dgvPaths_CellValueChanged(sender, e);
        }
    }
}

现在,使用绑定的DataSource,行dgvPaths.Rows.Add(row);不再工作。因此,我调整了代码如下:

代码语言:javascript
运行
复制
            // --- variante new 1: bound list, working on dgv -----------------------------
            dgvPaths.Rows[e.RowIndex].Cells["Path"].Value = LastSelectedPath;
            dgvPaths.Rows[e.RowIndex].Cells["Type"].Value = LibraryPathType.Movies;
            dgvPaths.Rows[e.RowIndex].Cells["TypeCbx"].Value = LibraryPathType.Movies;
            dgvPaths.Rows[e.RowIndex].Cells["TypeCbx"].ReadOnly = false;
            dgvPaths_CellValueChanged(sender, e);

问题1:现在,数据被写入DataGridView的行中,但是DataGridView并不将其解释为输入,因此没有真正添加它--它仍然是等待输入的“新行”。我需要手动进入行的Path列并按一个键,以便DataGridView接受它作为一个有效的条目,并显示一个新的“新行”行。

=>如何通知DataGridView,以编程方式输入的数据应该像用户输入一样处理?

问题2:此外,当我在DataGridView中手动输入一个条目并在添加的行和新的“新行”行之间的"Path“列中单击时,会引发第一次机会异常。

=>第一次机会例外的原因是什么?

然后,我读到您不应该操作DataGridView,而应该操作BindingSouce或BindingSource.DataSource,我尝试将代码更改为:

代码语言:javascript
运行
复制
            // --- variante new 2: bound list, working on datasource ----------------------
            Library.Current.AddDirtyPath(LibraryPathType.Movies, LastSelectedPath);

问题3:在此,当这个条目被添加到源列表时,我也会得到一个第一次的异常。

=>第一次机会例外的原因是什么?

这里的正确方法是什么?

=>是否需要操作DataGridView的行、BindingSource的条目或BindingSource.DataSource的条目?

问题4:绑定DataGridView在加载DataSource时抛出了另一个异常,并且"0“没有枚举值(我猜是"new”行)。因此,我需要将一个设置为"0“的虚拟枚举值添加到我的枚举值列表中,对于实际的ComboBox选择值,我需要再次跳过该列表。它起作用了,但它把代码搞砸了。

=>有可能完全避免这个虚拟值吗?

在阅读了Caius的建议后,我决定更新这个问题,因为我能够遵循SBL方法并减少问题。

这里正确的方法是对所有工作数据使用一个主要的“存储”SBL (最初通过读取DB来填充),并从其中创建一个过滤的SBL,该SBL用作DGV的DS,用户可以在其中工作并使用排序和筛选方法。通过添加/更新/删除数据,您必须确保“存储”SBL中的所有调整保持同步。然后,当您想更新DB时,使用“存储”SBL。

因此,只剩下一个问题:如果您想在DGV本身中添加一个带有按钮列函数的新行。在从DGV内部调用add函数时,当实际的新行的状态发生变化时,它将失败。有两种“修复”方法:

( a)您需要完全(!)将用作DGV的DS的SBL添加到这个SBL中(按照“存储”列表中的所有其他现有行)。因此,新行的状态也被更改,但是当它被完全删除时,状态也被清除。在此之后,您需要Refresh()的DGV。在此,你将失去实际细胞的焦点。

b)通过btnAdd.PerformClick()在DGV之外使用一个隐藏按钮,该按钮调用add函数,在该函数中不需要Clear()绑定到DGV的SBL。这似乎很奇怪,但它很有效(为什么),您将注意力集中在“新行”行(而不是添加的行)上。

所有其他功能,如更新和删除现有的DGV行,都可以由DGV本身中的其他按钮列调用,而不会出现问题。

枚举问题不是一个问题,因为对空条目使用虚拟零值是常见的。如果我发现,如何摆脱虚拟值,我将相应地更新这个问题。

为了填充combobox列,而不是在display列和value列之间进行分割,只需确保combobox列的DataPropertyName设置为DB列名。

--U P D A T E: 2

我创建了一个关于我的方法的详细视频(教程):https://www.youtube.com/watch?v=W_afaNf7nz8

从1:31:20开始,我展示了通过外部按钮和内部按钮添加新行之间的区别,以及直接使用该方法(=>错误)或触发使用相同方法的外部按钮(=>无错误)的奇怪行为。

EN

回答 1

Stack Overflow用户

发布于 2021-07-15 05:39:00

这里的正确方法是什么?

你有一个数据视图

将其DataSource绑定到datatable

您有一个datagridviewcombobox

将它的DataSource绑定到一个完全不同的数据表

您可以从它自己的datatable中告诉组合体哪些列将用于显示,以及网格绑定到的表中的哪个列将被更新/用于决定显示哪个值。

代码语言:javascript
运行
复制
var gdt = new DataTable();
gdt.Columns.Add("FileType", typeof(FileType));//enum
gdt.Columns.Add("Path");

gdt.Rows.Add(FileType.Text, "c:\my.txt");

var fdt = new DataTable();
fdt.Columns.Add("Val", typeof(FileType));//enum
fdt.Columns.Add("Disp");

foreach(FileType t in Enum.GetValues<FileType>())
{
  fdt.Rows.Add(t, t.ToString());
}

//now wire it up
datagridviewWhatever.DataSource = gdt; //makes columns 

var c = new DataGridViewComboBoxColumn();
c.DisplayMember = "Disp"; //name of column in fdt to use for show
c.ValueMember = "Val"; //name of column in fdt to use for value during lookup/set operations
c.DataPropertyName = "FileType"; //name of column in gdt that this combo shall show/set
c.DataSource = fdt; //set DataSource last (performance reasons)

组合体现在将为每一行获取gdt.FileType (例如FileType.Text)中的值,在它自己的表fdt.Val中查找值,使用fdt.Disp中的相关值(例如"Text")在列表中显示。如果用户选择了一个新的列表项(例如"Excel"),它就会选择相应的Val (例如FileType.Excel)并将其推入gdt.FileType中。

实际上,我从来没有用枚举类型的值来实现它;无法理解为什么它不能工作,但是如果它带来了麻烦,那么切换到使用int而不是FileTypes可能更简单--让您的typed ()调用typed of (Int),并在将其添加到行集合时将FileType t转换为int。

最后,当您认为这是一个概念时,我建议抛弃它,使用视觉设计器来完成它--它将使它的使用变得更好、更好、更容易:

  • 向项目
  • 添加一个DataSet类型文件,如果有数据库支持所有这些,则打开
  • ,右键单击设计图面并选择添加TableAdapter,输入连接字符串,选择返回行、放置SELECT * FROM MovieFiles WHERE ID = @id或其他查询,命名为FillById/GetDataById,如果没有支持所有这些操作的数据库,则完成
  • ,添加一个名为MovieFiles的数据表,右键单击它并添加像FileType这样的列(在属性网格中设置为int类型)、路径等H 212H 113右击空格,然后添加另一个表,称为FileTypes,放置一个名为Disp的字符串列和一个名为Val的int列(听起来像familiar?)
  • save,然后切换到)。打开数据源工具面板(查看菜单..。其他窗口submenu)
  • Drag将MovieFiles节点从数据源中取出并放入表单中。在底部托盘中会出现一个数据视图以及一堆东西。网格已经正确绑定到包含DataSet表的MovieFiles。您可以在Forms.Designer中看到VS为您编写的所有代码。它看起来很像我手动放在
  • 编辑datagridview的列之上的代码,将文件类型列更改为组合框类型,将DataSource设置为FileTypes表,DisplayMember设置为Disp,ValueMemberto Val。检查电影中的DataPropertyName仍然是FileType --这应该很熟悉,因为它是上面代码的可视版本)
  • 唯一要做的就是将一些代码放在构造函数中,从枚举中填充FileTypes表(或者诚实地说,只需在DB中创建一个名为FileTypes的表,有一个int和一个字符串列,添加一个制表符,然后选择它们。)这样,当您添加文件类型时,就不必重新编译整个程序了)

为什么我提倡使用这些可视化设计的数据,而不是代码?因为他们在每个方面都要好得多。它们有逻辑方法和列的命名属性,它们只是使用LINQ,它们的行为就像.net类,它们是POCOs的集合,而不是由字符串索引的object的2D数组,需要转换才能使它们有用。与之相比,使用DataTable是很糟糕的。它们包围了基本的DataTable,并且确实公开了它们,但是遵循这样一条规则:如果您公开了它,那么您可能出错了。

代码语言:javascript
运行
复制
//no; using .Tables puts you back in "world of pain"
var p = (string)datasetX.Tables["MovieTypes"].Rows.Cast<DataRow>().First()["Path"];

//yes - using named property for MovieTypes is good, LINQ works, Path is a string property etc
var p = datasetX.MovieFiles.First().Path;
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/68346589

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档