WPF DispatcherTimer on Modalless window - c#

I have a wpf progress window defined as following:
<Window x:Class="NeoinfoXmlEditor.WPF.Forms.ProgressDisplayForm"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="84" Width="505" x:Name="root" WindowStartupLocation="CenterScreen">
<Grid>
<ProgressBar Height="15" x:Name="MessageProgessBar" HorizontalAlignment="Stretch" VerticalAlignment="Top" Maximum="10000" Margin="10,2,10,2" >
<ProgressBar.Triggers>
<EventTrigger RoutedEvent="ProgressBar.Loaded">
<BeginStoryboard>
<Storyboard x:Name="sb">
<DoubleAnimation Storyboard.TargetName="MessageProgessBar"
Storyboard.TargetProperty="Value"
From="0" To="10000" Duration="0:0:45"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</ProgressBar.Triggers>
</ProgressBar>
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Bottom" Margin="5" Text="{Binding ElementName=root, Path=Message}" />
</Grid>
</Window>
And a code behind file as follows:
public partial class ProgressDisplayForm : Window
{
public static readonly DependencyProperty MessageProperty =
DependencyProperty.Register("Message", typeof (string), typeof (ProgressDisplayForm));
public string Message
{
get { return (string) GetValue(MessageProperty); }
set { SetValue(MessageProperty, value); }
}
public ProgressDisplayForm()
{
InitializeComponent();
}
public void DisplayWindow()
{
this.Show();
this.BeginStoryboard(sb);
}
}
You can see that I try to start a progressBar animation in two ways:
-using EventTrigger, on ProgressBar.Loaded
-from code behind, explicitely
The problem is - neither works.
Note - I need to open this window and start animation as modalless window, so ShowDialog() is not na option. Also, I tried using DispatcherTimer, but it somehow doesn't work, niether the this.Dispatcher.Invoke() while using System.Timers.Timer class.
I'm calling the DisplayWindow() method from the main app window.
What am I missing?
Thanks in advance

I couldn't reproduce your problem, your XAML animation is working just fine!, try to copy your XAML code to a new project without the code-behind. I tried that and worked just fine :D

I found out what the problem was - i called NewWindow.Show(), and then continued with some high CPU computing, assuming that the new window will be on separate thread if not called with ShowDialog().
I fixed it using BackgroundWorker!
Thanks for help anyways!

Related

FadeIn and FadeOut Theme Animation works only once and only one way,why?

Anyone can help me please?
What I'm trying to do is, make a random quote fading in and out on my textblock.
I'm trying to use something called "FadeInThemeAnimation".
The thing is that I don't know how to make it work endlessly ,so that my random quote appears and disappears after the time a set up. I've tested a dozen attributes in xaml to see what happens but it seems like I have no clue what to do. At the moment the quote appears in the textblock(without any fade in effect) and slowly fades out, that is it. Please , how do I make it work. I'm pasting in a chunk of xaml code I have got.
<TextBlock x:Name="FamousQuoteTextBlock" Grid.Row="4" HorizontalAlignment="Stretch" Margin="5,5,5,5"
FontSize="15" TextAlignment="Justify" TextWrapping="Wrap" VerticalAlignment="Stretch"
Loaded="FamousQuoteTextBlock_Loaded"
FontFamily="Edwardian Script ITC"
DataContext="{Binding quote}"
Text="{Binding RandomQuote,Mode=TwoWay}"
/>
<StackPanel>
<StackPanel.Resources>
<Storyboard x:Name="EnterStoryboard">
<FadeInThemeAnimation Storyboard.TargetName="FamousQuoteTextBlock"/>
</Storyboard>
<Storyboard x:Name="ExitStoryboard">
<FadeOutThemeAnimation Storyboard.TargetName="FamousQuoteTextBlock"/>
</Storyboard>
</StackPanel.Resources>
</StackPanel>
I have tried to use something like speedratio,duratio and so on to see what happens but it didn't make any difference:(
My C# code behind:
private void FamousQuoteTextBlock_Loaded(object sender, RoutedEventArgs e)
{
Delay(sender, e);
}
private async void Delay(object sender, RoutedEventArgs e)
{
await System.Threading.Tasks.Task.Delay(TimeSpan.FromSeconds(3));
generator_Tick(this, e);
DispatcherTimer generator = new DispatcherTimer();
generator.Tick += generator_Tick;
generator.Interval = TimeSpan.FromSeconds(5);
generator.Start();
}
private void generator_Tick(object sender, object e)
{
Quote quote = new Quote();
FamousQuoteTextBlock.DataContext = quote;
quote.GenerateQuote();
}
You can create your own animation like this :
<TextBlock x:Name="QuoteTextBlock" Text="My Quote here">
<TextBlock.Resources>
<Storyboard x:Key="FadeStoryboard">
<DoubleAnimation From="1" To="0.1" Storyboard.TargetProperty="Opacity"
Storyboard.TargetName="QuoteTextBlock"
RepeatBehavior="Forever" AutoReverse="True">
<DoubleAnimation.EasingFunction>
<CubicEase/>
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
</Storyboard>
</TextBlock.Resources>
</TextBlock>
And start it like this :
var storyboard = QuoteTextBlock.Resources["FadeStoryboard"] as Storyboard;
if (storyboard != null)
{
storyboard.Begin();
}
The small tricks you were looking for were AutoReverse which means that after the animation is performed, the reverse animation will follow up. If combined with RepeatBehaviour="Forever", it might achieve what you were looking for.
I've added an Ease. You can customize the easing function a lot. It means that the animation step won't be performed in a linear manner, but in this case will be cubic.
Some links which can provide you with more details on this matter :
MSDN : Quickstart - Animating your UI using XAML
MSDN : Storyboarded Animations

