WPF/Silverlight: How to DataTrigger a Storyboard Animation in MVVM? - wpf

I have a boolean property called IsLoginWrong, I want to then play a storyboard animation if the IsLoginWrong is true. (IsLoginWrong does an OnPropertyChanged event, so I know the binding is ok) But I'm having a hard time with the syntax. This might not even be right, but I think datatriggers can only live in Styles...
<UserControl.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsLoginWrong}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard Storyboard="{StaticResource LoginWrong}"/>
</DataTrigger.EnterActions>
</DataTrigger>
</Style.Triggers>
</Style>
</UserControl.Style>
But this throws an exception "A storyboard tree in a Style cannot specify a TargetName"... beause styles canno refer to items specifically.. awesome. so how do I do what I'm trying to do? (play animation if a boolean changes in mvvm)
Thanks

Within a style you cannot refer to a storyboard name. So the way I got it to work is to shove your storyboard within the actual style:
<UserControl.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsLoginWrong}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
.... PUT YOUR ACTUAL STORY BOARD IN HERE ...
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
</Style.Triggers>
</Style>
</UserControl.Style>
Now DataTriggers can either be put into styles or control templates, so there might be a nicer way to do this with control templates. but this is what I came up with for the time being.

One option would be to start the storyboard using the VisualStateManager. The article at http://blogs.infosupport.com/blogs/alexb/archive/2010/04/02/silverlight-4-using-the-visualstatemanager-for-state-animations-with-mvvm.aspx explains how to control the current state of the VisualStateManager from the view model using an attached property.

Related

How to create a single style for multiple mouse over events?

I have four buttons and four text boxes where each button is linked to one of the textblocks. When the mouse is over the button I want the corresponding textblock to fade in (and out on mouse leave). There are plenty of examples of this showing a single button and textblock where you can simply bind a datatrigger to the button name in the textblock style.
Here's what I've got so far (all of this is in a textblock style):
<DataTrigger Binding="{Binding ElementName=UpdateButton, Path=IsMouseOver}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard TargetProperty ="Opacity" Duration="00:00:01">
<DoubleAnimation From="0" To="1" Duration="00:00:01"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard>
<Storyboard TargetProperty ="Opacity" Duration="00:00:01">
<DoubleAnimation From="1" To="0" Duration="00:00:01"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
As of right now, when I mouse over the Update Button, all of the textblocks show instead of just the one associated with the Update Button.
To fix this I could create styles for each textblock by their name and bind to the appropriate button, but this is a huge amount of repetition. I could likely used "BasedOn" to separate the button binding, but then we're still duplicating all of the code for the Storyboards and whatnot. But does anyone know a better way?
It would seem like there should be a way create this all in a single style using a single generic binding but link the specific buttons to their textblocks, so the button only triggers the Storyboard for it's linked textblock. Anyone know how to do this, or a better way?
A good way to handle this is to create a custom inherited TextBlock that can store reference to a button.
Example
using System.Windows;
using System.Windows.Controls;
//Custom TextBlock
public class SpecialTextBlock : TextBlock
{
//This will be the button reference
public Button BoundButton { get; set; }
//Register the BoundButton as a dependency to allow binding
public static readonly DependencyProperty ButtonProperty = DependencyProperty.Register
(
"BoundButton",
typeof(Button),
typeof(SpecialTextBlock),
new FrameworkPropertyMetadata(default(Button))
);
}
Now that your new SpecialTextBlock is set up, you can create a new style for it. Use your original style, but apply it to TargetType="local:SpecialTextBlock" instead of TargetType="TextBlock".
Then update your DataTrigger from your example within the style so that the trigger binds to itself (the SpecialTextBlock), and then looks at the referenced Button path.
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=BoundButton.IsMouseOver}" Value="True">
...
Now you are set up and can create your TextBlocks like so without having to restyle.
//Set your BoundButton binding to specify which button triggers the animation.
<local:SpecialTextBlock BoundButton="{Binding ElementName=UpdateButton}" />
<StackPanel>
<Button x:Name="MouseTarget"
Content="Mouse Over This"
/>
<Button Content="This one changes...">
<Button.Style>
<Style TargetType="Button">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=MouseTarget, Path=IsMouseOver}" Value="True">
<Setter Property="Background" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
</StackPanel>

Animating ItemsControl items

