向 DataGridView 添加行时出现 NullReferenceException

NullReferenceException while adding row to DataGridView

提问人:Heri 提问时间:3/22/2021 更新时间:3/22/2021 访问量:375

问:

我在 C# 中添加行很重要,我试图添加一个 as 参数,但总是同样的问题,现在这是我的最终代码,它不再起作用,总是:DataGridViewString[]DataGridView.Rows.AddNullReferenceException

        {
            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);

C# WinForms NullReferenceException DataGridViewRow(数据网格视图行)

评论

6赞 Caius Jard 3/22/2021
请确认您已经阅读了 what-is-a-nullreferenceexception-and-how-do-i-fix-it,理解它,应用了它的建议,并准确说明您无法使其工作的原因,否则这个问题最终将作为该问题的副本关闭。可能 liste[i] 为 null

答:

0赞 Harald Coppoolse 3/22/2021 #1

你确定 every 不是 null,并且每个 liste[i] 的每个开始小时和每个结束小时都不是 null 吗?如果这些值中的任何一个为 null,您希望显示什么?liste[i]

唉,你忘了告诉我们 的返回值,但我很确定要么等于 null,要么属性 et 可以为 nulldb.Find(...)liste[i]heureDebutheureFinDatetime

如果已将单元格定义为可以显示可为 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!

评论

0赞 Heri 3/22/2021
我试图在控制台中显示每个结果,但它们不是空的,所以我认为它谈到了这一点liste[i]dataGridViewListerDV