C#,从 DataGridView 填充 iTextSharp PDF 时出现空异常问题

C#, Null Exception Problem while Populating iTextSharp PDF from DataGridView

终极菜鸟的问候。

我正在使用 C# 在 WinForms 中为我的地质学家团队准备时间 sheet 计划程序,但在从 DataGridView 值填充 PDF 表时遇到问题。

我的计划中有一个每月的时间表。当您单击 Get Excel(我将其更改为 PDF)时,程序会根据 DataGridView 值创建一个 pdf 文件。如果您有 31 天的完整数据网格视图,则没有问题。但是当这个月有30天或者用户想在月底之前获取PDF时,它会给出空异常。

代码在这里。

#region Functions


    DataTable MakeDataTable()
    {
        //Create time sheet table object
        DataTable dtgdata = new DataTable();

        //Define columns
        dtgdata.Columns.Add("Date");
        dtgdata.Columns.Add("Type of Work");
        dtgdata.Columns.Add("Contract");
        dtgdata.Columns.Add("Personnel ID");

       


        #region PopulateData



        string date_1 = dtgList.Rows[0].Cells[1].Value.ToString();
        string date_2 = dtgList.Rows[1].Cells[1].Value.ToString();
        string date_3 = dtgList.Rows[2].Cells[1].Value.ToString();
        string date_4 = dtgList.Rows[3].Cells[1].Value.ToString();
        string date_5 = dtgList.Rows[4].Cells[1].Value.ToString();
        string date_6 = dtgList.Rows[5].Cells[1].Value.ToString();
        string date_7 = dtgList.Rows[6].Cells[1].Value.ToString();
        string date_8 = dtgList.Rows[7].Cells[1].Value.ToString();
        string date_9 = dtgList.Rows[8].Cells[1].Value.ToString();
        string date_10 = dtgList.Rows[9].Cells[1].Value.ToString();
        string date_11 = dtgList.Rows[10].Cells[1].Value.ToString();
        string date_12 = dtgList.Rows[11].Cells[1].Value.ToString();
        string date_13 = dtgList.Rows[12].Cells[1].Value.ToString();
        string date_14 = dtgList.Rows[13].Cells[1].Value.ToString();
        string date_15 = dtgList.Rows[14].Cells[1].Value.ToString();
        string date_16 = dtgList.Rows[15].Cells[1].Value.ToString();
        string date_17 = dtgList.Rows[16].Cells[1].Value.ToString();
        string date_18 = dtgList.Rows[17].Cells[1].Value.ToString();
        string date_19 = dtgList.Rows[18].Cells[1].Value.ToString();
        string date_20 = dtgList.Rows[19].Cells[1].Value.ToString();
        string date_21 = dtgList.Rows[20].Cells[1].Value.ToString();
        string date_22 = dtgList.Rows[21].Cells[1].Value.ToString();
        string date_23 = dtgList.Rows[22].Cells[1].Value.ToString();
        string date_24 = dtgList.Rows[23].Cells[1].Value.ToString();
        string date_25 = dtgList.Rows[24].Cells[1].Value.ToString();
        string date_26 = dtgList.Rows[25].Cells[1].Value.ToString();
        string date_27 = dtgList.Rows[26].Cells[1].Value.ToString();
        string date_28 = dtgList.Rows[27].Cells[1].Value.ToString();
        string date_29 = dtgList.Rows[28].Cells[1].Value.ToString();
        string date_30 = dtgList.Rows[29].Cells[1].Value.ToString();
        string date_31 = dtgList.Rows[30].Cells[1].Value.ToString();


        ////////////////////////////////////////////////////////////



        string work_1 = dtgList.Rows[0].Cells[3].Value.ToString();
        string work_2 = dtgList.Rows[1].Cells[3].Value.ToString();
        string work_3 = dtgList.Rows[2].Cells[3].Value.ToString();
        string work_4 = dtgList.Rows[3].Cells[3].Value.ToString();
        string work_5 = dtgList.Rows[4].Cells[3].Value.ToString();
        string work_6 = dtgList.Rows[5].Cells[3].Value.ToString();
        string work_7 = dtgList.Rows[6].Cells[3].Value.ToString();
        string work_8 = dtgList.Rows[7].Cells[3].Value.ToString();
        string work_9 = dtgList.Rows[8].Cells[3].Value.ToString();
        string work_10 = dtgList.Rows[9].Cells[3].Value.ToString();
        string work_11 = dtgList.Rows[10].Cells[3].Value.ToString();
        string work_12 = dtgList.Rows[11].Cells[3].Value.ToString();
        string work_13 = dtgList.Rows[12].Cells[3].Value.ToString();
        string work_14 = dtgList.Rows[13].Cells[3].Value.ToString();
        string work_15 = dtgList.Rows[14].Cells[3].Value.ToString();
        string work_16 = dtgList.Rows[15].Cells[3].Value.ToString();
        string work_17 = dtgList.Rows[16].Cells[3].Value.ToString();
        string work_18 = dtgList.Rows[17].Cells[3].Value.ToString();
        string work_19 = dtgList.Rows[18].Cells[3].Value.ToString();
        string work_20 = dtgList.Rows[19].Cells[3].Value.ToString();
        string work_21 = dtgList.Rows[20].Cells[3].Value.ToString();
        string work_22 = dtgList.Rows[21].Cells[3].Value.ToString();
        string work_23 = dtgList.Rows[22].Cells[3].Value.ToString();
        string work_24 = dtgList.Rows[23].Cells[3].Value.ToString();
        string work_25 = dtgList.Rows[24].Cells[3].Value.ToString();
        string work_26 = dtgList.Rows[25].Cells[3].Value.ToString();
        string work_27 = dtgList.Rows[26].Cells[3].Value.ToString();
        string work_28 = dtgList.Rows[27].Cells[3].Value.ToString();
        string work_29 = dtgList.Rows[28].Cells[3].Value.ToString();
        string work_30 = dtgList.Rows[29].Cells[3].Value.ToString();
        string work_31 = dtgList.Rows[30].Cells[3].Value.ToString();


        ///////////////////////////////////////////////////////////

        string contract_1 = dtgList.Rows[0].Cells[4].Value.ToString();
        string contract_2 = dtgList.Rows[1].Cells[4].Value.ToString();
        string contract_3 = dtgList.Rows[2].Cells[4].Value.ToString();
        string contract_4 = dtgList.Rows[3].Cells[4].Value.ToString();
        string contract_5 = dtgList.Rows[4].Cells[4].Value.ToString();
        string contract_6 = dtgList.Rows[5].Cells[4].Value.ToString();
        string contract_7 = dtgList.Rows[6].Cells[4].Value.ToString();
        string contract_8 = dtgList.Rows[7].Cells[4].Value.ToString();
        string contract_9 = dtgList.Rows[8].Cells[4].Value.ToString();
        string contract_10 = dtgList.Rows[9].Cells[4].Value.ToString();
        string contract_11 = dtgList.Rows[10].Cells[4].Value.ToString();
        string contract_12 = dtgList.Rows[11].Cells[4].Value.ToString();
        string contract_13 = dtgList.Rows[12].Cells[4].Value.ToString();
        string contract_14 = dtgList.Rows[13].Cells[4].Value.ToString();
        string contract_15 = dtgList.Rows[14].Cells[4].Value.ToString();
        string contract_16 = dtgList.Rows[15].Cells[4].Value.ToString();
        string contract_17 = dtgList.Rows[16].Cells[4].Value.ToString();
        string contract_18 = dtgList.Rows[17].Cells[4].Value.ToString();
        string contract_19 = dtgList.Rows[18].Cells[4].Value.ToString();
        string contract_20 = dtgList.Rows[19].Cells[4].Value.ToString();
        string contract_21 = dtgList.Rows[20].Cells[4].Value.ToString();
        string contract_22 = dtgList.Rows[21].Cells[4].Value.ToString();
        string contract_23 = dtgList.Rows[22].Cells[4].Value.ToString();
        string contract_24 = dtgList.Rows[23].Cells[4].Value.ToString();
        string contract_25 = dtgList.Rows[24].Cells[4].Value.ToString();
        string contract_26 = dtgList.Rows[25].Cells[4].Value.ToString();
        string contract_27 = dtgList.Rows[26].Cells[4].Value.ToString();
        string contract_28 = dtgList.Rows[27].Cells[4].Value.ToString();
        string contract_29 = dtgList.Rows[28].Cells[4].Value.ToString();
        string contract_30 = dtgList.Rows[29].Cells[4].Value.ToString();
        string contract_31 = dtgList.Rows[30].Cells[4].Value.ToString();


        /////////////////////////////

        string p_id = txtPersonelID.Text;



        //Populate with DataGridView
        dtgdata.Rows.Add($"{date_1}", $"{work_1}", $"{contract_1}", $"{p_id}");
        dtgdata.Rows.Add($"{date_2}", $"{work_2}", $"{contract_2}", $"{p_id}");
        dtgdata.Rows.Add($"{date_3}", $"{work_3}", $"{contract_3}", $"{p_id}");
        dtgdata.Rows.Add($"{date_4}", $"{work_4}", $"{contract_4}", $"{p_id}");
        dtgdata.Rows.Add($"{date_5}", $"{work_5}", $"{contract_5}", $"{p_id}");
        dtgdata.Rows.Add($"{date_6}", $"{work_6}", $"{contract_6}", $"{p_id}");
        dtgdata.Rows.Add($"{date_7}", $"{work_7}", $"{contract_7}", $"{p_id}");
        dtgdata.Rows.Add($"{date_8}", $"{work_8}", $"{contract_8}", $"{p_id}");
        dtgdata.Rows.Add($"{date_9}", $"{work_9}", $"{contract_9}", $"{p_id}");
        dtgdata.Rows.Add($"{date_10}", $"{work_10}", $"{contract_10}", $"{p_id}");
        dtgdata.Rows.Add($"{date_11}", $"{work_11}", $"{contract_11}", $"{p_id}");
        dtgdata.Rows.Add($"{date_12}", $"{work_12}", $"{contract_12}", $"{p_id}");
        dtgdata.Rows.Add($"{date_13}", $"{work_13}", $"{contract_13}", $"{p_id}");
        dtgdata.Rows.Add($"{date_14}", $"{work_14}", $"{contract_14}", $"{p_id}");
        dtgdata.Rows.Add($"{date_15}", $"{work_15}", $"{contract_15}", $"{p_id}");
        dtgdata.Rows.Add($"{date_16}", $"{work_16}", $"{contract_16}", $"{p_id}");
        dtgdata.Rows.Add($"{date_17}", $"{work_17}", $"{contract_17}", $"{p_id}");
        dtgdata.Rows.Add($"{date_18}", $"{work_18}", $"{contract_18}", $"{p_id}");
        dtgdata.Rows.Add($"{date_19}", $"{work_19}", $"{contract_19}", $"{p_id}");
        dtgdata.Rows.Add($"{date_20}", $"{work_20}", $"{contract_20}", $"{p_id}");
        dtgdata.Rows.Add($"{date_21}", $"{work_21}", $"{contract_21}", $"{p_id}");
        dtgdata.Rows.Add($"{date_22}", $"{work_22}", $"{contract_22}", $"{p_id}");
        dtgdata.Rows.Add($"{date_23}", $"{work_23}", $"{contract_23}", $"{p_id}");
        dtgdata.Rows.Add($"{date_24}", $"{work_24}", $"{contract_24}", $"{p_id}");
        dtgdata.Rows.Add($"{date_25}", $"{work_25}", $"{contract_25}", $"{p_id}");
        dtgdata.Rows.Add($"{date_26}", $"{work_26}", $"{contract_26}", $"{p_id}");
        dtgdata.Rows.Add($"{date_27}", $"{work_27}", $"{contract_27}", $"{p_id}");
        dtgdata.Rows.Add($"{date_28}", $"{work_28}", $"{contract_28}", $"{p_id}");
        dtgdata.Rows.Add($"{date_29}", $"{work_29}", $"{contract_29}", $"{p_id}");
        dtgdata.Rows.Add($"{date_30}", $"{work_30}", $"{contract_30}", $"{p_id}");
        dtgdata.Rows.Add($"{date_31}", $"{work_31}", $"{contract_31}", $"{p_id}");


        #endregion


        return dtgdata;
    }
    #endregion