I am a bit stuck as to how to approach this problem.
I have a tabcontrol on my solution and on a datatrigger I hide the tabcontrol and show an itemscontrol in its place with each item(tab) in a shape of a rectangular tile. And on selecting (simple mousedown) a 'tile' (another datatrigger ) I hide the itemscontrol and show the tabcontrol with the selected tile as the selected tab.
The above works fine without any issue. Now I am trying to see if I can animate this whole process to make it look nice visually. My goal is to make all the tiles appear nicely with a fade in effect when the itemscontrol becomes visible and then fade nice gracefully when the itemscontrol become collapsed.
My idea is to actually set all the items opacity to 0 to start with then run a DoubleAnimation setting the opacity back to 1 on the datatrigger(giving a nice fade in effect). But I am not sure really how to approach this problem.
Can anyone help me with this one please? Sorry I havent posted any xaml but my efforts trying to animate a listbox instead, failed
Edit- Posting my efforts so far. Ideally I want it to work with an ItemsControl instead of a listbox as I dont need any interactions such as SelectedItem or anything
please let me know if my approach itself is wrong so that I dont end up in wrong direction.
I started with a listbox and in my resources i got the below style. Please ignore my multi trigger as I had few more conditions earlier but left it like that.
<Style x:Key="CustomStyle" TargetType="{x:Type ListBoxItem}">
<Setter property="opacity" value="0"/>
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type ListBox}},Path=Visibility}" Value="true"/>
</MultiDataTrigger.Conditions>
<MultiDataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard Duration="0:0:1">
</ObjectAnimationUsingKeyFrames>-->
<DoubleAnimation Duration="0:0:1" Storyboard.TargetProperty="Opacity" To="1"/>
</Storyboard>
</BeginStoryboard>
</MultiDataTrigger.EnterActions>
</MultiDataTrigger>
</Style.Triggers>
</Style>
Then my listbox style
<ListBox Background="Transparent" ItemsSource="{Binding Children}" Visibility="{Binding ShowAnimation,Converter={StaticResource BooleanToVisibilityConverter}}">
<ListBox.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding ShowAnimation}" Value="True">
<Setter Property="ListBox.ItemContainerStyle" Value="{StaticResource CustomStyle}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.Style>
</ListBox>

WPF: Selecting the Target of an Animation

I am trying to create a simple (I think) animation effect based on a property change in my ViewModel. I would like the target to be a specific textblock in the control template of a custom control, which inherits from Window.
From the article examples I've seen, a DataTrigger is the easiest way to accomplish this. It appears that Window.Triggers doesn't support DataTriggers, which led me to try to apply the trigger in the style. The problem I am currently having is that I can't seem to target the TextBlock (or any other child control)--what happens is which the code below is that the animation is applied to the background of the whole window.
If I leave off StoryBoard.Target completely, the effect is exactly the same.
Is this the right approach with the wrong syntax, or is there an easier way to accomplish this?
<Style x:Key="MyWindowStyle" TargetType="{x:Type Window}">
<Setter Property="Template" Value="{StaticResource MyWindowTemplate}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ChangeOccurred}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard BeginTime="00:00:00" Duration="0:0:2" Storyboard.Target="{Binding RelativeSource={RelativeSource AncestorType=TextBlock}}"
Storyboard.TargetProperty="(Background).(SolidColorBrush.Color)">
<ColorAnimation FillBehavior="Stop" From="Black" To="Red" Duration="0:0:0.5" AutoReverse="True"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
</Style.Triggers>
</Style>
Update
Should have also mentioned that I tried to name the TextBlock and reference it via StoryBoard.TargetName (as Timores suggested), and got the error "TargetName property cannot be set on a Style Setter."
EDIT: I have overseen the fact that the TextBlock is in the ControlTemplate of your custom Window/Control. I do not think that it is possible to target a control within the ControlTemplate from a Storyboard outside of this ControlTemplate. You could however define a property on your custom Window which you then databind to your ChangeOccurred property, and then add the trigger to your ControlTemplate which will now get triggered by the custom Control's property rather than the Window's ViewModel's property (of course, indirectly it is triggered by the ViewModel because ChangeOccurred is bound to the property of the custom Window which in turn triggers the animation - uh, complex sentence, hope you understand). Is this an option? Could you follow? ;-)
Maybe some code helps:
public class MyCustomWindow : Window
{
public static readonly DependencyProperty ChangeOccurred2 = DependencyProperty.Register(...);
public bool ChangeOccurred2 { ... }
// ...
}
And some XAML:
<local:MyCustomWindow ChangeOccurred2="{Binding ChangeOccurred}" ... >
<!-- Your content here... -->
</local:MyCustomWindow>
<!-- Somewhere else (whereever your ControlTemplate is defined) -->
<ControlTemplate TargetType="{x:Type local:MyCustomWindow}">
<!-- your template here -->
<ControlTemplate.Triggers>
<Trigger Property="ChangeOccurred2" Value="True">
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard BeginTime="00:00:00" Duration="0:0:2"
Storyboard.TargetName="txtWhatever"
Storyboard.TargetProperty="(Background).(SolidColorBrush.Color)">
<ColorAnimation FillBehavior="Stop"
From="Black" To="Red"
Duration="0:0:0.5"
AutoReverse="True"/>
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
Note: I named the Window's property ChangeOccurred2 because I wanted it to be distinguishable from the ViewModel's ChangeOccurred property. Of course, you should choose a better name for this property. However, I am missing the background for such a decision.
My old answer:
So, you want to animate a TextBlock which is in the content of a (custom) Window?!
Why do you want to set the style on the Window, and not on the TextBlock itself? Maybe you should try something like this (did not test this!):
<local:MyCustomWindow ... >
<!-- ... -->
<TextBlock x:Name="textBlockAnimated" ... >
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Style.Triggers>
<DataTrigger Binding="{Binding ChangeOccurred}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard BeginTime="00:00:00" Duration="0:0:2"
Storyboard.TargetProperty="(Background).(SolidColorBrush.Color)">
<ColorAnimation FillBehavior="Stop"
From="Black" To="Red"
Duration="0:0:0.5"
AutoReverse="True"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<!-- ... -->
</local:MyCustomWindow>
The {Binding ChangeOccurred} might not be sufficient. You might have to add a DataContext to the TextBlock, or add a RelativeSource or something.
Is the TextBlock in the MyWindowTemplate ?
If so, give the TextBlock a name and use Storyboard.TargetName to reference it.
See another question in SO

