自己直接读写 DataGridView 很少是个好主意。最好将模型与模型的显示方式分开。
如果您的数据可以在没有显示的情况下存在,那么更改显示方式会更容易,在另一个控件或不同格式中重用数据会更容易。更容易对数据处理进行单元测试(无需表单!),更改数据的内部布局,而无需更改显示此数据的人员。
View 和 Model 之间的这种分离是在 MVVM 模型中以极值方式实现的,但 windows Forms 也通过 Data Binding 支持这一点。
只要你有一个控件可以显示一系列相似项的信息,无论是 ListBox、DataGridView 还是 GraphView,你都会发现它有一个属性DataSource。
您所要做的就是将要显示的数据放入数据源中。其他属性定义必须如何显示 DataSource 中的数据。如果操作员改变了显示的数据,那么原始数据会自动更新。
假设您的 DataGridView 必须显示一组客户:每行一个客户。
您的 DataGridView 有多个 DataGridViewColumns。每列将显示其中一个属性的值。要显示的属性名称在属性DataGridViewColumn.DataPropertyName 中。
您所要做的就是制作一系列相似的项目,并将其分配给 DataGridView.DataSource。
举个例子会有所帮助。
假设您需要展示一系列学生:
class Customer
{
public int Id {get; set;}
public string Name {get; set;}
public DateTime BirthDay {get; set;}
...
}
您希望在列中显示这三个学生属性。使用 Visual Studio 设计器添加列并分配 DataPropertyName。结果可以在InitializeComponents找到,也可以自己动手:
private readonly DataGridViewColumn columnId = new DataGridViewColumn();
private readonly DataGridViewColumn columnName = new DataGridViewColumn();
private readonly DataGridViewColumn columnBirthDay = new DataGridViewColumn();
// constructor
public MyForm()
{
InitializeComponents();
// define which column should display which Student property
this.columnId.DataPropertyName = nameof(Student.Id);
this.columnName.DataPropertyName = nameof(Student.Name);
this.columnBirthDay.DataPropertyName = nameof(Student.BirthDay);
// the the DataGridView to use these columns:
this.dataGridView1.Columns.Add(this.columnId);
this.dataGridView1.Columns.Add(this.columnName);
this.dataGridView1.Columns.Add(this.columnBirthday);
}
通常您设置其他列属性:设置标题的文本、列顺序,有时您定义显示格式,例如:对于生日,您不想显示一天中的时间。但是,如果您愿意,您可以将列 ID 设为只读,使用粗体等。
请注意,到目前为止,我们在没有一个实际学生的情况下完成了所有工作。
如果你想显示Students但不想编辑它们,将它们放在一个Collection中并将这个集合分配给DataGridView的DataSource就足够了
List<Student> students = ...
this.DataGridView1.DataSource = students;
但是,如果您希望操作员可以添加/删除/更改/重新排序学生,您需要一种机制来更新学生集合中的更改。如果您将数据放入 BindingList
,则会自动完成此操作
IList<Student> students = ...
BindingList<Student> displayedStudents = new BindingList<Student>(students);
this.DataGridView1.DataSource = displayedStudents;
显示所有学生。操作员可以根据需要添加/删除/更改学生(除了您只读的列)。所有更改都会在 BindingList 中自动更新。
您可以从 BndingList 订阅事件以对这些更改做出反应,但更常见的情况是您什么都不做,直到操作员告诉您他完成更改,例如按下按钮:
private void ButtonOk_Clicked(object sender, ...)
{
DataGridView dataGridView = (DataGridView)sender;
BindingList<Student> students = (BindingList<Student>)dataGridView.DataSource;
foreach (Student student in students)
{
this.ProcessStudent(student);
}
如果您保存了原始学生的副本,您可以检测哪些学生被更改、添加、删除并决定如何处理它们。
如果您需要对选定的行执行某些操作:
IEnumerable<Student> selectedStudents = this.dataGridView1.SelectedRows
.Cast<DataGridViewRow>()
.Select(row => row.DataBoundItem)
.Cast<Student>();
一个不错的功能:假设您希望您的软件分配学生的 ID,而不是操作员。然后,每当操作员想要添加新行时,您都必须使用唯一的 Id 创建学生。这是通过处理事件BindingList.AddingNew
来完成的
private void StudentCollection_AddingNew(object sender, AddingNewEventArgs e)
{
// You need to make a Student with a unique Id:
int newStudentId = this.CreateUniqueStudentId();
e.NewObject = new Student
{
Id = newStudentId,
// the operator has to fill in the rest of the properties
}
}
您是否注意到您不必关心学生的显示方式:您不必关心哪些属性、什么显示格式或列顺序。如果将来有人决定不再显示学生的生日,或重新排列列,可能会更改以大写字母显示学生的姓名:所有这些方法仍然有效。