Starting a XAML animation in WPF - c#

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.

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

Property Value Animation triggered by a custom DependencyProperty in UserControl?

Question Prelude :
How can I animate the Angle Property of a RotateTransform of
an UIElement A when the value of a Custom DependencyProperty
of type boolean becomes True when I click on an
UIElement B, all inside an UserControl ?
And in XAML ONLY (or mostly) ? if possible :)
I've written all the following to provide all the required details of my issue. You can stop reading from top to bottom anytime; even directly jump to the actual question, which is within the first quarter of the post.
Context :
The question is about Animation Triggers and Custom Property Binding, all within a single UserControl. No Window involved so far.
To begin with, let's assume I created an UserControl, which has a main Grid that contains two other Grids. Simpliest schemas :
<!-- MyControl.xaml -->
<UserControl ...blahblahblah>
<Grid>
<Grid x:Name="TiltingGrid">
<!-- This Grid contains UIElements that I want to tilt alltogether -->
</Grid>
<Grid>
<Ellipse x:Name="TiltingTrigger" ...blahblahblah>
<!-- This Ellipse is my "click-able" area -->
</Ellipse>
</Grid>
</Grid>
</UserControl>
Then, in Code Behind, I have a DependencyProperty called IsTilting.
// MyControl.xaml.cs
public bool IsTilting
{
// Default value is : false
get { return (bool)this.GetValue(IsTiltingProperty); }
set { this.SetValue(IsTiltingProperty, value); }
}
private static readonly DependencyProperty IsTiltingProperty =
DependencyProperty.Register(
"IsTilting",
typeof(bool),
typeof(MyControl),
new FrameworkPropertyMetadata(
false,
new PropertyChangedCallback(OnIsTiltingPropertyChanged)));
private static void OnIsTiltingPropertyChanged(...) { ... }
// .. is a classic Callback which calls
// private void OnIsTiltingChanged((bool)e.NewValue)
// and/or
// protected virtual void OnIsTiltingChanged(e) ...
Then, I defined some Properties for my Grid named TiltingGrid in the XAML :
<Grid x:Name="TiltingGrid"
RenderTransformOrigin="0.3, 0.5">
<Grid.RenderTransform>
<RotateTransform
x:Name="TiltRotate" Angle="0.0" />
<!-- Angle is the Property I want to animate... -->
</Grid.RenderTransform>
<!-- This Grid contains UIElements -->
<Path ... />
<Path ... />
<Ellipse ... />
</Grid>
And I would like to trigger the tilting upon clicking on a specific area inside this UserControl : An Ellipse, in the secund Grid :
<Grid>
<Ellipse x:Name="TiltingTrigger"
... Fill and Stroke goes here ...
MouseLeftButtonDown="TryTilt_MouseLeftButtonDown"
MouseLeftButtonUp="TryTilt_MouseLeftButtonUp">
</Ellipse>
</Grid>
If I'm not mistaken, Ellipse doesn't have a Click Event, so I had to create two EventHandlers for MouseLeftButtonDown and MouseLeftButtonUp. I had to do it that way to be able to :
Make the Ellipse capture Mouse upon MouseLeftButtonDown, and set a private field to true
Test whether the Mouse Point is inside the Ellipse upon MouseLeftButtonUp, set the value of the private field to false, then Release the Mouse.
Invert the value of the DependencyProperty IsTilting (true/false) if something looking like a "Click" occurs (..which would trigger the tilting animation if I'm able to resolve the appropriate Binding..)
I'll save you the MouseLeftDown/Up code, but I can provide it if required. What they do is to change the value of the DP.
Issue(s) :
I don't know how to trigger the Angle Animation when my DependencyProperty is updated. Well. That's not an actual issue, it's a lack of knowledge I reckon :
I don't know how to capture a custom event to be used with <EventTrigger>
I don't know how and where to trigger a StoryBoard using a True/False DependencyProperty.
And the actual question is :
From now on, how do I declare the code that makes the Angle
Property of the RotateTransform to animate from 0.0 to
45.0 (Rendering Transform of my Grid "TiltingGrid") when my DP IsTilting is set to true, and animate back to 0.0
when it's False ?
mostly in XAML way ..?
I do have a working code in C# code behind (detailed below) What I'm looking for is a workable solution in XAML (because it's usually very easy to rewrite almost anything in CodeBehind when you know how to do it in XAML)
What I tried so far...
From now on, you don't have to read further unless you absolutely want to know all the details...
1) Triggering the animation using natively defined Ellipse EventTriggers works only for Events defined for this specific UIElement (Enter/Leave/MouseLeftDown...) Done that alot with many UIElements.
But those triggers are not the ones I need : My Grid should tilt based on an On/Off or True/False custom state in a DP, not when something like a Mouse activity occurs.
<Ellipse.Triggers>
<EventTrigger RoutedEvent="UIElement.MouseEnter">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="TiltRotate"
Storyboard.TargetProperty="Angle"
From="0.0" To="45.0"
Duration="0:0:0.2" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<EventTrigger RoutedEvent="UIElement.MouseLeave">
...
</Ellipse.Triggers>
When the mouse enters the Ellipse, my Grid is tilting accordingly, but hence, How do I have access to custom Events defined in my UserControl ?
2) Then, based on the above scheme, I supposed I just had to create a Routed Event on my MyControl Class, or two, actually :
TiltingActivated
TiltingDisabled
.
public static readonly RoutedEvent TiltingActivatedEvent =
EventManager.RegisterRoutedEvent(
"TiltingActivated",
RoutingStrategy.Bubble,
typeof(RoutedEventHandler),
typeof(EventHandler));
public event RoutedEventHandler TiltingActivated
{
add { AddHandler(MyControl.TiltingActivatedEvent, value); }
remove { RemoveHandler(MyControl.TiltingActivatedEvent, value); }
}
private void RaiseTiltingActivatedEvent()
{
RoutedEventArgs newEventArgs =
new RoutedEventArgs(MyControl.TiltingActivatedEvent, this);
RaiseEvent(newEventArgs);
}
Then, I'm calling RaiseTiltingActivatedEvent() in one method called by my IsTilting DependencyProperty Callback when its new value is true, and RaiseTiltingDisabledEvent() when its new value is false.
Note : IsTilting value is changed to either true of false upon Ellipse "Click", and the two events are fired accordingly. But there's a problem : it's not the Ellipse that fires the Events, but the UserControl itself.
Anyway, I tried to replace the <EventTrigger RoutedEvent="UIElement.MouseEnter"> with the followings :
Attempt one :
<EventTrigger RoutedEvent="
{Binding ic:MyControl.TiltingActivated,
ElementName=ThisUserControl}">
.. and I get :
"System.Windows.Markup.XamlParseException: (...)"
"A 'Binding' can only be set on a DependencyProperty of a DependencyObject."
I'm assuming I cannot bind to an Event ?
Attempt two :
<EventTrigger RoutedEvent="ic:MyControl.TiltingActivated">
.. and I get :
"System.NotSupportedException:"
"cannot convert RoutedEventConverter from system.string"
I'm assuming the RoutedEvent name cannot be resolved ? Anyway, this approach make me drift far from my initial goal : Trigger a DoubleAnimation when a custom Property changes (because in more complex scenarios, wouldn't it be easier to trigger different animations and call specific methods, all in CodeBehind when we can have dozens of different values, than creating lengthy and tricky XAML things ? Best would be learning how to do both of course. I'm eager to know)
3) Then I came across this article : Beginner's WPF Animation Tutorial.
A Code Behind Animation Creation. That's the thing I wanted to learn after knowing how to do it in XAML. Anyway, let's have a try.
a) Create two Animation Properties (private), one for tilting animate and another for tilting animate back.
private DoubleAnimation p_TiltingPlay = null;
private DoubleAnimation TiltingPlay
{
get {
if (p_TiltingPlay == null) {
p_TiltingPlay =
new DoubleAnimation(
0.0, 45.0, new Duration(TimeSpan.FromSeconds(0.2)));
}
return p_TiltingPlay;
}
}
// Similar thing for TiltingReverse Property...
b) Subscribe to the two events then set the Angle Animation of our RotateTransform live at runtime in code behind :
private void MyControl_TiltingActivated(object source, EventArgs e)
{
TiltRotate.BeginAnimation(
RotateTransform.AngleProperty, TiltingPlay);
}
// Same thing for MyControl_TiltingDisabled(...)
// Subscribe to the two events in constructor...
public MyControl()
{
InitializeComponent();
this.TiltingActivated +=
new RoutedEventHandler(MyControl_TiltingActivated);
this.TiltingDisabled +=
new RoutedEventHandler(MyControl_TiltingDisabled);
}
Basically, when I "click" (MouseButtonLeftDown + Up) on the Ellipse :
Mouse hit spot is resolved
if within the Ellipse area, change DP IsTilting to not IsTilting.
IsTilting then fires either TiltingActivated or TiltingDisabled.
Both are captured, then the related tilting animation (private properties) is activated on the named <RotateTransform ..> of the Grid.
And it works !!!
I said it would be very easy in code behind ! (lengthy code .. yes, but it works) Hopefully, with snippets templates, it's not that boring.
But I still don't know how to do it in XAML. :/
4) Since my custom events seems to be out of scope in the XAML side, what about <Style> ? Usually, binding in a Style is like breathing. But honestly, I don't know where to begin.
the animation target is the Angle Property of a <RotateTransform /> applied to a Grid.
the binded Dep. Property IsTilting is a custom DP of MyControl, not UserControl.
and one Ellipse drives the updating of the DP.
let's try something like <RotateTransform.Style>
<RotateTransform ...>
<RotateTransform.st...>
</RotateTransform>
<!-- such thing does not exists -->
or RotateTransform.Triggers ? ... doesn't exist either.
UPDATE :
This approach works by declaring the Style in the Grid to animate, as explained in Clemens's answer. To resolve the custom
UserControl Property binding, I just had to use
RelativeSource={RelativeSource AncestorType=UserControl}}. And to
"target" the Angle Property of the RotateTransform, I just had to use
RenderTransform.Angle.
What else ?
I often see samples that sets the DataContext to something like "self". I don't really understand what's a DataContext, but I'm assuming it makes all Path resolving point to the declared Class by default, for Bindings. I already used that in one UserControl which solved my issue, but I didn't dig deeper to understand the how and why. Perhaps this could help resolve capturing custom Events in code behind directly from the XAML side ?
One XAML mostly way I'm nearly sure will work is :
to create a custom UserControl for that Ellipse, say, EllipseButton, with its own Events and Properties
then, embed that in MyControl UserControl.
Capture the TiltingActivated Event of the EllipseButton to trigger the DoubleAnimation in a Storyboard of the EllipseButton, just like it could be done for the Click event of a Button.
That would work fine, but I find it hacky to create and embed another control just to be able to access the appropriate custom event. MyControl is not a SuperWonderfulMegaTop project that would require such surgery. I'm sure I'm missing something soooooooo obvious; can't believe something that simple outside the WPF world can't be even simplier in WPF.
Anyway, such cross-connections are highly subject to memory leaks (perhaps not the case here, but I try to avoid that whenever possible...)
Perhaps defining <Grid.Style> or alike would do the trick ... but I don't know how. I only know how to use <Setter>. I don't know how to create EventTriggers in a Style declaration. UPDATE : Explained by Clemens's answer.
This SO question (Fire trigger in UserControl based on DependencyProperty) suggests to create a Style in UserControl.Resources. Tried the following... It doesn't work (and there is no animation there anyway - I don't know how to declare animation in Style yet)
.
<Style TargetType="RotateTransform">
<Style.Triggers>
<DataTrigger
Binding="{Binding IsTilting, ElementName=ThisUserControl}" Value="True">
<Setter Property="Angle" Value="45.0" />
</DataTrigger>
</Style.Triggers>
</Style>
This SO question (Binding on RotateTransform Angle in DataTemplate not taking effect) has a lot of unknown knowledge to me to be understandable. However, assuming the suggested workaround works, I don't see anywhere something looking like an animation. Just a binding to a value that is not animated. I don't think the Angle animates itself magically.
In Code Behind like the working code above, I could create another DependencyProperty called GridAngle (double), then bind the Angle Property of RotateTransform to that new DP, then animate that DP directly ??? Worth a try, but at a later time : I'm tired.
Just found that my Registered Events are of Bubble Strategy. This would matter if the Event is to be captured by some parent containers, but I want to handle everything directly inside the UserControl, not like on this SO question. However, Tunneling strategy - that I don't understand yet - may play a role : would Tunneling allows my Ellipse to capture the Events of my UserControl ? Have to read the documentation again and again because it's still very obscure to me... What bugs me now is that I am still unable to use my custom events in this UserControl :/
What about a CommandBinding ? That seems very interresting, but it's a whole different chapter to learn. It seems to involve a lot of code behind, and since I already have a working code behind (which looks more readable to me...)
In this SO question (WPF Data Triggers and Story Boards), the accepted answer seems to only work if I'm animating a property of an UI Element that can have a UIElement.Style definition. RotateTransform doesn't have such ability.
Another answer suggest the use of ContentControl, ControlTemplate... Just like CommandBinding above, I haven't dig deep enough to understand how I could adapt that to my UserControl.
However, those answers seems the ones that mostly fit my needs, expecially that ContentControl way. I'll have some tries at a later time, and see if it solves the XAML mostly way of implementing the desired behaviour. :)
And last, this SO question (EventTrigger bind to event from DataContext) suggest the use of Blend/Interactivity. The approach looks nice, but I don't have Blend SDK and not really willing to unless I absolutely have to... Again : another whole Chapter to eat... :/
Side note :
As you would have guessed, I'm a beginner in WPF/XAML (I know it's not an excuse) which I started to learn a few weeks ago. I'm kind of "the whole stuff would be very easy to do in WinForms right now..." but perhaps you could help me figure out how easy it would be to achieve it in WPF :)
I've searched alot (I know it's not an excuse either) but I have no luck for this time. - Okay, I've just read three dozens of articles, code projects and SO topics, and the MSDN documentation about triggers, animations, routed events.. just seems to polish the surface without digging deep in the core (seems like MS think inheriting from Button is the way to solve almost anything...)
Long question, short answer. Use Visual States:
<UserControl ...>
<Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup>
<VisualState x:Name="TiltedState">
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="TiltingGrid"
Storyboard.TargetProperty="RenderTransform.Angle"
To="45" Duration="0:0:0.2"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid x:Name="TiltingGrid" RenderTransformOrigin="0.3, 0.5">
<Grid.RenderTransform>
<RotateTransform/>
</Grid.RenderTransform>
...
</Grid>
</Grid>
</UserControl>
Whenever an appropriate condition is met, call
VisualStateManager.GoToState(this, "TiltedState", true);
in the UserControl's code behind. This may of course also be called in the PropertyChangedCallback of a dependency property.
Without using Visual States, you might create a Style for your TiltingGrid which uses a DataTrigger with a Binding to your UserControl's IsTilted property:
<Grid x:Name="TiltingGrid" RenderTransformOrigin="0.3, 0.5">
<Grid.Style>
<Style TargetType="Grid">
<Style.Triggers>
<DataTrigger Binding="{Binding IsTilted,
RelativeSource={RelativeSource AncestorType=UserControl}}"
Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetProperty="RenderTransform.Angle"
To="45" Duration="0:0:0.2"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetProperty="RenderTransform.Angle"
To="0" Duration="0:0:0.2"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Style>
<Grid.RenderTransform>
<RotateTransform/>
</Grid.RenderTransform>
...
</Grid>

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.

