How to animate TextBlock when its value changes in WinRT XAML? - c#

I have the following Storyboard
<Storyboard x:Name="DeleteStoryboard">
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="StatusTextBlock">
<EasingDoubleKeyFrame KeyTime="0" Value="1"/>
<EasingDoubleKeyFrame KeyTime="0:0:4" Value="1"/>
<EasingDoubleKeyFrame KeyTime="0:0:5" Value="0"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
and the following TextBlock
<TextBlock x:Name="StatusTextBlock" Text="{Binding Status}">
Both are in SettingsFlyout not a Page.
I want the Storyboard to start when the TextBlock value changes.
I'm using MVVM, so please no code-behind stuff unless absolutely necessary.
I tries searching for hints and tried different combination of Behaviors, Triggers and VisualState but reached nowhere.

Again, not sure if we are 100% agreeing. But, still, here's how you can do it:
public class MyViewModel : INotifyPropertyChanged
{
public string Text { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
}
public void Loaded()
{
var myBox = new TextBox();
var myAni = new Storyboard();
var MyVvm = new MyViewModel();
// sensible approach
myBox.TextChanged += (s, e) => myAni.Begin();
// forced approach
MyVvm.PropertyChanged += (s, e) =>
{
if (e.PropertyName.Equals("Text"))
myAni.Begin();
};
}
In the end, you are the developer of your own app. not me.
If you are willing to use behaviors, you can also do the same thing this way:
<Page.Resources>
<Storyboard x:Name="FadeAway">
<DoubleAnimation Duration="0:0:1" To="0" Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="textBox" d:IsOptimized="True"/>
</Storyboard>
</Page.Resources>
<TextBox x:Name="textBox">
<Interactivity:Interaction.Behaviors>
<Core:EventTriggerBehavior EventName="TextChanged">
<Media:ControlStoryboardAction Storyboard="{StaticResource FadeAway}"/>
</Core:EventTriggerBehavior>
</Interactivity:Interaction.Behaviors>
</TextBox>
I suppose you can have your "pure" MVVM approach using a behavior. It gets you 100% XAML, and that makes some developers feel warm and fuzzy; I get that. And, I like behaviors. Look. I don't want to argue with you here, it's just that the top approach is certainly not "wrong".
Learn more about behaviors: http://blog.jerrynixon.com/2013/10/everything-i-know-about-behaviors-in.html
Best of luck.

Related

MVVM Navigation Switching ViewModels causes an error with animations

I am trying to make a WPF application using the MVVM pattern.
I now want to be able to navigate between ViewModels, and for that I've used this article: https://rachel53461.wordpress.com/2011/12/18/navigation-with-mvvm-2/
Basically, the Main Window Resources contain DataTemplates for each View Model linking it to it's view, and a Content Control bound to CurrentPageViewModel like this:
<Window.Resources>
<DataTemplate DataType="{x:Type ViewModels:HomeViewModel}">
<Views:HomeView />
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModels:VaultViewModel}">
<Views:VaultView />
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModels:VaultsViewModel}">
<Views:VaultsView />
</DataTemplate>
</Window.Resources>
<ContentControl Content="{Binding CurrentPageViewModel}" />
In the constructor of the Main Window I set CurrentPageViewModel to a new instance of my home screen view model and when I want to navigate, all I need to do is change CurrentPageViewModel and WPF will do the rest because of my use of INotifyPropertyChanged.
I am sending Change Page requests to the main window from my View Models by copying the code from MVVM Light's messenger implementing the Mediator pattern, and this works fine, except when I have Storyboards playing.
I have two very nice buttons inside of my home page, which will grow a bit when you hover over them:
<Button Style="{DynamicResource MaterialButton}" Width="400px" Height="400px" Margin="15px"
FontSize="24" FontFamily="Roboto" FontWeight="Bold"
Effect="{DynamicResource HomeButtonDropShadowEffect}" Command="{Binding DisplayVaultsView}"
Content="Vaults">
<!--#region button effects -->
<Button.RenderTransform>
<ScaleTransform x:Name="scaleTransform" CenterX="200" CenterY="200" ScaleX="1.0" ScaleY="1.0"/>
</Button.RenderTransform>
<Button.Triggers>
<EventTrigger RoutedEvent="Button.MouseEnter">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="scaleTransform"
Storyboard.TargetProperty="ScaleX"
To="1.08" Duration="0:0:0.1"/>
<DoubleAnimation
Storyboard.TargetName="scaleTransform"
Storyboard.TargetProperty="ScaleY"
To="1.08" Duration="0:0:0.1"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<EventTrigger RoutedEvent="Button.MouseLeave">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="scaleTransform"
Storyboard.TargetProperty="ScaleX"
To="1.0" Duration="0:0:0.1" FillBehavior="Stop"/>
<DoubleAnimation
Storyboard.TargetName="scaleTransform"
Storyboard.TargetProperty="ScaleY"
To="1.0" Duration="0:0:0.1" FillBehavior="Stop"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Button.Triggers>
<!--#endregion-->
</Button>
As you can see, the button's Command is set to DisplayVaultsView, which is a method inside of the HomeViewModel which sends the Change Page request to the Main Window.
Without these storyboards inside the button, this works just fine and the new page is displayed. However, with the storyboards, the following error is thrown once I click the button:
System.InvalidOperationException: ''scaleTransform' name cannot be found in the name scope of 'System.Windows.Controls.Button'.'
This has led me to believe that the animation is still attempting to play, but since the Button is not in the current context anymore it can't find the property it's supposed to animate.
It's also important to note that the HomeViewModel is not thrown away when you navigate to another page, I've written my own Navigator class which keeps track of the ViewModels so you can navigate forwards/backwards without losing any changes (and without having to reload everything).
I'd like to know how I can solve this error, but it has also led me to the realization that I might need to "unload" ViewModels when switching to another one. I'm currently just keeping all ViewModel references inside of a List inside the Navigator class. So my other question is, should I "unload" the ViewModels when switching to another, and how?
I found a solution which works, but I'm not sure as to why it worked.
I removed all of the XAML concerned with the animations, and moved the animating to the code-behind in HomeView.xaml.
Here's the code:
public partial class HomeView : UserControl
{
private Storyboard _mouseEnterStoryboard;
private Storyboard _mouseLeaveStoryboard;
private DoubleAnimation _scaleX_In;
private DoubleAnimation _scaleY_In;
private DoubleAnimation _scaleX_Out;
private DoubleAnimation _scaleY_Out;
public HomeView()
{
InitializeComponent();
_mouseEnterStoryboard = new Storyboard();
ScaleTransform scaleVaultsButton = new ScaleTransform(1.0, 1.0);
ScaleTransform scaleFilesButton = new ScaleTransform(1.0, 1.0);
VaultsButton.RenderTransformOrigin = new Point(0.5, 0.5);
VaultsButton.RenderTransform = scaleVaultsButton;
FilesButton.RenderTransformOrigin = new Point(0.5, 0.5);
FilesButton.RenderTransform = scaleFilesButton;
_scaleX_In = new DoubleAnimation()
{
Duration = TimeSpan.FromMilliseconds(100),
From = 1.0,
To = 1.08
};
_scaleY_In = new DoubleAnimation()
{
Duration = TimeSpan.FromMilliseconds(100),
From = 1.0,
To = 1.08
};
_mouseEnterStoryboard.Children.Add(_scaleX_In);
_mouseEnterStoryboard.Children.Add(_scaleY_In);
Storyboard.SetTargetProperty(_scaleX_In, new PropertyPath("RenderTransform.ScaleX"));
Storyboard.SetTargetProperty(_scaleY_In, new PropertyPath("RenderTransform.ScaleY"));
_mouseLeaveStoryboard = new Storyboard();
_scaleX_Out = new DoubleAnimation()
{
Duration = TimeSpan.FromMilliseconds(100),
From = 1.08,
To = 1.0
};
_scaleY_Out = new DoubleAnimation()
{
Duration = TimeSpan.FromMilliseconds(100),
From = 1.08,
To = 1.0
};
_mouseLeaveStoryboard.Children.Add(_scaleX_Out);
_mouseLeaveStoryboard.Children.Add(_scaleY_Out);
Storyboard.SetTargetProperty(_scaleX_Out, new PropertyPath("RenderTransform.ScaleX"));
Storyboard.SetTargetProperty(_scaleY_Out, new PropertyPath("RenderTransform.ScaleY"));
VaultsButton.MouseEnter += new MouseEventHandler(Button_MouseEnter);
VaultsButton.MouseLeave += new MouseEventHandler(Button_MouseLeave);
FilesButton.MouseEnter += new MouseEventHandler(Button_MouseEnter);
FilesButton.MouseLeave += new MouseEventHandler(Button_MouseLeave);
}
public void Button_MouseEnter(object sender, MouseEventArgs e)
{
Storyboard.SetTarget(_scaleX_In, (Button)sender);
Storyboard.SetTarget(_scaleY_In, (Button)sender);
_mouseEnterStoryboard.Begin();
}
public void Button_MouseLeave(object sender, MouseEventArgs e)
{
Storyboard.SetTarget(_scaleX_Out, (Button)sender);
Storyboard.SetTarget(_scaleY_Out, (Button)sender);
_mouseLeaveStoryboard.Begin();
}
}
I'm not sure as to why this does work and the XAML version doesn't, so I'd still like an answer to that question.

