如何实现带滚动字幕效果的C# WinForms DataGridViewTextBoxCell?
DataGridView单元格跑马灯效果实现方案
该需求完全可行,以下是具体实现思路和代码示例,解决你之前遇到的卡顿、无法部分绘制字符的问题:
核心实现思路
- 自定义
DataGridViewTextBoxCell,维护滚动偏移量与定时器,通过定时器逐像素调整偏移量实现平滑滚动 - 重写
Paint方法,利用文本裁剪绘制实现部分文本显示,替代逐字符修改formattedValue的方案 - 仅当文本长度超出单元格宽度时启动定时器,减少不必要的性能消耗
代码实现
自定义跑马灯单元格类
public class MarqueeDataGridViewTextBoxCell : DataGridViewTextBoxCell { private int _scrollOffset; private Timer _scrollTimer; private int _textWidth; private string _currentText; public MarqueeDataGridViewTextBoxCell() { // 设置50ms间隔,实现平滑滚动 _scrollTimer = new Timer { Interval = 50 }; _scrollTimer.Tick += ScrollTimer_Tick; } private void ScrollTimer_Tick(object sender, EventArgs e) { if (OwningColumn == null || OwningRow == null) return; // 更新当前文本与宽度,避免文本变化时偏移混乱 var formattedText = GetFormattedValue(Value, OwningRow.Index, ref Style, null, null, DataGridViewDataErrorContexts.Display)?.ToString() ?? string.Empty; if (formattedText != _currentText) { _currentText = formattedText; _textWidth = TextRenderer.MeasureText(formattedText, Style.Font).Width; _scrollOffset = 0; } // 文本未超出宽度时停止滚动 if (_textWidth <= ContentBounds.Width) { _scrollTimer.Stop(); return; } // 逐像素调整偏移量,超出文本宽度后重置 _scrollOffset += 1; if (_scrollOffset > _textWidth) { _scrollOffset = -ContentBounds.Width; } // 重绘当前单元格 OwningRow.Cells[ColumnIndex].Invalidate(); } protected override void Paint(Graphics graphics, Rectangle clipBounds, Rectangle cellBounds, int rowIndex, DataGridViewElementStates cellState, object value, object formattedValue, string errorText, DataGridViewCellStyle cellStyle, DataGridViewAdvancedBorderStyle advancedBorderStyle, DataGridViewPaintParts paintParts) { // 调用基类绘制但跳过文本内容,避免重复绘制 base.Paint(graphics, clipBounds, cellBounds, rowIndex, cellState, value, formattedValue, errorText, cellStyle, advancedBorderStyle, paintParts & ~DataGridViewPaintParts.ContentForeground); var text = formattedValue?.ToString() ?? string.Empty; var contentBounds = cellStyle.Padding.ApplyPadding(cellBounds); _textWidth = TextRenderer.MeasureText(text, cellStyle.Font).Width; // 文本未超出宽度时正常绘制 if (_textWidth <= contentBounds.Width) { TextRenderer.DrawText(graphics, text, cellStyle.Font, contentBounds, cellStyle.ForeColor, TextFormatFlags.Left | TextFormatFlags.VerticalCenter); _scrollTimer.Stop(); return; } // 启动定时器(仅未启动时) if (!_scrollTimer.Enabled) { _scrollTimer.Start(); } // 计算绘制区域,通过裁剪实现部分文本显示 var drawRect = new Rectangle(contentBounds.X - _scrollOffset, contentBounds.Y, _textWidth, contentBounds.Height); using (var clipRegion = new Region(contentBounds)) { graphics.Clip = clipRegion; TextRenderer.DrawText(graphics, text, cellStyle.Font, drawRect, cellStyle.ForeColor, TextFormatFlags.Left | TextFormatFlags.VerticalCenter); graphics.ResetClip(); } } // 释放定时器资源,防止内存泄漏 protected override void Dispose(bool disposing) { if (disposing) { _scrollTimer?.Dispose(); } base.Dispose(disposing); } }
自定义列类(方便在设计器中使用)
public class MarqueeDataGridViewTextBoxColumn : DataGridViewTextBoxColumn { public MarqueeDataGridViewTextBoxColumn() { CellTemplate = new MarqueeDataGridViewTextBoxCell(); } }
关键说明
- 使用
TextRenderer替代Graphics.DrawString,确保文本绘制精度,避免字符截断问题 - 定时器间隔设为50ms、滚动步长1像素,实现接近原生的平滑滚动效果,解决逐字符滚动的卡顿感
- 仅在文本超出单元格宽度时启动定时器,单元格不可见或文本长度不足时自动停止,优化性能
- 通过
Region裁剪绘制区域,实现文本的部分显示,无需修改formattedValue
内容的提问来源于stack exchange,提问作者Brandon Domonoski
相关产品推荐
相关产品推荐

