This commit is contained in:
xyy
2026-03-23 17:31:25 +08:00
parent 140bd2af8e
commit 9447fa9df4
8 changed files with 220 additions and 13 deletions

164
AlarmScrollingText.cs Normal file
View File

@@ -0,0 +1,164 @@
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Threading;
namespace PLCDataMonitor
{
/// <summary>
/// 自定义滚动报警控件(跑马灯效果 + 闪烁)
/// </summary>
public class AlarmScrollingText : ContentControl
{
private TextBlock _textBlock;
private Border _clipBorder;
private Storyboard _scrollStoryboard;
private Storyboard _blinkStoryboard;
private double _currentOffset = 0;
private DispatcherTimer _resetTimer;
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(AlarmScrollingText),
new FrameworkPropertyMetadata(string.Empty, OnTextChanged));
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
private static void OnTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var control = d as AlarmScrollingText;
string newText = e.NewValue as string ?? string.Empty;
control?.OnTextChanged(newText);
}
public AlarmScrollingText()
{
// 默认样式
DefaultStyleKey = typeof(AlarmScrollingText);
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
_textBlock = GetTemplateChild("PART_TextBlock") as TextBlock;
_clipBorder = GetTemplateChild("PART_ClipBorder") as Border;
if (_textBlock != null && _clipBorder != null)
{
_textBlock.Loaded += OnTextBlockLoaded;
_textBlock.SizeChanged += OnTextBlockSizeChanged;
InitializeAnimations();
}
}
private void OnTextBlockLoaded(object sender, RoutedEventArgs e)
{
StartScrollingIfNeeded();
}
private void OnTextBlockSizeChanged(object sender, SizeChangedEventArgs e)
{
StartScrollingIfNeeded();
}
private void OnTextChanged(string newText)
{
if (_textBlock != null)
{
_textBlock.Text = newText;
StartScrollingIfNeeded();
StartBlinking(newText);
}
}
private void InitializeAnimations()
{
// 滚动动画
_scrollStoryboard = new Storyboard();
var doubleAnimation = new DoubleAnimation
{
From = 0,
To = -500, // 临时值,后面动态计算
Duration = TimeSpan.FromSeconds(8),
RepeatBehavior = RepeatBehavior.Forever
};
Storyboard.SetTargetProperty(doubleAnimation, new PropertyPath("(UIElement.RenderTransform).(TranslateTransform.X)"));
_scrollStoryboard.Children.Add(doubleAnimation);
// 闪烁动画(前景色交替变化)
_blinkStoryboard = new Storyboard { RepeatBehavior = RepeatBehavior.Forever };
var colorAnimation = new ColorAnimation
{
From = Colors.Red,
To = Colors.Orange,
Duration = TimeSpan.FromSeconds(0.5),
AutoReverse = true,
RepeatBehavior = RepeatBehavior.Forever
};
Storyboard.SetTargetProperty(colorAnimation, new PropertyPath("Foreground.Color"));
_blinkStoryboard.Children.Add(colorAnimation);
}
private void StartScrollingIfNeeded()
{
if (_textBlock == null || _clipBorder == null) return;
// 停止当前动画
_scrollStoryboard?.Stop(_textBlock);
_textBlock.RenderTransform = new TranslateTransform();
// 如果没有报警或报警文本为空,不启动滚动
if (string.IsNullOrWhiteSpace(Text) || Text == "无报警")
return;
// 等待布局完成后获取实际宽度
Dispatcher.BeginInvoke(DispatcherPriority.Loaded, new Action(() =>
{
double textWidth = _textBlock.ActualWidth;
double containerWidth = _clipBorder.ActualWidth;
if (textWidth <= containerWidth)
{
// 文本不够长,不需要滚动,但保持静止
return;
}
// 重新设置动画的目标偏移量
double targetOffset = containerWidth - textWidth;
var animation = _scrollStoryboard.Children[0] as DoubleAnimation;
if (animation != null)
{
animation.From = 0;
animation.To = targetOffset;
animation.Duration = TimeSpan.FromSeconds(Math.Max(4, textWidth / 50.0)); // 速度自适应
}
_textBlock.RenderTransform = new TranslateTransform();
_scrollStoryboard.Begin(_textBlock, true);
}));
}
private void StartBlinking(string alarmText)
{
if (_textBlock == null) return;
// 停止当前闪烁
_blinkStoryboard?.Stop(_textBlock);
// 如果是无效报警或无报警,恢复正常颜色并停止闪烁
if (string.IsNullOrWhiteSpace(alarmText) || alarmText == "无报警")
{
_textBlock.Foreground = SystemColors.ControlTextBrush;
return;
}
// 开始闪烁动画
_blinkStoryboard.Begin(_textBlock, true);
}
}
}