Storyboard inside a user control is not working

I am developing a Windows 8 Store App. I have created the following user control:
<UserControl
x:Class="RTV_W8.AnimatedImage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:RTV_W8"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400">
<UserControl.Resources>
<!-- Animates the rectangle's opacity. -->
<Storyboard x:Name="ContainerGridStoryboard">
<DoubleAnimation
Storyboard.TargetName="MainGrid"
Storyboard.TargetProperty="Opacity"
From="0" To="1" Duration="0:0:5"
AutoReverse="False">
</DoubleAnimation>
</Storyboard>
</UserControl.Resources>
<Grid x:Name="MainGrid" CacheMode="BitmapCache">
<Image Stretch="UniformToFill" x:Name="PictureImage"/>
</Grid>
</UserControl>
with the following cs file (only partially displayed here)
public AnimatedImage()
{
this.InitializeComponent();
this.Loaded += AnimatedImage_Loaded;
this.PictureImage.Loaded += PictureImage_Loaded;
}
void AnimatedImage_Loaded(object sender, RoutedEventArgs e)
{
this.ContainerGridStoryboard.Begin();
}
my problem is the storyboard is not launching when the image is loaded. This user control is used inside another usercontrol. I have tried multiple variants trying to make the animation to work, none of them worked. I want the user control to animate it's opacity into view, instead of just pooping up. Although i can see via breakpoints that the ContainerGridStoryboard.Begin(); is executed nothing happens on screen
EDIT: In another usercontrol witch is later used in a datatemplate, if i apply the storyboard on the main grid it works, the usercontrol gets animated. But if i apply it on the second grid (contained in the main grid) or any other element, the animation does not work. That is why i created animatedimage in the first place.
I have just solved the problem. So my usercontrol was inside a couple of grids, inside another usercontrol, inside a datatemplate inside a gridview. The problem was that the main grid witch contained the animatedimage was set with CacheMode="BitmapCache". When i removed that property the images faded in.
When I set the image's source, and embed the UserControl into a page, the image fades in as expected.

Handling a Button's Click event in XAML?

