I have a Stackpanel One, which has some content, an Image, and a defualt hidden SubStackpanel. When clickin the Image, the image should rotate 90 degrees, and slide down the SubStackpanel.
When clicking the Image again, the Image should rotate back to its original position, and the SubStackpanel should slide up to the default hidden position.
I almost got this working, the problem is that I dont know how to use the same Trigger event, on two different Storyboard animations. So right now only the first animation on the button and the SubStackpanel occurs, everytime the Image is clicked.
I´ve tried the AutoReverse property, but it fires immediately after the animation is done. This should of course only be happening when the user clicks the Image the second time.
I would like to achieve this, only using markup.
This is my currently code:
<Grid>
<StackPanel Grid.Row="0" Orientation="Vertical" Background="Beige" >
<StackPanel.Triggers>
<EventTrigger SourceName="ImageShowPanelTwo" RoutedEvent="Image.MouseDown">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetName="SubPanel" Storyboard.TargetProperty="(StackPanel.Height)" From="0" To="66" Duration="0:0:0.5" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<EventTrigger SourceName="ImageShowPanelTwo" RoutedEvent="Image.MouseDown">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetName="SubPanel" Storyboard.TargetProperty="(StackPanel.Height)" From="66" To="0" Duration="0:0:0.5" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</StackPanel.Triggers>
<TextBlock>Panel One</TextBlock>
<Image Name="ImageShowPanelTwo" Width="26" Height="26" Source="ImageRotate.png" RenderTransformOrigin=".5,.5" >
<Image.RenderTransform>
<RotateTransform x:Name="AnimatedRotateTransform" Angle="0" />
</Image.RenderTransform>
<Image.Triggers>
<EventTrigger RoutedEvent="Image.MouseDown">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetName="AnimatedRotateTransform"
Storyboard.TargetProperty="Angle"
By="0"
To="90"
Duration="0:0:0.5"
AutoReverse="True"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<EventTrigger RoutedEvent="Image.MouseDown">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetName="AnimatedRotateTransform"
Storyboard.TargetProperty="Angle"
By="90"
To="0"
Duration="0:0:0.5"
AutoReverse="True"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Image.Triggers>
</Image>
<StackPanel Name="SubPanel" Background="LightGreen" Height="66">
<TextBlock>SubPanel</TextBlock>
<TextBlock>SubPanel</TextBlock>
</StackPanel>
</StackPanel>
</Grid>
Hope you can help :)
Blend for Visual Studio can be used to do it in a better, simpler way, without dealing with a lot of code.
Here is the list of steps of doing it.
Step 1: Open the project in Blend for Visual Studio and in Visual Studio simultaneously.
Step 2: In Blend, create a new storyboard around all the elements that you wish to animate with it.
Let's call it "Storyboard1".
Step 3: Now, open the storyboard that we just created and click on the small arrow just beside the "+" and click on "Duplicate" in the drop down menu. Then, a new storyboard called "Storyboard1_Copy" will be created.
Step 4: Rename this new storyboard to something that you like, say, "Storyboard1_Rev".
Step 5: You must have guessed it by now. Select the duplicated storyboard and from the drop down menu, click on "Reverse".
Step 6: Now you have two storyboards ready: one for animating some elements as you like and the other for reversing that sequence of animation. Just like you call a storyboard from the C# code, you can call the reversing storyboard from the same, subject to some conditions which check if the elements are already animated or not. For this, I use a bool variable, whose value is changed each time some animation occurs on a set of elements(that is, false if the elements are not already animated and true if they are).
Illustration with an example:
I'll create an application with a simple layout. It has a button, an image and a rectangular area on the screen.
The idea is that, whenever you click on the button once, the image should be maximized and should be minimized back to original size when the button is clicked twice and so on. That is, the reverse animation should happen every other time the button is clicked. Here are some screenshots showing how it happens:
You can see that the current state of the image is shown in the button. It shows "Zoom In" when the image is in its initial small size and "Zoom Out" when it is maximized.
And finally, here is the C# code for handling the clicks of the button:
bool flag = false;
private void Button_Click(object sender, RoutedEventArgs e)
{
if (!flag)
{
Button.Content = "Zoom Out";
Storyboard1.Begin();
flag = true;
}
else
{
Button.Content = "Zoom In";
Storyboard1_Rev.Begin();
flag = false;
}
}
All you have to do is have a status flag that shows the current status of the element(s) that you wish to animate and animate it(them) in forward or reverse timeline as per the value of the flag.
Instead of trying to set the Animation using that event, use a bool property to bind to instead:
<Image Name="ImageShowPanelTwo" Width="26" Height="26" Source="ImageRotate.png"
RenderTransformOrigin=".5,.5" >
<Image.RenderTransform>
<RotateTransform x:Name="AnimatedRotateTransform" Angle="0" />
</Image.RenderTransform>
<Image.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding IsRotated}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="AnimatedRotateTransform"
Storyboard.TargetProperty="Angle"
By="0"
To="90"
Duration="0:0:0.5"
AutoReverse="True"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
<DataTrigger Binding="{Binding IsRotated}" Value="False">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="AnimatedRotateTransform"
Storyboard.TargetProperty="Angle"
By="90"
To="0"
Duration="0:0:0.5"
AutoReverse="True"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
I trust that you can define your own bool property and invert it upon each MouseDown event occurrence to complete this functionality. As it is set to true the first Animation will start and as it is set to false the second will start.
This is how I solved my problem:
Created four storyboards for the Stackpanel and the arrow:
<Storyboard x:Key="RotateIconUp">
<DoubleAnimation Storyboard.TargetName="IconExpand" Storyboard.TargetProperty="(TextBlock.RenderTransform).(RotateTransform.Angle)" From="0" To="90" Duration="0:0:0.4" />
</Storyboard>
<Storyboard x:Key="RotateIconDown">
<DoubleAnimation Storyboard.TargetName="IconExpand" Storyboard.TargetProperty="(TextBlock.RenderTransform).(RotateTransform.Angle)" From="90" To="0" Duration="0:0:0.4" />
</Storyboard>
<Storyboard x:Key="SlideGridDown">
<DoubleAnimation Storyboard.TargetName="GridDetails" Storyboard.TargetProperty="(Grid.Height)" From="0" To="180" Duration="0:0:0.4" />
</Storyboard>
<Storyboard x:Key="SlideGridUp">
<DoubleAnimation Storyboard.TargetName="GridDetails" Storyboard.TargetProperty="(Grid.Height)" From="180" To="0" Duration="0:0:0.4" />
</Storyboard>
Then I trigger the storyboards from codebehind when the arrow is clicked:
private void ExpandDetails() {
try {
if (!pm_IsExanded) {
Storyboard Storyboard = (Storyboard)FindResource("RotateIconUp");
Storyboard.Begin(this);
Storyboard = (Storyboard)FindResource("SlideGridDown");
Storyboard.Begin(this);
pm_IsExanded = true;
BorderMain.BorderBrush = pm_BrushConverter.ConvertFromString("#000000") as Brush;
} else {
Storyboard Storyboard = (Storyboard)FindResource("RotateIconDown");
Storyboard.Begin(this);
Storyboard = (Storyboard)FindResource("SlideGridUp");
Storyboard.Begin(this);
pm_IsExanded = false;
BorderMain.BorderBrush = pm_BrushConverter.ConvertFromString("#d0d0d0") as Brush;
}
} catch (Exception ee) {
GlobalResource.WriteToLog("Error in ExpandDetails", ee);
}
}
Related
I have a ToggleButton which, when clicked, slides out so the user can verify/edit the URL (textbox in button) they have copied (clipboard). The ToggleButton is then clicked again, which sets in motion various URL checks and slides back in to its original start position. This works perfectly. (code below).
What I would really like to do a this point is to disable this trigger. I want the button to not 'slide out', because on the next click it will download. So I'm trying to find a way to disable the trigger. When the file has downloaded all OK I'll re-enable the trigger to a state it was initially.
The togglebutton style starts like this (in my Application.xaml:
<Style x:Key="ToggleButtonURL" TargetType="{x:Type ToggleButton}">
It sets various colour/font properties and the triggers of interest:
<Style.Triggers>
<EventTrigger RoutedEvent="Checked">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation From="-554" To="0" Duration="0:0:0.2" Storyboard.TargetProperty="(Canvas.Left)" AutoReverse="False" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<EventTrigger RoutedEvent="Unchecked">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation From="0" To="-554" Duration="0:0:0.150" Storyboard.TargetProperty="(Canvas.Left)" AutoReverse="False" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Style.Triggers>
I'm just trying to find a way the enable/disable this trigger from code. Thank you if you can be of any help.
Try setting the e.Handled = true of your event argument only on the condition when you don't want the animation to run. If that is still beating you with the quick in-out movement then use a PreviewEvent instead of a direct event.
In my WPF appliction, I have a partially visible grid at the bottom of the visible space. When you mouseover it, it slides up and everything in the grid is visible. When the mouse leaves the grid, it slides back down. There are Combo and TextBoxes in the grid that will contain info and be able to be updated by the user.
How do I make it so that if I click anywhere in the grid, the MouseLeave animation no longer goes off (the grid stays up even you if your mouse leaves)?
I have implemented my animation 2 different ways in an attempt to get it to behave the way I want, but no luck so far.
Implementation 1:
`<Grid x:Name="PopupAnimation" HorizontalAlignment="Stretch" VerticalAlignment="Bottom" Height="30">
<Grid.Triggers>
<EventTrigger RoutedEvent="MouseEnter">
<BeginStoryboard Name="MouseOn">
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Height" From="30" To="90" Duration="0:0:.15"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<EventTrigger RoutedEvent="MouseLeave">
<BeginStoryboard Name="MouseOff">
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Height" From="90" To="30" Duration="0:0:0.15"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<EventTrigger RoutedEvent="MouseLeftButtonDown">
<StopStoryboard BeginStoryboardName="MouseOff" />
</EventTrigger>
</Grid.Triggers>
`
Implementation 2:
<Grid>
<Grid.Style>
<Style TargetType="Grid">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Trigger.EnterActions>
<BeginStoryboard Name="MouseOn">
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Height" From="30" To="90" Duration="0:0:.15"/>
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
<Trigger.ExitActions>
<BeginStoryboard Name="MouseOff">
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Height" From="90" To="30" Duration="0:0:0.15"/>
</Storyboard>
</BeginStoryboard>
</Trigger.ExitActions>
</Trigger>
</Style.Triggers>
</Style>
</Grid.Style>
</Grid>
Are either of these heading down the right road? How do I disable (and enable) an animation?
How do I make it so that if I click anywhere in the grid, the MouseLeave animation no longer goes off (the grid stays up even you if your mouse leaves)?
Don't execute the up and down movement by style, instead subscribe to the all events in question, and with the events of mouse enter and mouse leave run the storyboards in code-behind.
Then in the final event, in code behind grid click event, set a Boolean flag which will be read by the mouse leave event and allow it to not execute the storyboard.
Here is code I have to move a window in/out in codebehind to give you an idea about the storyboards:
if (moveRight)
{
(Resources["MoveToOpen"] as Storyboard)?.Begin(this, false);
(Resources["FlipArrowClose"] as Storyboard)?.Begin(this, false);
}
else
{
if (closeWindow == false)
{
(Resources["MoveToClose"] as Storyboard)?.Begin(this, false);
(Resources["FlipArrowOpen"] as Storyboard)?.Begin(this, false);
}
}
I'm triying to hide/show a stackpanel when I click in a button. This is what I made so far:
<Button.Triggers>
<EventTrigger RoutedEvent="Mouse.PreviewMouseDown">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Height"
Storyboard.TargetName="PanelDeCampos"
From="{Binding ElementName=PanelDeCampos,Path=ActualHeight}"
To="0"
Duration="0:0:0.25" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Button.Triggers>
This animation works well, and it hides the panel when I click on it. But now I need to find a way to launch the reverse animation when the button is clicked again. Could I store the current state and decide what animation launch or something like this?
Thanks.
You can change your button to a ToggleButton and use the Checked and Unchecked routed events to set up your 2 storyboards:
<ToggleButton>
<ToggleButton.Triggers>
<EventTrigger RoutedEvent="Checked">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Height"
Storyboard.TargetName="PanelDeCampos"
From="{Binding ElementName=PanelDeCampos,Path=ActualHeight}"
To="0"
Duration="0:0:0.25" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<EventTrigger RoutedEvent="Unchecked">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Height"
Storyboard.TargetName="PanelDeCampos"
From="0"
Duration="0:0:0.25"
To="1000" /> <!-- or whatever height you want-->
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</ToggleButton.Triggers>
</ToggleButton>
What about a little trick:
Together with the storyboard you have in place now, you are hidding the current button and setting another button (which looks the same) visible.
The other button has the reverse storyboard, and reverse button visibility settings.
With that you don't have to worry about a state, and you can do it only in XAML.
Other Idea would be to handle the click in the code behind, maintain a flag there, and trigger the storyboard from the code. As this is a view-only functionality I don't see a conflict with MVVM.
I would like to zoom in on a Image when Image.MouseEnter fires and then zoom out when Image.MouseLeave fires.
I thought of creating a Trigger, but no luck.
This is what i tried so far:
<Image Name="logo" Source="{Binding Path=ImagePath}"
Width="50" Height="50">
<Image.RenderTransform>
<ScaleTransform x:Name="TransRotate" />
</Image.RenderTransform>
<Image.Triggers>
<EventTrigger RoutedEvent="Image.MouseEnter">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetName="TransRotate"
Storyboard.TargetProperty="ScaleX"
From="0" To="100"
BeginTime="0:0:0"
Duration="0:0:10"
AutoReverse="False"/>
<DoubleAnimation Storyboard.TargetName="TransRotate"
Storyboard.TargetProperty="ScaleY"
From="0" To="100"
BeginTime="0:0:0"
Duration="0:0:10"
AutoReverse="False"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Image.Triggers>
</Image>
Is this the correct way, or is there a better way?
I would remove the From property, this makes the animation jump, apart from that you just seem to be lacking the reverse animations on MouseLeave. Also to center the zoom you can set the RenderTransformOrigin of the Image to 0.5,0.5.
Instead of using those two events i usually prefer a trigger on IsMouseOver with Enter and ExitActions.
If you want to retain the space the image takes you can place it in a container with fixed size and set ClipToBounds to true.
I've got a canvas control that grows in height when you move the mouse over it and shrinks back on mouse leave.
<Canvas x:Name="infoBar" Width="720" Height="39" Background="Red">
<Canvas.Triggers>
<EventTrigger RoutedEvent="Canvas.MouseEnter">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="infoBar"
Storyboard.TargetProperty="Height"
From="39" To="255" Duration="0:0:0.5"
/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<EventTrigger RoutedEvent="Canvas.MouseLeave">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="infoBar"
Storyboard.TargetProperty="Height"
From="255" To="39" Duration="0:0:0.5"
/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Canvas.Triggers>
<StackPanel>
<TextBlock/>
<TextBlock/>
</StackPanel>
</Canvas>
This works fine. However if two quick consecutive events take place (mouseleave before mouse enter animation finishes) it goes nuts.
Is there anyway i can tell it to cancel out any other events that happen before an animation finishes?
Using your event triggers you can perform pause, stop, resume, etc. commands on named storyboards.
This article should answer your questions.