WPF only drawing 4 images (with C# & XAML) - c#

I'm trying to customize a digital photo frame program template I found on MSDN but I found that it only shows 4 images at the max. If I add a 5th image to the ArrayList, the entire screen goes blank.
Does anyone know what's going on and why any images after the 4th added to the ArrayList will cause the image to disappear and window to blank out?
[post edit: included full source]
C# source:
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Media.Animation;
using System.Windows.Media.Imaging;
namespace CSWPFAnimatedImage
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
int nextImageIndex;
List<BitmapImage> images = new List<BitmapImage>();
private void Window_Loaded(object sender, RoutedEventArgs e)
{
// Initialize the images collection
images.Add(new BitmapImage(new Uri("Images/image1.jpg", UriKind.Relative)));
images.Add(new BitmapImage(new Uri("Images/image2.jpg", UriKind.Relative)));
images.Add(new BitmapImage(new Uri("Images/image3.jpg", UriKind.Relative)));
images.Add(new BitmapImage(new Uri("Images/image4.jpg", UriKind.Relative)));
nextImageIndex = 2;
}
private void VisbleToInvisible_Completed(object sender, EventArgs e)
{
// Change the source of the myImage1 to the next image to be shown
// and increase the nextImageIndex
this.myImage1.Source = images[nextImageIndex++];
// If the nextImageIndex exceeds the top bound of the collection,
// get it to 0 so as to show the first image next time
if (nextImageIndex == images.Count)
{
nextImageIndex = 0;
}
// Get the InvisibleToVisible storyboard and start it
Storyboard sb = this.FindResource("InvisibleToVisible") as Storyboard;
sb.Begin(this);
}
private void InvisibleToVisible_Completed(object sender, EventArgs e)
{
// Change the source of the myImage2 to the next image to be shown
// and increase the nextImageIndex
this.myImage2.Source = images[nextImageIndex++];
// If the nextImageIndex exceeds the top bound of the collection,
// get it to 0 so as to show the first image next time
if (nextImageIndex == images.Count)
{
nextImageIndex = 0;
}
// Get the VisibleToInvisible storyboard and start it
Storyboard sb = this.FindResource("VisibleToInvisible") as Storyboard;
sb.Begin(this);
}
}
}
XAML:
<Window x:Class="CSWPFAnimatedImage.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="WPF Animated Image Sample" Height="300" Width="300" Loaded="Window_Loaded">
<Window.Resources>
<Storyboard x:Key="VisibleToInvisible" Completed="VisbleToInvisible_Completed" >
<DoubleAnimation Storyboard.TargetName="TransparentStop"
Storyboard.TargetProperty="Offset" To="0" Duration="0:0:2" />
<DoubleAnimation Storyboard.TargetName="BlackStop"
Storyboard.TargetProperty="Offset" To="0" Duration="0:0:2"
/>
</Storyboard>
<Storyboard x:Key="InvisibleToVisible" Completed="InvisibleToVisible_Completed">
<DoubleAnimation Storyboard.TargetName="TransparentStop"
Storyboard.TargetProperty="Offset" To="1" Duration="0:0:2" />
<DoubleAnimation Storyboard.TargetName="BlackStop"
Storyboard.TargetProperty="Offset" To="1" Duration="0:0:2" />
</Storyboard>
</Window.Resources>
<Window.Triggers>
<EventTrigger RoutedEvent="Window.Loaded">
<EventTrigger.Actions>
<BeginStoryboard Storyboard="{StaticResource VisibleToInvisible}"/>
</EventTrigger.Actions>
</EventTrigger>
</Window.Triggers>
<Grid Name="grid">
<Image x:Name="myImage2" Source="Images/image2.jpg" />
<Image x:Name="myImage1" Source="Images/image1.jpg">
<Image.OpacityMask>
<LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
<GradientStop Offset="1" Color="Black" x:Name="BlackStop"/>
<GradientStop Offset="1" Color="Transparent" x:Name="TransparentStop"/>
</LinearGradientBrush>
</Image.OpacityMask>
</Image>
</Grid> </Window>

here this works
images.Add(new BitmapImage(new Uri("Images/image1.jpg", UriKind.Relative)));
and this doesn't
images.Add(new BitmapImage(new Uri("D:\\image2jpg", UriKind.Relative)));
so. change it to
images.Add(new BitmapImage(new Uri("D:\\image2jpg", UriKind.Absolute)));
It works when you copy an image from somewhere and paste it to the Images folder of the project and add it to the image list.

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.

C# WPF Make a Button Randomly Move When Moused Over

