WPF 动态创建情节提要

Posted

技术标签:

【中文标题】WPF 动态创建情节提要【英文标题】:WPF Create Storyboard Dynamically 【发布时间】:2020-11-30 09:53:38 【问题描述】:

我在为 .net WPF 中动态创建的 UserControl 运行 Storyboard 时遇到问题。

这些是我的课程示例:

class EventsPage 

    //  ...    

    public void AddEvent(Event @event) 
        var eventUC = new EventUserContrl(@event);
        eventUC.ExpandCollapseAnimation += ExpandCollapseAnimation;
        EventsStackPanel.Children.Add(eventUC);
    

    private ExpandCollapseAnimation(EventUserControl eventUC, double height, double time) 

        //  Create frames using custom functions for creating them.
        var frames = new DoubleKeyFrameCollection() 
            StoryBoardsBuilder.CreateEasingDoubleKeyFrame(0.0, eventUC.ActualHeight),
            StoryBoardsBuilder.CreateEasingDoubleKeyFrame(time, destinationHeight)
        ;

        //  Create Animation.
        var heightSizeAnimation= StoryBoardsBuilder.BuildDoubleAnimationUsingKeyFrames(
                FillBehavior.Stop, frames, eventUC.Name, new PropertyPath("Height"));

        //  Create StoryBoard.
        var storyboard = new Storyboard();

        //  Add Animations into StoryBoard.
        storyboard.Children.Add(heightSizeAnimation);

        //  Create final function.
        storyboard.Completed += (sender, e) => 
            eventUC.Height = destinationHeight;
        ;

        //  Run animation.
        storyboard.Begin(this);
    

    //  ...


启动后,在storyboard.Begin(this),显示异常:System.InvalidOperationException: „Name „” cannot be found in the namespace „ProjectName.Pages.EventsPage ”.”

我做了类似的事情,但在页面中手动放置用户控件,它可以工作,但这不会。

这是 StoryBuilder 代码:

public static EasingDoubleKeyFrame CreateEasingDoubleKeyFrame(
    double frameTimeInSeconds,
    double value) 

    //  Create double key frame.
    return new EasingDoubleKeyFrame() 
        KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(frameTimeInSeconds)),
        Value = value
    ;


public static DoubleAnimationUsingKeyFrames BuildDoubleAnimationUsingKeyFrames(
    FillBehavior fillBehavior,
    DoubleKeyFrameCollection keyFrames,
    string targetName,
    PropertyPath targetProperty) 

    //  Create animation.
    var animation = new DoubleAnimationUsingKeyFrames();

    //  Set animation end behavior.
    animation.FillBehavior = fillBehavior;

    //  Set animation frames.
    animation.KeyFrames = keyFrames;

    //  Set animation target object.
    Storyboard.SetTargetName(animation, targetName);

    //  Set animation target property.
    Storyboard.SetTargetProperty(animation, targetProperty);

    return animation;

【问题讨论】:

能否请您展示 StoryBoardsBuilder,尤其是 StoryBoardsBuilder.BuildDoubleAnimationUsingKeyFrames。看起来您正在尝试将情节提要目标设置为 eventUC.Name。我想应该是 eventUC 。请向建造者展示,以便我们真正看到您做错了什么。现在所有相关细节都被隐藏了。 @BionicCode 我用 StoryBuilder 的代码更新了帖子——这是用于快速实现故事板的工具代码。 谢谢。如何显示用户控件? 用户控件放置在 StackPanel EventsStackPanel.Children.Add(eventUC); 中,即 ScrollViewer 中 【参考方案1】:

您似乎将Storyboard 的目标设置错了。

Storyboard.SetTargetName 需要一个注册的元素名称。

您有两个选择:要么使用Storyboard.SetTarget,要么注册控件的名称并使用Storyboard.SetTargetName

解决方案 1:Storyboard.SetTarget(推荐)

推荐的方法是在 C# 中创建动画时使用Storyboard.SetTarget(而不是更方便的 XAML)。 Storyboard.SetTargetName 需要 XAML 名称范围内的元素,使用 X:Name 指令设置 FrameworkElement.Name。使用Storyboard.SetTarget 消除了在名称范围内注册目标元素的要求。

StoryBoardBuilder.cs

