web-dev-qa-db-ja.com

WPFフェードアニメーション

コントロールが表示可能になったときに、どのようにコントロールをフェードイン/フェードアウトしますか?.

以下は私の失敗した試みです:

<Window x:Class="WadFileTester.Form1"
    xmlns="http://schemas.Microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.Microsoft.com/winfx/2006/xaml"
    Name="MyWindow" Title="WAD File SI Checker" Height="386" Width="563" WindowStyle="SingleBorderWindow" DragEnter="Window_DragEnter" DragLeave="Window_DragLeave" DragOver="Window_DragOver" Drop="Window_Drop" AllowDrop="True">
    <Window.Resources>
        <Style TargetType="ListView" x:Key="animatedList">
            <Style.Triggers>
                <DataTrigger Binding="{Binding Visibility}" Value="Visible">
                    <DataTrigger.EnterActions>
                        <BeginStoryboard>
                            <Storyboard>
                                <DoubleAnimation
                                    Storyboard.TargetProperty="Opacity"
                                    From="0.0" To="1.0" Duration="0:0:5"
                                    />
                            </Storyboard>
                        </BeginStoryboard>
                    </DataTrigger.EnterActions>
                    <DataTrigger.ExitActions>
                        <BeginStoryboard>
                            <Storyboard>
                                <DoubleAnimation
                                    Storyboard.TargetProperty="Opacity"
                                    From="1.0" To="0.0" Duration="0:0:5"
                                    />
                            </Storyboard>
                        </BeginStoryboard>
                    </DataTrigger.ExitActions>
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </Window.Resources>
    <Grid>
        <ListView Name="listView1" Style="{StaticResource animatedList}" TabIndex="1" Margin="12,41,12,12" Visibility="Hidden">
        </ListView>
    </Grid>
</Window>
36
Adam Tegen

純粋なXAMLで両方のアニメーション(フェードインとフェードアウト)を行う方法がわかりません。ただし、単純なフェードアウトは比較的簡単に実現できます。 DataTriggersをTriggersに置き換え、Fade outシナリオでは意味をなさないため、ExitActionsを削除します。これはあなたが持っているものです:

 <Style TargetType="FrameworkElement" x:Key="animatedList">
  <Setter Property="Visibility" Value="Hidden"/>
  <Style.Triggers>
    <Trigger Property="Visibility" Value="Visible">
      <Trigger.EnterActions>
        <BeginStoryboard>
          <Storyboard>
            <DoubleAnimation Storyboard.TargetProperty="Opacity"
                             From="0.0" To="1.0" Duration="0:0:0.2"/>
          </Storyboard>
        </BeginStoryboard>
      </Trigger.EnterActions>
    </Trigger>
  </Style.Triggers>
</Style>

しかし、あきらめないでください。両方のアニメーションをサポートする場合は、XAMLの背後にある小さなコーディングをお勧めします。トリックを行った後、XAMLに1行のコードを追加することで、必要なものを取得します。

<Button Content="Fading button"
        x:Name="btn"
        loc:VisibilityAnimation.IsActive="True"/>

Btn.VisibilityをVisibleからHidden/Collapsedボタンに変更するたびにフェードアウトします。そして、Visibilityを元に戻すたびに、ボタンはフェードインします。このトリックは、FrameworkElement(ListView :)を含む)で機能します。