问题是;

当 DataGridView 行由于某种原因为空时;代码给出空异常。

我想做什么?

我想检查这些值是否为空,如果它们为空,则将它们转换为字符串(如“n/a”)。

我想我需要使用 foreach,但我不知道该怎么做。

如果您尝试通过访问单元格来获取 datagridview 中的数据,那您就过不去。

通常更容易告诉 DataGridView 它将显示哪种项目,并告诉每一列它应该显示的 属性 的名称。最后,您为 datagridView 提供了一个集合,其中包含它应该显示的项目。

当操作员编辑数据网格视图、添加和删除行、更改值时,您什么都不做。一旦操作员通知您他已完成编辑,例如通过单击“立即应用”或“确定”按钮,您就可以获取数据源。您可以确定它是 up-to-date,您不必自己从单元格中读取值。

class AssignedWork
{
    public DateTime Date {get; set;}
    public string WorkType {get; set;}     // can you make this an enum?
    public string Contract {get; set;}
    public int PersonnelId {get; set;}
}

使用visual studio 设计器添加列。设置数据属性名称

var columnDate = new DataGridViewColumn
{
    ...
    DataPropertyName = nameof(AssignedWork.Date),
}
var columnPersonnelId = new DataGridViewColumn
{
    ...
    DataPropertyName = nameof(AssignedWork.PersonnelId),
}
var columnWorkType : new DataGridViewComboBoxColumn()
{
    ...
    DataPropertyName = nameof(AssignedWork.WorkType),
}