fade in out UWP with asynch method to populate the Xaml control

I am missing something there can anyone help?
In myFlipview I am trying to display messages and I would like to make invisible the moment the flipview goes to his next item
<FlipView x:Name="fvWelcome" VerticalAlignment="Center" >
<FlipView.Triggers>
<EventTrigger>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="fvWelcome"
Storyboard.TargetProperty="(FlipView.Opacity)"
AutoReverse="False"
From="0" To="1" Duration="0:0:4"
RepeatBehavior="Forever" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</FlipView.Triggers>
<FlipView.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</FlipView.ItemsPanel>
<FlipView.ItemTemplate >
<DataTemplate>
<Grid>
<TextBox x:Name="GuestNameTextBox"
Grid.Row="1"
Margin="252,0,0,0"
Foreground="White"
FontFamily="Segoe UI"
BorderBrush="#FF1F4E79"
BorderThickness="0" Text="{Binding}"
VerticalAlignment="Center" FontSize="84"
TextWrapping="Wrap" AcceptsReturn="True"
Background="#FF1F4E79"
>
<TextBox.Triggers>
<EventTrigger>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="GuestNameTextBox"
Storyboard.TargetProperty="(TextBox.Opacity)"
AutoReverse="False"
From="0" To="1" Duration="0:0:4"
RepeatBehavior="Forever" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</TextBox.Triggers>
</TextBox>
</Grid>
</DataTemplate>
</FlipView.ItemTemplate>
</FlipView>
in My mainPage c# side I've got this
public sealed partial class MainPage : Page
{
private DispatcherTimer _welcomeTimer;
public MainPage()
{
this.InitializeComponent();
StartWelcomeGuest();
}
private void StartWelcomeGuest()
{
List<string> myList = new List<string>();
myList.Add("Un");
myList.Add("deux");
myList.Add("trois");
myList.Add("quatre");
myList.Add("cinq");
myList.Add("six");
myList.Add("sept");
fvWelcome.ItemsSource = myList;
fvWelcome.SelectedIndex = 0;
_welcomeTimer = new DispatcherTimer()
{
Interval = TimeSpan.FromSeconds(4)
};
_welcomeTimer.Tick += __welcomeTimer_Tick;
_welcomeTimer.Start();
}
private void __welcomeTimer_Tick(object sender, object e)
{
if (fvWelcome.SelectedIndex < fvWelcome.Items.Count - 1)
{
fvWelcome.SelectedIndex++;
}
else
{
//_welcomeFade.Stop();
_welcomeTimer.Stop();
}
}
}
I've got the fade in /out effect synchronized with the visbility of the fliView and the textbox how can I do the same with an async method to populate my FlipView
private async Task<List<string>> GuestGreetings()
{
IList<ServiceNameGuest.MyObject> myvalue = await ServiceNameGuest.GetBriefingsByDateAndCenter
(new DateTime(2016, 03, 14), new DateTime(2016, 03, 18), "Brussels");
foreach (var item in myvalue)
{
ListName.Add(item.BriefingTitle);
}
ListName = ListName.Distinct().ToList();
if (App.AddedGuest.Count>0)
{
foreach (var item in App.AddedGuest)
{
if(item!="")
ListName.Add( item);
};
}
return ListName;
}
private async void UserControl_Loaded(object sender, RoutedEventArgs e)
{
try
{
fvWelcome.ItemsSource = await GuestGreetings();
}
catch (Exception ex)
{
throw ex;
}
this.StartWelcomeGuest();
}
just because to populate my flipview I used an asnych method ,I am losing my fade in/out effect, I thought I could store my data in DB but there must be an easier way do you have an idea
I used a static variable to store my guestname instead of db as I thaught initially
eventually I understood the problem, as I was dealing with seconds I had to synchronize with my storyboard,
*give a name to the storyboard :FlpVOpacity
<FlipView x:Name="fvWelcome" VerticalAlignment="Center" Background="#FF1F4E79" >
<FlipView.Triggers>
<!-- Theme animations like this can use the default behavior like
this example or you could use properties like BeginTime and
Duration to tweak when and how quickly the animation occures.
If you want more control over a fade animation (e.g. just partial
fade), you will need to use DoubleAnimation to animate the Opacity
property of the element you want to fade in or out. -->
<EventTrigger>
<BeginStoryboard>
<Storyboard x:Name="FlpVOpacity">
<DoubleAnimation
Storyboard.TargetName="fvWelcome"
Storyboard.TargetProperty="(FlipView.Opacity)"
AutoReverse="True"
From="0" To="1" Duration="0:0:4"
RepeatBehavior="1x" />
</Storyboard> </BeginStoryboard>
</EventTrigger>
</FlipView.Triggers>
then just where I am going to the next item in my flipView,i just used seek to my storyboard FlpVOpacity.Seek(TimeSpan.Zero);
private void __welcomeTimer_Tick(object sender, object e)
{
if (fvWelcome.SelectedIndex < fvWelcome.Items.Count - 1)
{
fvWelcome.SelectedIndex++;
FlpVOpacity.Seek(TimeSpan.Zero);
}
else
{
//_welcomeFade.Stop();
_welcomeTimer.Stop();
StartVideos();
}
}

