绑定到故事板上的附加行为

时间:2009-09-18 12:26:51

标签: wpf data-binding xaml dependency-properties storyboard

我为Storyboards创建了一个附加的依赖项属性,目的是让我在Storyboard Completed事件触发时调用我的ViewModel上的方法:

public static class StoryboardExtensions
{
    public static ICommand GetCompletedCommand(DependencyObject target)
    {
        return (ICommand)target.GetValue(CompletedCommandProperty);
    }

    public static void SetCompletedCommand(DependencyObject target, ICommand value)
    {
        target.SetValue(CompletedCommandProperty, value);
    }

    public static readonly DependencyProperty CompletedCommandProperty =
        DependencyProperty.RegisterAttached(
            "CompletedCommand",
            typeof(ICommand),
            typeof(StoryboardExtensions),
            new FrameworkPropertyMetadata(null, OnCompletedCommandChanged));

    static void OnCompletedCommandChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
    {
        Storyboard storyboard = target as Storyboard;
        if (storyboard == null) throw new InvalidOperationException("This behavior can be attached to Storyboard item only.");
        storyboard.Completed += new EventHandler(OnStoryboardCompleted);
    }

    static void OnStoryboardCompleted(object sender, EventArgs e)
    {                        
        Storyboard item = ... // snip
        ICommand command = GetCompletedCommand(item);
        command.Execute(null);
    }
}

然后我尝试在XAML中使用它,使用Binding语法:

<Grid>
    <Grid.Resources>
        <Storyboard x:Key="myStoryboard" my:StoryboardExtensions.CompletedCommand="{Binding AnimationCompleted}">
            <DoubleAnimation Storyboard.TargetProperty="Opacity" From="1" To="0" Duration="0:0:5" />
        </Storyboard>

        <Style x:Key="myStyle" TargetType="{x:Type Label}">
            <Style.Triggers>
                <DataTrigger 
                 Binding="{Binding Path=QuestionState}" Value="Correct">
                    <DataTrigger.EnterActions>
                        <BeginStoryboard Storyboard="{StaticResource myStoryboard}" />
                    </DataTrigger.EnterActions>
                </DataTrigger>
            </Style.Triggers>
        </Style>

    </Grid.Resources>
    <Label x:Name="labelHello" Grid.Row="0" Style="{StaticResource myStyle}">Hello</Label>
</Grid>

此操作失败,但出现以下异常:

  

发生了System.Windows.Markup.XamlParseException   Message =“无法将属性'Style'中的值转换为'System.Windows.Style'类型的对象。无法冻结此Storyboard时间轴树以供跨线程使用。标记文件'TestWpfApp; component / window1中对象'labelHello'出错.xaml'

有没有办法让Binding语法与Storyboard的附加ICommand属性一起使用?

3 个答案:

答案 0 :(得分:1)

这是设计的东西。如果您有一个放入样式的freezable对象,则该样式将被冻结以允许跨线程访问。但是绑定本质上是一个表达式,这意味着它不能被冻结,因为数据绑定是单线程的。

如果您需要这样做,请将触发器置于框架元素下而不是样式中。您可以在Grid.Triggers部分中执行此操作。这样做有点糟糕,因为你的风格不再完整,你必须复制触发器,但它是WPF中的“按设计”功能。

MSDN社交论坛上的完整答案是here

答案 1 :(得分:0)

您可以创建一个新的Freezable派生类来将故事板作为垫片启动。将该填充程序对象上的属性绑定到故事板名称。这样,您就不必复制触发器或将它们存储在样式之外。

答案 2 :(得分:0)

为了解决这个问题,我创建了一堆名为Storyboard Helpers(source code here)的附加属性。我放弃了尝试将它们附加到Storyboard本身,现在附加到任何(任意)框架元素以在故事板完成时在我的ViewModel上调用ICommand,以及绑定到我的ViewModel上的特定事件以启动Storyboard 。第三个附加属性指定我们正在处理的故事板:

<FrameworkElement
   my:StoryboardHelpers.Storyboard="{StaticResource rightAnswerAnimation}"
   my:StoryboardHelpers.Completed="{Binding CompletedCommand}"
   my:StoryboardHelpers.BeginEvent="{Binding StartCorrectAnswer}" />