现在获取要显示的数据,并将其放入BindingList,或者从一个空的DataGridView开始:

IList<AssignedWork> initialData = FetchInitialData();
BindingList<AssignedWork> displayedData = new BindingList<AssignedWork>(initialData);
this.dataGridView1.DataSource = displayedData;

您在列中指定了哪些列是可编辑的、列的顺序以及 属性 必须显示的格式,例如:您想如何显示日期时间?格式类似于 String.Format.

操作员可以添加/更新/删除行。删除/更新行时,您无需执行任何操作。

如果操作员添加新行,您必须用初始数据填充该行。

displayedData.AddingNew += OnAddingNewAssignedWork;

操作员添加新Row时,会调用下面的方法,可以为添加的Row指定初始内容。如果需要,您甚至可以显示一个对话框,操作员必须在其中 select 一个 PersonnelId。

void OnAddingNewAssignedWork(object sender, AddingNewEventArgs e)
{
    e.NewObject = new AssignedWork
    {
         Date = DateTime.Today,
         ...
    };
}

如果您不订阅该活动,将添加默认 new AssignedWork 作为新行

当操作员正在编辑单元格时,他可能想让单元格暂时处于无效格式,例如因为他想 copy-paste 单元格中的数据。是否允许无效数据由您决定。 您可以通过处理事件 CellValidating / RowValidating / Validating 来做到这一点。

