提问人:Heri 提问时间:3/22/2021 更新时间:3/22/2021 访问量:375
向 DataGridView 添加行时出现 NullReferenceException
NullReferenceException while adding row to DataGridView
问:
我在 C# 中添加行很重要,我试图添加一个 as 参数,但总是同样的问题,现在这是我的最终代码,它不再起作用,总是:DataGridView
String[]
DataGridView.Rows.Add
NullReferenceException
{
ConnectDB con = new ConnectDB();
CrudDB db = new CrudDB();
try
{
DispoProf disp = new DispoProf(res.ID);
con.Connexion.Open();
List<DispoProf> liste = db.Find("dispoprof", disp, "", con.Connexion);
for (int i = 0; i < liste.Count; i += 1)
{
//string[] ligne = { liste[i].date, liste[i].heureDebut, liste[i].heureFin, null, null };
dataGridViewListerDV.Rows.Add(liste[i].date, liste[i].heureDebut, liste[i].heureFin, null, null);
}
}
catch(Exception ex)
{
Console.WriteLine("Exception :: {0} :: {1} :: {2}",ex.Message, ex.Source , ex.StackTrace);
}
finally
{
con.Connexion.Close();
}
}
它抛出一个 NullReferenceException
dataGridViewListerDV.Rows.Add(liste[i].date, liste[i].heureDebut, liste[i].heureFin, null, null);
答:
你确定 every 不是 null,并且每个 liste[i] 的每个开始小时和每个结束小时都不是 null 吗?如果这些值中的任何一个为 null,您希望显示什么?liste[i]
唉,你忘了告诉我们 的返回值,但我很确定要么等于 null,要么属性 et 可以为 nulldb.Find(...)
liste[i]
heureDebut
heureFin
Datetime
如果已将单元格定义为可以显示可为 null 的日期时间,请考虑更改代码:
var itemToDisplay = liste[i];
if (itemToDisplay != null)
{
dataGridViewListerDV.Rows.Add(itemToDisplay.date,
itemToDisplay.heureDebut, itemToDisplay.heureFin, null, null);
}
else
{
// decide what to do if item equals null
}
此外,如果 HeureDebut/HeureFine 可能为 null,请考虑更改 DataGridViewColumns,以便它们可以显示可为 null 的 DateTime 而不是 DateTime。
还有改进的余地
初次使用 DataGridView 的用户倾向于直接修改 DataGridView 中的行和单元格。通过这样做,您可以将数据(您的模型)与此数据的显示方式(您的视图)交织在一起。
在相当长的一段时间里,有一种将这两者分开的趋势。如果将模型与视图分开,则可以轻松更改视图,而无需更改模型,例如,如果要在图形中显示数据,而不是在表格中显示数据,或者如果要将数据保存在 XML 文件而不是表格中,则无需更改模型。同样,如果不需要窗体来显示模型,则对模型进行单元测试要容易得多。
将模型与视图分开的第三个原因是,它使你可以自由地更改 DataGridView,而不必更改模型:您可以添加/删除列、更改 DateTime 的显示方式、为负值显示不同的颜色:所有这些更改都可以在不更改模型的情况下完成。
若要将模型和视图分开,需要在两者之间设置一个适配器类,用于将模型转换为所需的显示方式。此适配器通常称为 ViewModel。
如果几年后将使用 WPF 而不是窗体,您将看到,通过使用不同的语言来描述视图 (XAML),模型和视图之间的这种分离几乎是强制执行的。
但 Forms 也支持这种分离。
首先,您需要定义将在一行中显示的类。像这样的东西:
class WorkingHours
{
public DateTime Date {get; set;}
public TimeSpan? StartTime {get; set;}
public TimeSpan? EndTime {get; set;}
}
这样,可以确定 StartTime 和 EndTime 在同一天。如果您有夜班,请考虑:
class WorkingHours
{
public DateTime? StartTime {get; set;}
public DateTime? EndTime {get; set;}
}
但随后你会遇到问题:如果你没有 StartTime,要显示什么日期。在转换模型之前,请直接处理模型,以便明确定义属性:哪些值始终可用,哪些值可以为空,它们是否会超出范围?
使用 Visual Studio 设计器时,您可能已经定义了列。您的列有一个属性 DataPropertyName,它告诉您要显示的内容:
columnDate.DataPropertyName = nameof(WorkingHours.Date);
columnStartTime.DataPropertyName = nameof(WorkingHours.StartTime);
columnFinishTime.DataPropertyName = nameof(WorkingHours.EndTime);
如果 StartTime 和 EndTime 可能为 null,请考虑添加如何显示 null 值: 红色背景?或者只是一个“-”,也许什么都不显示?
看:因为你是分开你的模型和你的视图的,所以改变视图不会影响你的模型!
我们需要一种方法来获取您的数据。这是您在问题中的方法:
private IEnumerable<WorkingHours> GetWorkingHours(...)
{
using (var dbConnection = new ConnectedDb(...))
{
... // Create DbCommand, ExecuteQuery and use DbReader to fill WorkingHours
}
}
注意:如果您将来决定更改获取数据的方式,例如使用实体框架或 Dapper,或者从 XML 文件中读取工作时间,这是唯一会更改的地方?或者再次更改数据库布局:模型更改不会影响您的视图。
现在我们能够获取显示的数据,显示是一个语句:
this.dataGridView1.DataSource = GetWorkingHours(...).ToList();
瞧!所有获取的数据都会立即显示出来。
但是,这仅是显示。不监视更改。如果您想了解更改:添加/删除/更改行,则数据应位于实现 IBindingList 的对象中,例如 BindingList<T>
为此,我们需要一行代码:
private BindlingList<WorkingHours> DisplayedWorkingHours
{
get => (BindingList<WorkingHours>)this.dataGridView1.DataSource;
set => this.dataGridView1.DataSource = value;
}
因此,要显示您的数据:
void InitDisplayedData()
{
this.DisplayedWorkingHours = new BindingList<WorkingHours>(this.GetWorkingHours().ToList());
}
现在,运算符所做的每个更改都会在 bindingList 中自动更新。您不必读取“行”或“单元格”,只需等到操作员指示他完成了数据编辑,例如通过单击按钮:
private void OnButtonOk_Clicked(object sender, ...)
{
IReadOnlyCollection<WorkingHours> editedWorkingHours = this.DisplayedWorkingHours;
// Detect which items are added / removed / changed and process the changes:
this.ProcessEditedWorkingHours(editedWorkingHours);
}
再说一遍:你有没有看到,因为我将实际的数据处理与数据的显示方式分开,所以所有模型功能都可以在没有表单的情况下进行测试。如果您更改了数据的显示方式,则您的模型不必更改,如果您更改了模型,则显示也不必更改。
如果需要处理选定的行,请考虑为此添加功能:
private WorkingHours CurrentWorkingHours =>
(WorkingHours)this.dataGridView1.CurrentRow?.DataBoundItem;
private IEnumerable<WorkingHours> SelectedWorkingHours =>
this.dataGridView1.SelectedRows.Cast<DataGridViewRow>()
.Select(row => row.DataBoundItem)
.Cast<WorkingHours>();
}
结论
通过将模型与视图分离,可以更轻松地更改视图或模型,而不必更改另一个。在没有视图的情况下对模型进行单元测试更容易,如果出现问题,可以在没有实际数据库的情况下调试视图。
Model 和 View 之间的 ViewModel 适配器通常由几个单行方法组成。
简单的comme bonjour!
评论
liste[i]
dataGridViewListerDV
评论