c# - 为什么这个 MultiDataTrigger 会在动画上抛出异常?

标签 c# wpf mvvm

在 View 模型上显示多个 View 的 WPF-MVVM 应用程序。用户可以在运行时从不太详细的 View 转到更详细的 View 。 View 包含在边界内。在一组条件下, View 模型会触发警报。警报在 View 中呈现为边框背景上闪烁颜色的动画,以吸引用户注意。

问题是,当触发警报并且用户在运行时更改数据模板以获取更多详细信息时,WPF 引擎在使用多数据触发警报时会引发动画异常。该引擎在使用 Datatrigger 时工作,并在 MultiDataTrigger 上崩溃,其他一切都相同。

异常(exception)情况是:无法在不可变对象(immutable对象)上为 '(0).(1)' 设置动画

演示问题的示例应用程序:

1 MainWindow 用于托管和切换 View 。

2 View ,大小。

1 个 View 模型。

1 多和单数据触发器的资源字典

应用程序.Xaml:

<Application x:Class="AnimationSample.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:AnimationSample"
             StartupUri="MainWindow.xaml">
    <Application.Resources>
        <ResourceDictionary Source="Dictionary1.xaml"/>
    </Application.Resources>
</Application>

MainWindow.xaml:
<Window x:Class="AnimationSample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:AnimationSample"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <DataTemplate DataType="{x:Type local:SampleViewModel}" x:Key="smallTemplate">
            <local:SmallUserControl />
        </DataTemplate>
        <DataTemplate DataType="{x:Type local:SampleViewModel}" x:Key="largeTemplate">
            <local:LargeUserControl />
        </DataTemplate>        
        <DataTemplate DataType="{x:Type local:SampleViewModel}" x:Key="mainTemplate">
            <DataTemplate.Triggers>
                <DataTrigger Binding="{Binding ElementName=ZoomSlider, Path=Value}" Value="1">                 
                    <Setter Property="ContentTemplate" Value="{StaticResource smallTemplate}"/>
                </DataTrigger>
                <DataTrigger Binding="{Binding ElementName=ZoomSlider, Path=Value}" Value="2">                        
                    <Setter Property="ContentTemplate" Value="{StaticResource largeTemplate}"/>
                </DataTrigger>                
            </DataTemplate.Triggers>
        </DataTemplate>
        <Style TargetType="{x:Type ContentControl}" x:Key="DisplayStyle">
            <Setter Property="ContentTemplate" Value="{StaticResource smallTemplate}" />
            <Style.Triggers>
                <DataTrigger Binding="{Binding ElementName=ZoomSlider, Path=Value}" Value="1">
                    <Setter Property="ContentTemplate" Value="{StaticResource smallTemplate}"/>
                </DataTrigger>
                <DataTrigger Binding="{Binding ElementName=ZoomSlider, Path=Value}" Value="2">
                    <Setter Property="ContentTemplate" Value="{StaticResource largeTemplate}"/>
                </DataTrigger>                
            </Style.Triggers>
        </Style>
    </Window.Resources>
    <StackPanel Margin="8">
        <Slider x:Name="ZoomSlider" Minimum="1" Maximum="2" IsSnapToTickEnabled="True" />
        <ContentControl Content="{Binding}" Style="{StaticResource DisplayStyle}">
        </ContentControl>
    </StackPanel>
</Window>

主窗口.cs:
using System.Windows;

namespace AnimationSample
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            DataContext = new SampleViewModel();
        }
    }
}

SampleViewModel.cs:
using System;
using System.ComponentModel;
using System.Linq.Expressions;
using System.Windows;

namespace AnimationSample
{
    public class SampleViewModel : NotifyPropertyChangedBase<SampleViewModel>
    {
        private bool _alarm;
        public bool Alarm
        {
            get { return _alarm; }
            set
            {
                if (!_alarm.Equals(value))
                {
                    _alarm = value;
                    OnPropertyChanged("Alarm");                    
                }
            }
        }

        private bool _flash;
        public bool Flash
        {
            get { return _flash; }
            set
            {
                if (!_flash.Equals(value))
                {
                    _flash = value;
                    OnPropertyChanged("Flash");
                }
            }
        }


        private string _description = "I am an alarm!";
        public string Description
        {
            get { return _description; }
            set
            {
                if(!_description.Equals(value))
                {
                    _description = value;
                    OnPropertyChanged("Description");
                }
            }
        }
    }

    public abstract class NotifyPropertyChangedBase<T> : DependencyObject, INotifyPropertyChanged
    {

        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            { PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName)); }
        }

        protected virtual void OnPropertyChanged<T2>(Expression<Func<T, T2>> accessor)
        {
            OnPropertyChanged(PropertyName(accessor));
        }        

        public static string PropertyName<T2>(Expression<Func<T, T2>> accessor)
        {
            return ((MemberExpression)accessor.Body).Member.Name;
        }
    }
}