CellValidating 是最简单的:您获得必须验证的单元格的索引,因此您可以获取单元格和单元格中的值。您知道该列,所以您知道其中应该包含什么:如果值不正确,请将 e.Cancel 设置为 true。

CellValidating 很容易,但不是很用户友好:操作员必须完成单元格的内容才能离开。他无法从 datagridview 中的另一行复制粘贴值。所以考虑 RowValidating 或 Validating。

如果您希望操作员在没有有效值时离开单元格/行/网格,请考虑禁用“确定”按钮,直到它具有所有有效值。您还可以为无效单元格的背景着色,以警告操作员该单元格具有无效值。

dataGridView1.RowValidating += ValidatingRow;

void ValidatingRow(object sender, DataGridViewCellCancelEventArgs e)
{
    // use e.RowIndex to get the Row, and check the validity of the Cells
    // color the BackGround of the Cells that contain invalid data
}
     

操作员完成所有编辑后,他可以点击一个按钮:

private void OnButtonOk_Clicked(object sender, ...)
{
    // Get the data from the datagridview and process it.
    // The updated data is already in the datasource:
    this.ProcessData(this.displayedData);

    // or if you forgot to remember the displayed data:
    BindingList<AssignedWork> displayedData = (BindingList<AssignedWork>)this.dataGridView.DataSource;
    this.ProcessData(this.displayedData);
}

void ProcessData(ICollection<AssignedWork> dataToProcess)
{
    foreach (AssignedWork assignedWork in dataToProcess)
    {
        ...
    }
}
    

不清楚 dtgList 是什么。它似乎是 DataGridView。我假设 dtgListDataGridView.

如果 dtgList 有一个 DataSource,那么您应该使用那个 DataSource 而不是在网格中循环。然而,如果网格没有 DataSource 那么你显然需要遍历网格来获取数据。

使用某种“bindingsource/datasource”几乎总是更好。这将使您的事情变得容易得多。学习如何使用数据源而不是“手动”将数据输入网格。

鉴于此,我假设 DataGridView dtgList 没有 DataSource 而您想从 DataGridView 中创建 DataTable .显然,您可以按照发布的代码中的路线进行操作;但是,您已经知道这种方法的问题……即少于 31 天的月份或 运行 31 天之前的代码将在当前状态下失败。

正如我评论的那样,您的代码是固定的,并且仅在 31 天时有效。这个明显的限制意味着您将不得不做其他事情,当然正如您所说的那样……“我想我需要使用 foreach……”……您是对的。这里需要一个 foreach 或一个简单的 for 循环。

