在实际应用开发中,我们常常需要向用户展示数据,而数据的呈现方式通常以二维表格,折线图,柱状图等图表为主,最常见的就是表格,如销售的统计报表,明细列表等。今天我们以一些简单的小例子,简述在基于.NET的Windows窗体编程中数据表格的应用,仅供学习分享使用,如有不足之处,还请指正。

概述
在WinForms开发中,数据表格控件为DataGridView,它提供了一种强大且灵活的方法来显示表格格式数据。使用DataGridView控件,可以显示和编辑来自多种不同类型的数据源的表格数据,同时DataGridView 控件高度可配置且可扩展,它提供许多属性、方法和事件来自定义其外观和行为。建议在 Windows 窗体应用程序中显示表格数据时,优先使用 DataGridView 控件,而不是使用其他控件(例如,DataGrid)。
将数据绑定到 DataGridView 控件非常简单,只需要设置 DataSource 属性即可,如果绑定的数据源包含多个列表或表,则需要将 DataMember 属性设置为指定要绑定到的列表或表的名称。DataGridView 控件支持标准 Windows 窗体数据绑定模型,它可以绑定到以下数据类型列表:
- 实现 IList 接口的任何类,包括一维数组。
- 实现 IListSource 接口的任何类,例如 DataTable 和 DataSet 类。
- 实现 IBindingList 接口的任何类,如 BindingList<T> 类。
- 实现 IBindingListView 接口的任何类,如 BindingSource 类。
通常,将DataGridView绑定到 BindingSource 组件,并将 BindingSource 组件绑定到另一个数据源,或者使用业务对象填充BindingSource 。BindingSource 组件是首选数据源,它可以绑定到各种数据源,并且可以自动解决许多数据绑定问题。那什么是BindingSource呢?
BindingSource
BindingSource,它封装一个数据源以便绑定到控件,此组件有两个用途:
- 将一个窗体的DataGridView控件绑定到数据时,该组件会提供一个间接层。 要实现此目标,可以先将 BindingSource 组件绑定到你的数据源,然后再将窗体上的控件绑定到 BindingSource 组件。 与数据的所有进一步交互(包括导航、排序、筛选和更新)都是通过调用 BindingSource 组件来完成的。
- BindingSource 组件可以充当强类型的数据源。 使用 BindingSource 方法将类型添加到 Add 组件将创建该类型的列表。
下图展示了 BindingSource 组件在现有数据绑定架构中的角色和位置。

同时对于需要导航窗体上数据的用户,BindingNavigator 组件使你能够与 BindingSource 组件协调导航和作数据。
关键属性
DataGridView关键属性,主要有以下几个:
- Name,DataGridView控件在Form表单中的唯一标识。
- Dock,定义空间的停靠方式,主要有None,Top,Bottom,Left,Right,Fill等几种方式,默认为None(不停靠),通过Location定义控件在父容器中的位置。
- Columns,DataGridView控件的列属性,点击它可以打开"编辑列"对话框。
- DataMember,如果绑定的数据源有多个子表时,可以通过DataMember属性指定需要绑定的数据表。
- MultiSelect,标识是否可以一次选择多个单元格或行或列。
- DataSource,表示DataGridView控件绑定的数据源。
- AutoGenerateColumns 是否根据数据源中的数据自动生成列,默认为true。
添加或编辑列
在Visual Studio设计器中,选中DataGridView控件,然后点击右侧的箭头,在弹出的"DataGridView任务"菜单中,可以点击"添加列..."或"编辑列...",建议点击"编辑列...",因为在弹出的"编辑列"窗口中,即可以添加列,也可以编辑现有列。如下图所示:

