Animation when changing ContentTemplate - wpf

I have a window where different controls had to be displayed over time. I searched for a solution with using the mvvm pattern and ended up with this
<ContentControl Content="{Binding}">
<ContentControl.Style>
<Style TargetType="ContentControl">
<Style.Triggers>
<DataTrigger Binding="{Binding ViewType}" Value="RecipeList">
<Setter Property="ContentTemplate" Value="{StaticResource RecipeTemplate}"/>
</DataTrigger>
<DataTrigger Binding="{Binding ViewType}" Value="Default">
<Setter Property="ContentTemplate" Value="{StaticResource DefaultTemplate}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
This works fine so far but i'm curious about two things:
is there a better approach with mvvm?
how can i execute an animation for the items in the new datatemplate that is about to be shown?

For the question #2:
You could use EventTrigger in controls within you templates to start animation like it is done below:
<Window x:Class="WpfApplication1.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">
<Grid>
<Grid.Triggers>
<EventTrigger RoutedEvent="FrameworkElement.Loaded">
<BeginStoryboard>
<Storyboard x:Name="SomeStoryBoard"/>
</BeginStoryboard>
</EventTrigger>
</Grid.Triggers>
</Grid>
</Window>

Since Animations are View-Specific actions, they should be run from the Code-Behind the View, not the ViewModel. In the past, I've hooked into an Event and just run the following from the code-behind:
Storyboard animation = (Storyboard)panel.FindResource("MyAnimation");
animation.Begin();
As for question #1, I don't see any problem with your code for displaying a different View based on a property in the ViewModel.

Related

Thickness animation changes brush color Textbox | WPF .NET Framework 8

I'm working on a style library for my own on a MVVM WPF App.
When i use a custom style in XAML on my textbox which contains a Trigger.EnterActions with a ThicknessAnimation on my border, my animation will append but it changes the initial BorderBrush of my textbox by the default blue color.
Here is my XAML Dictionnary
<Style x:Key="TEST1"
TargetType="TextBox">
<!--SETTERS-->
<Style.Setters>
<Setter Property="VerticalContentAlignment"
Value="Center" />
</Style.Setters>
<!--TRIGGERS-->
<Style.Triggers>
<Trigger Property="IsKeyboardFocused"
Value="True">
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<ThicknessAnimation Duration="0:0:0.100" Storyboard.TargetProperty="BorderThickness" To="1 1 1 3" />
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
<Trigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<ThicknessAnimation Duration="0:0:0.300" To="1" Storyboard.TargetProperty="BorderThickness" />
</Storyboard>
</BeginStoryboard>
</Trigger.ExitActions>
</Trigger>
</Style.Triggers>
</Style>
Here is my textbox on my window :
<Window x:Class="Design.View.Textboxes"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Design.View"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
Title="Textboxes"
WindowStyle="SingleBorderWindow">
<Border Width="500"
Height="Auto">
<StackPanel Orientation="Horizontal">
<TextBox Width="100"
Height="30"
Margin="4"/>
<TextBox Width="100"
Height="30"
Margin="4"
BorderBrush="{StaticResource Primary}"
Style="{StaticResource TEST1}" />
</StackPanel>
</Border>
</Window>
I was expecting the border to fit the color already set in the BorderBrush field of the Textbox Template during the animation but it doesn't.
I tried to use ColorAnimation and didn't know how to make it functional and i do believe that it is a weird way to solve this problem and not clean at all.
I thought about creating a custom textbox inherating of TextBox class with a new dependency containing the color and set it in every Storyboard...
But i want to know if there is a better way to make it work easily and let the code as clean as possible in pure XAML without any code behind.

Change visibility in EventTrigger

