簡體   English   中英

WPF 用於滑入/滑出頁面的框架 animation 無法與數據觸發器一起正常工作

[英]WPF Frame animation to slide in/out pages is not working properly with data triggers

目標

使用我的應用程序,我嘗試在 WPF 中創建一個“單窗口”應用程序。 因此,框架的內容(如下所示)是通過數據綁定設置的。 為了動畫這個過程,我觸發了 ViewModel 之外的AnimateFrameOutToLeft - 和AnimateFrameInToLeft - 屬性。 這應該會產生新頁面“滑入”或“滑出”的效果。 更改框架內容的過程步驟(在 ViewModel 中):

  1. AnimateFrameOutToLeft = true; AnimateFrameOutToLeft = false;
  2. 改變框架的內容
  3. AnimateFrameInToLeft = true; AnimateFrameInToLeft = false;

問題

按此順序(關於數據觸發器),“滑動”頁面的 animation 未顯示。 框架的不透明度為 0。更改順序后,“滑出”頁面的 animation 未顯示。 為什么呢? 我該如何解決這個問題?

方法

  • 我使用自定義附加屬性調試了數據觸發器:它們都被正確觸發
  • 我使用自定義附加屬性調試了動畫(CompleteEvent):按預期工作
  • 更改了數據觸發器的順序:當AnimateFrameOutToLeft第二次被觸發時,它不顯示 animation

ShellView.xaml

<Frame Margin="0" Background="White" Content="{Binding FrameContent, Mode=OneWay}" Focusable="False">
    <Frame.Style>
        <Style TargetType="{x:Type Frame}">
            <Style.Triggers>
                <DataTrigger Binding="{Binding AnimateFrameInToLeft}" Value="True">
                    <DataTrigger.EnterActions>
                        <BeginStoryboard>
                            <Storyboard>
                                <ThicknessAnimation Duration="0:0:0.25" Storyboard.TargetProperty="Margin" From="50,0,0,0" To="0" FillBehavior="HoldEnd"/>
                                <DoubleAnimation Duration="0:0:0.25" Storyboard.TargetProperty="Opacity" From="0" To="1" FillBehavior="HoldEnd"/>
                            </Storyboard>
                        </BeginStoryboard>
                    </DataTrigger.EnterActions>
                </DataTrigger>
                <DataTrigger Binding="{Binding AnimateFrameOutToLeft}" Value="True">
                    <DataTrigger.EnterActions>
                        <BeginStoryboard>
                            <Storyboard>
                                <ThicknessAnimation Duration="0:0:0.25" Storyboard.TargetProperty="Margin" From="0" To="0,0,50,0" FillBehavior="HoldEnd"/>
                                <DoubleAnimation Duration="0:0:0.25" Storyboard.TargetProperty="Opacity" From="1" To="0" FillBehavior="HoldEnd"/>
                            </Storyboard>
                        </BeginStoryboard>
                    </DataTrigger.EnterActions>
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </Frame.Style>
   ...
</Frame>

編輯(從 ControlTemplate 中引用綁定問題)

ShellView.xaml

<Window ...>
    <Window.InputBindings>
        <KeyBinding Key="Esc" Command="{Binding KeyPressedCommand}" CommandParameter="Esc"/>
    <!-- this binding works -->
    </Window.InputBindings>
    <Grid>
        <Frame Margin="0" Background="White" Content="{Binding FrameContent, Mode=OneWay}" Focusable="False">
            <Frame.ContentTemplate>
                <ItemContainerTemplate>
                    <ContentControl Content="{Binding}">
                        <ContentControl.Style>
                            <Style TargetType="{x:Type ContentControl}">
                                <Setter Property="RenderTransform">
                                    <Setter.Value>
                                        <TranslateTransform/>
                                    </Setter.Value>
                                </Setter>
                                <Style.Triggers>
                                    <DataTrigger Binding="{Binding Path=DataContext.AnimateFrameToLeft, RelativeSource={RelativeSource AncestorType={x:Type local:ShellView}}}"  Value="True">
                                <!-- this binding does not work -->
