1

This piece of code animates the movement of an ellipse if click on this. How can I return (in an animated way - as the storyboard does) the ellipse in the initial position by clicking again on its new position. Is that possible? (preferably only in XAML)

<Ellipse x:Name="circle_button" HorizontalAlignment="Left" Height="100" Margin="30,40,0,0" VerticalAlignment="Top" Width="100" Fill="#FF33D3A7" >
        <Ellipse.Triggers>
            <EventTrigger RoutedEvent="Ellipse.MouseDown" >
                <BeginStoryboard>
                    <Storyboard>
                        <ThicknessAnimationUsingKeyFrames Storyboard.TargetProperty="Margin" BeginTime="00:00:00">
                            <SplineThicknessKeyFrame KeyTime="00:00:00" Value="30,40,0,0" />
                            <SplineThicknessKeyFrame KeyTime="00:00:00.4" Value="95,120,0,0" />
                        </ThicknessAnimationUsingKeyFrames>
                    </Storyboard>
                </BeginStoryboard>
            </EventTrigger>
        </Ellipse.Triggers>

gts13
  • 1,048
  • 1
  • 16
  • 29
  • 1
    You can stop [storyboard](https://msdn.microsoft.com/en-us/library/ms742868(v=vs.110).aspx) (by default animations are [holding value](https://msdn.microsoft.com/en-us/library/system.windows.media.animation.timeline.fillbehavior(v=vs.110).aspx) you can use `"HoldBehavior=Stop"` to release it to initial at the end of storyboard). – Sinatr Mar 27 '17 at 08:46
  • @Sinatr there is no HoldBehavior property. Only FillBehavior and it doesn't work. – gts13 Mar 27 '17 at 08:54
  • if i add FillBehavior="Stop" it automatically returns the ellipse in the previous position. I am not trying to achieve that. I want to click again on its new position and then send it back to the initial position – gts13 Mar 27 '17 at 09:08
  • 1
    Sorry for typo, `FillBehavior=Stop` indeed. I didn't meant it as solution, but as a way to see what animations are *holding* values. If you want to avoid code-behind you have to use 2 ellipses, as it's not possible purely in xaml handle state switching logic (you can write behavior to do so). Otherwise using 2 `VisualState` and switching between them in code-behind (similar to @macieqqq answer) should be more appropriate – Sinatr Mar 27 '17 at 10:05

2 Answers2

2

An alternative is to use visual states and simply switch between them in code-behind. That might be more clearer approach compared to holding animations as resources.

xaml:

<Ellipse x:Name="circle_button"
         HorizontalAlignment="Left"
         Height="100"
         Margin="30,40,0,0"
         VerticalAlignment="Top"
         Width="100"
         Fill="#FF33D3A7"
         MouseDown="circle_button_MouseDown">
    <VisualStateManager.VisualStateGroups>
        <VisualStateGroup>
            <VisualState x:Name="A">
                <Storyboard>
                    <ThicknessAnimationUsingKeyFrames Storyboard.TargetProperty="Margin">
                        <SplineThicknessKeyFrame KeyTime="0:0:0.4" Value="95,120,0,0" />
                    </ThicknessAnimationUsingKeyFrames>
                </Storyboard>
            </VisualState>
            <VisualState x:Name="B">
                <Storyboard>
                    <ThicknessAnimationUsingKeyFrames Storyboard.TargetProperty="Margin">
                        <SplineThicknessKeyFrame KeyTime="0:0:0.4" Value="30,40,0,0" />
                    </ThicknessAnimationUsingKeyFrames>
                </Storyboard>
            </VisualState>
        </VisualStateGroup>
    </VisualStateManager.VisualStateGroups>
</Ellipse>

cs:

bool _isStateB;

void circle_button_MouseDown(object sender, MouseButtonEventArgs e)
{
    _isStateB = !_isStateB;
    VisualStateManager.GoToElementState(circle_button, _isStateB ? "B" : "A", true);
}

Demo:

Instead of Ellipse a Button can be used (with style containing such ellipse), then you'll have Click event and ability to focus and click element with keyboard.

P.S.: after writing the answer I suddenly have a though.. ToggleButton has 2 states, you can in fact use IsChecked to toggle between 2 positions (and run different animations)... until you add third, then solution with visual states is preferable.

Sinatr
  • 20,892
  • 15
  • 90
  • 319
  • Interesting idea the ToggleButton but could you provide a link with some sample or similar? I don't have great experience on WPF and XAML. Thanks a lot for your answer. – gts13 Mar 27 '17 at 10:27
1

I think that one of possible ways is to define to storyboard and use some code behind to trigger animations.

Here is an example:

Xaml:

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApplication1"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <Storyboard x:Key="ElipseStoryboard">
            <ThicknessAnimationUsingKeyFrames Storyboard.TargetProperty="Margin" BeginTime="00:00:00">
                <SplineThicknessKeyFrame KeyTime="00:00:00" Value="30,40,0,0" />
                <SplineThicknessKeyFrame KeyTime="00:00:00.4" Value="95,120,0,0" />
            </ThicknessAnimationUsingKeyFrames>
        </Storyboard>
        <Storyboard x:Key="ElipseStoryboardReversed">
            <ThicknessAnimationUsingKeyFrames Storyboard.TargetProperty="Margin" BeginTime="00:00:00">
                <SplineThicknessKeyFrame KeyTime="00:00:00" Value="95,120,0,0" />
                <SplineThicknessKeyFrame KeyTime="00:00:00.4" Value="30,40,0,0" />
            </ThicknessAnimationUsingKeyFrames>
        </Storyboard>
    </Window.Resources>
    <Grid x:Name="CP">
        <Ellipse x:Name="circle_button" MouseDown="Circle_button_OnMouseDown" HorizontalAlignment="Left" Height="100" Margin="30,40,0,0" VerticalAlignment="Top" Width="100" Fill="#FF33D3A7" >
        </Ellipse>
    </Grid>
</Window>

Code behind:

namespace WpfApplication1
{    
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private bool flag = false;
        private void Circle_button_OnMouseDown(object sender, MouseButtonEventArgs e)
        {
            if (flag)
            {
                var storyboard = this.Resources["ElipseStoryboard"] as Storyboard;
                if (storyboard != null)
                    storyboard.Begin(circle_button);
            }
            else
            {
                var storyboard = this.Resources["ElipseStoryboardReversed"] as Storyboard;
                if (storyboard != null)
                    storyboard.Begin(circle_button);
            }
            flag = !flag;
        }
    }
}

