Bind to an attached behavior on a Storyboard

家住魔仙堡 提交于 2019-12-12 15:14:56

问题


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?


回答1:


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.




回答2:


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.




回答3:


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}" />


来源:https://stackoverflow.com/questions/1444252/bind-to-an-attached-behavior-on-a-storyboard

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!