2008-09-17 67 views
15

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

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

我尝试添加了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中使用数据绑定技术来解决这个问题,而不是必须通过代码公开事件并开始/停止动画。

回答

29

你想要什么,宣布动画的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); 
     } 
    } 
} 

希望这有助于!

1

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

+1

那么,那正是我所希望的避免...但是,说我这样做:关于如何在不从UIElement派生的类中实现RoutedEvent的任何示例? – 2008-09-17 07:18:19

1

您可以订阅DataObject类的PropertyChanged事件,并根据Usercontrol级别进行RoutedEvent触发。

对于RoutedEvent的工作,我们需要有从DependancyObject

+0

我认为你是正确的...从UserControl接口暴露RoutedEvent像最明显的解决方案...但是,我还没有放弃基于数据执行任意故事板。但是,感谢输入! – 2008-09-18 00:55:07

0

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

就像我说的,这是在用户控制级别,我只接受EventTriggers(不是Property-或DataTriggers)。此外,IsBusy不是UserControl上的属性,而是设置为DataContext(ViewModel)的对象, – 2008-09-17 09:36:21

4

尽管将动画直接附加到要动画的元素上的答案在简单的情况下解决了这个问题,但是当您需要定位多个元素的复杂动画时,这并不可行。 (当然你可以给每个元素添加一个动画,但是它的管理非常糟糕。)

所以有另一种解决方法可以让你使用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中有相当多的优化来使元素能够有效地被重用,所以也许其中之一就是难以支持的元素。)