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

对我来说,最困难的部分是它一开始运行得很好。如果它要失败,为什么不第一次失败呢?我将真诚地感谢任何对此的投入。我必须想出一些办法,我觉得我没有什么可做的了。

谢谢!

在LinearGradientBrush中设置停止动画时出现不一致错误

这里问题的关键是使用异步绑定的多值转换器。以以下为例:

<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属性。虽然它确实起了作用,但每当屏幕更新时,它都会造成明显的延迟。将它们放回并实现逻辑,以便在执行逻辑之前查找所有必要的值,效果非常好。