using AntdUI; using System; using System.ComponentModel; using System.Diagnostics; using System.Drawing; using System.Drawing.Text; using System.Runtime.CompilerServices; using System.Windows.Forms; namespace JinYuan.ControlLib { public partial class UCRollText : UserControl { private Rectangle _textRect; // 文字绘制矩形 private int _moveDirection = 1; // 移动方向 private const int MIN_MOVE_PIXELS = 1; private const int DEFAULT_MOVE_STEP = 2; // 减小步长,使滚动更平滑 private const int MIN_INTERVAL = 80; // 最小间隔 16ms ≈ 60fps private Timer _timer; private long _lastTickTime; private bool _isAnimating = false; // 用于标记是否有异步操作正在执行,避免并发问题 private string _lastText = ""; // 缓存上次文本,避免重复测量 private Size _textSize; // 缓存文本尺寸 #region Properties public override Font Font { get => base.Font; set { base.Font = value; _textSize = Size.Empty; // 字体改变,需要重新测量 UpdateTextRect(); } } public override string Text { get => base.Text; set { if (base.Text != value) { base.Text = value; _lastText = value; _textSize = Size.Empty; // 文本改变,需要重新测量 UpdateTextRect(); if (_isChangeReset) ResetTextPosition(); } } } private RollStyle _rollStyle = RollStyle.LeftToRight; public RollStyle RollStyle { get => _rollStyle; set { if (_rollStyle != value) { _rollStyle = value; _moveDirection = value == RollStyle.RightToLeft ? -1 : 1; ResetTextPosition(); } } } private bool _isChangeReset = true; [Description("文本改变是否重新从边缘运动"), Category("自定义")] public bool IsChangeReset { get => _isChangeReset; set => _isChangeReset = value; } private bool _isExit; [Description("是否退出"), Category("自定义")] public bool IsExit { get => _isExit; set { _isExit = value; if (value) StopAnimation(); } } private int _moveInterval = MIN_INTERVAL; [Description("每次滚动间隔时间,越小速度越快"), Category("自定义")] public int MoveInterval { get => _moveInterval; set { _moveInterval = Math.Max(value, MIN_INTERVAL); if (_timer != null) { _timer.Interval = _moveInterval; } } } #endregion public UCRollText() { InitializeControl(); StartAnimation(); } private void InitializeControl() { // 高性能绘制样式 SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.ResizeRedraw | ControlStyles.UserPaint | ControlStyles.SupportsTransparentBackColor, true); DoubleBuffered = true; Size = new Size(450, 30); Text = "滚动文字"; ForeColor = Color.FromArgb(255, 77, 59); } private void StartAnimation() { if (_isAnimating || _timer != null) return; _isAnimating = true; _lastTickTime = Stopwatch.GetTimestamp(); _timer = new Timer(); _timer.Interval = _moveInterval; _timer.Tick += Timer_Tick; _timer.Start(); } private void Timer_Tick(object sender, EventArgs e) { if (!_isAnimating || !Visible || this.IsDisposed) return; try { long now = Stopwatch.GetTimestamp(); long elapsedMs = (now - _lastTickTime) * 1000 / Stopwatch.Frequency; _lastTickTime = now; // 根据经过的时间计算平滑步长 int step = (int)(DEFAULT_MOVE_STEP * (elapsedMs / (float)_moveInterval)); if (step < 1) step = 1; UpdateTextPosition(step); //// 调试输出(发布时可注释) //Console.WriteLine($"UCRollText Tick elapsed={elapsedMs}ms"); } catch { } } private void StopAnimation() { _isAnimating = false; if (_timer != null) { _timer.Stop(); _timer.Dispose(); _timer = null; } } private void UpdateTextPosition(int step) { if (_textRect.IsEmpty) return; // 只有在BackAndForth模式下才检查文字宽度 if (_rollStyle == RollStyle.BackAndForth && _textRect.Width >= Width) return; int oldX = _textRect.X; // 更新位置 _textRect.X += step * _moveDirection; // 边界处理 switch (_rollStyle) { case RollStyle.BackAndForth: if (_textRect.X <= 0) _moveDirection = 1; else if (_textRect.Right >= Width) _moveDirection = -1; break; case RollStyle.LeftToRight: if (_textRect.X > Width) _textRect.X = -_textRect.Width - 1; break; case RollStyle.RightToLeft: if (_textRect.Right < 0) _textRect.X = Width; break; } // 只有位置变化才重绘 if (Math.Abs(_textRect.X - oldX) >= MIN_MOVE_PIXELS) { if (IsHandleCreated && !IsDisposed && Visible) { Invalidate(); } } } private void UpdateTextRect() { if (string.IsNullOrEmpty(Text)) { _textRect = Rectangle.Empty; return; } // 只有在文本或字体改变时才重新测量 if (_textSize.IsEmpty || Text != _lastText) { _textSize = TextRenderer.MeasureText(Text, Font); _lastText = Text; } _textRect = new Rectangle( _isChangeReset ? 0 : _textRect.X, (Height - _textSize.Height) / 2, _textSize.Width, _textSize.Height ); if (IsHandleCreated && Visible) Invalidate(); } private void ResetTextPosition() { if (_textRect.IsEmpty) return; switch (_rollStyle) { case RollStyle.LeftToRight: _textRect.X = -_textRect.Width - 1; break; case RollStyle.RightToLeft: _textRect.X = Width; break; default: _textRect.X = 0; break; } if (IsHandleCreated && Visible) Invalidate(); } protected override void OnPaint(PaintEventArgs e) { // 禁用基类绘制,减少绘制层次 // base.OnPaint(e); if (!_textRect.IsEmpty && !string.IsNullOrEmpty(Text) && Visible) { // 使用TextRenderer的优化方法,指定文本格式 TextRenderer.DrawText(e.Graphics, Text, Font, _textRect, ForeColor, TextFormatFlags.NoPrefix | TextFormatFlags.SingleLine | TextFormatFlags.NoClipping); } } protected override void OnSizeChanged(EventArgs e) { base.OnSizeChanged(e); if (_textRect != Rectangle.Empty) { _textRect.Y = (Height - _textRect.Height) / 2; Invalidate(); } } protected override void OnVisibleChanged(EventArgs e) { base.OnVisibleChanged(e); // 控件不可见时停止动画,节省资源 if (Visible) { StartAnimation(); } else { StopAnimation(); } } protected override void OnHandleDestroyed(EventArgs e) { StopAnimation(); base.OnHandleDestroyed(e); } private void InitializeComponent() { this.SuspendLayout(); this.Name = "UCRollText"; this.Size = new Size(450, 30); this.ResumeLayout(false); } } public enum RollStyle { LeftToRight, RightToLeft, BackAndForth } }