【问题标题】:WPF Data Triggers and Story BoardsWPF 数据触发器和故事板
【发布时间】:2010-09-09 23:19:47
【问题描述】:

我试图在 ViewModel/Presentation Model 忙碌时触发进度动画。我有一个 IsBusy 属性,并将 ViewModel 设置为 UserControl 的 DataContext。当 IsBusy 属性为真时,触发“progressAnimation”故事板的最佳方式是什么? Blend 只允许 med 在 UserControl 级别添加 Event-Triggers,而我只能在我的数据模板中创建属性触发器。

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

我尝试将 DataTriggers 添加为 UserControl 上的样式,但是当我尝试启动 StoryBoard 时出现以下错误:

'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 中解决这个问题,而不是通过代码公开事件和启动/停止动画。

【问题讨论】:

    标签: wpf animation expression-blend


    【解决方案1】:

    您可以通过在 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>
    

    后面的代码:

        using System.Windows;
    using System.Windows.Controls;
    
    namespace TriggerSpike
    {
        public partial class UserControl1 : UserControl
        {
            private MyViewModel myModel;
    
            public UserControl1()
            {
                myModel=new MyViewModel();
                DataContext = myModel;
                InitializeComponent();
            }
    
            private void Button_Click(object sender, RoutedEventArgs e)
            {
                myModel.Search(myModel.SearchClause);
            }
        }
    }
    

    视图模型:

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

    希望这会有所帮助!

    【讨论】:

      【解决方案2】:

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

      因此,有一种替代方法可以解决此问题,您可以使用 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 中有很多优化可以使样式的元素能够被有效地重用,所以也许其中之一就是难以支持的原因。)

      【讨论】:

        【解决方案3】:

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

        【讨论】:

        • 嗯,这就是我希望避免的......但是,假设我这样做:关于如何在不从 UIElement 派生的类中实现 RoutedEvent 的任何示例?
        【解决方案4】:

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

        要让 RoutedEvent 工作,我们需要从 DependancyObject 派生类

        【讨论】:

        • 我认为你是对的......从 UserControl 接缝中暴露一个 RoutedEvent 就像最明显的解决方案......但是,我还没有放弃基于数据执行任意故事板。 . 但是感谢您的意见!
        【解决方案5】:

        您可以使用 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>
        

        【讨论】:

        • 就像我说的,这是在用户控制级别,我只接受 EventTriggers(而不是 Property- 或 DataTriggers)。此外,IsBusy 不是 UserControl 上的属性,而是设置为 DataContext(ViewModel)的对象
        猜你喜欢
        • 2017-02-09
        • 1970-01-01
        • 2020-07-01
        • 1970-01-01
        • 2014-11-16
        • 1970-01-01
        • 1970-01-01
        • 2010-09-23
        • 2014-11-08
        相关资源
        最近更新 更多