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);
}
}
}

View File

@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8" ?> <?xml version="1.0" encoding="utf-8" ?>
<configuration> <configuration>
<appSettings> <appSettings>
<!--<add key="PLC_IP" value="192.168.1.170" />--> <add key="PLC_IP" value="192.168.1.170" />
<add key="PLC_IP" value="127.0.0.1" /> <!--<add key="PLC_IP" value="127.0.0.1" />-->
<add key="PLC_PORT" value="502" /> <add key="PLC_PORT" value="502" />
</appSettings> </appSettings>
</configuration> </configuration>

View File

@@ -1,9 +1,13 @@
<Application x:Class="滑动摩擦测试仪.App" <Application x:Class="PLCDataMonitor.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:滑动摩擦测试仪" xmlns:local="clr-namespace:PLCDataMonitor"
StartupUri="MainWindow.xaml"> StartupUri="MainWindow.xaml">
<Application.Resources> <Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Themes/Generic.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources> </Application.Resources>
</Application> </Application>

View File

@@ -2,7 +2,7 @@
using System.Data; using System.Data;
using System.Windows; using System.Windows;
namespace namespace PLCDataMonitor
{ {
/// <summary> /// <summary>
/// Interaction logic for App.xaml /// Interaction logic for App.xaml

View File

@@ -31,12 +31,12 @@
<!-- 在 ResumeButton 后添加 --> <!-- 在 ResumeButton 后添加 -->
<TextBlock x:Name="AlarmTextBlock"
Text="无报警" <local:AlarmScrollingText x:Name="AlarmScrollingCtrl"
FontSize="10" Text="无报警"
Foreground="Red" Background="#FFF0F0F0"
Margin="15,0,0,0" Height="40"
VerticalAlignment="Center"/> Margin="5"/>
</StackPanel> </StackPanel>
</Border> </Border>

View File

@@ -31,7 +31,9 @@ namespace PLCDataMonitor
public void UpdateAlarms(string alarmMessage) public void UpdateAlarms(string alarmMessage)
{ {
Dispatcher.Invoke(() => AlarmTextBlock.Text = alarmMessage);
Dispatcher.Invoke(() => // 更新滚动报警控件的文本
AlarmScrollingCtrl.Text = alarmMessage);
} }
// 更新数据显示 // 更新数据显示

31
Themes/Generic.xaml Normal file
View File

@@ -0,0 +1,31 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:PLCDataMonitor">
<Style TargetType="{x:Type local:AlarmScrollingText}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:AlarmScrollingText}">
<Border Name="PART_ClipBorder"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
ClipToBounds="True"
Height="40">
<TextBlock Name="PART_TextBlock"
Text="{TemplateBinding Text}"
FontSize="18"
FontWeight="Bold"
Foreground="Red"
VerticalAlignment="Center"
RenderTransformOrigin="0,0.5">
<TextBlock.RenderTransform>
<TranslateTransform X="0"/>
</TextBlock.RenderTransform>
</TextBlock>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>

View File

@@ -20,4 +20,10 @@
<PackageReference Include="WinForms.DataVisualization" Version="1.10.0" /> <PackageReference Include="WinForms.DataVisualization" Version="1.10.0" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Compile Update="AlarmScrollingText.cs">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Compile>
</ItemGroup>
</Project> </Project>