...

AnimateFrameToLeft-Property 定義在與上述命令相同的 class 中

由於FillBehavior.HoldEnd ,以前的 animation 正在阻止以下 animation (或動畫屬性)。

另一個錯誤是您操作Frame.Margin屬性的方式。
請注意,如果您想向左滑出,您的ThicknessAnimation應該將Margin0設置為-50,0,0,0 (而不是0,0,50,0 )。
這是因為正的FrameworkElement.Margin只影響周圍的元素,而不影響實際的元素。 這意味着因為Frame右側沒有相鄰元素,所以您看不到任何效果(否則右側相鄰元素將被向右推 50 像素)。
要影響Frame ,您必須使用Margin來“拖動”它。
推薦的方法是為TranslateTransform設置動畫而不是邊距的厚度。

另請注意,為Frame本身設置動畫可能不是最佳解決方案。 如果您在Frame上顯示導航控件,則看到所有內容都進出會看起來很奇怪。 您應該直接為Frame.Content設置動畫。

版本 1(推薦)

DataTrigger基於屬性的 state。 它由 state 更改觸發並執行DataTrigger.EnterActions 一旦 state 回落到原來的 state,就會執行DataTrigger.ExitActions 入口 animation(使用FillBehavior.HoldEnd )持有的“屬性值鎖”不會影響出口 animation。
您應該利用 DataTrigger 的自動 state 跟蹤,並將第二個滑入式DataTrigger移動到DataTrigger.ExitActions集合。

在以下示例中, Frame.ContentTemplate用於為內容設置動畫,而不是Frame本身。 此示例使用DoubleAnimation來為框架的ContentControl.RenderTransform屬性(設置為TranslateTransform )設置動畫,而不是ThicknessAnimation

觸發 animation

private async void ChangePage_OnButtonClick(object sender, RoutedEventArgs e)
{
  // Slide current Frame.Content out to the left
  AnimateFrameOutToLeft = true;

  
  await Task.Delay(5000);

  // Slide new Frame.Content in from left to right left
  AnimateFrameOutToLeft = false;
}

Animation定義

<Frame x:Name="Frame" NavigationUIVisibility="Visible">
  <Frame.ContentTemplate>
    <ItemContainerTemplate>
      <ContentControl Content="{Binding}">
        <ContentControl.Style>
          <Style TargetType="ContentControl">
            <Setter Property="RenderTransform">
              <Setter.Value>
                <TranslateTransform />
              </Setter.Value>
            </Setter>

            <Style.Triggers>
              <DataTrigger Binding="{Binding AnimateFrameOutToLeft}" Value="True">
                <DataTrigger.EnterActions>
                  <BeginStoryboard>
                    <Storyboard>
                      <DoubleAnimation Storyboard.TargetProperty="RenderTransform.(TranslateTransform.X)" 
                                       Duration="0:0:0.25"
                                       To="-50" 
                                       FillBehavior="HoldEnd" />
                      <DoubleAnimation Storyboard.TargetProperty="Opacity"
                                       Duration="0:0:0.25" 
                                       From="1" To="0"
                                       FillBehavior="HoldEnd" />
                    </Storyboard>
                  </BeginStoryboard>
                </DataTrigger.EnterActions>

                <DataTrigger.ExitActions>
                  <BeginStoryboard>
                    <Storyboard>
                      <DoubleAnimation Storyboard.TargetProperty="RenderTransform.(TranslateTransform.X)"
                                       Duration="0:0:0.25"
                                       To="0" 
                                       FillBehavior="HoldEnd" />
                        <DoubleAnimation Storyboard.TargetProperty="Opacity"
                                         Duration="0:0:0.25" 
                                         From="0" To="1"
                                         FillBehavior="HoldEnd" />
                    </Storyboard>
                  </BeginStoryboard>
                </DataTrigger.ExitActions>
              </DataTrigger>
            </Style.Triggers>
          </Style>
        </ContentControl.Style>
      </ContentControl>
    </ItemContainerTemplate>
  </Frame.ContentTemplate>
