2026/2/18 23:54:24
网站建设
项目流程
杭州网站开发公司排名,ppt现成作品,青浦手机网站建设,公司网站改版多少钱# 多单元格拖动填充DataGridView功能实现总结 本文档围绕WinForm的DataGridView控件#xff0c;实现了一套贴近Excel体验的多单元格拖动填充功能#xff0c;核心涵盖高亮反馈、内容预览、循环填充三大核心特性#xff0c;兼顾实用性与用户体验。 功能实现以自定义控件为载体…# 多单元格拖动填充DataGridView功能实现总结 本文档围绕WinForm的DataGridView控件实现了一套贴近Excel体验的多单元格拖动填充功能核心涵盖高亮反馈、内容预览、循环填充三大核心特性兼顾实用性与用户体验。 功能实现以自定义控件为载体继承原生DataGridView并扩展核心逻辑。通过监听鼠标按下、移动、抬起事件精准判断填充柄操作仅当鼠标点击选中区域右下角5x5填充柄时触发拖动状态否则保留原生控件操作避免功能冲突。拖动过程中实时计算目标区域行列范围绘制半透明浅蓝色高亮背景与深蓝色边框清晰标记填充范围。 同时新增内容预览功能基于循环取模算法同步计算目标单元格对应起始区域的数据以深灰色半透明文字居中绘制预览内容确保预览与最终填充结果一致且仅预览待填充区域不重复绘制起始数据。填充逻辑支持多单元格矩形区域起始通过取模运算实现数据循环复制跳过只读单元格兼顾安全性与兼容性。 优化层面启用双缓冲减少闪烁通过缩小高亮绘制区域避免遮挡原生内容增加行列范围容错判断防止索引越界。整体功能既保留了DataGridView原生操作体验又通过Excel风格的视觉反馈与交互逻辑提升了数据填充的便捷性可直接应用于各类WinForm数据管理场景。using System; using System.Drawing; using System.Windows.Forms; namespace DataGridViewFillHandleDemo { /// summary /// 支持多单元格拖动填充高亮内容实时预览的DataGridView兼容Excel风格 /// /summary public class DataGridViewWithMultiCellFill : DataGridView { #region 升级后的状态变量定义 // 填充柄大小单元格右下角小方块默认5x5像素 private readonly Size _fillHandleSize new Size(8, 8); // 是否处于拖动填充状态 private bool _isDragging; // 是否鼠标悬停在填充柄上 private bool _isHoverFillHandle; // 起始选中区域多单元格记录行列范围 private int _startMinCol, _startMaxCol, _startMinRow, _startMaxRow; // 起始选中区域的原始数据二维集合保存多单元格数据 private object[,] _startCellData; // 鼠标起始位置 private Point _startMousePos; // 填充目标区域的结束单元格 private DataGridViewCell _endCell; // 高亮颜色半透明浅蓝不遮挡内容 private readonly Color _highlightColor Color.FromArgb(150, 210, 255); // 预览文字颜色深灰色区别于原生内容 private readonly Color _previewTextColor Color.FromArgb(100, 0, 0, 0); #endregion #region 构造函数 public DataGridViewWithMultiCellFill() { // 启用双缓冲减少闪烁 this.DoubleBuffered true; // 允许单元格多选支持多单元格选中 this.SelectionMode DataGridViewSelectionMode.CellSelect; this.MultiSelect true; // 启用鼠标移动监听 this.MouseMove DataGridViewWithMultiCellFill_MouseMove; this.MouseUp DataGridViewWithMultiCellFill_MouseUp; } #endregion #region 核心鼠标事件处理升级多单元格支持 /// summary /// 鼠标按下判断填充柄基于选中区域右下角初始化多单元格拖动状态 /// /summary protected override void OnMouseDown(MouseEventArgs e) { // 仅处理左键按下且必须有选中单元格 if (e.Button ! MouseButtons.Left || this.SelectedCells.Count 0) return; // 1. 获取当前选中区域的行列范围多单元格 GetSelectedCellRange(out _startMinCol, out _startMaxCol, out _startMinRow, out _startMaxRow); // 2. 选中区域的右下角单元格填充柄所在位置与Excel一致 var bottomRightCell this[_startMaxCol, _startMaxRow]; if (bottomRightCell null) return; // 3. 判断鼠标是否在右下角单元格的填充柄内 if (IsPointInFillHandle(bottomRightCell, e.Location)) { // 4. 初始化拖动状态保存起始区域原始数据 _isDragging true; _startMousePos e.Location; _endCell bottomRightCell; _startCellData GetSelectedCellData(); // 保存多单元格数据 this.Cursor Cursors.Cross; // 切换鼠标样式为十字光标 } else { base.OnMouseDown(e); } } /// summary /// 鼠标移动更新填充区域支持多单元格拖动预览高亮内容预览 /// /summary private void DataGridViewWithMultiCellFill_MouseMove(object sender, MouseEventArgs e) { // 1. 非拖动状态判断是否悬停在填充柄上更新鼠标样式 if (!_isDragging) { _isHoverFillHandle false; if (this.SelectedCells.Count 0) { // 获取选中区域右下角单元格 GetSelectedCellRange(out int minCol, out int maxCol, out int minRow, out int maxRow); var bottomRightCell this[maxCol, maxRow]; if (bottomRightCell ! null) { _isHoverFillHandle IsPointInFillHandle(bottomRightCell, e.Location); } } // 切换填充柄悬停鼠标样式黑色十字 this.Cursor _isHoverFillHandle ? Cursors.Cross : Cursors.Default; this.Invalidate(); // 重绘控件显示填充柄 return; } // 2. 拖动状态更新目标单元格触发重绘实现高亮内容预览 if (e.Button ! MouseButtons.Left) return; var hitTestInfoDrag this.HitTest(e.X, e.Y); if (hitTestInfoDrag.Type DataGridViewHitTestType.Cell) { _endCell this[hitTestInfoDrag.ColumnIndex, hitTestInfoDrag.RowIndex]; this.Invalidate(); // 关键重绘控件触发OnPaint绘制高亮和内容 } } /// summary /// 鼠标抬起结束拖动执行多单元格数据批量填充逻辑 /// /summary private void DataGridViewWithMultiCellFill_MouseUp(object sender, MouseEventArgs e) { if (!_isDragging || _startCellData null || _endCell null) { ResetDragState(); // 重置状态 return; } // 执行多单元格数据填充核心循环复制起始区域数据到目标区域 FillMultiCellData(); // 重置所有拖动状态 ResetDragState(); } #endregion #region 辅助方法新增/升级支持多单元格 /// summary /// 获取当前选中单元格的行列范围多单元格 /// /summary private void GetSelectedCellRange(out int minCol, out int maxCol, out int minRow, out int maxRow) { minCol int.MaxValue; maxCol int.MinValue; minRow int.MaxValue; maxRow int.MinValue; // 遍历所有选中单元格确定行列极值 foreach (DataGridViewCell cell in this.SelectedCells) { if (cell.ColumnIndex minCol) minCol cell.ColumnIndex; if (cell.ColumnIndex maxCol) maxCol cell.ColumnIndex; if (cell.RowIndex minRow) minRow cell.RowIndex; if (cell.RowIndex maxRow) maxRow cell.RowIndex; } // 容错没有选中单元格时重置为0 if (minCol int.MaxValue) minCol maxCol 0; if (minRow int.MaxValue) minRow maxRow 0; } /// summary /// 获取选中区域的所有单元格数据存入二维数组保留原始结构 /// /summary private object[,] GetSelectedCellData() { // 计算起始区域的行列数量 int colCount _startMaxCol - _startMinCol 1; int rowCount _startMaxRow - _startMinRow 1; object[,] cellData new object[rowCount, colCount]; // 遍历选中区域保存每个单元格的数据 for (int row 0; row rowCount; row) { for (int col 0; col colCount; col) { var targetCell this[_startMinCol col, _startMinRow row]; cellData[row, col] targetCell.Value ?? DBNull.Value; } } return cellData; } /// summary /// 验证鼠标坐标是否位于指定单元格的右下角填充柄内 /// /summary private bool IsPointInFillHandle(DataGridViewCell cell, Point mousePos) { Rectangle cellRect this.GetCellDisplayRectangle(cell.ColumnIndex, cell.RowIndex, false); if (cellRect.IsEmpty) return false; // 计算填充柄的区域单元格右下角 5x5 方块 Rectangle fillHandleRect new Rectangle( cellRect.Right - _fillHandleSize.Width, cellRect.Bottom - _fillHandleSize.Height, _fillHandleSize.Width, _fillHandleSize.Height ); return fillHandleRect.Contains(mousePos); } #endregion #region 核心业务多单元格数据填充逻辑循环复制贴近Excel /// summary /// 多单元格批量填充将起始区域数据循环复制到目标拖动区域 /// /summary private void FillMultiCellData() { // 1. 确定目标填充区域的行列范围 int targetMinCol Math.Min(_startMinCol, _endCell.ColumnIndex); int targetMaxCol Math.Max(_startMaxCol, _endCell.ColumnIndex); int targetMinRow Math.Min(_startMinRow, _endCell.RowIndex); int targetMaxRow Math.Max(_startMaxRow, _endCell.RowIndex); // 2. 起始区域的行列数量用于循环填充 int startColCount _startMaxCol - _startMinCol 1; int startRowCount _startMaxRow - _startMinRow 1; if (startColCount 0 || startRowCount 0) return; // 3. 锁定控件更新避免填充过程中闪烁 this.SuspendLayout(); this.BeginEdit(false); // 4. 遍历目标区域循环复制起始区域数据Excel默认填充逻辑 for (int targetRow targetMinRow; targetRow targetMaxRow; targetRow) { for (int targetCol targetMinCol; targetCol targetMaxCol; targetCol) { // 计算当前目标单元格对应起始区域的索引循环取模 int startRowIndex (targetRow - _startMinRow) % startRowCount; int startColIndex (targetCol - _startMinCol) % startColCount; // 避免索引越界处理反向拖动 if (startRowIndex 0) startRowIndex startRowCount; if (startColIndex 0) startColIndex startColCount; var targetCell this[targetCol, targetRow]; // 跳过只读单元格避免报错 if (!targetCell.ReadOnly) { // 复制起始区域对应位置的数据 targetCell.Value _startCellData[startRowIndex, startColIndex]; } } } // 5. 解锁控件更新刷新显示 this.EndEdit(); this.ResumeLayout(true); this.Invalidate(); } #endregion #region 辅助方法重置状态绘制填充柄拖动高亮内容预览 /// summary /// 重置所有拖动相关的状态变量 /// /summary private void ResetDragState() { _isDragging false; _isHoverFillHandle false; _startCellData null; _endCell null; _startMinCol _startMaxCol _startMinRow _startMaxRow 0; this.Cursor Cursors.Default; this.Invalidate(); // 重置时清除高亮和预览内容 } /// summary /// 重绘控件绘制填充柄 拖动高亮 实时内容预览 /// /summary protected override void OnPaint(PaintEventArgs e) { base.OnPaint(e); // 1. 绘制填充柄原有逻辑 if (this.SelectedCells.Count 0 _isHoverFillHandle) { GetSelectedCellRange(out int minCol, out int maxCol, out int minRow, out int maxRow); var bottomRightCell this[maxCol, maxRow]; if (bottomRightCell ! null) { Rectangle cellRect this.GetCellDisplayRectangle(bottomRightCell.ColumnIndex, bottomRightCell.RowIndex, false); if (!cellRect.IsEmpty) { Rectangle fillHandleRect new Rectangle( cellRect.Right - _fillHandleSize.Width, cellRect.Bottom - _fillHandleSize.Height, _fillHandleSize.Width, _fillHandleSize.Height ); using (var brush new SolidBrush(Color.Black)) { e.Graphics.FillRectangle(brush, fillHandleRect); } } } } // 2. 绘制拖动目标区域高亮 实时内容预览 if (_isDragging _endCell ! null _startCellData ! null) { // 计算目标区域的行列范围 int targetMinCol Math.Min(_startMinCol, _endCell.ColumnIndex); int targetMaxCol Math.Max(_startMaxCol, _endCell.ColumnIndex); int targetMinRow Math.Min(_startMinRow, _endCell.RowIndex); int targetMaxRow Math.Max(_startMaxRow, _endCell.RowIndex); // 起始区域的行列数量用于循环计算预览值 int startColCount _startMaxCol - _startMinCol 1; int startRowCount _startMaxRow - _startMinRow 1; // 遍历目标区域的每个单元格 for (int targetRow targetMinRow; targetRow targetMaxRow; targetRow) { for (int targetCol targetMinCol; targetCol targetMaxCol; targetCol) { // 强容错跳过超出表格范围、表头行列 if (targetRow 0 || targetCol 0 || targetRow this.RowCount || targetCol this.ColumnCount) continue; Rectangle cellRect this.GetCellDisplayRectangle(targetCol, targetRow, false); if (cellRect.IsEmpty) continue; // 步骤1绘制半透明高亮背景不覆盖原生内容 using (var highlightBrush new SolidBrush(_highlightColor)) { // 缩小绘制区域留出1像素边距 Rectangle drawRect new Rectangle( cellRect.X 1, cellRect.Y 1, cellRect.Width - 2, cellRect.Height - 2 ); e.Graphics.FillRectangle(highlightBrush, drawRect); } // 步骤2计算并绘制预览内容 // 计算当前目标单元格对应起始区域的索引循环取模 int startRowIndex (targetRow - _startMinRow) % startRowCount; int startColIndex (targetCol - _startMinCol) % startColCount; // 处理反向拖动的负索引 if (startRowIndex 0) startRowIndex startRowCount; if (startColIndex 0) startColIndex startColCount; // 获取预览值跳过起始区域自身只预览填充的新内容 bool isInStartArea targetRow _startMinRow targetRow _startMaxRow targetCol _startMinCol targetCol _startMaxCol; if (!isInStartArea) { object previewValue _startCellData[startRowIndex, startColIndex]; if (previewValue ! DBNull.Value previewValue ! null) { // 设置文字格式居中对齐与DataGridView原生样式一致 StringFormat sf new StringFormat { Alignment StringAlignment.Center, LineAlignment StringAlignment.Center }; // 绘制预览文字深灰色半透明区别于原生内容 using (var textBrush new SolidBrush(_previewTextColor)) { e.Graphics.DrawString( previewValue.ToString(), this.Font, textBrush, cellRect, sf ); } } } // 步骤3绘制单元格边框增强视觉效果 using (var borderPen new Pen(Color.DarkBlue, 1)) { e.Graphics.DrawRectangle(borderPen, cellRect); } } } } } #endregion } }