i have style that have rectangle which visibility=hidden.
i want change visibility when mouse enter rectangle.
forasmuch as rectangle doesn't have 'IsMouseOver' property i cant use trigger.
how i can do that? (how can change property with animation)
thanks.
I've looking for an button to write a comment, but i dont found it.
So here comes an answer.
Two things:
How should it possible to set Visisbility of an Element to Visible, if it is hidden? The MouseEnter and MouseLeave events will not be called. So the IsMouseOver Property is always False.
Second thing is, that i'm wondering that the IsMouseOver Property will not work in a trigger (i've tried it, too and....got an exception).
An alternative way is to use EventTriggers on MouseEnter and MouseLeave.
kr
sb
<Rectangle Width="400" Height="400" Fill="Red" Opacity="0">
<Rectangle.Style>
<Style TargetType="Rectangle">
<Style.Triggers>
<EventTrigger RoutedEvent="MouseEnter">
<BeginStoryboard>
<Storyboard TargetProperty="Opacity">
<DoubleAnimation From="0" To="1" Duration="0:0:2" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<EventTrigger RoutedEvent="MouseLeave">
<BeginStoryboard>
<Storyboard TargetProperty="Opacity">
<DoubleAnimation From="1" To="0" Duration="0:0:2" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Style.Triggers>
</Style>
</Rectangle.Style>
</Rectangle>
Ok, to sum up and add to what others wrote:
The rectangle does have an IsMouseOver property. So it is possible to create a trigger (inside a style) that will work with this property. However, this will not work. Why? Because as far as WPF is concerned, if the element is not visible, the mouse is never over it. In other words, is the element is hidden, IsMouseOver will always be false. Therefore, you can't use it to make the element visible when the user puts the mouse over the place where it should be.
If you are working, with a Rectangle, there is another way: instead of making it not visible, you can change the Rectangle's color to be transparent. That way, it IsMouseOver will work as it should and the following code (as an example) will do what you want:
<Rectangle Width="200" Height="200">
<Rectangle.Style>
<Style TargetType="Rectangle">
<Setter Property="Fill" Value="Transparent"></Setter>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Fill" Value="Yellow"></Setter>
</Trigger>
</Style.Triggers>
</Style>
</Rectangle.Style>
</Rectangle>
Of course, the usage depends on what exactly you want to do, which your question doesn't mention. Another way might be to create another Rectangle with the same dimensions and position as the one you need to hide/show. This new Rectangle would be transparent, but always visible. Then, you can bind your Rectangle's Visibility to this new Rectangle's IsMouseOver.
Visibility has three enumeration, Visible Hidden and Collapsed, therefore you cant directly bind to a bool property or for that matter any property that is not a Visibility property. You can write or find a converter, search on WPF Visibility Converter. Or you can try this:
Use the tag property and bind it to the visibility property, it works fine, it is simple and it is entirely in your style setters and triggers. Of course if your using your tag for something else oh well..
In this case I have two TextBlocks, I want one textblock visible when the mouse enters the other, So when the mouse is over the first, I change its tag property to Visible and bind the second text box Visibility property to the firsts tag property.
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal ">
<TextBlock Name="TextBlockTitle" Text="{Binding Title}">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock }">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Tag" Value="Visible"/>
</Trigger>
<Trigger Property="IsMouseOver" Value="False">
<Setter Property="Tag" Value="Hidden"/>
</Trigger>
</Style.Triggers>
</Style>
</TextBlock.Style></TextBlock>
<TextBlock Name="TextBlockAdd" Text=" + Add New" MouseLeftButtonDown="TextBlockAdd_OnMouseLeftButtonDown">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock }">
<Setter Property="Visibility" Value="{Binding ElementName=TextBlockTitle,Path=Tag}"></Setter>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Visibility" Value="Visible"/>
</Trigger>
<EventTrigger RoutedEvent="MouseLeftButtonDown" ></EventTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>

