绑定到 Storyboard 上的附加行为

发布于 2024-08-05 07:25:35 字数 2656 浏览 1 评论 0原文

我为 Storyboard 创建了一个附加的依赖属性,目的是让我能够在 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 中使用它,并使用绑定语法:

<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”类型的对象。无法冻结此情节提要时间线树以供跨线程使用。标记文件“TestWpfApp;component/window1”中的对象“labelHello”出错.xaml'

有没有办法让绑定语法与情节提要的附加 ICommand 属性一起使用?

I have created an attached dependency property for Storyboards, with the intention of enabling me to call a method on my ViewModel when a Storyboard Completed event fires:

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

then I try to use it in XAML, with a Binding syntax:

<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>

This fails with the following exception:

System.Windows.Markup.XamlParseException occurred
Message="Cannot convert the value in attribute 'Style' to object of type 'System.Windows.Style'. Cannot freeze this Storyboard timeline tree for use across threads. Error at object 'labelHello' in markup file 'TestWpfApp;component/window1.xaml'

Is there any way to get the Binding syntax working with an attached ICommand property for a Storyboard?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

绻影浮沉 2024-08-12 07:25:35

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

如果需要执行此操作,请将触发器放在框架元素下的样式之外,而不是放在样式中。您可以在 Grid.Triggers 部分中执行此操作。这确实有点糟糕,因为您的样式不再完整,并且您必须复制触发器,但这是 WPF 中的“设计”功能。

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

This is something by design. If you have a freezable object that is put into a style, the style will be frozen to allow cross-thread access. But you binding is essentially an expression which means it cannot be frozen as data binding is single threaded.

If you need to do this, put the trigger outside the style under a framework element instead of in a style. You can do this in your Grid.Triggers section. This does suck a little as your style is no longer complete and you have to duplicate the triggers but it is a "by design" feature in WPF.

The full answer on MSDN Social forums is here.

空袭的梦i 2024-08-12 07:25:35

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

You could create a new Freezable-derived class to launch a storyboard as a shim. Bind a property on that shim object to the storyboard name. That way, you won't have to duplicate triggers or store them outside the style.

迷路的信 2024-08-12 07:25:35

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

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

To get around this problem, I created a bunch of Attached Properties, called Storyboard Helpers (source code here). I gave up trying to attach them to the Storyboard itself, and now attach to any (arbitrary) framework element to call an ICommand on my ViewModel when the storyboard is completed, as well as binding to a particular event on my ViewModel to launch the Storyboard. A third attached property specifies the Storyboard we are dealing with:

<FrameworkElement
   my:StoryboardHelpers.Storyboard="{StaticResource rightAnswerAnimation}"
   my:StoryboardHelpers.Completed="{Binding CompletedCommand}"
   my:StoryboardHelpers.BeginEvent="{Binding StartCorrectAnswer}" />
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文