VisibilityAnimation.IsActive添付プロパティのコードは次のとおりです。

  public class VisibilityAnimation : DependencyObject
  {
    private const int DURATION_MS = 200;

    private static readonly Hashtable _hookedElements = new Hashtable();

    public static readonly DependencyProperty IsActiveProperty =
      DependencyProperty.RegisterAttached("IsActive", 
      typeof(bool), 
      typeof(VisibilityAnimation),
      new FrameworkPropertyMetadata(false, new PropertyChangedCallback(OnIsActivePropertyChanged)));

    public static bool GetIsActive(UIElement element)
    {
      if (element == null)
      {
        throw new ArgumentNullException("element");
      }

      return (bool)element.GetValue(IsActiveProperty);
    }

    public static void SetIsActive(UIElement element, bool value)
    {
      if (element == null)
      {
        throw new ArgumentNullException("element");
      }
      element.SetValue(IsActiveProperty, value);
    }

    static VisibilityAnimation()
    {
      UIElement.VisibilityProperty.AddOwner(typeof(FrameworkElement),
                                            new FrameworkPropertyMetadata(Visibility.Visible, new PropertyChangedCallback(VisibilityChanged), CoerceVisibility));
    }

    private static void VisibilityChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
      // So what? Ignore.
    }

    private static void OnIsActivePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
      var fe = d as FrameworkElement;
      if (fe == null)
      {
        return;
      }
      if (GetIsActive(fe))
      {
        HookVisibilityChanges(fe);
      }
      else
      {
        UnHookVisibilityChanges(fe);
      }
    }

    private static void UnHookVisibilityChanges(FrameworkElement fe)
    {
      if (_hookedElements.Contains(fe))
      {
        _hookedElements.Remove(fe);
      } 
    }

    private static void HookVisibilityChanges(FrameworkElement fe)
    {
      _hookedElements.Add(fe, false);
    }

    private static object CoerceVisibility(DependencyObject d, object baseValue)
    {
      var fe = d as FrameworkElement;
      if (fe == null)
      {
        return baseValue;
      }

      if (CheckAndUpdateAnimationStartedFlag(fe))
      {
        return baseValue;
      }
      // If we get here, it means we have to start fade in or fade out
      // animation. In any case return value of this method will be
      // Visibility.Visible. 

      var visibility = (Visibility)baseValue;

      var da = new DoubleAnimation
      {
        Duration = new Duration(TimeSpan.FromMilliseconds(DURATION_MS))
      };

      da.Completed += (o, e) =>
                        {
                          // This will trigger value coercion again
                          // but CheckAndUpdateAnimationStartedFlag() function will reture true
                          // this time, and animation will not be triggered.
                          fe.Visibility = visibility;
                          // NB: Small problem here. This may and probably will brake 
                          // binding to visibility property.
                        };

      if (visibility == Visibility.Collapsed || visibility == Visibility.Hidden)
      {
        da.From = 1.0;
        da.To = 0.0;
      }
      else
      {
        da.From = 0.0;
        da.To = 1.0;
      }

      fe.BeginAnimation(UIElement.OpacityProperty, da);
      return Visibility.Visible;
    }

    private static bool CheckAndUpdateAnimationStartedFlag(FrameworkElement fe)
    {
      var hookedElement = _hookedElements.Contains(fe);
      if (!hookedElement)
      {
        return true; // don't need to animate unhooked elements.
      }

      var animationStarted = (bool) _hookedElements[fe];
      _hookedElements[fe] = !animationStarted;

      return animationStarted;
    }
  }

ここで最も重要なことは、CoerceVisibility()メソッドです。ご覧のとおり、フェードアニメーションが完了するまでこのプロパティを変更することはできません。

このコードはスレッドセーフでもバグフリーでもありません。その唯一の意図は、方向を示すことです:)。だから、自由に改善、編集、評判を得る;)。

54
Anvaka

Vidibilityプロパティをフェードアウトに直接使用することはできません。トリガーを設定すると、最初にコントロールを非表示/折りたたみ、アニメーション化します。したがって、基本的に、折りたたまれたコントロールにアニメーションが表示されます=>何もありません。

「信頼できる」方法の1つは、IsOpenと言い、プロパティトリガーIsOpen=Trueその上:

  • VisibilityがVisibleに設定されていることを確認してください
  • 不透明度を0から1にフェードイン
  • キーフレーム0でVisibleに設定され、最後のキーフレームでCollapsed/Hiddenに設定された可視性
  • 不透明度を1から0にフェードアウトします。

以下に例を示します。