This feels like a terribly basic question but I am sure there is a better way to do this. I have a Button in my UI which selects a specific tab and fire a Command from the ViewModel
Here is the current code (which works fine):
XAML:
<Button Content="Button!" Click="OnButtonClick" Command="{Binding WhateverCommand}" />
Code behind:
private void OnButtonClick(object sender, RoutedEventArgs e)
{
theTab.IsSelected = true;
}
Isn't there any cleaner, XAML-only way to do that UI operation? I was thinking about something like:
<Button Content="Button!" Click="OnButtonClick" Command="{Binding WhateverCommand}">
<Button.Triggers>
<EventTrigger RoutedEvent="Click">
<Setter TargetName="theTab" Property="IsSelected" Value="True" />
</EventTrigger>
</Button.Trigger>
</Button>
But unfortunately it seems like the EventTrigger won't support a Click event. Why so? I am still sometimes confused with triggers after a few years working in WPF, and this pretty much sums it up. When trying to build that I have an error on the Setter line:
A value of type 'Setter' cannot be added to a collection or dictionary of type 'TriggerActionCollection'.
Thank you!
EDIT since I was ask the XAML structure of my Window, it looks like this:
<DockPanel LastChildFill="True">
<Ribbon DockPanel.Dock="Top">
<Button Content="Button!" Click="OnButtonClick" Command="{Binding WhateverCommand}" />
</Ribbon>
<TabControl>
<TabItem x:Name="theTab" />
</TabControl>
</DockPanel>
Error sums it up. You cannot use Setter in EventTrigger. If you want to do it in XAML you can use Storyboard that will select given tab when button is pressed. Something like this:
<Button Content="Click">
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<BeginStoryboard>
<Storyboard>
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="theTab" Storyboard.TargetProperty="IsSelected">
<DiscreteBooleanKeyFrame KeyTime="0:0:0" Value="True"/>
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Button.Triggers>
</Button>
There definitely is a better way. With the help of the Windows.Interactivity assembly you are able to bind the event source to a singe class, containing only the associated action. With this you can almost do everything you ned.
The action class has to derive from TriggerAction. By overriding the Invoke-method you can specify the action.
Despite this scenario it also possible to bind the EventTrigger to a command (e.g. relay command), allowing a clean MMVM implementation.
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
<Button x:Name="button">
<i:Interaction.Triggers>
<i:EventTrigger SourceName="button" EventName="Click">
<app:MyAction/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
Public Class MyAction
Inherits Interactivity.TriggerAction(Of UIElement)
Protected Overrides Sub Invoke(parameter As Object)
MsgBox("Clicked")
End Sub
End Class
I updated the code to meet your specific requirements. The TriggerAction class now also contains a dependency property, which can be cound to your tab control:
<TabControl x:Name="tab"/>
<Button x:Name="button">
<i:Interaction.Triggers>
<i:EventTrigger SourceName="button" EventName="Click">
<app:MyAction Target="{Binding ElementName=tab}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
Public Class MyAction
Inherits Interactivity.TriggerAction(Of UIElement)
Protected Overrides Sub Invoke(parameter As Object)
DirectCast(Target, TabControl).SelectedIndex = 0
End Sub
Shared Sub New()
_targetProperty = DependencyProperty.Register(
"Target",
GetType(UIElement),
GetType(MyAction),
New UIPropertyMetadata(Nothing))
End Sub
Private Shared _targetProperty As DependencyProperty
Public Shared ReadOnly Property TargetProperty As DependencyProperty
Get
Return _targetProperty
End Get
End Property
Property Target As UIElement
Get
Return DirectCast(GetValue(TargetProperty), UIElement)
End Get
Set(value As UIElement)
SetValue(TargetProperty, value)
End Set
End Property
End Class
You can introduce a bool property IsMyTabSelected to your VM, bind it to TabItem.IsSelected:
<TabItem x:Name="theTab" IsSelected="{Binding IsMyTabSelected}" >
Then you just set this flag in the WhateverCommand handler for the Button.
Note: The IsMyTabSelected property must implement System.ComponentModel.INotifyPropertyChanged.

Switching mediaElements videos