class StoryBoardBuilder

  public static DoubleAnimationUsingKeyFrames BuildDoubleAnimationUsingKeyFrames(
    FillBehavior fillBehavior,
    DoubleKeyFrameCollection keyFrames,
    DependencyObject target,
    PropertyPath targetProperty) 
  
    //  Create animation.
    var animation = new DoubleAnimationUsingKeyFrames();

    //  Set animation end behavior.
    animation.FillBehavior = fillBehavior;

    //  Set animation frames.
    animation.KeyFrames = keyFrames;

    //  Set animation target object.
    Storyboard.SetTarget(animation, target);

    //  Set animation target property.
    Storyboard.SetTargetProperty(animation, targetProperty);

    return animation;
  

示例

class EventsPage 

    //  ...    

    public void AddEvent(Event @event) 
        var eventUC = new EventUserContrl(@event);
        eventUC.ExpandCollapseAnimation += ExpandCollapseAnimation;
    

    private ExpandCollapseAnimation(EventUserControl eventUC, double height, double time) 
    
        ...

        //  Create Animation.
        var heightSizeAnimation= StoryBoardBuilder.BuildDoubleAnimationUsingKeyFrames(
          FillBehavior.Stop, 
          frames, 
          eventUC, 
          new PropertyPath("Height"));

        ...
    

    //  ...


解决方案 2:Storyboard.SetTargetName

Storyboard.SetTargetName 需要一个名为 FrameworkElement 的动画目标。此元素的名称必须在当前 XAML 名称范围内注册。这在使用 x:Name 指令在 XAML 中命名元素时自动完成。 由于您决定在 C# 中创建元素,因此您必须手动注册元素名称。如果没有活动的名称范围,您还必须手动创建一个新的名称范围。

最容易访问现有名称范围的是命名 XAML 元素的引用。在这个元素上,你调用FrameworkElement.RegisterName 来注册一个元素。请参阅Microsoft Docs: Targeting Framework Elements, Framework Content Elements, and Freezables 了解更多信息。

StoryBoardBuilder.cs

class StoryBoardBuilder

  public static DoubleAnimationUsingKeyFrames BuildDoubleAnimationUsingKeyFrames(
    FillBehavior fillBehavior,
    DoubleKeyFrameCollection keyFrames,
    string targetName,
    PropertyPath targetProperty) 
  
    //  Create animation.
    var animation = new DoubleAnimationUsingKeyFrames();

    //  Set animation end behavior.
    animation.FillBehavior = fillBehavior;

    //  Set animation frames.
    animation.KeyFrames = keyFrames;

    //  Set animation target object.
    Storyboard.SetTargetName(animation, targetName);

    //  Set animation target property.
    Storyboard.SetTargetProperty(animation, targetProperty);

    return animation;
  

示例

class EventsPage 

    //  ...    

    public void AddEvent(Event @event) 
    
        var eventUC = new EventUserContrl(@event);

        // Name the element
        eventUC.Name = "MyEventUserContrl";

        // Register the element name in the current name scope manually
        // using an existing named element 
        EventsStackPanel.RegisterName(eventUC.Name, eventUC);

        EventsStackPanel.Children.Add(eventUC);

        eventUC.ExpandCollapseAnimation += ExpandCollapseAnimation;
    

    private ExpandCollapseAnimation(EventUserControl eventUC, double height, double time) 
    
        ...

        //  Create Animation.
        var heightSizeAnimation= StoryBoardBuilder.BuildDoubleAnimationUsingKeyFrames(                    
          FillBehavior.Stop, 
          frames, 
          eventUC.Name, 
          new PropertyPath("Height"));

        ...
    

    //  ...

【讨论】:

非常感谢您的帮助? 不客气。我有一个错字。当然使用Storyboard.SetTarget 是推荐的解决方案。我不小心复制并粘贴了Storyboard.SetTargetName,在使用 C# 而不是 XAML 时绝对不建议这样做。 Storyboard.SetTarget 不需要名称范围注册的元素名称。

以上是关于WPF 动态创建情节提要的主要内容,如果未能解决你的问题,请参考以下文章

ios tableview动态原型在情节提要中不起作用

动态更改情节提要或情节提要上的场景

用 XIB 替换情节提要

WPF 动画:绑定到情节提要动画的“To”属性

我应该为情节提要效果使用哪些 WPF 布局和控件

使用动态高度时 UITableViewCell 的情节提要冲突约束