形状填充颜色更新停止故事板闪烁动画
Shape Fill color update stops Storyboard blinking animation
我的 WPF 应用程序是生产系统的监视器。主 window 显示显示连接状态(DOWN、LOADING 或 UP)的 3 个控件之一。 UP 控件有几个 high-level 按钮形状(下例中的椭圆),代表生产系统的各种元素(路由器、配置、内部连接、外部连接……)。 object 的颜色反映了其 children(或 grandchildren,或 great-grandchildren ...)的“最差”状态。如果 children 之一改变状态,它也会 flash/blink(通过 Storyboard 动画)。
每个 Button 形状映射到一个 ColorStatus object:
public class ColorStatus: INotifyPropertyChanged
{
private eStatusColor StatusColor {get; set;}
public bool IsFlashing {get; set;}
// ...
}
eStatusColor 是一个枚举(INACTIVE、NORMAL、WARNING、CRITICAL),我在其中使用 Converter Level2Color_Converter() 将其更改为 SolidColorBrush (grey/green/yellow/red),并将其应用于形状填充。 bool IsFlashing 用于触发填充颜色的动画(将从当前颜色变为黑色的 StatusBlinked,然后返回当前颜色,持续时间约为 1 秒)。这是一个示例按钮形状(路由器椭圆形):
<!-- Router Oval -->
<Button Grid.Column="1" Margin="0,3"
VerticalAlignment="Center" HorizontalAlignment="Center"
Focusable="False" ToolTip="Open Router Window"
Command="{Binding OpenRouterWindow_Command}" >
<Button.Template>
<!-- This part turns off button borders/on mouseover animations -->
<ControlTemplate TargetType="Button">
<ContentPresenter Content="{TemplateBinding Content}"/>
</ControlTemplate>
</Button.Template>
<Grid>
<Ellipse HorizontalAlignment="Center" VerticalAlignment="Center"
Height="60" Width="155"
Stroke="Black" StrokeThickness="2"
Fill="{Binding RouterStatus.StatusColor,
Converter={local:Level2Color_Converter}}">
<Ellipse.Style>
<Style TargetType="{x:Type Ellipse}">
<Style.Triggers>
<DataTrigger Binding="{Binding RouterStatus.IsFlashing}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard Name="Router_storyboard">
<BeginStoryboard.Storyboard>
<Storyboard>
<ColorAnimation To="{StaticResource StatusBlinked}"
Storyboard.TargetProperty="(Path.Fill).(SolidColorBrush.Color)"
AutoReverse="True"
RepeatBehavior="Forever"
Duration="0:0:0.5"/>
</Storyboard>
</BeginStoryboard.Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
<DataTrigger Binding="{Binding RouterStatus.IsFlashing}" Value="False">
<DataTrigger.EnterActions>
<StopStoryboard BeginStoryboardName="Router_storyboard"/>
</DataTrigger.EnterActions>
</DataTrigger>
</Style.Triggers>
</Style>
</Ellipse.Style>
</Ellipse>
<TextBlock VerticalAlignment="Center" HorizontalAlignment="Center"
FontFamily="Arial" FontSize="13"
Foreground="White" Text="ROUTER">
<TextBlock.Effect>
<DropShadowEffect/>
</TextBlock.Effect>
</TextBlock>
</Grid>
</Button>
问题是,如果 object 已经在闪烁并且您更改了颜色(例如,从黄色变为红色),它会关闭故事板动画(即使 bool IsFlashing 仍然为真)。
在手动测试中(我有一个测试 window 带有强制 color/flashing 状态更改的按钮),我通过以下方式解决了这个问题:
if (mockup_IsBlinking)
{
// Toggle is blinking to keep it blinking
mockup_IsBlinking = false;
mockup_IsBlinking = true;
}
也就是说,如果它正在闪烁,请关闭闪烁然后再次打开以重新启动动画。
对于实际测试(从我正在监视的应用程序获取状态更新),我修改了 ColorStatus 以通过修改 setter:
来模拟相同的方法
public class ColorStatus: INotifyPropertyChanged
{
private eStatusColor _StatusColor;
public eStatusColor StatusColor { get
{
return _StatusColor;
}
set
{
_StatusColor = value;
if (IsFlashing)
{
IsFlashing = false;
IsFlashing = true;
}
}
}
public bool IsFlashing {get; set;}
// ...
}
但是,当我连接到我正在监视的实际应用程序时,我相信 children 状态更新的冲击如此之快以至于动画不同步(我们得到 135 children 添加为 NORMAL,这会导致 top-level 按钮形状从 INACTIVE 变为 NORMAL,IsFlashing=false,然后实际的 child 状态更新出现,3 个 NORMAL 到 WARNING,IsFlashing=true 和 27 个 NORMAL 到 CRITICAL使用 IsFlashing=true)。当我启动监视器时,~ 1/5 的时间顶层闪烁是正确的(闪烁),4/5 的时间是不正确的(IsFlashing 是真的,但故事板动画不工作)。
注意,children状态的接收没有固定的顺序(我们得到的是顺序
它们是从生产应用程序发送的)。另请注意,这是一个 multi-threaded 应用程序(reader 线程排队
消息到消息处理程序线程,它更新 ViewModel,并通过 INotifyPropertyChanged 更新 GUI。
有没有办法解决这个问题,如果 IsFlashing 为真,故事板动画会继续,而不管填充颜色如何变化? (或者在颜色变化时不同步?)
最初,我使用了 Clemens 的评论,在它后面放置一个黑色椭圆,并将动画与不透明度相关联,同时将颜色与填充相关联。如果只有少数项目闪烁,这很好。
但是有这么多项目闪烁,似乎性能受到影响(至少在调试器中),而且所有项目在不同时间进入动画时都“不同步”。
所以,我把动画全部去掉了。相反,我启动了一个 Heartbeat 线程,它每秒切换一个 bool 值 FlashMaster。在我的 .xaml 中,我使用 Elipse.Fill MultiBinding:
<Ellipse.Fill>
<MultiBinding Converter="{StaticResource LevelBoolFlash2Color_Converter}">
<Binding Path="MrGWColorStatus.StatusColor"/>
<Binding Path="MrGWColorStatus.IsFlashing"/>
<Binding Path="AppVM.FlashMaster"/>
</MultiBinding>
</Ellipse.Fill>
其中 StatusColor 是状态级别(当前颜色),IsFlashing 是表示是否应该闪烁的布尔值,FlashMaster 是每秒切换的布尔值。
这是转换器:
/// <summary>
/// A converter that takes in
/// [0] StatusLevel enum
/// [1] IsFlashing boolean
/// [2] FlashMaster boolean
/// and converts it to the correct WPF brush for Normal/Warning/Critical/Inactive (if not Error bool), or StatusERROR if error.
/// </summary>
public class LevelBoolFlash2Color_Converter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
SolidColorBrush returnValue;
if ( (bool)values[1] && (bool)values[2])
{
// Blink is True, so use StatusBlinked (black) instead of using eStatusColor
returnValue = new SolidColorBrush((Color)Application.Current.FindResource("StatusBlinked"));
}
else
{
// Use the status color
switch ((eStatusColor)values[0])
{
case eStatusColor.NORMAL: returnValue = new SolidColorBrush((Color)Application.Current.FindResource("StatusNormal" )); break;
case eStatusColor.WARNING: returnValue = new SolidColorBrush((Color)Application.Current.FindResource("StatusWarning" )); break;
case eStatusColor.CRITICAL: returnValue = new SolidColorBrush((Color)Application.Current.FindResource("StatusCritical")); break;
default: returnValue = new SolidColorBrush((Color)Application.Current.FindResource("StatusInactive")); break;
}
}
return returnValue;
}
public object[] ConvertBack(object value, Type[] targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
其中 StatusBlinked 为黑色,.NORMAL 为绿色,.WARNING 为黄色,.CRITICAL 为红色,默认为灰色
我的 WPF 应用程序是生产系统的监视器。主 window 显示显示连接状态(DOWN、LOADING 或 UP)的 3 个控件之一。 UP 控件有几个 high-level 按钮形状(下例中的椭圆),代表生产系统的各种元素(路由器、配置、内部连接、外部连接……)。 object 的颜色反映了其 children(或 grandchildren,或 great-grandchildren ...)的“最差”状态。如果 children 之一改变状态,它也会 flash/blink(通过 Storyboard 动画)。
每个 Button 形状映射到一个 ColorStatus object:
public class ColorStatus: INotifyPropertyChanged
{
private eStatusColor StatusColor {get; set;}
public bool IsFlashing {get; set;}
// ...
}
eStatusColor 是一个枚举(INACTIVE、NORMAL、WARNING、CRITICAL),我在其中使用 Converter Level2Color_Converter() 将其更改为 SolidColorBrush (grey/green/yellow/red),并将其应用于形状填充。 bool IsFlashing 用于触发填充颜色的动画(将从当前颜色变为黑色的 StatusBlinked,然后返回当前颜色,持续时间约为 1 秒)。这是一个示例按钮形状(路由器椭圆形):
<!-- Router Oval -->
<Button Grid.Column="1" Margin="0,3"
VerticalAlignment="Center" HorizontalAlignment="Center"
Focusable="False" ToolTip="Open Router Window"
Command="{Binding OpenRouterWindow_Command}" >
<Button.Template>
<!-- This part turns off button borders/on mouseover animations -->
<ControlTemplate TargetType="Button">
<ContentPresenter Content="{TemplateBinding Content}"/>
</ControlTemplate>
</Button.Template>
<Grid>
<Ellipse HorizontalAlignment="Center" VerticalAlignment="Center"
Height="60" Width="155"
Stroke="Black" StrokeThickness="2"
Fill="{Binding RouterStatus.StatusColor,
Converter={local:Level2Color_Converter}}">
<Ellipse.Style>
<Style TargetType="{x:Type Ellipse}">
<Style.Triggers>
<DataTrigger Binding="{Binding RouterStatus.IsFlashing}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard Name="Router_storyboard">
<BeginStoryboard.Storyboard>
<Storyboard>
<ColorAnimation To="{StaticResource StatusBlinked}"
Storyboard.TargetProperty="(Path.Fill).(SolidColorBrush.Color)"
AutoReverse="True"
RepeatBehavior="Forever"
Duration="0:0:0.5"/>
</Storyboard>
</BeginStoryboard.Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
<DataTrigger Binding="{Binding RouterStatus.IsFlashing}" Value="False">
<DataTrigger.EnterActions>
<StopStoryboard BeginStoryboardName="Router_storyboard"/>
</DataTrigger.EnterActions>
</DataTrigger>
</Style.Triggers>
</Style>
</Ellipse.Style>
</Ellipse>
<TextBlock VerticalAlignment="Center" HorizontalAlignment="Center"
FontFamily="Arial" FontSize="13"
Foreground="White" Text="ROUTER">
<TextBlock.Effect>
<DropShadowEffect/>
</TextBlock.Effect>
</TextBlock>
</Grid>
</Button>
问题是,如果 object 已经在闪烁并且您更改了颜色(例如,从黄色变为红色),它会关闭故事板动画(即使 bool IsFlashing 仍然为真)。
在手动测试中(我有一个测试 window 带有强制 color/flashing 状态更改的按钮),我通过以下方式解决了这个问题:
if (mockup_IsBlinking)
{
// Toggle is blinking to keep it blinking
mockup_IsBlinking = false;
mockup_IsBlinking = true;
}
也就是说,如果它正在闪烁,请关闭闪烁然后再次打开以重新启动动画。
对于实际测试(从我正在监视的应用程序获取状态更新),我修改了 ColorStatus 以通过修改 setter:
来模拟相同的方法 public class ColorStatus: INotifyPropertyChanged
{
private eStatusColor _StatusColor;
public eStatusColor StatusColor { get
{
return _StatusColor;
}
set
{
_StatusColor = value;
if (IsFlashing)
{
IsFlashing = false;
IsFlashing = true;
}
}
}
public bool IsFlashing {get; set;}
// ...
}
但是,当我连接到我正在监视的实际应用程序时,我相信 children 状态更新的冲击如此之快以至于动画不同步(我们得到 135 children 添加为 NORMAL,这会导致 top-level 按钮形状从 INACTIVE 变为 NORMAL,IsFlashing=false,然后实际的 child 状态更新出现,3 个 NORMAL 到 WARNING,IsFlashing=true 和 27 个 NORMAL 到 CRITICAL使用 IsFlashing=true)。当我启动监视器时,~ 1/5 的时间顶层闪烁是正确的(闪烁),4/5 的时间是不正确的(IsFlashing 是真的,但故事板动画不工作)。
注意,children状态的接收没有固定的顺序(我们得到的是顺序 它们是从生产应用程序发送的)。另请注意,这是一个 multi-threaded 应用程序(reader 线程排队 消息到消息处理程序线程,它更新 ViewModel,并通过 INotifyPropertyChanged 更新 GUI。
有没有办法解决这个问题,如果 IsFlashing 为真,故事板动画会继续,而不管填充颜色如何变化? (或者在颜色变化时不同步?)
最初,我使用了 Clemens 的评论,在它后面放置一个黑色椭圆,并将动画与不透明度相关联,同时将颜色与填充相关联。如果只有少数项目闪烁,这很好。
但是有这么多项目闪烁,似乎性能受到影响(至少在调试器中),而且所有项目在不同时间进入动画时都“不同步”。
所以,我把动画全部去掉了。相反,我启动了一个 Heartbeat 线程,它每秒切换一个 bool 值 FlashMaster。在我的 .xaml 中,我使用 Elipse.Fill MultiBinding:
<Ellipse.Fill>
<MultiBinding Converter="{StaticResource LevelBoolFlash2Color_Converter}">
<Binding Path="MrGWColorStatus.StatusColor"/>
<Binding Path="MrGWColorStatus.IsFlashing"/>
<Binding Path="AppVM.FlashMaster"/>
</MultiBinding>
</Ellipse.Fill>
其中 StatusColor 是状态级别(当前颜色),IsFlashing 是表示是否应该闪烁的布尔值,FlashMaster 是每秒切换的布尔值。
这是转换器:
/// <summary>
/// A converter that takes in
/// [0] StatusLevel enum
/// [1] IsFlashing boolean
/// [2] FlashMaster boolean
/// and converts it to the correct WPF brush for Normal/Warning/Critical/Inactive (if not Error bool), or StatusERROR if error.
/// </summary>
public class LevelBoolFlash2Color_Converter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
SolidColorBrush returnValue;
if ( (bool)values[1] && (bool)values[2])
{
// Blink is True, so use StatusBlinked (black) instead of using eStatusColor
returnValue = new SolidColorBrush((Color)Application.Current.FindResource("StatusBlinked"));
}
else
{
// Use the status color
switch ((eStatusColor)values[0])
{
case eStatusColor.NORMAL: returnValue = new SolidColorBrush((Color)Application.Current.FindResource("StatusNormal" )); break;
case eStatusColor.WARNING: returnValue = new SolidColorBrush((Color)Application.Current.FindResource("StatusWarning" )); break;
case eStatusColor.CRITICAL: returnValue = new SolidColorBrush((Color)Application.Current.FindResource("StatusCritical")); break;
default: returnValue = new SolidColorBrush((Color)Application.Current.FindResource("StatusInactive")); break;
}
}
return returnValue;
}
public object[] ConvertBack(object value, Type[] targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
其中 StatusBlinked 为黑色,.NORMAL 为绿色,.WARNING 为黄色,.CRITICAL 为红色,默认为灰色