using JinYuan.ControlLib.UCHelper; using System; using System.ComponentModel; using System.Diagnostics; using System.Drawing; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; namespace JinYuan.ControlLib { public partial class UCRollText : UserControl { private Rectangle _rectText; private int _moveDirection = 1; private const int DEFAULT_MOVE_STEP = 2; // 减小步长,使滚动更平滑 private const int DEFAULT_SLEEP_TIME = 20; // 减小刷新间隔,提高流畅度 private readonly SemaphoreSlim _updateLock = new SemaphoreSlim(1, 1); private CancellationTokenSource _cts; private volatile bool _isStop = true; #region Properties public override Font Font { get => base.Font; set { base.Font = value; UpdateTextRectangle(); } } public override string Text { get => base.Text; set { base.Text = value; UpdateTextRectangle(); } } private RollStyle _rollStyle = RollStyle.LeftToRight; public RollStyle RollStyle { get => _rollStyle; set { _rollStyle = value; _moveDirection = value == RollStyle.RightToLeft ? -1 : 1; ResetTextPosition(); } } private bool _isChangeReset; [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 _moveSleepTime = DEFAULT_SLEEP_TIME; [Description("每次滚动间隔时间,越小速度越快"), Category("自定义")] public int MoveSleepTime { get => _moveSleepTime; set => _moveSleepTime = value > 0 ? value : DEFAULT_SLEEP_TIME; } #endregion public UCRollText() { InitializeControl(); InitializeEvents(); StartAnimation(); } private void InitializeControl() { SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.DoubleBuffer | ControlStyles.ResizeRedraw | ControlStyles.Selectable | ControlStyles.SupportsTransparentBackColor | ControlStyles.UserPaint, true); DoubleBuffered = true; // 启用双缓冲,减少闪烁 Size = new Size(450, 30); Text = "滚动文字"; ForeColor = Color.FromArgb(255, 77, 59); } private void InitializeEvents() { Load += UCRollText_Load; SizeChanged += UCRollText_SizeChanged; VisibleChanged += (s, e) => { _isStop = !Visible; if (Visible) StartAnimation(); }; } private void UCRollText_SizeChanged(object sender, EventArgs e) { if (_rectText != Rectangle.Empty) { _rectText.Y = (Height - _rectText.Height) / 2 + 1; } } private void UCRollText_Load(object sender, EventArgs e) { ResetTextPosition(); } private async void StartAnimation() { if (!_isStop) return; _isStop = false; _cts = new CancellationTokenSource(); try { await Task.Run(async () => { var stopwatch = new Stopwatch(); stopwatch.Start(); while (!_isStop && !_isExit) { try { var elapsed = stopwatch.ElapsedMilliseconds; stopwatch.Restart(); await Task.Delay(MoveSleepTime, _cts.Token); if (!IsHandleCreated || IsDisposed) continue; await UpdateTextPositionAsync(elapsed); } catch (OperationCanceledException) { break; } catch (Exception ex) { Debug.WriteLine($"Animation error: {ex.Message}"); } } }, _cts.Token); } catch (OperationCanceledException) { } } private void StopAnimation() { _isStop = true; _cts?.Cancel(); _cts?.Dispose(); _cts = null; } private async Task UpdateTextPositionAsync(long elapsedMilliseconds) { if (_rectText == Rectangle.Empty) return; if (_rollStyle == RollStyle.BackAndForth && _rectText.Width >= Width) return; await _updateLock.WaitAsync(); try { await Task.Run(() => { if (InvokeRequired) { BeginInvoke((MethodInvoker)(() => UpdateTextPosition(elapsedMilliseconds))); } else { UpdateTextPosition(elapsedMilliseconds); } }); } finally { _updateLock.Release(); } } private void UpdateTextPosition(long elapsedMilliseconds) { // 根据时间调整滚动步长,确保动画平滑 int step = (int)(DEFAULT_MOVE_STEP * (elapsedMilliseconds / (double)MoveSleepTime)); _rectText.X += step * _moveDirection; switch (_rollStyle) { case RollStyle.BackAndForth: if (_rectText.X <= 0) _moveDirection = 1; else if (_rectText.Right >= Width) _moveDirection = -1; break; case RollStyle.LeftToRight: if (_rectText.X > Width) _rectText.X = -_rectText.Width - 1; break; case RollStyle.RightToLeft: if (_rectText.Right < 0) _rectText.X = Width + _rectText.Width + 1; break; } Invalidate(); } private void UpdateTextRectangle() { if (string.IsNullOrEmpty(Text)) return; using (var g = CreateGraphics()) { var size = g.MeasureString(Text, Font); _rectText = new Rectangle( _isChangeReset ? 0 : _rectText.X, (Height - (int)size.Height) / 2 + 1, (int)size.Width, (int)size.Height ); } } private void ResetTextPosition() { if (_rectText == Rectangle.Empty) return; switch (_rollStyle) { case RollStyle.LeftToRight: _rectText.X = -_rectText.Width - 1; break; case RollStyle.RightToLeft: _rectText.X = Width + _rectText.Width + 1; break; default: _rectText.X = 0; break; } } protected override void OnPaint(PaintEventArgs e) { base.OnPaint(e); if (_rectText != Rectangle.Empty) { e.Graphics.SetGDIHigh(); using (var brush = new SolidBrush(ForeColor)) { e.Graphics.DrawString(Text, Font, brush, _rectText.Location); } } } } public enum RollStyle { LeftToRight, RightToLeft, BackAndForth } }