WPF DispatcherTimer on Modalless window

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!

Architecting...a slideshow

I know this sounds silly and I could use some out-of-the-box solution, but I really want to build my own simple image slideshow. I've been doing application development in Silverlight/WPF for some time, but for whatever reason I can't wrap my head around this.
I have an observable collection of SlideshowItem
Each SlideshowItem has Source which indicates where the image for it is located
I show a translucent box for each SlideshowItem (horizontal list using a stackpanel) and when you click, you should transition to that slide
So here's my problem: If I have that list with a stackpanel template, and under the list is an image taking up the size of the canvas, I can bind the context of the image to the selected SlideshowItem. That's all well and good. But when I click/change the selected index of the list, I want to do a crossfade or slide between two images.
How should I represent this in Silverlight? Should I actually have a scroll panel or something with all the images and then change between them? Or is it sufficient to use a single image control? Can I do this with states, or do I need to explicitly run a storyboard? Any samples would be appreciated.
You can use the TransitioningContentControl from the Silverlight Toolkit, however if you want to roll your own you will need two content controls and swap out the "Active" one on SelectionChanged events. You also can fire your storyboards here.
ContentControl _active;
private void LB_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (_active == Content1)
{
_active = Content2;
Content2Active.Begin();
} else
{
_active = Content1;
Content1Active.Begin();
}
_active.Content = LB.SelectedItem;
}
And the Xaml looks something like this. I just use strings and text, but this approach should work reasonable well for images too:
<Grid x:Name="LayoutRoot" Background="White" MaxHeight="200">
<Grid.Resources>
<Storyboard x:Name="Content1Active">
<DoubleAnimation From="0" To="1" Storyboard.TargetName="Content1" Storyboard.TargetProperty="(UIElement.Opacity)" />
<DoubleAnimation To="0" Storyboard.TargetName="Content2" Storyboard.TargetProperty="(UIElement.Opacity)" />
</Storyboard>
<Storyboard x:Name="Content2Active">
<DoubleAnimation From="0" To="1" Storyboard.TargetName="Content2" Storyboard.TargetProperty="(UIElement.Opacity)" />
<DoubleAnimation To="0" Storyboard.TargetName="Content1" Storyboard.TargetProperty="(UIElement.Opacity)" />
</Storyboard>
</Grid.Resources>
<StackPanel>
<ListBox x:Name="LB" SelectionChanged="LB_SelectionChanged" xmlns:sys="clr-namespace:System;assembly=mscorlib">
<sys:String>Red</sys:String>
<sys:String>Green</sys:String>
<sys:String>Blue</sys:String>
</ListBox>
<Grid>
<ContentControl x:Name="Content1" FontSize="40" Foreground="{Binding Content, RelativeSource={RelativeSource Self}}">
</ContentControl>
<ContentControl x:Name="Content2" FontSize="40" Foreground="{Binding Content, RelativeSource={RelativeSource Self}}">
</ContentControl>
</Grid>
</StackPanel>
</Grid>
Definitely you don't need the entire Image collection displayed in a scrollviewer/stackpanel. You can implement this in many different ways. I can explain a simple idea of Using one Image : As you said , define a SelectedSlide property in your ViewModel and bind that to an Image control ( Preferably a ContentControl with Image as its part of the ContentTemplate, so that you can have descriptions and other items in the same). This solution can give you the opportunity to add some storyboards so that if you increase your SelectedIndex(Another VM property) fire a storyboard to do a 'Left Move' animation and if you decrease do a 'Right Move' animation makes user feels like slides are coming from one side and going the other way. You can do pretty good UX on that set of storyboards.
Update (Idea 2) : Yes if we need the notion of the previous one leaving the view when new one coming in, we can architect it by using two ContentControls wrapped inside a CustomControl ( lets call it as SlideShowControl). SlideShowControl will have its mechanism to properly set DataContext of the two ContentControl based on the selectedIndex position. I have successfully made this control in one of my projects, the logic here is to switch the ContentControls through a storyboard so that we can have many different effects by swapping the storyboard. Suppose you move from Index 1 to 2, ContentControlA will animate to left, and B will come in to the View, and based on your next click ControlA will go sit either left or right of the View, and comes with new DataContext of the selected View.

Categories

Resources