Please try it.

Alternative only Xaml solution :

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApplication1"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">

    <Grid x:Name="CP">
        <Ellipse x:Name="circle_button" HorizontalAlignment="Left" Height="100" Margin="30,40,0,0" VerticalAlignment="Top" Width="100" Fill="#FF33D3A7" >
            <Ellipse.Triggers>
                <EventTrigger RoutedEvent="Ellipse.MouseDown" >
                    <BeginStoryboard>
                        <Storyboard>
                            <ThicknessAnimationUsingKeyFrames Storyboard.TargetProperty="Margin" BeginTime="00:00:00">
                                <SplineThicknessKeyFrame KeyTime="00:00:00" Value="30,40,0,0" />
                                <SplineThicknessKeyFrame KeyTime="00:00:00.4" Value="95,120,0,0" />
                            </ThicknessAnimationUsingKeyFrames>
                            <DoubleAnimation Storyboard.TargetProperty="Opacity" Storyboard.TargetName="circle_button"
                        From="1.0" To="0.0" Duration="0:0:0" BeginTime="00:00:00.4"></DoubleAnimation>
                            <DoubleAnimation Storyboard.TargetProperty="Opacity" Storyboard.TargetName="circle_button2"
                        From="0.0" To="1.0" Duration="0:0:0" BeginTime="00:00:00.4"></DoubleAnimation>
                        </Storyboard>
                    </BeginStoryboard>
                </EventTrigger>
            </Ellipse.Triggers>
            </Ellipse>

        <Ellipse x:Name="circle_button2" Opacity="0" HorizontalAlignment="Left" Height="100" Margin="95,120,0,0" VerticalAlignment="Top" Width="100" Fill="#FF33D3A7" >
            <Ellipse.Triggers>
                <EventTrigger RoutedEvent="Ellipse.MouseDown" >
                    <BeginStoryboard>
                        <Storyboard>
                            <ThicknessAnimationUsingKeyFrames Storyboard.TargetProperty="Margin" BeginTime="00:00:00" Storyboard.TargetName="circle_button">
                                <SplineThicknessKeyFrame KeyTime="00:00:00" Value="95,120,0,0" />
                                <SplineThicknessKeyFrame KeyTime="00:00:00.4" Value="30,40,0,0" />
                            </ThicknessAnimationUsingKeyFrames>
                            <DoubleAnimation Storyboard.TargetProperty="Opacity" Storyboard.TargetName="circle_button2"
                        From="1.0" To="0.0" Duration="0:0:0" ></DoubleAnimation>
                            <DoubleAnimation Storyboard.TargetProperty="Opacity" Storyboard.TargetName="circle_button"
                        From="0.0" To="1.0" Duration="0:0:0"></DoubleAnimation>
                        </Storyboard>
                    </BeginStoryboard>
                </EventTrigger>
            </Ellipse.Triggers>
        </Ellipse>
    </Grid>

</Window>
macieqqq
  • 363
  • 2
  • 11
  • thanks a lot for you nice answer. Besides the possible ways to do that in conjunction with code, do you think it is possible to be done only in XAML? – gts13 Mar 27 '17 at 09:22
  • 2
    I think that it is possible. But it will not be easy. You can for example create two elipses put trigger with proper storyboard to each one and manipulate visibility of the elipses at the end of the storyboard. I think that i can show you an example when I find a time to prepare it. – macieqqq Mar 27 '17 at 09:45
  • this is something that I already thought. But I think it doesn't make much sense to create two different ellipses for representing a single button. If there is no other way, I prefer to stick to your solution. Thanks. – gts13 Mar 27 '17 at 09:53
  • 1
    I have just prepared the example - it is added to this post. It is up to you how to implement it. I prefer implementation in code behind. Xaml could become more and more complicated during application grow. – macieqqq Mar 27 '17 at 10:00