WPF 数据触发器和故事板
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/80388/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
WPF Data Triggers and Story Boards
提问by Jonas Folles?
I'm trying to trigger a progress animation when ever the ViewModel/Presentation Model is Busy. I have a IsBusy Property, and the ViewModel is set as the DataContext of the UserControl. What is the best way to trigger a "progressAnimation" story board when the IsBusy property is true? Blend only let med add Event-Triggers on a UserControl level, and I can only create property triggers in my data templates.
我试图在 ViewModel/Presentation Model 忙碌时触发进度动画。我有一个 IsBusy 属性,ViewModel 被设置为 UserControl 的 DataContext。当 IsBusy 属性为真时,触发“progressAnimation”故事板的最佳方法是什么?Blend 只让 med 在 UserControl 级别添加事件触发器,我只能在我的数据模板中创建属性触发器。
The "progressAnimation" is defined as a resource in the user control.
“progressAnimation”被定义为用户控件中的资源。
I tried adding the DataTriggers as a Style on the UserControl, but when I try to start the StoryBoard I get the following error:
我尝试将 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 is the name of the object I'm trying to animate, so removing target name is obvisouly NOT what I want.
ProgressWheel 是我要设置动画的对象的名称,因此删除目标名称显然不是我想要的。
I was hoping to solve this in XAML using data binding techniques, in stead of having to expose events and start/stop the animation through code.
我希望使用数据绑定技术在 XAML 中解决这个问题,而不必通过代码公开事件和启动/停止动画。
回答by Dabblernl
What you want is possible by declaring the animation on the progressWheel itself: The XAML:
您可以通过在 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>
Code behind:
后面的代码:
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);
}
}
}
The 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);
}
}
}
Hope this helps!
希望这可以帮助!
回答by Ian Griffiths
Although the answer that proposes attaching the animation directly to the element to be animated solves this problem in simple cases, this isn't really workable when you have a complex animation that needs to target multiple elements. (You can attach an animation to each element of course, but it gets pretty horrible to manage.)
尽管建议将动画直接附加到要动画的元素的答案在简单的情况下解决了这个问题,但是当您有一个需要针对多个元素的复杂动画时,这并不是真正可行的。(当然,您可以为每个元素附加动画,但管理起来非常糟糕。)
So there's an alternative way to solve this that lets you use a DataTrigger
to run an animation that targets named elements.
因此,有一种替代方法可以解决这个问题,让您可以使用 aDataTrigger
运行以命名元素为目标的动画。
There are three places you can attach triggers in WPF: elements, styles, and templates. However, the first two options don't work here. The first is ruled out because WPF doesn't support the use of a DataTrigger
directly on an element. (There's no particularly good reason for this, as far as I know. As far as I remember, when I asked people on the WPF team about this many years ago, they said they'd have liked to have supported it but didn't have time to make it work.) And styles are out because, as the error message you've reported says, you can't target named elements in an animation associated with a style.
您可以在 WPF 中的三个位置附加触发器:元素、样式和模板。但是,前两个选项在这里不起作用。第一个被排除,因为 WPF 不支持DataTrigger
直接在元素上使用 a 。(据我所知,这没有特别好的理由。据我所知,当我多年前向 WPF 团队的人询问这个问题时,他们说他们很想支持它,但没有有时间让它发挥作用。)并且样式已经过时,因为正如您报告的错误消息所说,您无法在与样式关联的动画中定位命名元素。
So that leaves templates. And you can use either control or data templates for this.
这样就剩下模板了。您可以为此使用控件或数据模板。
<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>
With this construction, WPF is happy to let the animation refer to named elements inside the template. (I've left both the animation and the template content empty here - obviously you'd populate that with your actual animation nd content.)
通过这种构造,WPF 很高兴让动画引用模板内的命名元素。(我在这里将动画和模板内容都留空了 - 显然你会用你的实际动画和内容填充它。)
The reason this works in a template but not a style is that when you apply a template, the named elements it defines will always be present, and so it's safe for animations defined within that template's scope to refer to those elements. This is not generally the case with a style, because styles can be applied to multiple different elements, each of which may have quite different visual trees. (It's a little frustrating that it prevents you from doing this even in scenarios when you can be certain that the required elements will be there, but perhaps there's something that makes it very difficult for the animation to be bound to the named elements at the right time. I know there are quite a lot of optimizations in WPF to enable elements of a style to be reused efficiently, so perhaps one of those is what makes this difficult to support.)
这适用于模板而不是样式的原因是当您应用模板时,它定义的命名元素将始终存在,因此在该模板范围内定义的动画可以安全地引用这些元素。样式通常不是这种情况,因为样式可以应用于多个不同的元素,每个元素可能具有完全不同的视觉树。(有点令人沮丧的是,即使在您可以确定所需元素将在那里的情况下,它也会阻止您这样做,但也许有些东西使动画很难绑定到右侧的命名元素时间。我知道 WPF 中有很多优化可以有效地重用样式元素,
回答by Jobi Joy
I would recommend to use RoutedEvent instead of your IsBusy property. Just fire OnBusyStarted and OnBusyStopped event and use Event trigger on the appropriate elements.
我建议使用 RoutedEvent 而不是您的 IsBusy 属性。只需触发 OnBusyStarted 和 OnBusyStopped 事件并在适当的元素上使用事件触发器。
回答by Jobi Joy
You can subscribe to the PropertyChanged event of the DataObject class and make a RoutedEvent fire from Usercontrol level.
您可以订阅 DataObject 类的 PropertyChanged 事件,并从用户控件级别触发 RoutedEvent。
For RoutedEvent to work we need to have the class derived from DependancyObject
为了让 RoutedEvent 工作,我们需要从 DependancyObject 派生类
回答by ligaz
You can use Trigger.EnterAction to start an animation when a property is changed.
您可以使用 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>