<Style TargetType="{x:Type local:TCMenu}">
    <Style.Resources>
        <Storyboard x:Key="FadeInMenu">
            <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="{x:Null}">
                <EasingDoubleKeyFrame KeyTime="0" Value="0"/>
                <EasingDoubleKeyFrame KeyTime="0:0:0.2" Value="1"/>
            </DoubleAnimationUsingKeyFrames>
                <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)" Storyboard.TargetName="{x:Null}">
                    <DiscreteObjectKeyFrame KeyTime="0:0:0.0" Value="{x:Static Visibility.Visible}"/>
                </ObjectAnimationUsingKeyFrames>
            </Storyboard>
        <Storyboard x:Key="FadeOutMenu">
            <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="{x:Null}">
                <EasingDoubleKeyFrame KeyTime="0" Value="1"/>
                <EasingDoubleKeyFrame KeyTime="0:0:0.2" Value="0"/>
            </DoubleAnimationUsingKeyFrames>
            <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)" Storyboard.TargetName="{x:Null}">
                    <DiscreteObjectKeyFrame KeyTime="0:0:0.0" Value="{x:Static Visibility.Visible}"/>
                    <DiscreteObjectKeyFrame KeyTime="0:0:0.2" Value="{x:Static Visibility.Collapsed}"/>
            </ObjectAnimationUsingKeyFrames>
        </Storyboard>
    </Style.Resources>
    <Style.Triggers>
        <Trigger Property="IsOpen" Value="true">
            <Trigger.EnterActions>
                <BeginStoryboard Storyboard="{StaticResource FadeInMenu}"/>
            </Trigger.EnterActions>
                <Trigger.ExitActions>
                    <BeginStoryboard Storyboard="{StaticResource FadeOutMenu}"/>
                </Trigger.ExitActions>
            </Trigger>
        </Style.Triggers>
        <Setter Property="Visibility" Value="Collapsed" />
</Style>
20
Nock

私はこの質問が少し古いことを知っていますが、私は今それを読んだだけで、アンヴァカによって与えられたコードを微調整しました。 Visibilityへのバインドをサポートしています(バインドモードがTwoWayに設定されている場合のみ)。また、FadeInとFadeOutの2つの異なる期間値もサポートしています。