I'm making a WPF application using C# and Visual Studio and I need a button to jump to a random location in the window when the mouse hovers over it. I then need the same thing to happen when you hover over it again. I also don't need an animation, just for it to jump there.
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void butNo_MouseEnter(object sender, MouseEventArgs e)
{
}
}
I tried this but it didn't work and is also an animation:
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation From="0" To="100" Duration="0:0:2" Storyboard.TargetProperty="(Canvas.Left)" AutoReverse="False" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Button.Triggers>
Any help would be much appreciated, thanks.
Edit:
Tried this but 'Location' comes with error code CS1061 in VS.
private void butNo_MouseEnter(object sender, MouseEventArgs e)
{
Random x = new Random();
Point pt = new Point(int.Parse(x.Next(200).ToString()), int.Parse(x.Next(250).ToString()));
butNo.Location = pt;
}
Here's a solution using a Canvas and animations.
<Canvas Name="cnv">
<Button Name="btn"
Content="Click Me!"
IsTabStop="False"
Canvas.Left="0"
Canvas.Top="0"
MouseEnter="Button_MouseEnter"/>
</Canvas>
And the code-behind:
Random rnd = new Random();
private void Button_MouseEnter(object sender, MouseEventArgs e)
{
//Work out where the button is going to move to.
double newLeft = rnd.Next(Convert.ToInt32(cnv.ActualWidth - btn.ActualWidth));
double newTop = rnd.Next(Convert.ToInt32(cnv.ActualHeight - btn.ActualHeight));
//Create the animations for left and top
DoubleAnimation animLeft = new DoubleAnimation(Canvas.GetLeft(btn), newLeft, new Duration(TimeSpan.FromSeconds(1)));
DoubleAnimation animTop = new DoubleAnimation(Canvas.GetTop(btn), newTop, new Duration(TimeSpan.FromSeconds(1)));
//Set an easing function so the button will quickly move away, then slow down
//as it reaches its destination.
animLeft.EasingFunction = new CubicEase() { EasingMode = EasingMode.EaseOut };
animTop.EasingFunction = new CubicEase() { EasingMode = EasingMode.EaseOut };
//Start the animation.
btn.BeginAnimation(Canvas.LeftProperty, animLeft, HandoffBehavior.SnapshotAndReplace);
btn.BeginAnimation(Canvas.TopProperty, animTop, HandoffBehavior.SnapshotAndReplace);
}
And here is a gif of it in action.
I really don't normally like doing homework problems for people, but I'm bored at work so here you go.
You need to just use the Margin and update the Left and Top values to random numbers within the height/width of your grid so it doesn't go outside of the view.
Also, make sure to use IsTabStop="False" otherwise, you could tab to it and still click it.
So, for the simplest case using codebehind:
XAML:
<Window x:Class="mousemove.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid x:Name="Grid1">
<Button Content="Button" Height="23" HorizontalAlignment="Left" Margin="0,0,0,0" Name="button1" Width="75" MouseEnter="button1_MouseEnter" IsTabStop="False"/>
</Grid>
MainWindow.xaml.cs
private void button1_MouseEnter(object sender, MouseEventArgs e)
{
int maxLeft = Convert.ToInt32(Grid1.ActualWidth - button1.Width);
int maxTop = Convert.ToInt32(Grid1.ActualHeight - button1.Height);
Random rand = new Random();
button1.Margin = new Thickness(rand.Next(maxLeft), rand.Next(maxTop), 0, 0);
}

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();
}
}

Sliding Child WIndow not working in wpf

I used translatetransform to slide my child window but I think there's something wrong with my xaml code. It's not the window is sliding but inside the window is sliding (or the grid).
This is my Child Window XAML:
<Window x:Class="SAMPLE.ChildWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
WindowStartupLocation="CenterOwner"
x:Name="HomeWindows"
Title="HomeWindow" Height="348" Width="440" Loaded="HomeWindows_Loaded">
<Window.RenderTransform>
<TranslateTransform />
</Window.RenderTransform>
<Window.Resources>
<Storyboard x:Key="SlaydAndFeyd" >
<DoubleAnimation Storyboard.TargetName="HomeWindows" Storyboard.TargetProperty="(Window.RenderTransform).(TranslateTransform.X)" From="50" To="0" Duration="0:0:0.4" />
</Storyboard>
</Window.Resources>
and then for my function, calling the storyboard:
public void SlaydAndFeyds()
{
(FindResource("SlaydAndFeyd") as Storyboard).Begin(this);
}
and now in the Main Window code:
namespace SAMPLE
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private ChildWindow NewChildWindow = new ChildWindow();
private void btnShowChild_Click(object sender, RoutedEventArgs e)
{
NewHomeWindow.Owner = this;
NewHomeWindow.Show();
}
private void btnSlideChild_Click(object sender, RoutedEventArgs e)
{
NewHomeWindow.SlaydAndFeyds();
}
or anyone have an idea how can I slide my child window?
The RenderTransform in that example will affect the contents of the window, not the window itself.
To move the window, adjust the Left/Top attributes using an EventTrigger. I'm taking a guess that you want to slide it left by 50 pix or so over 4 seconds, adjust the figures to suit:
<Window.Triggers>
<EventTrigger RoutedEvent="Window.Loaded">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation BeginTime="00:00:00"
Storyboard.TargetName="HomeWindows"
Storyboard.TargetProperty="(Window.Left)"
By="-12"
Duration="0:0:4" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Window.Triggers>

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