字典1.xaml:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:local="clr-namespace:AnimationSample">
    <Style x:Key="FlashyAlarmBorderStyle" TargetType="{x:Type Border}">
        <Setter Property="BorderBrush" Value="Silver"/>
        <Setter Property="Background" Value="Black" />
        <Setter Property="BorderThickness" Value="2" />
        <Setter Property="CornerRadius" Value="8" />
        <Style.Triggers>
            <MultiDataTrigger>
                <MultiDataTrigger.Conditions>
                    <Condition Binding="{Binding Alarm}" Value="True" />
                    <Condition Binding="{Binding Flash}" Value="True" />
                </MultiDataTrigger.Conditions>
                <Setter Property="Background" Value="Orange"/>
                <Setter Property="BorderBrush" Value="Orange"/>
                <MultiDataTrigger.EnterActions>
                    <BeginStoryboard Name="faultBoard">
                        <Storyboard>
                            <ColorAnimation AutoReverse="True" 
                                                RepeatBehavior="Forever" 
                                                Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)" 
                                                Duration="00:00:01"                                            
                                                From="Silver"
                                                To="Orange"/>
                        </Storyboard>
                    </BeginStoryboard>
                </MultiDataTrigger.EnterActions>
                <MultiDataTrigger.ExitActions>
                    <StopStoryboard BeginStoryboardName="faultBoard">
                    </StopStoryboard>
                </MultiDataTrigger.ExitActions>
            </MultiDataTrigger>
            <!--<DataTrigger Binding="{Binding Alarm}" Value="True" >
                <Setter Property="Background" Value="Orange"/>
                <Setter Property="BorderBrush" Value="Orange"/>
                <DataTrigger.EnterActions>
                    <BeginStoryboard Name="alarmBoard">
                        <Storyboard>
                            <ColorAnimation AutoReverse="True" 
                                                RepeatBehavior="Forever" 
                                                Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)" 
                                                Duration="00:00:01"                                            
                                                From="Silver"
                                                To="Orange"/>
                        </Storyboard>
                    </BeginStoryboard>
                </DataTrigger.EnterActions>
                <DataTrigger.ExitActions>
                    <StopStoryboard BeginStoryboardName="alarmBoard">
                    </StopStoryboard>
                </DataTrigger.ExitActions>
            </DataTrigger>-->          
        </Style.Triggers>
    </Style>
</ResourceDictionary>

大用户控件.xaml:
<UserControl x:Class="AnimationSample.LargeUserControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:AnimationSample"
             mc:Ignorable="d" >
    <Border Margin="8"
            Style="{DynamicResource FlashyAlarmBorderStyle}">
        <StackPanel>
            <Label HorizontalAlignment="Center" Content="LARGE VIEW" Foreground="White"/>
            <CheckBox Margin="8" IsChecked="{Binding Alarm}" Content="Turn on the alarm." Foreground="White" />
            <CheckBox Margin="8"  IsChecked="{Binding Flash}" Content="Turn on the flash!" Foreground="White" />
            <Label Margin="8" Content="{Binding Description}" Foreground="White"/>
        </StackPanel>
    </Border>
</UserControl>

SmallUserControl.xaml:
<UserControl x:Class="AnimationSample.SmallUserControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:AnimationSample"
             mc:Ignorable="d" >
    <Border Margin="8"
            Style="{DynamicResource FlashyAlarmBorderStyle}">
        <StackPanel>
            <Label HorizontalAlignment="Center" Content="SMALL VIEW" Foreground="White"/>
            <CheckBox Margin="4" IsChecked="{Binding Alarm}" Content="Turn on the alarm." HorizontalAlignment="Center" Foreground="White" />
            <CheckBox Margin="4" IsChecked="{Binding Flash}" Content="Turn on the flash!" HorizontalAlignment="Center" Foreground="White" />
        </StackPanel>
    </Border>
</UserControl>

如果您运行代码并单击警报和闪烁复选框,则控件将从黑色背景的银色边框变为闪烁的橙色。如果您移动 slider 以从小 View 转到大 View ,如果使用多数据触发器,您将从 wpf 获得运行时间。如果您在 Dictionar1.xaml 中取消注释 datatrigger 并注释 multidatatrigger,然后重复上述操作,应用程序运行正常。

为什么 DataTemplate 更改和 datatrigger 上的这个 multidatatrigger 噱头可以正常工作?与单个数据触发器的唯一区别是一个额外的 bool 值。如何修复?

(是的,可以通过在 View 模型上创建一个属性来聚合这些 bool 值来解决这个问题,但这不应该这样做,而且是个问题。这似乎是 wpf 的一个错误?)

最佳答案

删除设置您尝试设置动画的相同属性 ( Background ) 的 setter :

<MultiDataTrigger>
    <MultiDataTrigger.Conditions>
        <Condition Binding="{Binding Alarm}" Value="True" />
        <Condition Binding="{Binding Flash}" Value="True" />
    </MultiDataTrigger.Conditions>
    <!--<Setter Property="Background" Value="Orange"/>-->
    <Setter Property="BorderBrush" Value="Orange"/>
    <MultiDataTrigger.EnterActions>
        <BeginStoryboard Name="faultBoard">
            <Storyboard>
                <ColorAnimation AutoReverse="True" 
                                RepeatBehavior="Forever" 
                                Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)" 
                                Duration="00:00:01"                                            
                                From="Silver"
                                To="Orange"/>
            </Storyboard>
        </BeginStoryboard>
    </MultiDataTrigger.EnterActions>
    <MultiDataTrigger.ExitActions>
        <StopStoryboard BeginStoryboardName="faultBoard">
        </StopStoryboard>
    </MultiDataTrigger.ExitActions>
</MultiDataTrigger>

关于c# - 为什么这个 MultiDataTrigger 会在动画上抛出异常?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44952075/

相关文章:

android - 在 MVVM 架构中处理来自 api 端点的错误的最佳实践是什么?

c# - 如何从 ViewModel 调用 TreeView 上的焦点

c# - 谁能解释 string.Join 连接的顺序?

c# - 将 CheckBox 从 DataTemplate 绑定(bind)到 ListBox 中的 TemplatedParent

c# - 如何在 WPF 中以编程方式将滚动条添加到网格?

.net - http ://schemas. microsoft.com/winfx/2006/xaml/presentation 的架构文件的位置在哪里?

silverlight - MVVM触发事件返回查看

javascript - 在 ASP.NET MVC5 中向表单动态添加控件

c# - 从 silverlight 公开和使用 NetTcpBinding

c# - Azure IoT 中心 - 减少数据下载/上传