Repeat storyboard transitions definite number of times

I am trying to implement an opacity transition to a Button and TextBlock element in Windows Phone 8.
These are the Button and TextBlock element I'm transitioning:
<Button
x:Name="letterHole"
Width="230"
Height="387"
Opacity="0"
BorderThickness="0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Click="letterHole_Click">
<Button.Background>
<ImageBrush ImageSource="/Assets/Main/letterHoleBackground#2x.png"/>
</Button.Background>
</Button>
<TextBlock
Name="letter"
Text=""
FontSize="60"
TextAlignment="Center"
Foreground="Blue"
Margin="378,268,376,109"/>
The Button has a background image and the TextBlock is positioned on top of it.
These are the Storyboard transitions I'm using:
<Storyboard x:Name="fadeInAnimation">
<DoubleAnimation
Storyboard.TargetName="letterHole"
Storyboard.TargetProperty="Opacity"
From="0.0" To="1.0" Duration="0:0:1"/>
</Storyboard>
<Storyboard x:Name="wait">
<DoubleAnimation
Storyboard.TargetName="letterHole"
Storyboard.TargetProperty="Opacity"
From="1.0" To="1.0" Duration="0:0:2"/>
</Storyboard>
<Storyboard x:Name="fadeOutAnimation">
<DoubleAnimation
Storyboard.TargetName="letterHole"
Storyboard.TargetProperty="Opacity"
From="1.0" To="0.0" Duration="0:0:1"/>
</Storyboard>
I run them in the following order: fadeInAnimation, wait, fadeOutAnimation, the result being that the element fades in, waits 2 seconds and then fades out.
I'd like to have this animation sequence repeated definite number of times, - right now 6 - and every time it's repeated change the TextBlock's Text property.
This is the C# code I'm using:
public ConstructorOfClass()
{
InitializeComponent();
string[] letters = {"A", "B", "C", "D", "E", "F"};
int i = 0;
int show = 6;
animate();
}
private void animate()
{
if(show > 0)
{
fadeInAnimation.Begin();
fadeInAnimation.Completed += fadeInAnimation_Completed;
}
}
private void fadeInAnimation_Completed(object sender, EventArgs e)
{
letter.Text = letters[i];
wait.Begin();
wait.Completed += wait_Completed;
}
private void wait_Completed(object sender, EventArgs e)
{
fadeOutAnimation.Begin();
fadeOutAnimation.Completed +=fadeOutAnimation_Completed;
}
private void fadeOutAnimation_Completed(object sender, EventArgs e)
{
show--;
i++;
animate();
}
I've added some variables:
int show - number of repetions
int i - index of the letters[] array
At every repetion a new letter has to be shown.
The problem with this is that i and show don't increment the way I planned it, so after 2-3 repetions show gets down to 0.
Also, after trying to debug the problem I've noticed that in the method fadeOutAnimation_Completed() sometimes the animate() method isn't getting called, resulting that for some reason i and show gets incremented 2 or 3 times again.
What am I missing?
Every time wait is completed you do
fadeOutAnimation.Completed += fadeOutAnimation_Completed;
which means that fadeOutAnimation_Completed will be called once during 1st run, twice during 2nd run and 3 times during 3rd run so if you start with show = 6 after 1st run it will be 5, 2nd run 3 and 3rd will be 0. I think that what you do in 3 storyboards you can do in 2 if you use BeginTime on the second one:
<Storyboard x:Name="fadeInAnimation" Completed="fadeInAnimation_Completed">
<DoubleAnimation
From="0"
To="1"
Storyboard.TargetProperty="Opacity"
Storyboard.TargetName="letterHole"
Duration="0:0:1"/>
</Storyboard>
<Storyboard x:Name="fadeOutAnimation" Completed="fadeOutAnimation_Completed">
<DoubleAnimation
From="1"
To="0"
Storyboard.TargetProperty="Opacity"
Storyboard.TargetName="letterHole"
Duration="0:0:1"
BeginTime="0:0:3"/>
</Storyboard>
The problem with your code is that you subscribe to the Completed event of the Storyboards multiple times (each time a Completed event executes, you resubscribe to the Completed event of the next storyboard, which means that next time it executes it one more time).
You should simply subscribe to the Completed events from the XAML:
<Storyboard x:Name="fadeInAnimation" Completed="fadeInAnimation_Completed">
<DoubleAnimation
Storyboard.TargetName="letterHole"
Storyboard.TargetProperty="Opacity"
From="0.0" To="1.0" Duration="0:0:1"/>
</Storyboard>
<Storyboard x:Name="wait" Completed="wait_Completed">
<DoubleAnimation
Storyboard.TargetName="letterHole"
Storyboard.TargetProperty="Opacity"
From="1.0" To="1.0" Duration="0:0:2"/>
</Storyboard>
<Storyboard x:Name="fadeOutAnimation" Completed="fadeOutAnimation_Completed">
<DoubleAnimation
Storyboard.TargetName="letterHole"
Storyboard.TargetProperty="Opacity"
From="1.0" To="0.0" Duration="0:0:1"/>
</Storyboard>
And then in your code-behind (I just removed the event subscriptions):
private void animate()
{
if (show > 0)
{
fadeInAnimation.Begin();
}
}
private void fadeInAnimation_Completed(object sender, EventArgs e)
{
letter.Text = letters[i];
wait.Begin();
}
private void wait_Completed(object sender, EventArgs e)
{
fadeOutAnimation.Begin();
}
private void fadeOutAnimation_Completed(object sender, EventArgs e)
{
show--;
i++;
animate();
}