目前图片和代码有些混乱,因为代码(不必要地)创建了 93 个变量:31 个日期、31 个工作和 31 个合同变量。这些变量来自网格 dtgList 中的单元格。但是,当代码将行添加到 DataTable 时,它使用文本框中的 p_id 作为“id”列?图片显示 id 存在于网格中,我不知道为什么这个值是从文本框而不是网格中获取的。这令人困惑。

我假设您想使用文本框中的值。

假设 DataGridView dtgList 没有 DataSource,那么下面的代码应该从给定的 DataGridView 产生一个 DataTable 然后添加一个“ID”列,并用文本框中的值填充该列中的每一行。

方法是这样的……首先创建一个空的 DataTable。接下来,开始 foreach 循环以在网格中添加“所有”列。此示例假定 ID 列不在网格中。将列添加到 table 后,将创建一个 DataRow 变量 newRow 以提供我们需要将各个网格单元格值添加到的变量。该行... newRow = dt.NewRow(); 将创建一个包含适当列的新行。我们会将网格中的值添加到该行,然后将该行添加到 table.

接下来,我们启动一个 for 循环来遍历网格中的所有行。我们可以为此使用 foreach 循环,但是,如果我们使用 for 循环,我们可以在代码后面使用 for 循环索引来引用网格行。

接下来检查网格中的当前行是否是“新行”……我们要忽略这一行而不是将其添加到 table。接下来,另一个 for 循环开始循环遍历每一行中的每一列。在这个循环中,我们要检查并确保网格单元格中的 Value 不是 null。如果出于任何原因,dgv.Rows[rowIndex].Cells[colIndex].Value 处的单元格值为 null,当我们尝试在 null 对象上调用 ToString() 方法时,代码将抛出异常。如果值是 null 我们简单地忽略它,因为它的值显然什么都没有……即空。

最后,我们将该行中每个单元格的值添加到我们之前创建的 newRow 中。然后最后将该行添加到 DataTable.

接下来,由于原始网格中不存在 ID 列,我们需要添加 ID 列,然后遍历 table 中的所有行并将值添加到中的每一行ID 列。然后最后 return DataTable

private DataTable GetDTFromGrid(DataGridView dgv, string p_id) {
  DataTable dt = new DataTable();
  foreach (DataGridViewColumn column in dgv.Columns) {
    dt.Columns.Add(column.Name);
  }
  DataRow newRow;
  for (int rowIndex = 0; rowIndex < dgv.Rows.Count; rowIndex++) {
    if (!dgv.Rows[rowIndex].IsNewRow) {
      newRow = dt.NewRow();
      for (int colIndex = 0; colIndex < dgv.Columns.Count; colIndex++) {
        if (dgv.Rows[rowIndex].Cells[colIndex].Value != null) {
          newRow[colIndex] = dgv.Rows[rowIndex].Cells[colIndex].Value.ToString();
        }
      }
      dt.Rows.Add(newRow);
    }
  }
  // remove this code if p_id is already in dgv
  dt.Columns.Add(new DataColumn("ID"));
  foreach (DataRow row in dt.Rows) {
    row["ID"] = p_id;
  }
  return dt;
}

为了测试和完成答案,下面是一个完整的例子。加载时,表单会用一些数据填充网格(在左侧)。请注意,网格没有数据源。添加了一个按钮来调用上面的方法。然后returned DataTable作为DataSource到第二个格子(右边)。

public Form1() {
  InitializeComponent();
}

private void Form1_Load(object sender, EventArgs e) {
  FillGrid(dataGridView1);
}

private void FillGrid(DataGridView dgv) {
  AddColToDGV(dgv, "Date");
  AddColToDGV(dgv, "Type");
  AddColToDGV(dgv, "Contract");
  //AddColToDGV(dgv, "ID");
  DateTime date = DateTime.Now;
  for (int i = 0; i < 10; i++) {
    date = date.Subtract(new TimeSpan(24, 0, 0));
    dgv.Rows.Add(date, "Type_" + i, "Contact" + i);
  }
}

private void AddColToDGV(DataGridView dgv, string name) {
  DataGridViewTextBoxColumn col = new DataGridViewTextBoxColumn();
  col.Name = name;
  dgv.Columns.Add(col);
}

private void button1_Click(object sender, EventArgs e) {
  DataTable dt = GetDTFromGrid(dataGridView1, txtPersonelID.Text);
  dataGridView2.DataSource = dt;
}