WPF数据触发器和故事板

WPF数据触发器和故事板,wpf,animation,expression-blend,Wpf,Animation,Expression Blend,我试图在ViewModel/Presentation模型忙时触发进度动画。我有一个IsBusy属性,并且ViewModel被设置为UserControl的DataContext。当IsBusy属性为true时,触发“progressAnimation”故事板的最佳方法是什么?Blend只允许med在UserControl级别上添加事件触发器,我只能在数据模板中创建属性触发器 “progressAnimation”定义为用户控件中的资源 我尝试在UserControl上添加DataTriggers

我试图在ViewModel/Presentation模型忙时触发进度动画。我有一个IsBusy属性,并且ViewModel被设置为UserControl的DataContext。当IsBusy属性为true时,触发“progressAnimation”故事板的最佳方法是什么?Blend只允许med在UserControl级别上添加事件触发器,我只能在数据模板中创建属性触发器

“progressAnimation”定义为用户控件中的资源

我尝试在UserControl上添加DataTriggers作为一种样式,但当我尝试启动故事板时,出现以下错误:

'System.Windows.Style' value cannot be assigned to property 'Style' 
of object'Colorful.Control.SearchPanel'. A Storyboard tree in a Style 
cannot specify a TargetName. Remove TargetName 'progressWheel'.
ProgressWheel是我试图设置动画的对象的名称,因此删除目标名称显然不是我想要的


我希望使用数据绑定技术在XAML中解决这个问题,而不是通过代码公开事件和启动/停止动画。

我建议使用RouteEvent而不是IsBusy属性。只需触发OnBusyStarted和OnBusyStopped事件,并在适当的元素上使用事件触发器。

您可以订阅DataObject类的PropertyChanged事件,并从Usercontrol级别触发RoutedEvent


要使RoutedEvent正常工作,我们需要从DependencyObject派生类

,您可以使用Trigger.EnterAction在属性更改时启动动画

<Trigger Property="IsBusy" Value="true">
    <Trigger.EnterActions>
        <BeginStoryboard x:Name="BeginBusy" Storyboard="{StaticResource MyStoryboard}" />
    </Trigger.EnterActions>
    <Trigger.ExitActions>
        <StopStoryboard BeginStoryboardName="BeginBusy" />
    </Trigger.ExitActions>
</Trigger>

您可以通过在progressWheel上声明动画来实现所需: XAML:

<UserControl x:Class="TriggerSpike.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="300" Width="300">
<UserControl.Resources>
    <DoubleAnimation x:Key="SearchAnimation" Storyboard.TargetProperty="Opacity" To="1" Duration="0:0:4"/>
    <DoubleAnimation x:Key="StopSearchAnimation" Storyboard.TargetProperty="Opacity" To="0" Duration="0:0:4"/>
</UserControl.Resources>
<StackPanel>
    <TextBlock Name="progressWheel" TextAlignment="Center" Opacity="0">
        <TextBlock.Style>
            <Style>
                <Style.Triggers>
                    <DataTrigger Binding="{Binding IsBusy}" Value="True">
                        <DataTrigger.EnterActions>
                            <BeginStoryboard>
                                <Storyboard>
                                    <StaticResource ResourceKey="SearchAnimation"/>
                                </Storyboard>
                            </BeginStoryboard>
                        </DataTrigger.EnterActions>
                        <DataTrigger.ExitActions>
                            <BeginStoryboard>
                                <Storyboard>
                                   <StaticResource ResourceKey="StopSearchAnimation"/> 
                                </Storyboard>
                            </BeginStoryboard>
                        </DataTrigger.ExitActions>
                    </DataTrigger>
                </Style.Triggers>
            </Style>
        </TextBlock.Style>
        Searching
    </TextBlock>
    <Label Content="Here your search query"/>
    <TextBox Text="{Binding SearchClause}"/>
    <Button Click="Button_Click">Search!</Button>
    <TextBlock Text="{Binding Result}"/>
</StackPanel>
viewmodel:

    using System.ComponentModel;
using System.Threading;
using System.Windows;

namespace TriggerSpike
{
    class MyViewModel:DependencyObject
    {

        public string SearchClause{ get;set;}

        public bool IsBusy
        {
            get { return (bool)GetValue(IsBusyProperty); }
            set { SetValue(IsBusyProperty, value); }
        }

        public static readonly DependencyProperty IsBusyProperty =
            DependencyProperty.Register("IsBusy", typeof(bool), typeof(MyViewModel), new UIPropertyMetadata(false));



        public string Result
        {
            get { return (string)GetValue(ResultProperty); }
            set { SetValue(ResultProperty, value); }
        }

        public static readonly DependencyProperty ResultProperty =
            DependencyProperty.Register("Result", typeof(string), typeof(MyViewModel), new UIPropertyMetadata(string.Empty));