</Frame>

版本 2

屬性Frame.MarginFrame.Opacity仍被先前的 animation 阻止,由於FillBehavior.HoldEnd設置仍在執行中。
在執行時間軸中的下一個之前,您必須停止之前的 animation。

為開始的BeginStoryboard命名,例如SlideOutStoryboard 然后將針對前StopStoryboardBeginStoryboard添加到滑入式觸發器的EnterActions集合中:

<Frame.Style>
  <Style TargetType="{x:Type Frame}">
    <Style.Triggers>
      <DataTrigger Binding="{Binding AnimateFrameOutToLeft}" Value="True">
        <DataTrigger.EnterActions>
          <BeginStoryboard x:Name="SlideOutStoryboard">
            <Storyboard>
              <ThicknessAnimation Duration="0:0:0.25" Storyboard.TargetProperty="Margin" 
                                  From="0" To="-50,0,0,0" 
                                  FillBehavior="HoldEnd" />
              <DoubleAnimation Duration="0:0:0.25" Storyboard.TargetProperty="Opacity" 
                               From="1" To="0"
                               FillBehavior="HoldEnd" />
            </Storyboard>
          </BeginStoryboard>
        </DataTrigger.EnterActions>
      </DataTrigger>

      <DataTrigger Binding="{Binding AnimateFrameInToLeft}" Value="True">
        <DataTrigger.EnterActions>

          <!-- Stop the previous BeginStoryBoard "SlideOutStoryboard" -->
          <StopStoryboard BeginStoryboardName="SlideOutStoryboard" />

          <BeginStoryboard>
            <Storyboard>
              <ThicknessAnimation Duration="0:0:0.25" Storyboard.TargetProperty="Margin" 
                                  From="-50,0,0,0" To="0,0,0,0" 
                                  FillBehavior="HoldEnd" />
              <DoubleAnimation Duration="0:0:0.25" Storyboard.TargetProperty="Opacity" 
                               From="0" To="1"
                               FillBehavior="HoldEnd" />
            </Storyboard>
          </BeginStoryboard>
        </DataTrigger.EnterActions>
      </DataTrigger>
    </Style.Triggers>
  </Style>
</Frame.Style>

版本 3

這是一個擴展Frame的實現。 它與標准Frame一樣使用,只是它會在導航(或Content更改)時自動為Content設置動畫:

public class AnimatedFrame : Frame
{
  private bool IsAnimating { get; set; }
  private UIElement NextContent { get; set; }
  private UIElement PreviousContent { get; set; }
  private Action PreviousContentTransformCleanupDelegate { get; set; }
  private Action NextContentTransformCleanupDelegate { get; set; }

  public AnimatedFrame() => this.Navigating += OnNavigating;

  private void OnNavigating(object sender, NavigatingCancelEventArgs e)
  {
    if (this.IsAnimating 
      || !(e.Content is UIElement nextContent 
        && this.Content is UIElement))
    {
      return;
    }

    e.Cancel = true;
    this.PreviousContent = this.Content as UIElement;
    this.NextContent = nextContent;
    AnimateToNextContent();
  }

  private void AnimateToNextContent()
  {
    PrepareAnimation();
    StartPreviousContentAnimation();
  }

  private void PrepareAnimation()
  {
    this.IsAnimating = true;


    Transform originalPreviousContentTransform = this.PreviousContent.RenderTransform;
    this.PreviousContent.RenderTransform = new TranslateTransform(0, 0);
    this.PreviousContentTransformCleanupDelegate =
      () => this.PreviousContent.RenderTransform = originalPreviousContentTransform;


    Transform originalNextContentTransform = this.NextContent.RenderTransform;
    this.NextContent.RenderTransform = new TranslateTransform(0, 0);
    this.NextContentTransformCleanupDelegate = () => this.NextContent.RenderTransform = originalNextContentTransform;
  }