クラスは次のとおりです。

  public class VisibilityAnimation : DependencyObject
  {
    #region Private Variables

    private static HashSet<UIElement> HookedElements = new HashSet<UIElement>();
    private static DoubleAnimation FadeAnimation = new DoubleAnimation();
    private static bool SurpressEvent;
    private static bool Running;

    #endregion

    #region Attached Dependencies

    public static readonly DependencyProperty IsActiveProperty = DependencyProperty.RegisterAttached("IsActive", typeof(bool), typeof(VisibilityAnimation), new FrameworkPropertyMetadata(false, new PropertyChangedCallback(OnIsActivePropertyChanged)));
    public static bool GetIsActive(UIElement element)
    {
      if (element == null) throw new ArgumentNullException("element");
      return (bool)element.GetValue(IsActiveProperty);
    }
    public static void SetIsActive(UIElement element, bool value)
    {
      if (element == null) throw new ArgumentNullException("element");
      element.SetValue(IsActiveProperty, value);
    }

    public static readonly DependencyProperty FadeInDurationProperty = DependencyProperty.RegisterAttached("FadeInDuration", typeof(double), typeof(VisibilityAnimation), new PropertyMetadata(0.5));
    public static double GetFadeInDuration(UIElement e)
    {
      if (e == null) throw new ArgumentNullException("element");
      return (double)e.GetValue(FadeInDurationProperty);
    }
    public static void SetFadeInDuration(UIElement e, double value)
    {
      if (e == null) throw new ArgumentNullException("element");
      e.SetValue(FadeInDurationProperty, value);
    }

    public static readonly DependencyProperty FadeOutDurationProperty = DependencyProperty.RegisterAttached("FadeOutDuration", typeof(double), typeof(VisibilityAnimation), new PropertyMetadata(1.0));
    public static double GetFadeOutDuration(UIElement e)
    {
      if (e == null) throw new ArgumentNullException("element");
      return (double)e.GetValue(FadeOutDurationProperty);
    }
    public static void SetFadeOutDuration(UIElement e, double value)
    {
      if (e == null) throw new ArgumentNullException("element");
      e.SetValue(FadeOutDurationProperty, value);
    }

    #endregion

    #region Callbacks

    private static void VisibilityChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
      // So what? Ignore.
      // We only specified a property changed call-back to be able to set a coercion call-back
    }

    private static void OnIsActivePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
      // Get the framework element and leave if it is null
      var fe = d as FrameworkElement;
      if (fe == null) return;

      // Hook the element if IsActive is true and unhook the element if it is false
      if (GetIsActive(fe)) HookedElements.Add(fe);
      else HookedElements.Remove(fe);
    }

    private static object CoerceVisibility(DependencyObject d, object baseValue)
    {
      if (SurpressEvent) return baseValue;  // Ignore coercion if we set the SurpressEvent flag

      var FE = d as FrameworkElement;
      if (FE == null || !HookedElements.Contains(FE)) return baseValue;  // Leave if the element is null or does not belong to our list of hooked elements

      Running = true;  // Set the running flag so that an animation does not change the visibility if another animation was started (Changing Visibility before the 1st animation completed)

      // If we get here, it means we have to start fade in or fade out animation
      // In any case return value of this method will be Visibility.Visible

      Visibility NewValue = (Visibility)baseValue;  // Get the new value

      if (NewValue == Visibility.Visible) FadeAnimation.Duration = new Duration(TimeSpan.FromSeconds((double)d.GetValue(FadeInDurationProperty)));  // Get the duration that was set for fade in
      else FadeAnimation.Duration = new Duration(TimeSpan.FromSeconds((double)d.GetValue(FadeOutDurationProperty)));  // Get the duration that was set for fade out

      // Use an anonymous method to set the Visibility to the new value after the animation completed
      FadeAnimation.Completed += (obj, args) =>
      {
        if (FE.Visibility != NewValue && !Running)
        {
          SurpressEvent = true;  // SuppressEvent flag to skip coercion
          FE.Visibility = NewValue;
          SurpressEvent = false;
          Running = false;  // Animation and Visibility change is now complete
        }
      };

      FadeAnimation.To = (NewValue == Visibility.Collapsed || NewValue == Visibility.Hidden) ? 0 : 1;  // Set the to value based on Visibility

      FE.BeginAnimation(UIElement.OpacityProperty, FadeAnimation);  // Start the animation (it will only start after we leave the coercion method)

      return Visibility.Visible;  // We need to return Visible in order to see the fading take place, otherwise it just sets it to Collapsed/Hidden without showing the animation
    }

    #endregion

    static VisibilityAnimation()
    {
      // Listen for visibility changes on all elements
      UIElement.VisibilityProperty.AddOwner(typeof(FrameworkElement), new FrameworkPropertyMetadata(Visibility.Visible, new PropertyChangedCallback(VisibilityChanged), CoerceVisibility));
    }    
  }
6
Xtr

私はこれに少し異なった方法で来ました-崩壊または表示するすべてにFadeIn()およびFadeOut()拡張メソッドを追加する この質問 に対するRayの答えの拡張バージョンを持っています必要に応じて要素を表示し、オブジェクトを表示する代わりに、それらに対してFadeIn()およびFadeOut()を呼び出すだけで、特定のアニメーションコードなしで任意の要素が機能します。

    public static T FadeFromTo(this UIElement uiElement, double fromOpacity,
        double toOpacity, int durationInMilliseconds, bool loopAnimation,
        bool showOnStart, bool collapseOnFinish)
    {
        var timeSpan = TimeSpan.FromMilliseconds(durationInMilliseconds);
        var doubleAnimation =
              new DoubleAnimation(fromOpacity, toOpacity,
                                  new Duration(timeSpan));
            if (loopAnimation)
                doubleAnimation.RepeatBehavior = RepeatBehavior.Forever;
            uiElement.BeginAnimation(UIElement.OpacityProperty, doubleAnimation);
            if (showOnStart)
            {
                uiElement.ApplyAnimationClock(UIElement.VisibilityProperty, null);
                uiElement.Visibility = Visibility.Visible;
            }
            if (collapseOnFinish)
            {
                var keyAnimation = new ObjectAnimationUsingKeyFrames{Duration = new Duration(timeSpan) };
                keyAnimation.KeyFrames.Add(new DiscreteObjectKeyFrame(Visibility.Collapsed, KeyTime.FromTimeSpan(timeSpan)));
                uiElement.BeginAnimation(UIElement.VisibilityProperty, keyAnimation);
            }
            return uiElement;
    }

    public static T FadeIn(this UIElement uiElement, int durationInMilliseconds)
    {
        return uiElement.FadeFromTo(0, 1, durationInMilliseconds, false, true, false);
    }

    public static T FadeOut(this UIElement uiElement, int durationInMilliseconds)
    {
        return uiElement.FadeFromTo(1, 0, durationInMilliseconds, false, false, true);
    }