Trigger ExitAction on IsVisible equals true [duplicate]

How would I make a control fade in/out when it becomes Visible.
Below is my failed attempt:
<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>
I don't know how to do both animations (fade in and fade out) in pure XAML. But simple fade out can be achieved relatively simple. Replace DataTriggers with Triggers, and remove ExitActions since they makes no sense in Fade out scenario. This is what you will have:
<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>
But hey, don't give up. If you want to support both animations I can suggest small coding behind the XAML. After we do a trick, we will get what you want by adding one line of code in XAML:
<Button Content="Fading button"
x:Name="btn"
loc:VisibilityAnimation.IsActive="True"/>
Every time we change btn.Visibility from Visible to Hidden/Collapsed button will fade out. And every time we change Visibility back the button will fade in. This trick will work with any FrameworkElement (including ListView :) ).
Here is the code of VisibilityAnimation.IsActive attached property:
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;
}
}
The most important thing here is CoerceVisibility() method. As you can see we do not allow changing this property until fading animation is completed.
This code is neither thread safe nor bug free. Its only intention is to show the direction :). So feel free to improve, edit and get reputation ;).
You can't directly use the Visibility property for a fade out because setting a trigger on it will first Hide/Collapse the control, THEN animate it. So basically you'll got an animation on a collapsed control => nothing.
One "reliable" way would be to introduce a new Dependency Property (attached or not), say IsOpen and setting a property trigger IsOpen=True on it with:
EnterAction:
Make sure Visibility is set to Visible
Fade in the Opacity from 0 to 1
ExitAction:
Visibility set to Visible at keyframe 0 and Collapsed/Hidden at the last Keyframe
Fade out the Opacity from 1 to 0.
Here's an example:
<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>
I've been coming at this a slightly different way - I've got an extended version of Ray's answer to this question which adds a FadeIn() and FadeOut() extension method to everything that collapses or shows the element as appropriate, then instead of making objects visible I can just call FadeIn() and FadeOut() on them - and it will work an any element without any specific animation code.
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);
}
I realize this Question is a bit old, but I have only read it now and I have tweaked the code given by Anvaka. It supports binding to Visibility (only when binding mode is set to TwoWay). It also supports 2 different duration values for FadeIn and FadeOut.
Here is the class:
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));
}
}
This is best done using a behavior
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;
}
}
}
This is specifically being applied to a border - I haven't tried a user control but I expect the same applies.
To use it, you need the Blend Interactivity namespace:
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
And use this markup on the Border that you want the behavior on:
<i:Interaction.Behaviors>
<Interactivity:AnimatedVisibilityFadeBehavior AnimationDuration="0:0:0.3" InitialState="Collapsed" />
</i:Interaction.Behaviors>
You'll need to add in the namespace for the behavior class too..
Quite old now, but could you not just chain the 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>
You may want to try AutoReverse property ... though I am not sure if it works the way you want it to.
This is what I found on MSDN :
When a timeline's AutoReverse property is set to true and its RepeatBehavior property causes it to repeat, each forward iteration is followed by a backward iteration. This makes one repetition. For example, a timeline with an AutoReverse value of true with an iteration Count of 2 would play forward once, then backwards, then forwards again, and then backwards again.
I prefer Nock's XAML-only solution. Combined with the Mike Nakis' comment it gave me the perfect solution. I myself was struggling a bit with the DataTrigger. That's why I want to share it here:
<TextBlock x:Name="Text1" Text="Hello World!" FontSize="30" d:Visibility="Visible">
<TextBlock.Style>
<Style>
<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>
<DataTrigger Binding="{Binding IsOpen}" Value="true">
<DataTrigger.EnterActions>
<BeginStoryboard Storyboard="{StaticResource FadeInMenu}"/>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard Storyboard="{StaticResource FadeOutMenu}"/>
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
<Setter Property="TextBlock.Visibility" Value="Collapsed" />
</Style>
</TextBlock.Style>
</TextBlock>
Also do not forget to implement INotifyPropertyChanged for the Property IsOpen in code behind.