        public void Search(string search_clause)
        {
            Result = string.Empty;
            SearchClause = search_clause;
            var worker = new BackgroundWorker();
            worker.DoWork += worker_DoWork;
            worker.RunWorkerCompleted += worker_RunWorkerCompleted;
            IsBusy = true;
            worker.RunWorkerAsync();
        }

        void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            IsBusy=false;
            Result = "Sorry, no results found for: " + SearchClause;
        }

        void worker_DoWork(object sender, DoWorkEventArgs e)
        {
            Thread.Sleep(5000);
        }
    }
}

希望这有帮助

虽然建议将动画直接附加到要设置动画的元素的答案在简单的情况下解决了这个问题,但当您有一个需要以多个元素为目标的复杂动画时,这实际上是不可行的。(当然,您可以将动画附加到每个元素,但管理起来会非常糟糕。)

所以有另一种解决方法,可以使用
DataTrigger
运行以命名元素为目标的动画

在WPF中有三个地方可以附加触发器:元素、样式和模板。但是,前两个选项在这里不起作用。第一个被排除,因为WPF不支持直接在元素上使用
DataTrigger
。(据我所知,这没有什么特别好的理由。据我记忆所及,当我多年前向WPF团队的成员询问这一点时,他们说他们本想支持它,但没有时间让它发挥作用。)样式已经过时了,因为正如您报告的错误消息所说,不能在与样式关联的动画中以命名元素为目标

这样就剩下模板了。您可以使用控制模板或数据模板进行此操作

<ContentControl>
    <ContentControl.Template>
        <ControlTemplate TargetType="ContentControl">
            <ControlTemplate.Resources>
                <Storyboard x:Key="myAnimation">

                    <!-- Your animation goes here... -->

                </Storyboard>
            </ControlTemplate.Resources>
            <ControlTemplate.Triggers>
                <DataTrigger
                    Binding="{Binding MyProperty}"
                    Value="DesiredValue">
                    <DataTrigger.EnterActions>
                        <BeginStoryboard
                            x:Name="beginAnimation"
                            Storyboard="{StaticResource myAnimation}" />
                    </DataTrigger.EnterActions>
                    <DataTrigger.ExitActions>
                        <StopStoryboard
                            BeginStoryboardName="beginAnimation" />
                    </DataTrigger.ExitActions>
                </DataTrigger>
            </ControlTemplate.Triggers>

            <!-- Content to be animated goes here -->

        </ControlTemplate>
    </ContentControl.Template>
<ContentControl>

通过这种构造,WPF很乐意让动画引用模板内的命名元素。(我在这里把动画和模板内容都留空了——显然,您应该用实际的动画内容填充它们。)


这适用于模板而不是样式的原因是,应用模板时,它定义的命名元素将始终存在,因此在该模板范围内定义的动画引用这些元素是安全的。样式通常不是这样,因为样式可以应用于多个不同的元素,每个元素可能具有完全不同的视觉树。(有点令人沮丧的是,即使在您可以确定所需元素将在那里的场景中,它也会阻止您这样做,但可能有某种原因使得动画很难在正确的时间绑定到命名元素。我知道WPF中有很多优化来启用元素一种可以高效重用的风格,所以也许其中之一就是使其难以支持的原因。)

好吧,这就是我希望避免的。。。但是,假设我这样做:有没有关于如何在一个不是从UIElement派生的类中实现RouteEvent的示例?正如我所说,这是在用户控件级别,我只接受EventTriggers(而不是Property或DataTriggers)。另外,IsBusy不是UserControl上的属性,而是作为DataContext(ViewModel)设置的对象上的属性,我认为您是对的。。。像最明显的解决方案一样,从UserControl接缝公开RouteEvent。。。然而,我还没有放弃执行基于数据的任意故事板。。但是谢谢你的意见!
<ContentControl>
    <ContentControl.Template>
        <ControlTemplate TargetType="ContentControl">
            <ControlTemplate.Resources>
                <Storyboard x:Key="myAnimation">

                    <!-- Your animation goes here... -->

                </Storyboard>
            </ControlTemplate.Resources>
            <ControlTemplate.Triggers>
                <DataTrigger
                    Binding="{Binding MyProperty}"
                    Value="DesiredValue">
                    <DataTrigger.EnterActions>
                        <BeginStoryboard
                            x:Name="beginAnimation"
                            Storyboard="{StaticResource myAnimation}" />
                    </DataTrigger.EnterActions>
                    <DataTrigger.ExitActions>
                        <StopStoryboard
                            BeginStoryboardName="beginAnimation" />
                    </DataTrigger.ExitActions>
                </DataTrigger>
            </ControlTemplate.Triggers>

            <!-- Content to be animated goes here -->

        </ControlTemplate>
    </ContentControl.Template>
<ContentControl>