在 DataGridView 中实现分页功能,这是处理大量数据时的常用需求,能避免表格加载过慢、提升用户操作体验。核心思路是基于全局原始数据源,通过「页码 + 每页显示条数」截取对应数据片段作为当前页数据,再绑定到 DataGridView 展示,同时配套分页控件实现页码切换、页数统计等功能。下面基于之前的 Student 实体和全局数据源,完整实现分页功能。
一、前置准备
1. 保留全局原始数据源与通用绑定方法
// 全局学生集合(原始完整数据源,分页仅截取片段,不修改原始数据)
private List<Student> _studentGlobalList = new List<Student>()
{
new Student(){ Id=1, Name="张三", Age=18, ClassName="高一(1)班"},
new Student(){ Id=2, Name="李四", Age=17, ClassName="高一(2)班"},
new Student(){ Id=3, Name="王五", Age=18, ClassName="高一(1)班"},
new Student(){ Id=4, Name="张小三", Age=19, ClassName="高一(1)班"},
new Student(){ Id=5, Name="李小四", Age=17, ClassName="高一(2)班"},
new Student(){ Id=6, Name="赵五", Age=18, ClassName="高一(3)班"},
new Student(){ Id=7, Name="钱六", Age=19, ClassName="高一(3)班"},
new Student(){ Id=8, Name="孙七", Age=17, ClassName="高一(1)班"},
new Student(){ Id=9, Name="周八", Age=18, ClassName="高一(2)班"},
new Student(){ Id=10, Name="吴九", Age=19, ClassName="高一(3)班"}
};
// 通用数据绑定方法,接收分页结果集并绑定到 DataGridView
private void BindDataToDgv(List<Student> dataList)
{
dgvStudent.DataSource = null; // 清空原有绑定,避免数据残留
dgvStudent.DataSource = dataList;
// 优化表头显示
dgvStudent.Columns["Id"].HeaderText = "学生ID";
dgvStudent.Columns["Name"].HeaderText = "学生姓名";
dgvStudent.Columns["Age"].HeaderText = "年龄";
dgvStudent.Columns["ClassName"].HeaderText = "所在班级";
}
2. 定义分页核心变量(全局)
用于记录分页状态,确保页码切换时数据一致性:
private int _currentPage = 1; // 当前页码(默认第1页)
private int _pageSize = 3; // 每页显示条数(默认每页3条,可自定义)
private int _totalPages; // 总页数(根据原始数据总数和每页条数计算)
3. 窗体分页控件布局
添加以下控件实现分页交互(均命名便于代码调用):
- • 2 个 Button 控件:btnFirstPage(首页)、btnPrevPage(上一页)、btnNextPage(下一页)、btnLastPage(末页)(实际需 4 个按钮,覆盖完整分页操作)
- • 2 个 Label 控件:lblPageInfo(显示分页统计信息,如「当前第 1 页 / 共 4 页」)
- • 可选:NumericUpDown 控件(自定义每页显示条数)
二、分页核心原理与实现
1. 核心原理:LINQ 截取分页数据片段
分页的核心是通过 LINQ 的 Skip() 和 Take() 方法配合,从原始数据源中截取当前页对应的数据集:
- • Skip(n):跳过前 n 条数据(n = (当前页码-1) * 每页显示条数),定位到当前页的起始位置。
- • Take(m):从起始位置开始,获取 m 条数据(m = 每页显示条数),即当前页的展示数据。
- • 先计算总页数:总页数 = 向上取整(原始数据总数 / 每页显示条数),避免出现无数据的空页。
2. 分页数据加载核心方法(封装,方便重复调用)
private void LoadPageData()
{
// 1. 计算总页数(向上取整,避免余数数据无法展示)
int totalCount = _studentGlobalList.Count;
_totalPages = (int)Math.Ceiling((double)totalCount / _pageSize);
// 2. 边界验证:确保当前页码在有效范围内(避免页码越界)
if (_currentPage < 1) _currentPage = 1;
if (_currentPage > _totalPages && _totalPages > 0) _currentPage = _totalPages;
// 3. 核心:使用 LINQ Skip() + Take() 截取当前页数据
var currentPageData = _studentGlobalList
.Skip( (_currentPage - 1) * _pageSize ) // 跳过前 (当前页-1)*每页条数 条数据
.Take(_pageSize) // 获取当前页的 n 条数据
.ToList(); // 转为 List<Student>,方便绑定到 DataGridView
// 4. 绑定当前页数据,刷新表格展示
BindDataToDgv(currentPageData);
// 5. 更新分页统计信息,展示给用户
UpdatePageInfoLabel();
}
// 封装:更新分页信息标签
private void UpdatePageInfoLabel()
{
int totalCount = _studentGlobalList.Count;
lblPageInfo.Text = $"当前第 {_currentPage} 页 / 共 {_totalPages} 页 | 总数据 {totalCount} 条 | 每页 {_pageSize} 条";
}
3. 分页按钮点击事件实现(完整分页操作)
实现「首页、上一页、下一页、末页」的切换逻辑,核心是修改 _currentPage 并调用 LoadPageData() 刷新数据:
// 1. 首页
private void btnFirstPage_Click(object sender, EventArgs e)
{
_currentPage = 1; // 重置为第1页
LoadPageData();
}
// 2. 上一页
private void btnPrevPage_Click(object sender, EventArgs e)
{
if (_currentPage > 1) // 边界验证:不是第1页才允许上移
{
_currentPage--;
LoadPageData();
}
else
{
MessageBox.Show("当前已是首页,无法继续上一页!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
}
// 3. 下一页
private void btnNextPage_Click(object sender, EventArgs e)
{
if (_currentPage < _totalPages) // 边界验证:不是最后一页才允许下移
{
_currentPage++;
LoadPageData();
}
else
{
MessageBox.Show("当前已是末页,无法继续下一页!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
}
// 4. 末页
private void btnLastPage_Click(object sender, EventArgs e)
{
_currentPage = _totalPages; // 直接跳转到总页数
LoadPageData();
}
4. 初始化分页(窗体加载时)
private void Form1_Load(object sender, EventArgs e)
{
// 配置 DataGridView 基础属性
dgvStudent.AllowUserToAddRows = false;
dgvStudent.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
dgvStudent.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCells;
// 初始化加载第1页数据
LoadPageData();
}
三、关键知识点说明
1、LINQ Skip() 与 Take() 的核心作用:
这两个方法是实现内存分页的关键,仅对原始数据源进行「查询截取」,不修改原始数据,保证分页操作可重复、无数据丢失。注意:Skip() 和 Take() 仅支持 IEnumerable 类型,需确保数据源为泛型集合(如 List)。
2、总页数的正确计算(向上取整):
由于 int 类型除法会向下取整(如 10/3=3),而实际需要 4 页才能展示全部数据,因此必须将其中一个操作数转为 double 后,使用 Math.Ceiling() 进行向上取整,公式:
_totalPages = (int)Math.Ceiling((double)_studentGlobalList.Count / _pageSize);
若原始数据总数为 0,需额外处理(避免 _totalPages 为 0 导致异常)。
3、页码边界验证的必要性:
分页操作中必须对 _currentPage 进行边界限制(≥1 且 ≤_totalPages),避免用户异常操作(如手动修改页码、数据删除后总页数减少)导致 Skip() 方法传入负数或超出数据总数的参数,引发索引越界异常。
4、分页与筛选的结合(扩展):
若需要实现「筛选后分页」,只需先对 _studentGlobalList 进行筛选,得到筛选结果集,再以筛选结果集为数据源进行分页(修改 LoadPageData() 中的数据源即可),核心分页逻辑不变。
5、自定义每页显示条数(优化):
可通过 NumericUpDown 控件让用户自定义 _pageSize,修改后重置 _currentPage = 1 并调用 LoadPageData() 即可刷新分页:
private void numPageSize_ValueChanged(object sender, EventArgs e)
{
_pageSize = (int)numPageSize.Value;
_currentPage = 1; // 每页条数变化,重置为第1页
LoadPageData();
}
四、使用须知
- • 1、确保窗体上已添加对应的分页按钮(4 个)和分页信息标签,并绑定相应的点击事件。
- • 2、依赖之前定义的 Student 实体、全局数据源和 BindDataToDgv 绑定方法,无需额外修改已有代码。
- • 3、运行后,表格将展示第 1 页(3 条数据),点击分页按钮可切换页码,边界情况会给出友好提示,分页信息标签实时更新统计数据。
五、总结
- • 分页功能的核心:LINQ Skip() + Take() 截取数据片段 + 分页状态变量记录 + 重新绑定 DataGridView。
- • 关键步骤:计算总页数 → 边界验证页码 → 截取当前页数据 → 绑定展示 → 更新分页信息。
- • 核心注意点:总页数向上取整、页码边界验证、保留原始数据源不修改,确保分页稳定性和可扩展性。
- • 扩展方向:支持自定义每页条数、筛选后分页、页码输入跳转,满足复杂业务场景需求。
阅读原文:原文链接
该文章在 2026/1/19 10:37:59 编辑过