5
The Gribble

これは、ビヘイビアを使用して行うのが最適です

class AnimatedVisibilityFadeBehavior : Behavior<Border>
   {
      public Duration AnimationDuration { get; set; }
      public Visibility InitialState { get; set; }

      DoubleAnimation m_animationOut;
      DoubleAnimation m_animationIn;

      protected override void OnAttached()
      {
         base.OnAttached();

         m_animationIn = new DoubleAnimation(1, AnimationDuration, FillBehavior.HoldEnd);
         m_animationOut = new DoubleAnimation(0, AnimationDuration, FillBehavior.HoldEnd);
         m_animationOut.Completed += (sender, args) =>
            {
               AssociatedObject.SetCurrentValue(Border.VisibilityProperty, Visibility.Collapsed);
            };

         AssociatedObject.SetCurrentValue(Border.VisibilityProperty,
                                          InitialState == Visibility.Collapsed
                                             ? Visibility.Collapsed
                                             : Visibility.Visible);

         Binding.AddTargetUpdatedHandler(AssociatedObject, Updated);
      }

      private void Updated(object sender, DataTransferEventArgs e)
      {
         var value = (Visibility)AssociatedObject.GetValue(Border.VisibilityProperty);
         switch (value)
         {
            case Visibility.Collapsed:
               AssociatedObject.SetCurrentValue(Border.VisibilityProperty, Visibility.Visible);
               AssociatedObject.BeginAnimation(Border.OpacityProperty, m_animationOut);
               break;
            case Visibility.Visible:
               AssociatedObject.BeginAnimation(Border.OpacityProperty, m_animationIn);
               break;
         }
      }
   }

これは特に境界線に適用されています-ユーザーコントロールを試したことはありませんが、同じことが当てはまります。

これを使用するには、Blend Interactivity名前空間が必要です。

xmlns:i="clr-namespace:System.Windows.Interactivity;Assembly=System.Windows.Interactivity"

そして、振る舞いをしたい境界線でこのマークアップを使用します:

<i:Interaction.Behaviors>
                <Interactivity:AnimatedVisibilityFadeBehavior AnimationDuration="0:0:0.3" InitialState="Collapsed" />
</i:Interaction.Behaviors>

動作クラスの名前空間にも追加する必要があります。

3
Craig

かなり古いですが、DoubleAnimationsをチェーンするだけではできませんか?

<DataTrigger.EnterActions>
    <BeginStoryboard>
        <Storyboard>
            <DoubleAnimation
                Storyboard.TargetProperty="Opacity"
                From="0.0" To="1.0" Duration="0:0:5"
                />
            <DoubleAnimation
                Storyboard.TargetProperty="Opacity"
                From="1.0" To="0.0" Duration="0:0:5"
                />
        </Storyboard>
    </BeginStoryboard>
</DataTrigger.EnterActions>
2
Geoff

AutoReverseプロパティを試してみるとよいかもしれませんが、あなたが望むように機能するかどうかはわかりません。これは私がMSDNで見つけたものです:

タイムラインのAutoReverseプロパティがtrueに設定され、そのRepeatBehaviorプロパティによって繰り返される場合、各前方反復の後に後方反復が続きます。これは1回繰り返します。たとえば、反復カウントが2のAutoReverse値がtrueのタイムラインは、1回再生した後、逆方向に再生し、次に再び順方向に、次に逆方向に再生します。

2
illusion