Remove/Stop Storyboard on DataContext change

General context : MVVM application.
I have a View called JobView. Its DataContext is a class called Job. Within Job is a property called AuthorizationNeeded.
A Border in the view has a style (from a resource dictionary) and that style has a data trigger which starts and stops a storyboard based on the bound property AuthorizationNeeded.
<Style x:Key="AuthorizationNeededBorder"
TargetType="Border">
<Setter Property="Background"
Value="Yellow" />
<Setter Property="Opacity"
Value="0" />
<Style.Triggers>
<DataTrigger Binding="{Binding AuthorizationNeeded, FallbackValue=False}"
Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard Name="Flash"
Storyboard="{StaticResource OneSecondOpacityFlash}" />
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<RemoveStoryboard BeginStoryboardName="Flash" />
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
Everything works as expected. Changing AuthorizationNeeded's value starts the storyboard flash when moving to true and removes (and stops) the storyboard when moving to false.
However, if I change the DataContext of JobView to a different ViewModel (a different Job) while the storyboard is running, even if the value of AuthorizationNeeded is false in the new Job, the storyboard continues to run.
The data trigger is not seeing the change of value from AuthorizationNeeded true -> false during the DataContext change.
Any ideas on how I can get to the desired behavior of AuthorizationNeed = true = storboard running to AuthorizationNeeded = false = storyboard not running under all circumstances would be greatly appreciated. (I would prefer not to manually change the value of AuthorizationNeeded at a DataContext change because in reality there are many such triggers on this view...)
I would consider adding a trigger to the DataContextChanged event on the object. Something like:
<Style.Triggers>
<EventTrigger EventName="DataContextChanged">
<StopStoryboard Storyboard="{StaticResource OneSecondOpacityFlash}" />
</EventTrigger>
</Style.Triggers>
I would wonder, though, if you want to change the DataContext on an existing view object or if it would be better to create a new view bound to the new DataContext. Depending on what you're doing, I would think that swapping out DataContexts could result in extra handles being held. Depending on what your container is, it may be easier to remove and re-create the child view/viewmodel than to swap.

Begin Storyboard on more than one target

I have a subclassed ListBox with a SelectedItemChanging dependency property that is set to a Storyboard. When the selected item is changed, I want to run this Storyboard on each item in the ListBox.
How is this possible with a single instance of Storyboard?
Storyboards can be keyed and run from multiple triggers, and it works great as long as it's set up properly. If I am understanding you correctly, you're hoping to apply the storyboard to each individual ListBoxItem. In which case, why not make a style, and on that style's triggers, fire the storyboard.
Excuse my pseudocode.
<Storyboard x:Key="MyEnterStoryboard">
<!-- Do Enter Work -->
</Storyboard>
<Storyboard x:Key="MyExitStoryboard">
<!-- Do Exit Work -->
</Storyboard>
<Style TargetType="{x:Type ListBoxItem}">
<Style.Triggers>
<Trigger Property="SelectedItemChanging" Value="True">
<Trigger.EnterActions>
<BeginStoryboard Storyboard="{StaticResource MyEnterStoryboard}"/>
</Trigger.EnterActions>
<Trigger.ExitActions>
<BeginStoryboard Storyboard="{StaticResource MyExitStoryboard}"/>
</Trigger.ExitActions>
</Trigger>
</Style.Triggers>
</Style>
WPF Storyboards have a Clone method. Silverlight doesn't have this but thought I'd post it just in case someone stumbles across this post looking for a WPF solution.

Resources