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}" />
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 wish to attach a time delay to a mouseover event on a WPF expander I have on my form (xaml supported by VB.NET code behind). This mouseover event essentially triggers the expansion as oppose to clicking - but I'd like a short wait before the content is expanded. So far I have not managed to find anything to solve this via the wider internet.
The current xaml code to enable the trigger is:
<Style x:Key="HoverExpander" TargetType="{x:Type Expander}">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="IsExpanded" Value="True" />
</Trigger>
</Style.Triggers>
</Style>
This style is then applied to:
<Expander Style="{StaticResource HoverExpander}"
HorizontalAlignment="Right"
ExpandDirection="Left"
Height="Auto"
Width="Auto">
<!-- Content here -->
</Expander>
Note that I've stripped out other aesthetics (such as borders, gridrefs etc for readability).
I think there should be some way to set a delay on the MouseOver Trigger but haven't had much luck finding it. This could either be set in xaml or perhaps as an event in the code behind.
I'm working on this currently, so when I find a solution I shall post it here. Grateful for any ideas meantime. Thanks!
Use an EventTrigger on the MouseOver event and a Storyboard with a BooleanAnimationUsingKeyFrames instead. In the Timeline of the Storyboard, you could have KeyFrames, so that the animation waits for some time before it affects the properties you want to change.
This was the code I settled on - based on the ideas already given:
<Style x:Key="HoverExpander" TargetType="{x:Type Expander}">
<Style.Setters>
<Setter Property="IsExpanded" Value="False"/><!-- Initially collapsed -->
</Style.Setters>
<Style.Triggers>
<!-- Impose a short delay (500ms) before expanding control -->
<EventTrigger RoutedEvent="Expander.MouseEnter">
<BeginStoryboard>
<Storyboard>
<BooleanAnimationUsingKeyFrames
Storyboard.TargetProperty="IsExpanded"
Duration="0:0:0.5">
<DiscreteBooleanKeyFrame Value="True" KeyTime="100%"/><!-- I.E. after 500ms -->
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<!-- Collapse when mouse leaves control-->
<EventTrigger RoutedEvent="Expander.MouseLeave">
<BeginStoryboard>
<Storyboard>
<BooleanAnimationUsingKeyFrames
Storyboard.TargetProperty="IsExpanded"
Duration="0:0:0.1">
<DiscreteBooleanKeyFrame Value="False" KeyTime="0%"/><!-- I.E. Immediately -->
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Style.Triggers>
</Style>
Then apply as before. This was tested and works in .NET 4.0. Other neat tricks could be applied if you do so wish, I found the following to be quite helpful in getting ideas:
Animation Overview (MSDN)
Storyboards Overview (MSDN)
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.
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"/>