在"编辑列"窗口中,可以设置添加列,或编辑现有列,DataGridView控件的数据列,主要有以下常用属性:
- Name,指定数据列的唯一标识,它在Form表单或UserControl中具有唯一性。
- ColumnType,指定数据列的类型,表示数据将以何种形式呈现,它具有几种选择:
- DataGridViewTextBoxColumn,文本的形式展示数据
- DataGridViewImageColumn,列数据可以展示图片
- DataGridViewCheckBoxColumn,复选框的形式展示数据
- DataGridViewComoboBoxColumn,下拉框的形式展示数据
- DataGridViewButtonColumn,按钮的形式展示数据
- DataGridViewLinkColumn,超链接的形式展示数据。
- DataPropertyName,指定数据列绑定到数据源的列名称,用于标识显示数据源的哪一列。
- HeaderText,列标题,表示列标题单元格的文本内容。
- Visible,指定列是否可见,true:显示数据列,false:隐藏数据列。
- ReadOnly,指定用户是否可以编辑列,true:不可编辑,false:可编辑。
- Frozen,是否冻结列,水平滚动条移动时,列是否移动。
- Width,列的宽度,MinimumWidth,列的最小宽度。
注意:DataGridView默认会勾选"启用添加","启用编辑","启用删除"等功能,如果不需要,可以在点击"右箭头"弹出的"DataGridView任务"菜单中取消其功能。
DataGridView实例
接下来以一个实际案例,简述DataGridView控件的相关用法。
1. 构建数据源
通过BindingSource组件,数据源的支持所有IEnumerable 接口的实现,今天以学生列表为例,首先创建Student模型类,定于学生包含 的具体属性,如下所示:
cs
namespace Okcoder.WinForm.Demo
{
/// <summary>
/// 学生信息
/// </summary>
public class Student
{
/// <summary>
/// 唯一标识
/// </summary>
public int Id { get; set; }
/// <summary>
/// 姓名
/// </summary>
public string Name { get; set; }
/// <summary>
/// 年龄
/// </summary>
public int Age { get; set; }
/// <summary>
/// 性别 true:男 false:女
/// </summary>
public bool Gender { get; set; }
/// <summary>
/// 是否OK,true:是 false:否
/// </summary>
public bool IsOker { get; set; }
/// <summary>
/// 出生日期
/// </summary>
public DateTime BirthDate { get; set; }
}
}
创建SqlDataHelper类,用于构建数据源,模拟从数据库中获取数据。具体如下所示:
cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Okcoder.WinForm.Demo
{
/// <summary>
/// 数据帮助类
/// </summary>
public class SqlDataHelper
{
private List<Student> students;
/// <summary>
/// 构造函数
/// </summary>
public SqlDataHelper()
{
InitDataInfo();
}
/// <summary>
/// 使用列表,模拟数据库数据,假定数据库中有20名学生信息
/// </summary>
private void InitDataInfo()
{
students = new List<Student>();
students.Add(new Student() { Id = 1, Name = "张三丰", Age = 100, Gender = true, IsOker = true, BirthDate = DateTime.Now.AddYears(-100) });
students.Add(new Student() { Id = 2, Name = "宋远桥", Age = 50, Gender = true, IsOker = true, BirthDate = DateTime.Now.AddYears(-50) });
students.Add(new Student() { Id = 3, Name = "俞莲舟", Age = 48, Gender = true, IsOker = true, BirthDate = DateTime.Now.AddYears(-48) });
students.Add(new Student() { Id = 4, Name = "俞岱岩", Age = 46, Gender = true, IsOker = true, BirthDate = DateTime.Now.AddYears(-46) });
students.Add(new Student() { Id = 5, Name = "张松溪", Age = 44, Gender = true, IsOker = true, BirthDate = DateTime.Now.AddYears(-44) });
students.Add(new Student() { Id = 6, Name = "张翠山", Age = 45, Gender = true, IsOker = true, BirthDate = DateTime.Now.AddYears(-45) });
students.Add(new Student() { Id = 7, Name = "殷梨亭", Age = 43, Gender = true, IsOker = true, BirthDate = DateTime.Now.AddYears(-43) });
students.Add(new Student() { Id = 8, Name = "莫声谷", Age = 42, Gender = true, IsOker = true, BirthDate = DateTime.Now.AddYears(-42) });
students.Add(new Student() { Id = 9, Name = "宋青书", Age = 20, Gender = true, IsOker = false, BirthDate = DateTime.Now.AddYears(-20) });
students.Add(new Student() { Id = 10, Name = "张无忌", Age = 18, Gender = true, IsOker = true, BirthDate = DateTime.Now.AddYears(18) });
students.Add(new Student() { Id = 11, Name = "郭襄", Age = 100, Gender = true, IsOker = true, BirthDate = DateTime.Now.AddYears(-100) });
students.Add(new Student() { Id = 12, Name = "灭绝师太", Age = 51, Gender = false, IsOker = true, BirthDate = DateTime.Now.AddYears(-51) });
students.Add(new Student() { Id = 13, Name = "周芷若", Age = 18, Gender = false, IsOker = true, BirthDate = DateTime.Now.AddYears(-18) });
students.Add(new Student() { Id = 14, Name = "定敏君", Age = 19, Gender = false, IsOker = false, BirthDate = DateTime.Now.AddYears(-19) });
students.Add(new Student() { Id = 15, Name = "纪晓芙", Age = 43, Gender = false, IsOker = true, BirthDate = DateTime.Now.AddYears(-43) });
students.Add(new Student() { Id = 16, Name = "静玄", Age = 30, Gender = false, IsOker = true, BirthDate = DateTime.Now.AddYears(-30) });
students.Add(new Student() { Id = 17, Name = "静虚", Age = 29, Gender = false, IsOker = true, BirthDate = DateTime.Now.AddYears(-29) });
students.Add(new Student() { Id = 18, Name = "静空", Age = 28, Gender = false, IsOker = true, BirthDate = DateTime.Now.AddYears(-28) });
students.Add(new Student() { Id = 19, Name = "静慧", Age = 27, Gender = false, IsOker = true, BirthDate = DateTime.Now.AddYears(-27) });
students.Add(new Student() { Id = 20, Name = "静迦", Age = 26, Gender = false, IsOker = true, BirthDate = DateTime.Now.AddYears(-26) });
}
/// <summary>
/// 查询学生列表
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
public List<Student> Query(string name)
{
if (string.IsNullOrEmpty(name))
{
return this.students;
}
var list = this.students.Where(item => item.Name.Contains(name)).ToList();
return list;
}
/// <summary>
/// 根据ID获取学生信息
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public Student? Get(int id)
{
return students.FirstOrDefault(item => item.Id == id);
}
/// <summary>
/// 添加学生
/// </summary>
/// <param name="name"></param>
/// <param name="age"></param>
/// <param name="gender"></param>
/// <param name="isoker"></param>
/// <param name="birthdate"></param>
public void Add(string name,int age,bool gender,bool isoker,DateTime birthdate)
{
if (students.Count(item => item.Name == name) < 1)
{
int id = students.Count > 0 ? students.Max(item => item.Id) + 1 : 1;
students.Add(new Student() { Id = id, Name = name, Age = age, Gender = gender, IsOker = isoker, BirthDate = birthdate });
}
}
/// <summary>
/// 删除学生
/// </summary>
/// <param name="id"></param>
public void Delete(int id)
{
var student = students.FirstOrDefault(item => item.Id == id);
if (student != null)
{
this.students.Remove(student);
}
}
/// <summary>
/// 编辑学生
/// </summary>
/// <param name="id"></param>
/// <param name="name"></param>
/// <param name="age"></param>
/// <param name="gender"></param>
/// <param name="isoker"></param>
/// <param name="birthdate"></param>
public void Edit(int id, string name, int age, bool gender, bool isoker, DateTime birthdate)
{
var student = students.FirstOrDefault(item => item.Id == id);
if (student != null)
{
student.Name = name;
student.Age = age;
student.Gender = gender;
student.IsOker = isoker;
student.BirthDate = birthdate;
}
}
}
}
2. UI布局
在本实例中,通过两个SplitContainer布局容器控件实现上,中,下布局结构:
- 上边为查询条件,按钮等功能,实现数据列表的搜索。
- 中间为数据区,主要存放DataGridView控件,显示学生列表数据。
- 底部为导航区,主要放置BingNavigator控件。
设置步骤:
- 拖动DataGridView控件到数据区,且设置DataGridView控件的Dock属性为Fill,实现自动填充,并设置Name属性为dgvData。
- 拖动BindingSource组件到Form表单,由于BindingSource作为数据中间层,并没有UI呈现,所以在Form设计器的最底层,设置Name属性为bdsData。
- 将DataGridView的DataSource属性设置为上面的BindingSource组件,通过指定名称bdsData进行关联。
- 设置DataGridView需要显示的列,根据Student属性,为DataGridView添加需要显示的列,并设置对应的DataPropertyName属性。
具体如下所示:

3. 加载数据
在Form表单的类文件中,启动页面加载数据需要以下步骤:
- 初始化SqlDataHelper实例对象,
- Form表单的Load事件方法中,获取学生信息列表,并赋值给BindingSource组件的DataSource属性。
- 在Query按钮的事件处理方法中,根据用户输入的检索条件进行查询,将获取的列表重新Binding到BindingSource的DataSource属性中,即可实现重新赋值。
核心代码如下:
cs
namespace Okcoder.WinForm.Demo
{
public partial class FrmData : Form
{
private SqlDataHelper sqlDataHelper;
public FrmData()
{
InitializeComponent();
sqlDataHelper = new SqlDataHelper();
}
private void FrmData_Load(object sender, EventArgs e)
{
List<Student> students = sqlDataHelper.Query(string.Empty);
this.bdsData.DataSource = students;
}
private void btnQuery_Click(object sender, EventArgs e)
{
string name = this.txtName.Text.Trim();
List<Student> students = sqlDataHelper.Query(name);
this.bdsData.DataSource = students;
}
}
}
增加BindingNavigator,由于BindingNavigator并不在工具箱中出现,无法通过设计器拖动到Form表单中,但可以通过后台代码添加。本实例将BindingNavigator控件添加到UI布局的最底层部分。如下所示:
cs
private void FrmData_Load(object sender, EventArgs e)
{
List<Student> students = sqlDataHelper.Query(string.Empty);
this.bdsData.DataSource = students;
//添加BindingNavigator
BindingNavigator bdNav = new BindingNavigator(true);
bdNav.BindingSource = bdsData; // 需先创建 BindingSource
bdNav.Dock = DockStyle.Top;
this.splitContainer2.Panel2.Controls.Add(bdNav);
}
4. 运行示例
运行实例,效果如下所示:

在上述示例中,还存在4个问题:
- Gender是性别,希望看到男,女,而不是True,False。
- IsOker是否正派人士,希望看到是,否,而不是True,False。
- BirthDate是出生日期,希望看到年月日即可,不需要看到时分秒。
- DataGridView数据表格中每一行的背景颜色都相同,我们希望交替使用不同的背景色。
接下来将分别解决这4个问题。
日期格式
首先打开"编辑列"对话框,然后选择BirthDate列,在右侧的属性框中,选择DefaultCellStyle,然后点击右侧的"..."按钮,打开"CellStyle生成器"如下所示:

在"CellStyle生成器"中,选择Format,点击右侧的"..."按钮,打开"格式化字符串"对话框,如下所示:

在"格式化字符串"对话框,选择日期,可以选择需要显示的格式,如:"yyyy/MM/dd"。

也可以自定义格式,本示例自定义格式为"yyyy-MM-dd",如下所示:

设置格式后,BirthDate显示内容如下所示:

也可通过后台代码进行设置,如在Form的Load事件中设置默认单元格样式,如下所示:
cs
this.dgvColumnBirchDate.DefaultCellStyle.Format = "yyyy-MM-dd";
bool类型
为了将bool类型的值显示为更加语义化的文本,可以订阅DataGridView控件的CellFormatting事件。首先选择DataGridView控件,在右侧的属性窗口中,点击(⚡️)切换到事件Tab页,然后找到CellFormatting事件,在右侧的框中双击,即可为DataGridView控件添加相应事件。 如下所示:

在生成的dgvData_CellFormatting事件中,根据bool的值,重新为单元格赋值,如下所示:
cs
private void dgvData_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
{
if (e.ColumnIndex == 3)
{
//Gender
if (e.Value != null && ((bool)e.Value))
{
e.Value = "男";
}
else
{
e.Value = "女";
}
}
if (e.ColumnIndex == 4)
{
//IsOker
if (e.Value != null && ((bool)e.Value))
{
e.Value = "是";
}
else
{
e.Value = "否";
}
}
}
格式化后,数据显示如下所示:

行交替样式
表格数据通常以类似账本的格式向用户呈现,其中交替行具有不同的背景色,也是常用功能之一。通过这种格式,用户可以更轻松地识别每行中的单元格,特别是在有很多列的宽表格中。
首先选择DataGridView,然后在属性对话框中,选择RowDefaultCellStyle,在点击右侧的"..."按钮,弹出"CellStyle生成器",在其中设置BackColor属性,如下所示:

接着以同样方式设置AlternatingRowsDefaultCellStyle属性,中对应的BackColor属性,如下所示:

设置交替背景色后,效果如下所示:

上述交替行设置背景色效果,也可以在Form表单的Load事件中,添加如下代码:
cs
dgvData.RowsDefaultCellStyle.BackColor = Color.FromArgb(255, 192, 192);
dgvData.AlternatingRowsDefaultCellStyle.BackColor = Color.FromArgb(192, 255, 255);
以上就是《基于.NET的Windows窗体编程之WinForms数据表格》的全部内容,旨在抛砖引玉,希望可以一起学习,共同进步。