C# WinForms (.NET Framework) DataGridView with Button, Combobox and Text Box: 使用按钮添加新行添加组合框项目时出错

C# WinForms (.NET Framework) DataGridView with Button, Combo Box & Text Box: Errors adding New Row using Button to Add Combo Box Item

我有一个 Windows 表单应用程序 (.NET Framework) 来显示来自 SQL 数据库的记录。表单显示单个记录并逐条记录确定。每条记录可能有 none 或更多相关记录(磁盘上与父记录相关的文件列表)(在 M:M 关系中),因此我使用 DataGridView 来显示相关记录。 DGV 有 4 列:一个按钮列打开一个窗体,作为一个对话框将新文件添加到数据库的文件列表,第二个按钮列使用它通常的命令行打开文件,一个组合框列是绑定到所有文件的列表并显示文件名,以及一个显示文件类型的文本框列。

就显示而言一切正常,包括在浏览所有父记录、创建新父记录、删除父记录或更新父记录时。添加新行、删除行,甚至更新行中的选定文件,一切都正常。当尝试使用添加新按钮(第一个按钮列)添加列表中不存在的新文件时,问题就开始了。如果我在现有行上执行此操作,则没有问题,新文件会在下拉列中添加和选择,并且新文件的文件类型会显示在文本框列中。但是,我无法从新行开始这项工作,换句话说,我无法使用添加新按钮在 DGV 的新空白行中添加新文件。

我 运行 遇到的第一个错误是因为新行的 DataBoundItem 为空。由于那是只读 属性,我无法设置它。我尝试使用该过程以编程方式添加此处几篇文章中描述的新行,但出现错误,我无法以编程方式添加数据绑定的行。在这一点上,我真的不知道我错过了什么。

以下是代码的相关部分。选择父记录(或创建新记录)时,将调用 BindExperimentFilesDataGridView 方法:

private void BindExperimentFilesDataGridView(ExperimentModel currentExperiment)
    {
        // TODO -- set formatting properties for DataGridView
        List<FileModel> filesbyexperiment = _sql.GetFilesByExperimentId(currentExperiment.Id);
        _currentExperimentFileIds = new List<int>();
        foreach ( FileModel f in filesbyexperiment ) {
            _currentExperimentFileIds.Add(f.Id);
        }

        _experimentFiles = new BindingList<FileModel>(filesbyexperiment);
        experimentFilesDataGridView.AutoGenerateColumns = false;
        experimentFilesDataGridView.DataSource = _experimentFiles;
        FileNameColumn.DataSource = _allFiles;
        FileNameColumn.DataPropertyName = nameof(FileModel.FileName);
        FileNameColumn.DisplayMember = nameof(FileModel.FileName);
        FileNameColumn.ValueMember = nameof(FileModel.FileName);
        FileTypeColumn.DataPropertyName = nameof(FileModel.FileTypeName);
    }

单击添加新按钮是通过 CellClick 事件处理的:

private void ExperimentFilesDataGridView_CellClick(object sender, DataGridViewCellEventArgs e)
    {
        // ignore clicks that are not on button cells
        if ( e.RowIndex < 0 || e.ColumnIndex == addNewFileButtonColumn.Index ) {
            NewFileDialogForm newfiledialogform = new NewFileDialogForm(this, e.RowIndex);
            newfiledialogform.Show();
        }

        if ( e.RowIndex < 0 || e.ColumnIndex == openFileButtonColumn.Index ) {
            DataGridViewRow row = experimentFilesDataGridView.Rows[e.RowIndex];
            FileModel data = row.DataBoundItem as FileModel;
            OpenFile(data);
        }
    }

当 NewFileDialogForm returns 时,它调用父窗体的 SelectFiles 方法:

public void SelectFile(FileModel fileModel, int rowIndex)
    {
        DataGridViewRow row = experimentFilesDataGridView.Rows[rowIndex];
        DataGridViewCell cell = row.Cells[FileNameColumn.Index];
        cell.Value = fileModel.FileName;
    }

FileNameColumn(即ComboBoxColumn)中cell.Value的变化触发Cell Value Changed事件,其处理程序负责为Text Box列中的文件类型设置值: private void ExperimentFilesDataGridView_CellValueChanged(object sender, DataGridViewCellEventArgs e) { 如果 ( e.RowIndex < 0 || e.ColumnIndex < 0 ) { return; }

        DataGridViewRow row = experimentFilesDataGridView.Rows[e.RowIndex];
        DataGridViewCell cell = row.Cells[e.ColumnIndex];
        if ( cell is DataGridViewComboBoxCell ) {
            string newfilename = (string)cell.Value;
            FileModel newdatafilelink = _allFiles.Where(x => x.FileName == newfilename).FirstOrDefault();
            FileModel editedfilename = row.DataBoundItem as FileModel;
            editedfilename.Id = newdatafilelink.Id;
            editedfilename.FileName = newdatafilelink.FileName;
            editedfilename.FileTypeId = newdatafilelink.FileTypeId;
            editedfilename.FileType = newdatafilelink.FileType;
            editedfilename.CreatedDate = newdatafilelink.CreatedDate;
            editedfilename.LastUpdate = newdatafilelink.LastUpdate;
            row.Cells["FileTypeColumn"].Value = newdatafilelink.FileTypeName;
            experimentFilesDataGridView.InvalidateRow(e.RowIndex);
            if ( newdatafilelink.Id < 1 ) {
                DialogResult result = MessageBox.Show("Do you want to delete the row?", "Confirm Delete of Blank Row", MessageBoxButtons.YesNo, MessageBoxIcon.Warning);
                if ( result == DialogResult.Yes ) {
                    experimentFilesDataGridView.Rows.RemoveAt(e.RowIndex);
                }
            }
        }
    }

正如我所说,除了第 1 次从新行点击“添加新”按钮的情况外,一切正常。从现有行中点击“添加新”按钮效果很好。使用组合框列中的下拉列表在现有行和新行中都可以正常工作。知道我遗漏了什么或如何让它发挥作用吗?

请保持温和,因为这是我在 Whosebug 上的第一个问题。

提前致谢, 皮埃尔

首先,由于您的 table 是数据绑定的,因此不建议直接通过 cell.Value="something"; 修改其内容。最好修改数据源中的数据,例如_experimentFiles[i].FileName="something"。如果您的数据源实施正确,此更改将立即反映在 UI 中。直接修改在某些情况下也可能有效,但最好避免这种情况。

其次,对于新的行按钮处理程序。正如您已经发现的那样,这是一个极端情况,因为新行实际上是空的并且没有数据源。所以,你应该按照以下方式单独处理这种情况

if(row.DataBoundItem as FileModel is null)
{
    var fileName = //get filename as you want.
    var newFileModel = new FileModel{FileName = fileName};
     _experimentFiles.Add(fileName)
}
else
{
    //handle the normal case of existing row as you already doing.
}

您可以考虑将所有这些逻辑移动到您的 OpenFile 函数中,方法是检查 data 是否 null 并按照描述执行操作。