292 lines
8.4 KiB
C#
292 lines
8.4 KiB
C#
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
|
||
|
|
}
|
||
|
|
|
||
|
|
}
|