I am working on a video project. In that project there will be a video playing for the user, and if the user clicks a button- that video is changed to the next one. Point is, the next video will play from the point the previous one stopped in (So if the user presses the NEXT button at 00:00:30 the next video will play from that point).
The problem I am facing is that there are always a few moments of black screen until the next video will play, and I want the change to be smooth without the user watching a black screen for a second or two.
So far, I've tried to solve it with one mediaElement and with two mediaElements:
The single mediaElement change code:
TimeSpan Time = mediaElement1.Position;
mediaElement1.Source = new Uri("NEXT VIDEO LOCATION");
mediaElement1.Position = Time;
mediaElement1.Play();
Two mediaElements code:
TimeSpan Time = mediaElement1.Position;
mediaElement2.Source = new Uri("NEXT VIDEO LOCATION");
mediaElement2.Position = Time;
mediaElement1.Visibility = Visibility.Hidden;
mediaElement1.Source = null;
mediaElement2.Visibility = Visibility.Visible;
mediaElement2.Play();
At both tries there is no smooth switch between the videos. Thanks in advance for any light on that matter.
Sorry if I was not as clear as I would. In fact the MediaElement has the Position property but it is not a DependancyProperty and is not Bindable. There is also no way to retrieve the updated position with an event directly. So the way I used long time ago was the use of the MediaTimeLine in a StoryBoard. You should take a look on this post How to: Control a MediaElement by Using a Storyboard from Microsoft.
I used this way to seek in a video file using a MediaElement. The other way was the use of a third party library named WPFMediaKit which is very useful. You can find it there
WPF MediaKit - For webcam, DVD and custom video support in WPF
FIRST EXAMPLE : Using WPFMediaKit (no actual way to play MP4 files...)
The MediaUriElement in the WPF MediaKit allows you to bind its MediaPosition because it is a DependancyProperty. The MediaUriElement is a wrapper around the .Net MediaElement which is poor in functionnalities. So, refering to my first answer, I write a very simple POCO. You could write something like this :
MainWindow.xaml
<Window x:Class="Media.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:media="clr-namespace:WPFMediaKit.DirectShow.Controls;assembly=WPFMediaKit"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<Storyboard x:Key="SwitchToSecondPlayerStoryboard">
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)" Storyboard.TargetName="firstPlayer">
<DiscreteObjectKeyFrame KeyTime="0:0:0.7" Value="{x:Static Visibility.Collapsed}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)" Storyboard.TargetName="secondPlayer">
<DiscreteObjectKeyFrame KeyTime="0:0:0.7" Value="{x:Static Visibility.Visible}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</Window.Resources>
<Window.Triggers>
<EventTrigger RoutedEvent="ButtonBase.Click" SourceName="switchButton">
<BeginStoryboard Storyboard="{StaticResource SwitchToSecondPlayerStoryboard}"/>
</EventTrigger>
</Window.Triggers>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="60"/>
</Grid.RowDefinitions>
<media:MediaUriElement x:Name="firstPlayer" Source="firstFile.wmv" LoadedBehavior="Play"/>
<media:MediaUriElement x:Name="secondPlayer" Source="secondFile.wmv" MediaPosition="{Binding Path=MediaPosition, ElementName=firstPlayer}" Visibility="Collapsed" Volume="0"/>
<Button Grid.Row="1" x:Name="switchButton" Content="SWITCH NEXT FILE" HorizontalAlignment="Center" VerticalAlignment="Center" Click="OnSwitchButtonClick"/>
</Grid>
And here is the MainWindow.xaml.cs
namespace Media
{
/// <summary>
/// Logique d'interaction pour MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void OnSwitchButtonClick(object sender, RoutedEventArgs e)
{
//Do whatever you want with the players.
}
}
}
This program works and there is no "blank screen" effect. You just have to improve the Storyboard transition (example with a FadeIn effect), but this is the principle.
You would also note that the MediaPosition of the second is bound to the MediaPosition of the first one.
This implies to always check that the MediaPosition is lower than the Duration of the file to avoid errors, but this is just code stuff.
Note that I simplified my code avoiding the usage of MVVM and other architecture things in order to keep it clearer.
SECOND SOLUTION : Workaround using MediaElement
MainWindow.xaml
<Window x:Class="Media.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">
<Window.Resources>
<Storyboard x:Key="ShowSecond">
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)" Storyboard.TargetName="firstMediaElement">
<DiscreteObjectKeyFrame KeyTime="0:0:0.3" Value="{x:Static Visibility.Collapsed}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)" Storyboard.TargetName="secondMediaElement">
<DiscreteObjectKeyFrame KeyTime="0:0:0.3" Value="{x:Static Visibility.Visible}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</Window.Resources>
<Window.Triggers>
<EventTrigger RoutedEvent="ButtonBase.Click" SourceName="btnlaunchNext">
<BeginStoryboard Storyboard="{StaticResource ShowSecond}"/>
</EventTrigger>
</Window.Triggers>
<Grid>
<StackPanel Background="Black">
<MediaElement Name="firstMediaElement" LoadedBehavior="Manual" Source="firstFile.mp4" Width="260" Height="150" Stretch="Fill" />
<MediaElement Name="secondMediaElement" Volume="0" LoadedBehavior="Manual" Source="secondFile.mp4" Visibility="Collapsed" Width="260" Height="150" Stretch="Fill" />
<!-- Buttons to launch videos. -->
<Button x:Name="btnlaunch" Content="PLAY FIRST" Click="OnLaunchFirstButtonClick"/>
<Button x:Name="btnlaunchNext" Content="PLAY NEXT" Click="OnLaunchNextButtonClick"/>
</StackPanel>
</Grid>
</Window>
MainWindow.xaml.cs
using System;
using System.Timers;
using System.Windows;
namespace Media
{
/// <summary>
/// Interaction logic for pour MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public TimeSpan Position { get; set; }
Timer _updateTimer;
public MainWindow()
{
InitializeComponent();
//Timer interval fixed to 1 second to read the actual position of the first media element
this._updateTimer = new Timer(1000);
this._updateTimer.Elapsed += this.UpdateTimerElapsed;
}
//The callback of your update timer
private void UpdateTimerElapsed(object sender, ElapsedEventArgs e)
{
//I use the dispatcher because you call the Position from another thread so it has to be synchronized
Dispatcher.Invoke(new Action(() => this.Position = firstMediaElement.Position));
}
//When stopping the first, you start the second and set its Position
private void OnLaunchNextButtonClick(object sender, RoutedEventArgs e)
{
this.firstMediaElement.Stop();
this.secondMediaElement.Volume = 10;
this.secondMediaElement.Play();
this.secondMediaElement.Position = this.Position;
}
//When you start the first, you have to start the update timer
private void OnLaunchFirstButtonClick(object sender, RoutedEventArgs e)
{
this.firstMediaElement.Play();
this._updateTimer.Start();
}
}
}
These are the only simple solutions I can provide. There are different ways to do this, particularly using MediaTimeline, but the implementation is not as easy as it seems, regarding the problem you are facing. The second example, as the first is fully compiling and running and targets what you wanted to do I think.
Hope this helps. Feel free to give me your feedback.
Is there any way to preload the next video ? If it is the case, you could try to load both sources on each MediaElement and then bind the Position of the first to the second one while playing.
The second MediaElement is Collapsed by default (preferable from Hidden regarding performance when using heavy graphical elements) and will become visible and its Source is already loaded and its Position already bound on the first MediaElement position.
Hope this helps.

