在LinearGradientBrush中设置停止动画时出现不一致错误
本文关键字:不一致 错误 动画 LinearGradientBrush 设置 | 更新日期: 2023-09-27 18:21:02
更新我可能知道是什么原因造成的,但我仍然想了解为什么以及是否有更好的方法来做到这一点。
原因似乎是传递到转换器中的值的IsAsync="True"。我注意到,每次报警都会调用StatusBackgroundColorConverter2 3到4次。
- 第一次进入Convert方法时,Value和IsAcknowledge都设置为DependencyProperty.UnsetValue
- Value属性第二次出现,但IsAcknowledged为Unset
- 第三次通过Value和IsAcknowledge最终设置
因此,当MultiBinding中的各个Binding设置为Async时,这意味着只要任何绑定发生变化,就会运行Converters Convert方法。
我添加了另一个用于调试的绑定以查看项本身。当Value更改时,执行Convert方法并填充Value,但IsAcknowledged是UnsetValue,尽管我可以看到IsAcknowled是在实际对象上设置的。
我可以关闭IsAsync,但这样做会在状态更改时在UI中造成相当明显的滞后。是否有某种方法可以为绑定本身打开IsAsync,为其内部的各个绑定关闭IsAsync?
结束更新
我真的很为难,拼命想弄清楚这里的问题是什么。如有任何帮助,我们将不胜感激!我有一个关键的应用程序,它可以监控一些事情,并根据数据显示报警通知。
报警显示在ItemsControls中。各种显示属性(如"背景"answers"前景颜色")会根据状态进行更改。一些状态要求"闪烁",我将其实现为基于状态(黄色、红色等)的适当颜色的滚动渐变。
所有这些功能实际上都有效,但过了一段时间,警报开始抛出以下错误:
路径"(0)"中的"GradientStops"属性值。(1) [1]。(2) '指向"System.Windows.Media.GradientStopCollection".的不可变实例
这个错误暂时被接受了,但警报不再起作用。他们一个接一个都这样死去。为了重新创建它,我必须设置数据,每隔5秒来回更改许多警报的状态。30到60秒后,它开始发生。
矩形Xaml
<Rectangle Grid.Column="1" Grid.Row="0" >
<Rectangle.Fill >
<MultiBinding UpdateSourceTrigger="PropertyChanged" NotifyOnTargetUpdated="True" NotifyOnSourceUpdated="True">
<MultiBinding.Converter >
<conv:StatusBackgroundColorConverter2 />
</MultiBinding.Converter>
<Binding Path="Value" IsAsync="True" UpdateSourceTrigger="PropertyChanged" NotifyOnTargetUpdated="True" NotifyOnSourceUpdated="True" />
<Binding Path="IsAcknowledged" IsAsync="True" UpdateSourceTrigger="PropertyChanged" NotifyOnTargetUpdated="True" NotifyOnSourceUpdated="True" />
</MultiBinding>
</Rectangle.Fill>
<Rectangle.Style>
<Style>
<Style.Triggers>
<DataTrigger Value="true" >
<DataTrigger.Binding>
<MultiBinding UpdateSourceTrigger="PropertyChanged">
<MultiBinding.Converter>
<conv:IsBlinkingStatusConverter />
</MultiBinding.Converter>
<Binding Path="Value" IsAsync="True" UpdateSourceTrigger="PropertyChanged" />
<Binding Path="IsAcknowledged" IsAsync="True" UpdateSourceTrigger="PropertyChanged" />
</MultiBinding>
</DataTrigger.Binding>
<DataTrigger.EnterActions>
<RemoveStoryboard BeginStoryboardName="RollingGradient" />
<BeginStoryboard Name="RollingGradient">
<Storyboard RepeatBehavior="Forever">
<DoubleAnimation FillBehavior="Stop" Storyboard.TargetProperty="(Rectangle.Fill).(GradientBrush.GradientStops)[1].(GradientStop.Offset)" From="-.1" To="1.3" By=".1" Duration="0:0:3"/>
<DoubleAnimation FillBehavior="Stop" Storyboard.TargetProperty="(Rectangle.Fill).(GradientBrush.GradientStops)[2].(GradientStop.Offset)" From=".6" To="1.3" By=".1" Duration="0:0:3"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<RemoveStoryboard BeginStoryboardName="RollingGradient" />
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
</Rectangle.Style>
</Rectangle>
转换器:
public class StatusBackgroundColorConverter2 : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (values != null && values.Length > 1 && values[0] != DependencyProperty.UnsetValue && values[1] != DependencyProperty.UnsetValue)
{
Brush brush;
int status = System.Convert.ToInt32(values[0]);
bool acknowledged = System.Convert.ToBoolean(values[1]);
bool blink = blinkingStatuses.Contains(status) && !acknowledged;
Color c = new Color();
if (status < 0 || status > 12)
c = Color.FromRgb(255, 255, 255);
else if (status == 0)
c = Color.FromRgb(0, 0, 0);
else if (status < 5)
c = Color.FromRgb(255, 255, 80);
else if (status < 9)
c = Color.FromRgb(255, 153, 0);
else if (status < 13)
{
c = Color.FromRgb(255, 38, 0);
}
if (blink)
{
LinearGradientBrush lgb = new LinearGradientBrush();
lgb.StartPoint = new Point(0, 0.5);
lgb.EndPoint = new Point(1, 0.5);
Color backcolor = Color.FromRgb(0, 0, 0);
lgb.GradientStops.Add(new GradientStop { Color = backcolor, Offset = 0 });
lgb.GradientStops.Add(new GradientStop { Color = c, Offset = -.1 });
lgb.GradientStops.Add(new GradientStop { Color = backcolor, Offset = .6 });
brush = lgb;
}
else
// I was returning a SolidColorBrush if status was not blinking, but I changed to gradient brush and
// and just kept the colors the same hoping having the stops there would address the issue. It didn't.
// brush = new SolidColorBrush(c);
{
var b = new LinearGradientBrush();
b.GradientStops.Add(new GradientStop { Color = c, Offset = 0});
b.GradientStops.Add(new GradientStop { Color = c, Offset = -.1 });
b.GradientStops.Add(new GradientStop { Color = c, Offset = .6 });
brush = b;
}
return brush;
}
return new SolidColorBrush(Color.FromRgb(255, 255, 255));
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
return null;
}
private int[] blinkingStatuses = new int[] { 3, 4, 7, 8, 11, 12 };
}
对我来说,最困难的部分是它一开始运行得很好。如果它要失败,为什么不第一次失败呢?我将真诚地感谢任何对此的投入。我必须想出一些办法,我觉得我没有什么可做的了。
谢谢!
这里问题的关键是使用异步绑定的多值转换器。以以下为例:
<MultiBinding UpdateSourceTrigger="PropertyChanged">
<MultiBinding.Converter>
<conv:IsBlinkingStatusConverter />
</MultiBinding.Converter>
<Binding Path="Value" IsAsync="True" UpdateSourceTrigger="PropertyChanged" />
<Binding Path="IsAcknowledged" IsAsync="True" UpdateSourceTrigger="PropertyChanged" />
</MultiBinding>
我错误地认为,在所有参数值都可用并且具备所需条件后,我的多值转换器中的转换函数只会执行一次。事实并非如此。
相反,我发现,对于我的集合中的每一个项目,我的Convert方法都被调用了3次。
第一次我的所有参数都是DependencyProperty.UnsetValue。第二次设置Value,但IsAcknowledged为DependencyProperty.UnsetValue。第三次设置这两个属性。
在值转换器中并没有返回Binding.DoNothing的特定逻辑的情况下,它用不完整的数据执行了两次,最后一次用所有参数执行。所以你的结果基本上是坏的,坏的,好的。当系统没有负载时,这种情况发生得很快,所以你不会注意到。然而,在负载下,调用之间的间隔会变长一点。它在坏状态下停留的时间越长,其他东西在无效状态下作用的可能性就越大。
我尝试指定默认值,甚至去掉IsAsync属性。虽然它确实起了作用,但每当屏幕更新时,它都会造成明显的延迟。将它们放回并实现逻辑,以便在执行逻辑之前查找所有必要的值,效果非常好。