我有一个启用了输入的DataGridView绑定到一个BindingSource,该BindingSource有一个SortableBindingList作为DataSource。
没有与数据库的直接连接。该列表事先从数据库中读取一次。在用户完成编辑之后,他可以选择将更改的数据保存到数据库中或不保存。
该列表有两个字段:
由于我希望用户使用ComboBox来选择“类型”,所以我添加了一个附加列"TypeCbx“,该列绑定到枚举值。最初,列表中的所有"Type“值都被复制到列"TypeCbx”中,对于更改,"TypeCbx“的值在...CellEndEdit()事件中被复制回"Type”。
此外,我还有两个按钮栏:
中删除该行/条目
只要我使用了一个未绑定的DataTable,在该DataTable中,按钮“Browse”的代码如下:
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);
不再工作。因此,我调整了代码如下:
// --- 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,我尝试将代码更改为:
// --- 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开始,我展示了通过外部按钮和内部按钮添加新行之间的区别,以及直接使用该方法(=>错误)或触发使用相同方法的外部按钮(=>无错误)的奇怪行为。
发布于 2021-07-15 05:39:00
这里的正确方法是什么?
你有一个数据视图
将其DataSource绑定到datatable
您有一个datagridviewcombobox
将它的DataSource绑定到一个完全不同的数据表
您可以从它自己的datatable中告诉组合体哪些列将用于显示,以及网格绑定到的表中的哪个列将被更新/用于决定显示哪个值。
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。
最后,当您认为这是一个概念时,我建议抛弃它,使用视觉设计器来完成它--它将使它的使用变得更好、更好、更容易:
SELECT * FROM MovieFiles WHERE ID = @id
或其他查询,命名为FillById/GetDataById,如果没有支持所有这些操作的数据库,则完成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,并且确实公开了它们,但是遵循这样一条规则:如果您公开了它,那么您可能出错了。
//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;
https://stackoverflow.com/questions/68346589
复制相似问题