Debugging Triggers (or why doesn't this trigger work?)

I am trying to fade in a control when it becomes visible. The following compiles and runs fine, it just doesn't fade in (control instantly appears when IsActive is set to true)
<UserControl x:Class="blah"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:util="clr-namespace:blah.Util"
mc:Ignorable="d"
d:DesignHeight="250" d:DesignWidth="400">
<UserControl.Resources>
<util:BooleanToVisibilityConverter x:Key="BoolToVis" />
<Style TargetType="UserControl">
<Style.Triggers>
<Trigger Property="Visibility" Value="Visible">
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Opacity" From="0.0" To="1.0" Duration="0:0:1.25" />
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
</Trigger>
</Style.Triggers>
</Style>
</UserControl.Resources>
<UserControl.Visibility>
<Binding Path="IsActive" Converter="{StaticResource ResourceKey=BoolToVis}" ConverterParameter="False" />
</UserControl.Visibility>
<!-- Snip rest of simple control -->
</UserControl>
Firstly, I'd be grateful if anyone could tell me why this isn't working.
Secondly I was wondering if there is any way to debug such things, as I often find myself trying to get triggers working properly. Currently my debugging consists of staring at the XAML to try and see what's wrong, or randomly changing bits to try and narrow down the area.
What I really want to do it put a breakpoint on the <Trigger Property="Visibility" Value="Visible"> bit, to see if that is being triggered as a starting point. Obviously I can't do this but was wondering if there's any way to do more structured debugging rather than my current random poking at a blank wall. :-/
Set UserControl.Visibility in Style or you will override the Style.Trigger if you set the Visibility property explicitly.
<Setter Property="Visibility" Value="{Binding Path=IsActive, Converter={StaticResource ResourceKey=BoolToVis}, ConverterParameter=False}" />

WPF Windows Presentation Foundation Animation using MultiTrigger

I am trying to get the hang of how to do animations using WPF XAML. I'm struggeling to get my animation to trigger based on the right conditions and now I need some help.
Just for laughs I want to make a page with a button that will dodge any attempt to be pressed by moving to a different place on the canvas. Here is an illustration of my desired animations:
I want to solve the problem simply using XAML (and no code-behind), but I am open to a solution which uses code if the code is accompanied with an explanation of why this can not be solved simply by declaring the appropriate XAML.
This is what I have so far:
<Page x:Class="myApp.SecondPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
xmlns:c="clr-namespace:myApp"
d:DesignHeight="300" d:DesignWidth="500"
Title="SecondPage" Height="300" Width="500">
<Page.Resources>
<PathGeometry x:Key="AnimationPath" Figures="M 0,0 C -130,-100 -130,-60 -130,-0"/>
<Style x:Key="secondButton" TargetType="Button">
<Setter Property="Content" Value="Button"></Setter>
<Style.Triggers>
<MultiTrigger >
<MultiTrigger.Conditions>
<Condition Property="IsMouseOver" Value="True"></Condition>
<!--Doesn't work --><Condition Property="TranslateTransform.X" Value="0"></Condition>
</MultiTrigger.Conditions>
<MultiTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimationUsingPath x:Name="XAnim"
FillBehavior="HoldEnd"
Storyboard.TargetProperty="RenderTransform.X"
PathGeometry="{StaticResource AnimationPath}"
Source="X"
Duration="0:0:.2"
/>
<DoubleAnimationUsingPath x:Name="YAnim"
FillBehavior="HoldEnd"
Storyboard.TargetProperty="RenderTransform.Y"
PathGeometry="{StaticResource AnimationPath}"
Source="Y"
Duration="0:0:.2"
/>
</Storyboard>
</BeginStoryboard>
</MultiTrigger.EnterActions>
</MultiTrigger>
</Style.Triggers>
</Style>
</Page.Resources>
<Grid>
<Canvas HorizontalAlignment="Stretch" Name="MyCanvas" VerticalAlignment="Stretch" IsManipulationEnabled="True">
<Button Style="{StaticResource secondButton}" Canvas.Left="150" Canvas.Top="240" Height="48" x:Name="theButton" Width="115" FontSize="18" ForceCursor="False">
<Button.RenderTransform>
<TranslateTransform x:Name="AnimatedTranslateTransform"/>
</Button.RenderTransform>
</Button>
</Canvas>
</Grid>
In my second multitrigger condition I am trying to check where the button is (in order to start the right storyboard). I simply cannot seem to figure out which property and value to use in the condition to evaluate whether the button is on the left or right hand side. The TranslateTransform.X property doesn't work. Any suggestions?
You need to work with MultiDataTrigger here to get through the Transform:
<Style x:Key="secondButton" TargetType="Button">
<Style.Triggers>
<!-- ... -->
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding RelativeSource={RelativeSource Self}, Path=IsMouseOver}"
Value="True" />
<Condition Binding="{Binding RelativeSource={RelativeSource Self}, Path=RenderTransform.X}"
Value="0"/>
</MultiDataTrigger.Conditions>
<!-- ... -->
</MultiDataTrigger>
</Style.Triggers>
</Style>
(In a normal MultiTrigger you can only access immediate properties, Property="TranslateTransform.X" is not only wrong since it should be Property="RenderTransform.X" but it further would be evaluated as an attached property which does not exist)
Since you named the Transform you could also use a binding using ElementName instead of RelativeSource in the second condition.
<Condition Binding="{Binding ElementName=AnimatedTranslateTransform, Path=X}"
Value="0"/>

Animate 'Style' property of Control using ObjectAnimationUsingKeyFrames in WPF

I am trying to animate 'Style' property using ObjectAnimationUsingKeyFrames. When I run the sample below, I just see empty window and there are no any exceptions.
Almost the same sample works in Silverlight. In WPF it works too, if I assign 'Style' property of the control directly. Does anyone know if it is possible to animate 'Style' property in WPF?
Many thanks.
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:this="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525"
>
<Window.Resources>
<ResourceDictionary>
<Style x:Key="TestStyle" TargetType="Control">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Canvas x:Name="Rectangle">
<Rectangle Width="200" Height="150" Fill="Red"/>
</Canvas>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
</Window.Resources>
<Canvas>
<Canvas.Triggers>
<EventTrigger RoutedEvent="Canvas.Loaded">
<BeginStoryboard>
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Target" Storyboard.TargetProperty="Style" >
<DiscreteObjectKeyFrame KeyTime="0:0:0.0" Value="{StaticResource ResourceKey=TestStyle}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Canvas.Triggers>
<Canvas.Children>
<ContentControl x:Name="Target"/>
</Canvas.Children>
</Canvas>
When ObjectAnimationUsingKeyFrames tries to animate to a value that is derived from DependencyObject, it attempts to freeze the object first. If the object can't be frozen, it throws an exception and the animation does not run.
If you are animating a value of a custom type that you wrote, it appears you need to either derive from Freezable or NOT derive from DependencyObject.
For properties that already exist that derive from DependencyObject and not Freezable, you can't animate them (StyleProperty or TemplateProperty are cases in point). Try using a property setter inside of a style:
<Style.Triggers>
<Trigger Property="IsEnabled" Value="True">
<Setter Property="Template" Value="{StaticResource TestTemplate}"/>
</Trigger>
</Style.Triggers>
Build all of the transition logic into the style instead of switching between different styles. A challenge that you may have with this is that the target property has to be a dependency property so you can't use IsLoaded.
I hope you find this useful.
One final thought: It is possible to define custom animations, although I have not done this myself. There's an outside chance that you could write your own custom "ObjectAnimation" that would not be restricted to Freezable or non-DependencyObject classes.

Resources