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

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.

Related

Crash when animating a DataGridRow during a scroll

I am trying to make a DataGridRow's background colour animate itself when its data changes. I have used the advice in this post and hooked up an animation to the TargetUpdated event. This animation fires when the data is first loaded, when it scrolls and when the data changes.
My biggest issue is that when I try scrolling by dragging the scrollbar with the mouse I get a crash of the following sort:
System.InvalidOperationException: Cannot animate 'Background.Color' on an immutable object
When I scroll using the arrow buttons there's no crash. If I reach the bottom of the list and only then start dragging the scrollbar with the mouse, there's no crash.
Can anyone advise as to why this crash is happening? My best guess based on other reading is that there are several animations running for a single element concurrently because the animation is triggered by scrolling (maybe due to element resuse and contentdata switching?). There's certainly nothing in the code explicitly binding to the background color.
A related issue I have is that I only want the animation to trigger when the data actually changes - not when I scroll.
I apologise that I cannot post a copy of my code. My company's policies make that problematic. Here's a paraphrase (hopefully catching all salient details):
<Style x:Key="RowStyle" TargetType="DataGridRow">
<Style.Triggers>
<EventTrigger RoutedEvent="Binding.TargetUpdated">
<BeginStoryboard>
<Storyboard>
<ColorAnimation From="Red" Duration="0:0:1" Storyboard.TargetProperty="Background.Color">
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Style.Triggers>
</Style>
The columns with data I am interested in are similar to the following:
<DataGridTextColumn Binding="{Binding Value, Mode=OneWay, NotifyOnTargetUpdated=True}" />

How to 'Freeze' the UI (main window) when mouse is hovering over

What I want to achieve is that when the mouse is hovering over the main window, all the UI elements should freeze, which I think can be done by setting Window.IsEnabled to false, and after the mouse leaves the main window, everything should be back to normal.
I've tried to define a property trigger in a style targetting Window, but it doesn't work. The code is as lollow,
<Style.Triggers>
<Trigger Property="Window.IsMouseOver" Value="True">
<Setter Property="Window.IsEnabled" Value="false"/>
</Trigger>
</Style.Triggers>
In fact this kind of property trigger wouldn't work on Grid either. Can anyone make some explanations?
I also tried to explicitly use the MouseEnter and MouseLeave events on Window, and set the disable/enable logic in the handlers. This works. I wonder if it's possible to do this in XAML?
Well to be honest I don't know why your code doesn't work, I think it goes in some kind of conflict but I don't know why
Anyway you can do it in XAML using eventsetter, It's not so elegant but it works
<Window.Triggers>
<EventTrigger RoutedEvent="Window.MouseEnter">
<BeginStoryboard>
<Storyboard Name="sb">
<BooleanAnimationUsingKeyFrames Storyboard.TargetProperty="IsEnabled" >
<BooleanKeyFrameCollection>
<DiscreteBooleanKeyFrame Value="False" KeyTime="0:0:0:1"></DiscreteBooleanKeyFrame>
</BooleanKeyFrameCollection>
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Window.Triggers>

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)

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.

WPF Animation "Cannot freeze this Storyboard timeline tree for use across threads"

I currently have a listbox that has its selected item bound to a property on my ViewModel. Whenever the selected item isn't null I want to perform an animation on it. However I keep getting the following error "Cannot freeze this Storyboard timeline tree for use across threads" and from research sort of understand why this is happening. However I am unsure of what approach I need to take to get the behavior I want.
<Storyboard x:Key="ShowItemEdit">
<DoubleAnimation
Storyboard.TargetName="lstItemList"
Storyboard.TargetProperty="ListBox.Width"
To="{Binding ActualWidth, ElementName=UserControl}"
Duration="0:0:0.40" />
...
</Storyboard>
<Style x:Key="ListStyle">
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedItem, Converter={StaticResource IsNullConverter}}" Value="False">
<DataTrigger.EnterActions>
<BeginStoryboard Storyboard="{StaticResource ShowItemEdit}" />
</DataTrigger.EnterActions>
</DataTrigger>
</Style.Triggers>
</Style>
<ListBox x:Name="lstItemList" Style={StaticResource ListStyle}" SelectedItem="{Binding SelectedItem}">
...
</ListBox>
Can you post your Storyboard? It sounds like you have some kind of Binding in the Storyboard definition.
Ok so, as I suspected, it's because you're using a Binding in your Storyboard. You can't do this because WPF attempts to freeze all the resources leveraged by a template for efficiency and when you use a Binding on a Freezable, in this case the Storyboard, it prevents it from being able to be frozen.
There is a technique that you can use to get around the Freezable issue that allows you to use a binding for the "To" value of your animation (rather than hard-coding a value there). Its pretty straightforward and I've outlined it here.
Old question but might be useful for other people.
Sometimes creating the Storyboard in the code-behind can be simpler: https://stackoverflow.com/a/10848781/779521

Resources