Starting a XAML animation in WPF

I'm using this as a basis to make an animation start using code behind. Based on the contents of the article, I have the following:
<Window.Resources>
<Storyboard x:Key="sbdLabelRotation">
<DoubleAnimation
Storyboard.TargetName="lblHello"
Storyboard.TargetProperty="(TextBlock.RenderTransform).(RotateTransform.Angle)"
From="0"
To="360"
Duration="0:0:0.5"
RepeatBehavior="4x" />
</Storyboard>
</Window.Resources>
I have the following XAML (obviously):
<Label x:Name="lblHello" Content="test" Margin="20"/>
And the code behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
public void AnimateLabelRotation()
{
Storyboard sbdLabelRotation = (Storyboard)FindResource("sbdLabelRotation");
sbdLabelRotation.Begin(this);
}
Which I call from a button click event. The FindResource works and finds the storyboard, but nothing happens. I have managed to get the animation to work on an event trigger, but clearly I'm missing something for the code behind.
This:
<Label x:Name="lblHello" Content="test" Margin="20"/>
and this:
Storyboard.TargetProperty="(TextBlock.RenderTransform).(RotateTransform.Angle)"
are not compatible.
When the animation tries to find the property to animate, it goes to (TextBlock.RenderTransform) and finds null since you didn't declare it (actually it doesn't since you say TextBlock but apply it to Label, more on that later in the answer). Thus it cannot find .(RotateTransform.Angle).
To remedy the issue:
<Label x:Name="lblHello"
Content="test"
Margin="20"
RenderTransformOrigin="0.5,0.5">
<Label.RenderTransform>
<RotateTransform />
</Label.RenderTransform>
</Label>
Notice RenderTransformOrigin setting - this means that the axis of rotation will be in the center of the object (X and Y).
Also, in the animation it should be:
Storyboard.TargetProperty="(Label.RenderTransform).(RotateTransform.Angle)"
There is a link to download the whole project
http://www.galasoft.ch/mydotnet/articles/resources/article-2006102701/GalaSoftLb.Article2006102701.zip
You can study the code and see it running. Sometimes it's more helpful.
Also in your code the part:
sbdLabelRotation.Begin(this);
could be wrong. As you know the this keyword references the class itself, in your case the MainWindow class. You should try without the this keyword.

Categories

Resources