Fading out a wpf window on close

I want to fade a window in/out in my application.
Fading in occurs on Window.Loaded and I wanted to fade out on close (Window.Closed or Window.Closing).
Fading in works perfectly, but Window.Closing is not allowed value for RoutedEvent property.
What RoutedEvent should I be using for Close?
<Window.Triggers>
<EventTrigger RoutedEvent="Window.Loaded">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Opacity" From="0" To="1" Duration="0:0:2" FillBehavior="HoldEnd" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<EventTrigger RoutedEvent="Window.Closing">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Opacity" From="1" To="0" Duration="0:0:2" FillBehavior="HoldEnd" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Window.Triggers>
I get a error on , Value 'Window.Closing' cannot be assigned to property 'RoutedEvent'. Invalid event name.
Closing is not a routed event, so you can't use it in an EventTrigger. Perhaps you could start the storyboard in the handler of the ClosingEvent in the code-behind and cancel the event... something like that :
private bool closeStoryBoardCompleted = false;
private void Window_Closing(object sender, CancelEventArgs e)
{
if (!closeStoryBoardCompleted)
{
closeStoryBoard.Begin();
e.Cancel = true;
}
}
private void closeStoryBoard_Completed(object sender, EventArgs e)
{
closeStoryBoardCompleted = true;
this.Close();
}
I thought I'd add another solution of doing this, using behaviors from the Expression SDK and combining it with the solution from #Thomas. Using that, we can define a "CloseBehavior" that handles the code behind of starting a storyboard and closing the window when it's done.
using System.ComponentModel;
using System.Windows;
using System.Windows.Interactivity;
using System.Windows.Media.Animation;
namespace Presentation.Behaviours {
public class CloseBehavior : Behavior<Window> {
public static readonly DependencyProperty StoryboardProperty =
DependencyProperty.Register("Storyboard", typeof(Storyboard), typeof(CloseBehavior), new PropertyMetadata(default(Storyboard)));
public Storyboard Storyboard {
get { return (Storyboard)GetValue(StoryboardProperty); }
set { SetValue(StoryboardProperty, value); }
}
protected override void OnAttached() {
base.OnAttached();
AssociatedObject.Closing += onWindowClosing;
}
private void onWindowClosing(object sender, CancelEventArgs e) {
if (Storyboard == null) {
return;
}
e.Cancel = true;
AssociatedObject.Closing -= onWindowClosing;
Storyboard.Completed += (o, a) => AssociatedObject.Close();
Storyboard.Begin(AssociatedObject);
}
}
}
The behavior defines a storyboard as a dependency property, so we can set it in xaml and when the AssociatedObject (the window where we define the behavior) is closing, this storyboard is started using Storyboard.Begin(). Now, in xaml we simply add the behavior to the window using the following xaml
<Window x:Class="Presentation.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:behave="clr-namespace:Presentation.Behaviours"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
x:Name="window">
<Window.Resources>
<Storyboard x:Key="ExitAnimation">
<DoubleAnimation Storyboard.Target="{Binding ElementName='window'}"
Storyboard.TargetProperty="(Window.Opacity)"
Duration="0:0:1" From="1" To="0"/>
</Storyboard>
</Window.Resources>
<i:Interaction.Behaviors>
<behave:CloseBehavior Storyboard="{StaticResource ExitAnimation}"/>
</i:Interaction.Behaviors>
<Grid>
</Grid>
</Window>
Note the xml namespace i from the System.Windows.Interactivity dll, and also that the window is referenced, so it has to have a x:Name assigned. Now we simply add the behavior to every window on which we wish to execute a storyboard before closing the application, instead of copying the logic to every code-behind in each window.
I'm not an expert on WPF but I believe that unless you cancel the initial Closing event the window will be gone before the animation is even started.
Upon receiving the Window.Closing event, you should cancel the event and start the animation. When the animation is done you can close the window.
This is even simpler and shorter. Add a behavior as follows:
public class WindowClosingBehavior : Behavior<Window>
{
protected override void OnAttached()
{
AssociatedObject.Closing += AssociatedObject_Closing;
}
private void AssociatedObject_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
Window window = sender as Window;
window.Closing -= AssociatedObject_Closing;
e.Cancel = true;
var anim = new DoubleAnimation(0, (Duration)TimeSpan.FromSeconds(0.5));
anim.Completed += (s, _) => window.Close();
window.BeginAnimation(UIElement.OpacityProperty, anim);
}
protected override void OnDetaching()
{
AssociatedObject.Closing -= AssociatedObject_Closing;
}
}
Then in your window add a reference :
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:wt="clr-namespace:Desktop.Themes.WindowTask;assembly=Desktop.Themes"
Insert the behavior:
<i:Interaction.Behaviors>
<wt:WindowClosingBehavior />
</i:Interaction.Behaviors>
Set AutoReverse To "True"
<Window.Triggers>
<EventTrigger RoutedEvent="Window.Loaded">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Opacity" AutoReverse="True" From="0" To="1" Duration="0:0:0.5" FillBehavior="HoldEnd" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Window.Triggers>

Categories