  private void StartPreviousContentAnimation()
  {
    var unloadAnimation = new Storyboard();
    DoubleAnimation slideOutAnimation = CreateSlideOutAnimation();
    unloadAnimation.Children.Add(slideOutAnimation);

    DoubleAnimation fadeOutAnimation = CreateFadeOutAnimation();
    unloadAnimation.Children.Add(fadeOutAnimation);
    unloadAnimation.Completed += StartNextContentAnimation_OnPreviousContentAnimationCompleted;

    unloadAnimation.Begin();
  }

  private void StartNextContentAnimation_OnPreviousContentAnimationCompleted(object sender, EventArgs e)
  {
    this.Content = this.NextContent;

    var loadAnimation = new Storyboard();
    DoubleAnimation slideInAnimation = CreateSlideInAnimation();
    loadAnimation.Children.Add(slideInAnimation);

    DoubleAnimation fadeInAnimation = CreateFadeInAnimation();
    loadAnimation.Children.Add(fadeInAnimation);
    loadAnimation.Completed += Cleanup_OnAnimationsCompleted;

    loadAnimation.Begin();
  }

  private void Cleanup_OnAnimationsCompleted(object sender, EventArgs e)
  {
    this.PreviousContentTransformCleanupDelegate.Invoke();
    this.NextContentTransformCleanupDelegate.Invoke();
    this.IsAnimating = false;
  }

  private DoubleAnimation CreateFadeOutAnimation()
  {
    var fadeOutAnimation = new DoubleAnimation(1, 0, new Duration(TimeSpan.FromMilliseconds(250)), FillBehavior.HoldEnd)
      {BeginTime = TimeSpan.Zero};
    Storyboard.SetTarget(fadeOutAnimation, this.PreviousContent);
    Storyboard.SetTargetProperty(fadeOutAnimation, new PropertyPath(nameof(UIElement.Opacity)));
    return fadeOutAnimation;
  }

  private DoubleAnimation CreateSlideOutAnimation()
  {
    var slideOutAnimation = new DoubleAnimation(
        0,
        -50,
        new Duration(TimeSpan.FromMilliseconds(250)),
        FillBehavior.HoldEnd)
      {BeginTime = TimeSpan.Zero};

    Storyboard.SetTarget(slideOutAnimation, this.PreviousContent);
    Storyboard.SetTargetProperty(
      slideOutAnimation,
      new PropertyPath(
        $"{nameof(UIElement.RenderTransform)}.({nameof(TranslateTransform)}.{nameof(TranslateTransform.X)})"));
    return slideOutAnimation;
  }

  private DoubleAnimation CreateFadeInAnimation()
  {
    var fadeInAnimation = new DoubleAnimation(0, 1, new Duration(TimeSpan.FromMilliseconds(250)), FillBehavior.HoldEnd);
    Storyboard.SetTarget(fadeInAnimation, this.NextContent);
    Storyboard.SetTargetProperty(fadeInAnimation, new PropertyPath(nameof(UIElement.Opacity)));
    return fadeInAnimation;
  }

  private DoubleAnimation CreateSlideInAnimation()
  {
    var slideInAnimation = new DoubleAnimation(
      -50,
      0,
      new Duration(TimeSpan.FromMilliseconds(250)),
      FillBehavior.HoldEnd);

    Storyboard.SetTarget(slideInAnimation, this.NextContent);
    Storyboard.SetTargetProperty(
      slideInAnimation,
      new PropertyPath(
        $"{nameof(UIElement.RenderTransform)}.({nameof(TranslateTransform)}.{nameof(TranslateTransform.X)})"));
    return slideInAnimation;
  }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM