添加项目文件。
This commit is contained in:
@@ -0,0 +1,291 @@
|
||||
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
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user