题
当视图模型/演示模型繁忙时,我试图触发进度动画。我有一个 IsBusy 属性,并将 ViewModel 设置为 UserControl 的 DataContext。当 IsBusy 属性为 true 时,触发“progressAnimation”故事板的最佳方法是什么?Blend 只允许 med 在 UserControl 级别添加事件触发器,并且我只能在数据模板中创建属性触发器。
“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 中解决这个问题,而不必通过代码公开事件并启动/停止动画。
解决方案
您可以通过在 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);
}
}
}
希望这可以帮助!
其他提示
尽管建议将动画直接附加到要动画的元素的答案在简单的情况下解决了这个问题,但是当您有一个需要针对多个元素的复杂动画时,这实际上并不可行。(当然,您可以为每个元素附加一个动画,但管理起来非常糟糕。)
因此,有一种替代方法可以解决这个问题,让您使用 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 中有很多优化可以使样式的元素能够有效地重用,所以也许其中之一就是导致这种支持变得困难的原因。)
我建议使用 RoutedEvent 而不是 IsBusy 属性。只需触发 OnBusyStarted 和 OnBusyStopped 事件并在适当的元素上使用事件触发器。
您可以订阅 DataObject 类的 PropertyChanged 事件,并从用户控件级别触发 RoutedEvent。
为了让 RoutedEvent 工作,我们需要从 DependancyObject 派生类
您可以使用 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>