Comment puis-je faire un fondu d'entrée/sortie de contrôle lorsqu'il devient visible.
Voici ma tentative ratée:
<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>
Je ne sais pas comment faire les deux animations (fondu entrant et sortant) en XAML pur. Mais un fondu sortant simple peut être réalisé de manière relativement simple. Remplacez DataTriggers par Triggers et supprimez ExitActions car ils n'ont aucun sens dans le scénario Fade out. Voici ce que vous aurez:
<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>
Mais bon, n'abandonnez pas. Si vous souhaitez prendre en charge les deux animations, je peux suggérer un petit codage derrière le XAML. Après avoir fait un tour, nous obtiendrons ce que vous voulez en ajoutant une ligne de code en XAML:
<Button Content="Fading button"
x:Name="btn"
loc:VisibilityAnimation.IsActive="True"/>
Chaque fois que nous changeons btn, la visibilité du bouton Visible au bouton Caché/Réduit disparaîtra. Et chaque fois que nous modifions la visibilité, le bouton apparaît en fondu. Cette astuce fonctionne avec n'importe quel FrameworkElement (y compris ListView :)).
Voici le code de la propriété jointe 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;
}
}
La chose la plus importante ici est la méthode CoerceVisibility (). Comme vous pouvez le voir, nous ne permettons pas de modifier cette propriété tant que l'animation de fondu n'est pas terminée.
Ce code n'est ni thread-safe ni bug. Sa seule intention est de montrer la direction :). Alors n'hésitez pas à améliorer, éditer et obtenir de la réputation;).
Vous ne pouvez pas utiliser directement la propriété Visibility pour un fondu, car le fait de définir un déclencheur va d'abord masquer/réduire le contrôle, puis l'animer. Donc, fondamentalement, vous aurez une animation sur un contrôle réduit => rien.
Une façon "fiable" serait d'introduire une nouvelle propriété de dépendance (attachée ou non), par exemple IsOpen
et de définir un déclencheur de propriété IsOpen=True
dessus avec:
Voici un exemple:
<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>
Je me rends compte que cette question est un peu ancienne, mais je l'ai seulement lu maintenant et j'ai peaufiné le code donné par Anvaka. Il prend en charge la liaison à Visibility (uniquement lorsque le mode de liaison est défini sur TwoWay). Il prend également en charge 2 valeurs de durée différentes pour FadeIn et FadeOut.
Voici la classe:
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));
}
}
J'y suis arrivé d'une manière légèrement différente - j'ai une version étendue de la réponse de Ray à cette question qui ajoute une méthode d'extension FadeIn () et FadeOut () à tout ce qui s'effondre ou affiche l'élément comme il convient, puis au lieu de rendre les objets visibles, je peux simplement appeler FadeIn () et FadeOut () sur eux - et cela fonctionnera avec n'importe quel élément sans code d'animation spécifique.
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);
}
Il vaut mieux utiliser un comportement
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;
}
}
}
Ceci est spécifiquement appliqué à une bordure - je n'ai pas essayé de contrôle utilisateur mais j'espère que la même chose s'applique.
Pour l'utiliser, vous avez besoin de l'espace de noms Blend Interactivity:
xmlns:i="clr-namespace:System.Windows.Interactivity;Assembly=System.Windows.Interactivity"
Et utilisez ce balisage sur la bordure sur laquelle vous souhaitez que le comportement:
<i:Interaction.Behaviors>
<Interactivity:AnimatedVisibilityFadeBehavior AnimationDuration="0:0:0.3" InitialState="Collapsed" />
</i:Interaction.Behaviors>
Vous devrez également ajouter l'espace de noms pour la classe de comportement.
Assez vieux maintenant, mais ne pourriez-vous pas simplement enchaîner les 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>
Vous voudrez peut-être essayer la propriété AutoReverse ... mais je ne sais pas si cela fonctionne comme vous le souhaitez. Voici ce que j'ai trouvé sur MSDN:
Lorsque la propriété AutoReverse d'une chronologie est définie sur true et que sa propriété RepeatBehavior la fait se répéter, chaque itération vers l'avant est suivie d'une itération vers l'arrière. Cela fait une répétition. Par exemple, une chronologie avec une valeur AutoReverse true avec un nombre d'itérations de 2 jouerait en avant une fois, puis en arrière, puis en avant à nouveau, puis en arrière à nouveau.