Programmatically creating a Storyboard in a WPF Co

2020-03-07 06:15发布

问题:

I have a WPF application that loads Pages in a templated NavigationWindow. I'd like to implement a slide transition when a new page is loaded and since the window can be resized the target values for the transform need to be determined programatically as far as I am aware.

I tried the following in the NavigationWindow code-behind but it has no effect when it fires. The PageContentContainerTransform is also being correctly located as determined from the debugger.

public void DoTransition()
{
    double targetX = this.ActualWidth;

    this.TransitionStoryboard.Stop();
    this.TransitionStoryboard.Children.Clear();
    IEasingFunction easing = new QuadraticEase() { EasingMode = EasingMode.EaseOut };
    DoubleAnimation translateXAnim = new DoubleAnimation() {
        To = targetX,
        Duration = TimeSpan.FromMilliseconds(250),
        EasingFunction = easing,
    };

    DependencyObject d = this.Template.FindName("pageContentContainerTransform", this) as DependencyObject;
    Storyboard.SetTarget(translateXAnim, d);

    Storyboard.SetTargetProperty(translateXAnim, new PropertyPath(TranslateTransform.XProperty));
    this.TransitionStoryboard.Children.Add(translateXAnim);
    this.TransitionStoryboard.Begin();
}

The Template is a ControlTemplate containing the following bit of XAML,

...
<ContentPresenter 
    Grid.Row="1"
    x:Name="pageContentContainer"
    MaxHeight="{StaticResource ContentWindowMaxHeight}"
    MaxWidth="{StaticResource ContentWindowMaxWidth}"
    RenderTransformOrigin="0.5,0.5">
    <ContentPresenter.RenderTransform>
        <TranslateTransform x:Name="pageContentContainerTransform" X="0" Y="0" />
    </ContentPresenter.RenderTransform>
</ContentPresenter>
...

Why is there no effect?

Update

The animation works if you animate the element directly without wrapping in a Storyboard object. E.g.

public void DoTransition()
{
    double targetX = this.ActualWidth;

    this.TransitionStoryboard.Stop();
    this.TransitionStoryboard.Children.Clear();
    IEasingFunction easing = new QuadraticEase() { EasingMode = EasingMode.EaseOut };
    DoubleAnimation translateXAnim = new DoubleAnimation() {
        To = targetX,
        Duration = TimeSpan.FromMilliseconds(250),
        EasingFunction = easing,
    };

    TranslateTransform t = this.Template.FindName("pageContentContainerTransform", this) as TranslateTransform;
    t.BeginAnimation(TranslateTransform.XProperty, translateXAnim);

}

However presumably you miss out on some nice control elements for the animations that the Storyboard object provides e.g. managing the animations (Stop, Start etc.). There appears to be possible arguments to .Begin() on the storyboard object that are pertinent to use within a Template, however calling with .Begin(this, this.Template) also does not do anything.

回答1:

In the end a combination of factors got it to work. First, use Storyboard.SetTargetName rather than Storyboard.SetTarget. Secondly pass in the template context to the Begin() method. E.g.

public void DoTransition()
{
    double targetX = this.ActualWidth;

    this.TransitionStoryboard.Stop();
    this.TransitionStoryboard.Children.Clear();
    IEasingFunction easing = new QuadraticEase() { EasingMode = EasingMode.EaseOut };
    DoubleAnimation translateXAnim = new DoubleAnimation() {
        To = targetX,
        Duration = TimeSpan.FromMilliseconds(250),
        EasingFunction = easing,
    };

    // 1. Refer to the element by Name
    Storyboard.SetTargetName(translateXAnim, "pageContentContainerTransform");
    Storyboard.SetTargetProperty(translateXAnim, new PropertyPath(TranslateTransform.XProperty));
    this.TransitionStoryboard.Children.Add(translateXAnim);
    // 2. Pass in the template context here
    this.TransitionStoryboard.Begin(this, this.Template);    
}

I'm not clear why the SetTargetProperty does not work when you consider that FindName correctly identified the element within the template, but in any case the above methods works.