How to animate an Image in a button to shake every 30 seconds in WPF?

Deadly 提交于 2019-12-01 14:45:49

Here it is an attached behaviour. Just be careful as it may be destructive if your control has existing render transforms

public class Wibble
{
    public static bool GetWobble(DependencyObject obj)
    {
        return (bool)obj.GetValue(WobbleProperty);
    }

    public static void SetWobble(DependencyObject obj, bool value)
    {
        obj.SetValue(WobbleProperty, value);
    }

    public static readonly DependencyProperty WobbleProperty = DependencyProperty.RegisterAttached("Wobble", typeof(bool), typeof(Wibble), new UIPropertyMetadata(false, new PropertyChangedCallback(OnWobbleChanged)));

    private static void OnWobbleChanged(object sender, DependencyPropertyChangedEventArgs args)
    {
        var image = sender as Image;

        if (image == null)
            throw new InvalidOperationException("only images can wobble!");

        // don't really need this check (the find ancestor binding would still find the button), but the spec said the image should be the only child of the button
        var button = LogicalTreeHelper.GetParent(image) as Button;
        if (button == null)
            throw new InvalidOperationException("only images that are the only child of a button can wobble!");

        var previousStyle = image.Style;

        var newStyle = new Style(image.GetType(), previousStyle);

        // this will override any existing render transform + origin on the button, hope they didn't already have one (and I'm too lazy to check)
        newStyle.Setters.Add(new Setter(Image.RenderTransformProperty, new RotateTransform(0)));
        newStyle.Setters.Add(new Setter(Image.RenderTransformOriginProperty, new Point(0.5, 0.5)));

        var trigger = new DataTrigger();

        var binding = new Binding();

        var relativeSource = new RelativeSource();
        relativeSource.Mode = RelativeSourceMode.FindAncestor;
        relativeSource.AncestorType = typeof(Button);

        binding.RelativeSource = relativeSource;
        binding.Path = new PropertyPath(Button.VisibilityProperty);

        trigger.Binding = binding;
        trigger.Value = Visibility.Visible;

        var storyboard = new Storyboard();

        var animation = new DoubleAnimationUsingKeyFrames();
        animation.SetValue(Storyboard.TargetPropertyProperty, new PropertyPath("(0).(1)", Image.RenderTransformProperty, RotateTransform.AngleProperty));
        animation.Duration = new Duration(TimeSpan.FromSeconds(5)); // spec said 30, but i wanted to actually see it happen!
        animation.KeyFrames.Add(new LinearDoubleKeyFrame(-12, KeyTime.FromTimeSpan(TimeSpan.FromSeconds(0.2))));
        animation.KeyFrames.Add(new LinearDoubleKeyFrame(12, KeyTime.FromTimeSpan(TimeSpan.FromSeconds(0.4))));
        animation.KeyFrames.Add(new LinearDoubleKeyFrame(0, KeyTime.FromTimeSpan(TimeSpan.FromSeconds(0.5))));

        storyboard.Children.Add(animation);
        storyboard.RepeatBehavior = RepeatBehavior.Forever;

        var beginStoryboard = new BeginStoryboard();
        beginStoryboard.Storyboard = storyboard;
        beginStoryboard.Name = "its_wobble_time"; // it is

        trigger.EnterActions.Add(beginStoryboard);

        var removeStoryboard = new RemoveStoryboard();
        removeStoryboard.BeginStoryboardName = beginStoryboard.Name;

        trigger.ExitActions.Add(removeStoryboard);

        newStyle.Triggers.Add(trigger);

        image.Style = newStyle;
    }
}

here is how it would be used:

<Button Width="100" Height="25" >
        <Image Source="Untitled.png" xmlns:local="clr-namespace:WpfApplication17" local:Wibble.Wobble="True" />
    </Button>

Create a WPF custom control by adding a new item in VS and then navigating to the WPF templates. This will allow you to select "Custom Control (WPF)". Name it "ShakyImageControl". This will create a Themes folder with a generic.xaml in it and a "ShakyImageControl.cs" class file. In the generic.xaml replace the existing style with the following:

<Style TargetType="{x:Type local:ShakyImageControl}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type local:ShakyImageControl}">
                <Image x:Name="image" Source="{TemplateBinding ImageSource}" RenderTransformOrigin="0.5,0.5">
                    <Image.RenderTransform>
                        <TransformGroup>
                            <RotateTransform x:Name="Rotaty"/>
                        </TransformGroup>
                    </Image.RenderTransform>       
                </Image>
                <ControlTemplate.Triggers>
                    <DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Button}, Path=Visibility}" Value="Visible">
                        <DataTrigger.EnterActions>
                            <BeginStoryboard Name="fred">
                                <Storyboard AutoReverse="False" RepeatBehavior="Forever" Duration="0:0:30" Storyboard.TargetName="Rotaty" Storyboard.TargetProperty="Angle">
                                    <DoubleAnimationUsingKeyFrames>
                                        <EasingDoubleKeyFrame KeyTime="0:0:0.2" Value="-12.0"/>
                                        <EasingDoubleKeyFrame KeyTime="0:0:0.4" Value="12.0"/>
                                        <EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="0"/>
                                    </DoubleAnimationUsingKeyFrames>
                                </Storyboard>
                            </BeginStoryboard>
                        </DataTrigger.EnterActions>
                        <DataTrigger.ExitActions>
                            <StopStoryboard BeginStoryboardName="fred"/>
                        </DataTrigger.ExitActions>
                    </DataTrigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

In the ShakyImageControl class add a dependency property as follows:

static ShakyImageControl()
    {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(ShakyImageControl), new FrameworkPropertyMetadata(typeof(ShakyImageControl)));
    }

    public ImageSource ImageSource
    {
        get { return (ImageSource)GetValue(ImageSourceProperty); }
        set { SetValue(ImageSourceProperty, value); }
    }

    // Using a DependencyProperty as the backing store for ImageSource.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty ImageSourceProperty =
        DependencyProperty.Register("ImageSource", typeof(ImageSource), typeof(ShakyImageControl), new UIPropertyMetadata(null));

To use the shakyImage in a button just do:

<Button Height="50" Width="500" Name="showy" Visibility="Collapsed"> 
        <local:ShakyImageControl ImageSource="\Expand.png"/>
    </Button>

local is an xml namespace like "xmlns:local="clr-namespace:WpfApplication6"

NB: your custom control can be in a seperate assembly if you want

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!