Remove/Stop Storyboard on DataContext change - wpf

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.

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>

How do I animate a WPF control bound to a viewmodel property?

I have a WPF app, which uses MVVM. When the users edits data, if certain conditions are met, they will need to fill in revision notes for auditing purposes. If they don't need to, I hide the revision notes textbox to keep the UI clear.
At the moment this is done by binding the Visibility property of the Grid that surrounds the textbox (and its label) to a bool property on the viewmodel. When the bool changes, the revsion notes textbox is hidden or shown as necessary.
This works fine, but the textbox just appears. I would like to animate it, so it grows from zero height to its default, or something similar.
Any idea how I would do this? I have done animation before, but this was always when I manually triggered the animation. In this case, I want to declare the animation in XAML, so it happens automatically when the binding changes.
Anyone able to point me in the right direction?
Just use a DataTrigger to kick off an animation:
<Grid>
<Grid.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding MyVMBool}"
Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard >
<Storyboard>
<!-- DoubleAnimation on height or whatever -->
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<!-- you could animate close too if you wanted -->
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Style>
Sounds like you solve it with a DataStateBehavior. Here's the MSDN documentation http://msdn.microsoft.com/en-us/library/vstudio/dn195678(v=vs.110).aspx. You could also take a look at the GoToStateAction.

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

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.

Synchronize WPF ColorAnimation across several controls

I have several toggle-like buttons that I want to pulsate in unison when in the pressed state.
I have defined a style with a trigger that kicks-off the glow animation and this works just fine, apart from the fact that each button pulsates asynchronously from the others.
How can I have each button synchronize its pulse to the others?
Here's the style:
<Storyboard x:Key="pulseStory">
<ColorAnimation
Storyboard.TargetProperty="(Control.Background).(SolidColorBrush.Color)"
From="Red"
To="Transparent"
Duration="0:0:1" />
</Storyboard>
<Style x:Key="pulseButton" TargetType="Button">
<Style.Triggers>
<DataTrigger Binding="{Binding Tag,RelativeSource={RelativeSource Self}}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard Storyboard="{StaticResource pulseStory}"/>
</DataTrigger.EnterActions>
</DataTrigger>
</Style.Triggers>
</Style>
Cheers!
OK ... I'll take a stab at this one ...
The WPF framework doesn't have any facility for synchronizing animations that are running concurrently, so you are going to have to come up with a different method. One idea springs to mind ...
Animate some Color property of a hidden UI element within your storyboard, then use UI binding (i.e. ElementName bindings) to connect to the Color of each of your buttons to this hidden UI element.
Actually you should be doing this via a resource, at least using a hidden control is a bit too much of a hack for me personally.
What needs to be met for it to work:
The property you bind to needs to be a DependencyProperty, hence your enveloping object needs to be a DependencyObject.
You have to reference the object as a static resource (as opposed to a dynamic resource) like this:
<DoubleAnimation
Storyboard.Target="{StaticResource AnimationValue}"
Storyboard.TargetProperty="(local:WrappedValue.Value)"
To="0" Duration="0:0:1"/>
Well, admittedly it's a bit hacky as well to have a Wrapper-class for this but it's cleaner than a full control (if you want to use controls you can utilize some unused Tag-property, e.g. of the container that hosts all your controls)

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