编辑
2025-10-13
C#
00

目录

概述
主要特性
完整代码
调用
绘制逻辑
总结

概述

CustomScrollListView 是一个基于 C# 和 GDI+ 的自定义控件,继承自 Control 类,能够实现具有列头和项目的滚动列表视图。此控件支持多列展示,并可以通过定时器实现自动滚动效果,使得用户可以流畅地查看列表内容。


主要特性

  • 列头支持:允许用户添加和自定义列头的文本、宽度以及背景和前景颜色。
  • 项目管理:每列可以向其添加项目,并支持动态更新。
  • 滚动效果:通过定时器控制滚动速度,实现流畅的滾动效果。
  • 可见项目数控制:用户可以设置可见的项目数量,自动调整控件的尺寸以适应。

完整代码

C#
using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Windows.Forms; using Timer = System.Windows.Forms.Timer; namespace AppControls { public class CustomScrollListView : Control { private List<ColumnHeader> _columns; private List<List<string>> _columnItems; private int[] _currentIndex; private Timer _scrollTimer; private int _scrollSpeed = 2; // 像素/每次滚动 private int _itemHeight = 30; // 每个条目的高度 private int _headerHeight = 40; // 列头高度 private float _currentOffset = 0; private bool _isScrolling = false; private int _visibleItemCount = 4; // 可见条目数量 private int _columnCount = 1; // 列数 public CustomScrollListView() { _columns = new List<ColumnHeader>(); _columnItems = new List<List<string>>(); _columnCount = 1; _currentIndex = new int[_columnCount]; SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint, true); UpdateStyles(); _scrollTimer = new Timer(); _scrollTimer.Interval = 20; // 每20毫秒滚动一次 _scrollTimer.Tick += ScrollTimer_Tick; } /// <summary> /// 获取或设置可见的条目数量 /// </summary> [Category("Custom Properties")] [Description("设置列表中可见的条目数量")] [DefaultValue(4)] public int VisibleItemCount { get { return _visibleItemCount; } set { if (value > 0) { _visibleItemCount = value; // 重新调整控件高度 Height = _headerHeight + (_visibleItemCount * _itemHeight); // 重新绘制 Invalidate(); } else { throw new ArgumentException("可见条目数量必须大于0"); } } } // 列头类 public class ColumnHeader { public string Text { get; set; } public Color BackColor { get; set; } = Color.LightGray; public Color ForeColor { get; set; } = Color.Black; public int Width { get; set; } } // 添加列头方法 public void AddColumn(string text, int width = 100, Color? backColor = null, Color? foreColor = null) { var column = new ColumnHeader { Text = text, Width = width, BackColor = backColor ?? Color.LightGray, ForeColor = foreColor ?? Color.Black }; _columns.Add(column); _columnItems.Add(new List<string>()); _columnCount = _columns.Count; _currentIndex = new int[_columnCount]; Invalidate(); } // 修改列头属性 public void SetColumnHeader(int columnIndex, string text = null, int? width = null, Color? backColor = null, Color? foreColor = null) { if (columnIndex >= 0 && columnIndex < _columns.Count) { var column = _columns[columnIndex]; if (text != null) column.Text = text; if (width.HasValue) column.Width = width.Value; if (backColor.HasValue) column.BackColor = backColor.Value; if (foreColor.HasValue) column.ForeColor = foreColor.Value; Invalidate(); } } [Category("Custom Properties")] [Description("列头高度")] public int HeaderHeight { get { return _headerHeight; } set { _headerHeight = value; Invalidate(); } } // 为指定列添加条目 public void AddItemToColumn(int columnIndex, string item) { if (columnIndex >= 0 && columnIndex < _columnCount) { _columnItems[columnIndex].Add(item); Invalidate(); } } protected override void OnPaint(PaintEventArgs e) { base.OnPaint(e); if (_columnItems.Count == 0) return; Graphics g = e.Graphics; g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias; g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit; g.Clear(BackColor); // 计算每列宽度 int availableWidth = Width; int[] columnWidths = new int[_columnCount]; // 优先使用自定义宽度,否则平均分配 for (int col = 0; col < _columnCount; col++) { columnWidths[col] = _columns[col].Width > 0 ? _columns[col].Width : availableWidth / _columnCount; } int totalHeaderWidth = this.Width; g.FillRectangle(new SolidBrush(_columns[0].BackColor), 0, 0, totalHeaderWidth, _headerHeight); // 第一步:绘制固定的列头 - 不受滚动影响 g.SetClip(new Rectangle(0, 0, Width, _headerHeight)); for (int col = 0; col < _columnCount; col++) { int xPos = columnWidths.Take(col).Sum(); // 绘制列头背景 using (SolidBrush headerBrush = new SolidBrush(_columns[col].BackColor)) { g.FillRectangle(headerBrush, xPos, 0, columnWidths[col], _headerHeight); } // 绘制列头文本 using (SolidBrush textBrush = new SolidBrush(_columns[col].ForeColor)) { StringFormat sf = new StringFormat { Alignment = StringAlignment.Near, LineAlignment = StringAlignment.Center }; g.DrawString(_columns[col].Text, Font, textBrush, new RectangleF(xPos, 0, columnWidths[col], _headerHeight), sf); } } g.ResetClip(); // 第二步:绘制滚动内容 - 只在列头下方区域绘制 g.SetClip(new Rectangle(0, _headerHeight + 1, Width, Height - _headerHeight)); // 定义分隔线颜色 Color separatorColor = Color.LightGray; using (Pen separatorPen = new Pen(separatorColor, 1)) { for (int col = 0; col < _columnCount; col++) { int xPos = columnWidths.Take(col).Sum(); int startIndex = _currentIndex[col]; for (int i = 0; i < _visibleItemCount; i++) { // 计算索引,确保即使是负数也能正确取值 int index = (startIndex + i + _columnItems[col].Count) % _columnItems[col].Count; // 计算绘制位置 - 固定从列头高度后开始 float yPos = i * _itemHeight + _currentOffset + _headerHeight; // 绘制条目背景 g.FillRectangle(Brushes.White, xPos, yPos, columnWidths[col], _itemHeight); // 绘制底部分隔线 g.DrawLine(separatorPen, xPos, yPos + _itemHeight - 1, this.Width, yPos + _itemHeight - 1); // 绘制条目文本 using (SolidBrush textBrush = new SolidBrush(ForeColor)) { g.DrawString(_columnItems[col][index], Font, textBrush, xPos, yPos + (_itemHeight - Font.Height) / 2); } } } } g.ResetClip(); } // 重写大小调整方法 - 固定高度 protected override void OnResize(EventArgs e) { base.OnResize(e); // 高度 = 列头高度 + 可见条目高度 Height = _headerHeight + (_visibleItemCount * _itemHeight); Invalidate(); } // 开始滚动 public void StartScroll() { bool canScroll = true; for (int i = 0; i < _columnCount; i++) { if (_columnItems[i].Count <= _visibleItemCount) { canScroll = false; break; } } if (canScroll) { _isScrolling = true; _currentOffset = 0; _scrollTimer.Start(); } } // 停止滚动 public void StopScroll() { _scrollTimer.Stop(); _isScrolling = false; _currentOffset = 0; for (int i = 0; i < _columnCount; i++) { _currentIndex[i] = 0; } Invalidate(); } // 滚动事件处理 private void ScrollTimer_Tick(object sender, EventArgs e) { // 累加偏移量 _currentOffset -= _scrollSpeed; // 当偏移量超过一个条目高度时,切换到下一个条目 if (Math.Abs(_currentOffset) >= _itemHeight) { _currentOffset += _itemHeight; // 调整偏移量,保持连续性 // 更新每一列的索引 for (int i = 0; i < _columnCount; i++) { _currentIndex[i] = (_currentIndex[i] + 1) % _columnItems[i].Count; } } Invalidate(); } } }

调用

C#
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; namespace AppControls { public partial class Form17 : Form { private CustomScrollListView _customListView; public Form17() { InitializeComponent(); // 创建自定义滚动列表 _customListView = new CustomScrollListView(); _customListView.BackColor = Color.White; _customListView.ForeColor = Color.Black; _customListView.Font = new Font("Arial", 10); // 添加列头 _customListView.AddColumn("姓名", 100, Color.DarkGreen, Color.White); _customListView.AddColumn("年龄", 80, Color.DarkGreen, Color.White); _customListView.AddColumn("城市", 120, Color.DarkGreen, Color.White); _customListView.HeaderHeight = 30; // 设置列头高度为50像素 // 为每一列添加数据 // 第一列(姓名) _customListView.AddItemToColumn(0, "张三"); _customListView.AddItemToColumn(0, "李四"); _customListView.AddItemToColumn(0, "王五"); _customListView.AddItemToColumn(0, "赵六"); _customListView.AddItemToColumn(0, "孙七"); _customListView.AddItemToColumn(0, "周八"); _customListView.AddItemToColumn(0, "吴九"); // 第二列(年龄) _customListView.AddItemToColumn(1, "25"); _customListView.AddItemToColumn(1, "30"); _customListView.AddItemToColumn(1, "28"); _customListView.AddItemToColumn(1, "35"); _customListView.AddItemToColumn(1, "22"); _customListView.AddItemToColumn(1, "40"); _customListView.AddItemToColumn(1, "27"); // 第三列(城市) _customListView.AddItemToColumn(2, "北京"); _customListView.AddItemToColumn(2, "上海"); _customListView.AddItemToColumn(2, "广州"); _customListView.AddItemToColumn(2, "深圳"); _customListView.AddItemToColumn(2, "杭州"); _customListView.AddItemToColumn(2, "成都"); _customListView.AddItemToColumn(2, "武汉"); // 设置位置和大小 _customListView.Width = 300; _customListView.Dock = DockStyle.Top; // 添加到窗体 this.Controls.Add(_customListView); // 启动按钮 Button btnStartScroll = new Button(); btnStartScroll.Text = "开始滚动"; btnStartScroll.Location = new Point(10, 200); btnStartScroll.Click += (s, e) => _customListView.StartScroll(); this.Controls.Add(btnStartScroll); btnStartScroll.Dock=DockStyle.Bottom; // 停止按钮 Button btnStopScroll = new Button(); btnStopScroll.Text = "停止滚动"; btnStopScroll.Location = new Point(100, 200); btnStopScroll.Click += (s, e) => _customListView.StopScroll(); this.Controls.Add(btnStopScroll); btnStopScroll.Dock = DockStyle.Bottom; } } }

image.png


绘制逻辑

控件的绘制逻辑通过重写 OnPaint 方法实现。该方法使用 GDI+ 的 Graphics 对象进行自定义绘制,以下是该方法的主要步骤:

  1. 设置抗锯齿模式:提升绘制质量。
  2. 绘制列头:计算列头的宽度并绘制每列的背景和文本。
  3. 绘制滚动内容:根据当前可见数量和偏移量,绘制项目内容并绘制分隔线。

总结

CustomScrollListView 为需要展示多列数据的 WinForms 应用程序提供了一个灵活的解决方案。这种控件通过自定义绘制和使用 GDI+ 提供了平滑的视觉效果,并易于扩展与集成。使用此控件,开发者可以创造出更加动感和用户友好的界面响应。

本文作者:技术老小子

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!