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"/>
Related
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.
I have a problem with an animation within a template of an ItemsControl that is part of the template of another ItemsControl. I want a path, that is representing an Icon, to change its color and constantly rotate when a certain condition becomes true.
The DataTrigger generally works, causing the Fill of the Path changing from Gray to LightGreen when the producing Property changes to true. However, the animation does not start. When I let the animation start with the Loaded Event (as you can see in the commented section), it starts properly. So I know that the animation, as well as the DataTrigger, is configured correctly.
When I put the same Path (just copy and paste) in the outside ItemsControl and change the DataTrigger to a Property of the corresponding DataType, the animation also works as expected. So there seems to be a problem with the nested ItemsControls, but I have no idea what it might be.
<ItemsControl ItemsSource="{Binding Computers}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type models:ClientComputerWrapper}">
<Border Margin="5" BorderBrush="Black" BorderThickness="1" Padding="5">
<StackPanel>
<!-- Some Content -->
<ItemsControl ItemsSource="{Binding PlcReaderStatuses}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type resources:PlcReaderStatusResource}">
<DockPanel LastChildFill="False">
<!-- Some Content -->
<Path DockPanel.Dock="Left" HorizontalAlignment="Left" Data="{StaticResource GearIconGeometry}" Stretch="Uniform" RenderTransformOrigin="0.5, 0.5">
<Path.RenderTransform>
<RotateTransform x:Name="gearPathTransform"/>
</Path.RenderTransform>
<Path.Style>
<Style TargetType="Path">
<Setter Property="Fill" Value="Gray"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsProducing}" Value="True">
<Setter Property="Fill" Value="LightGreen"/>
<DataTrigger.EnterActions>
<BeginStoryboard x:Name="rotateStoryBoard">
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="(Path.RenderTransform).(RotateTransform.Angle)" To="360" Duration="0:0:2" RepeatBehavior="Forever"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<RemoveStoryboard BeginStoryboardName="rotateStoryBoard"/>
</DataTrigger.ExitActions>
</DataTrigger>
<EventTrigger RoutedEvent="Loaded">
<!--<BeginStoryboard x:Name="rotateStoryBoard">
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="(Path.RenderTransform).(RotateTransform.Angle)" To="360" Duration="0:0:2" RepeatBehavior="Forever"/>
</Storyboard>
</BeginStoryboard>-->
</EventTrigger>
</Style.Triggers>
</Style>
</Path.Style>
</Path>
</DockPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
I've finally found a solution. After trying to replace the DoubleAnimation with a ColorAnimation, using (Path.Fill).(SolidColorBrush.Color) as TargetProperty, I received an error that the path is not pointing to a DependencyProperty. This was only solved when I defined the SolidColorBrush explicitly in a Setter which led my to try the same with the RenderTransform of the Path:
<Path.Style>
<Style TargetType="Path">
<Setter Property="RotateTransform">
<Setter.Value>
<RotateTransform x:Name="gearPathTransform"/>
</Setter.Value>
</Setter>
<!-- ... -->
</Style>
Afterwards the animation works as expected. I still don't know why this is only necessary in the inner ItemsControl and it is also nasty that there is no error as is for the ColorAnimation, but at least it is working now.
this is a somewhat odd one.
I'm looking to use XAML to get an expander (or anything with checked and unchecked states such as a Togglebutton and the like) to set itself back to being collapsed/unchecked after being 'ignored' (for lack of a better term) after a given period of time.
Currently the parameters I'm using to decide if it's being 'ignored' are that
MouseOver=False and IsExpanded=True, when this trigger is raised, it'll begin a storyboard that, after about 3 seconds will set "IsExpanded" to "False".
Which is working just fine.
The problem is when you then want to RE-expand the expander - you can't, because the storyboard appears to be holding it as False.
I'm probably just doing something dumb/not considering some angle of this, but it's incredibly frustrating.
If it should turn out that it can't be done with XAML alone then that's okay, but i would prefer a purely XAML solution if one exists.
To better illustrate the issue, i've made a sample project you can look at that can be downloaded Here.
Alternatively, here's the code to view:
<Window.Resources>
<Style x:Key="ExpanderStyle" TargetType="{x:Type Expander}">
<Style.Resources>
<Storyboard x:Key="ExpanderSelfCollapseStoryboard">
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="{x:Null}" Storyboard.TargetProperty="(Expander.IsExpanded)">
<DiscreteBooleanKeyFrame KeyTime="0:0:3" Value="False" />
</BooleanAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(FrameworkElement.Tag)" Storyboard.TargetName="{x:Null}">
<DiscreteObjectKeyFrame KeyTime="0" Value="Not long..."/>
<DiscreteObjectKeyFrame KeyTime="0:0:3" Value="Notice you can't open the expander anymore :("/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</Style.Resources>
<Style.Triggers>
<MultiTrigger>
<MultiTrigger.EnterActions>
<BeginStoryboard x:Name="ExpanderSelfCollapseStoryboard_BeginStoryboard" Storyboard="{StaticResource ExpanderSelfCollapseStoryboard}" />
</MultiTrigger.EnterActions>
<MultiTrigger.Conditions>
<Condition Property="IsExpanded" Value="True" />
<Condition Property="IsMouseOver" Value="False" />
</MultiTrigger.Conditions>
</MultiTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<Expander x:Name="localExpander"
Background="DodgerBlue"
Header="Expand me!"
Style="{StaticResource ExpanderStyle}"
Tag="Up there!">
<Border Height="200"
Margin="5"
Background="Orange">
<TextBlock HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="20"
FontWeight="Bold"
Foreground="White"
Text="Now move your mouse over the whitespace for a few seconds please."
TextAlignment="Center"
TextWrapping="Wrap" />
</Border>
</Expander>
<TextBlock Grid.Row="1"
VerticalAlignment="Center"
FontSize="22"
Foreground="LightGray"
Text="{Binding ElementName=localExpander,
Path=Tag}"
TextAlignment="Center" />
</Grid>
I'd be thankful for any help or advice anyone can render.
Thanks,
-- Logan
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>
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.