diff --git a/AlarmScrollingText.cs b/AlarmScrollingText.cs new file mode 100644 index 0000000..5a32845 --- /dev/null +++ b/AlarmScrollingText.cs @@ -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 +{ + /// + /// 自定义滚动报警控件(跑马灯效果 + 闪烁) + /// + 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); + } + } +} \ No newline at end of file diff --git a/App.config b/App.config index 09fbc3f..1375969 100644 --- a/App.config +++ b/App.config @@ -1,8 +1,8 @@  - - + + diff --git a/App.xaml b/App.xaml index 9695b8d..90b02dc 100644 --- a/App.xaml +++ b/App.xaml @@ -1,9 +1,13 @@ - - + + + + + diff --git a/App.xaml.cs b/App.xaml.cs index 1ebebcf..9f34ac9 100644 --- a/App.xaml.cs +++ b/App.xaml.cs @@ -2,7 +2,7 @@ using System.Data; using System.Windows; -namespace 滑动摩擦测试仪 +namespace PLCDataMonitor { /// /// Interaction logic for App.xaml diff --git a/HomePage.xaml b/HomePage.xaml index 094788c..be86264 100644 --- a/HomePage.xaml +++ b/HomePage.xaml @@ -31,12 +31,12 @@ - + + diff --git a/HomePage.xaml.cs b/HomePage.xaml.cs index 717a5c3..d85675c 100644 --- a/HomePage.xaml.cs +++ b/HomePage.xaml.cs @@ -31,7 +31,9 @@ namespace PLCDataMonitor public void UpdateAlarms(string alarmMessage) { - Dispatcher.Invoke(() => AlarmTextBlock.Text = alarmMessage); + + Dispatcher.Invoke(() => // 更新滚动报警控件的文本 + AlarmScrollingCtrl.Text = alarmMessage); } // 更新数据显示 diff --git a/Themes/Generic.xaml b/Themes/Generic.xaml new file mode 100644 index 0000000..4073925 --- /dev/null +++ b/Themes/Generic.xaml @@ -0,0 +1,31 @@ + + + + \ No newline at end of file diff --git a/滑动摩擦测试仪.csproj b/滑动摩擦测试仪.csproj index 3980986..badea60 100644 --- a/滑动摩擦测试仪.csproj +++ b/滑动摩擦测试仪.csproj @@ -20,4 +20,10 @@ + + + Always + + +