如何在按钮中为图像添加动画以在WPF中每30秒振动一次?

时间:2012-06-19 01:37:34

标签: wpf c#-4.0 animation attachedbehaviors

在涉及风格和动画的任何事情时,我都不好。

我希望能够获得一些帮助,使按钮可见性设置为Visibility.Visible时,每隔30秒制作一次按钮抖动的唯一内容。

要引起用户注意,鼓励他们点击按钮。

我更愿意将此作为图像上的附加行为,或者如果可能的话甚至是UIControl,使其易于重复使用而不是弄乱样式,因为我已经使用了来自我的控件供应商的样式,而且我不喜欢我想编辑它。

以下是我使用的解决方案

这是可以应用于任何Attached BehaviorSystem.Windows.Controls.Image

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Interactivity;
using System.Windows.Media;
using System.Windows.Media.Animation;

namespace SampleShakeBehavior
{
    public class ShakeBehavior : Behavior<Image>
    {
        private const double DefaultRepeatInterval = 10.0;
        private const double DefaultSpeedRatio = 1.0;

        private const string RepeatIntervalName = "RepeatInterval";
        private const string SpeedRatioName = "SpeedRatio";

        public static readonly DependencyProperty RepeatIntervalProperty =
            DependencyProperty.Register(RepeatIntervalName,
                                        typeof(double),
                                        typeof(ShakeBehavior),
                                        new PropertyMetadata(DefaultRepeatInterval));

        public static readonly DependencyProperty SpeedRatioProperty =
            DependencyProperty.Register(SpeedRatioName,
                                        typeof(double),
                                        typeof(ShakeBehavior),
                                        new PropertyMetadata(DefaultSpeedRatio));

        /// <summary>
        /// Gets or sets the time interval in in seconds between each shake.
        /// </summary>
        /// <value>
        /// The time interval in in seconds between each shake.
        /// </value>
        /// <remarks>
        /// If interval is less than total shake time, then it will shake
        /// constantly without pause. If this is your intention, simply set
        /// interval to 0.
        /// </remarks>
        public double RepeatInterval
        {
            get { return (double)GetValue(RepeatIntervalProperty); }
            set { SetValue(RepeatIntervalProperty, value); }
        }

        /// <summary>
        /// Gets or sets the ratio at which time progresses on the Shakes
        /// Timeline, relative to its parent. 
        /// </summary>
        /// <value> 
        /// The ratio at which time progresses on the Shakes Timeline, relative 
        /// to its parent.
        /// </value>
        /// <remarks> 
        /// If Acceleration or Deceleration are specified, this ratio is the
        /// average ratio over the natural length of the Shake's Timeline. This 
        /// property has a default value of 1.0. If set to zero or less it
        /// will be reset back to th default value.
        /// </remarks>
        public double SpeedRatio
        {
            get { return (double)GetValue(SpeedRatioProperty); }
            set { SetValue(SpeedRatioProperty, value); }
        }

        private Style _orignalStyle;
        protected override void OnAttached()
        {
            _orignalStyle = AssociatedObject.Style;
            AssociatedObject.Style = CreateShakeStyle();
        }

        protected override void  OnDetaching()
        {
            AssociatedObject.Style = _orignalStyle;
        }

        private Style CreateShakeStyle()
        {
            Style newStyle = new Style(AssociatedObject.GetType(), AssociatedObject.Style);
            /**
             * The following will replace/override any existing RenderTransform
             * and RenderTransformOrigin properties on the FrameworkElement
             * once the the new Style is applied to it.
             */
            newStyle.Setters.Add(new Setter(UIElement.RenderTransformProperty, new RotateTransform(0)));
            newStyle.Setters.Add(new Setter(UIElement.RenderTransformOriginProperty, new Point(0.5, 0.5)));

            newStyle.Triggers.Add(CreateTrigger());

            return newStyle;
        }

        private DataTrigger CreateTrigger()
        {
            DataTrigger trigger = new DataTrigger
            {
                Binding = new Binding
                {
                    RelativeSource = new RelativeSource
                    {
                        Mode = RelativeSourceMode.FindAncestor,
                        AncestorType = typeof(UIElement)
                    },
                    Path = new PropertyPath(UIElement.IsVisibleProperty)
                },
                Value = true,
            };

            trigger.EnterActions.Add(new BeginStoryboard { Storyboard = CreateStoryboard() });

            return trigger;
        }

        private Storyboard CreateStoryboard()
        {
            double speedRatio = SpeedRatio;

            // Must be greater than zero
            if (speedRatio <= 0.0)
                SpeedRatio = DefaultSpeedRatio;

            Storyboard storyboard = new Storyboard 
            {
                RepeatBehavior = RepeatBehavior.Forever,
                SpeedRatio = speedRatio
            };

            storyboard.Children.Add(CreateAnimationTimeline());

            return storyboard;
        }

        private Timeline CreateAnimationTimeline()
        {
            DoubleAnimationUsingKeyFrames animation = new DoubleAnimationUsingKeyFrames();

            animation.SetValue(Storyboard.TargetPropertyProperty, new PropertyPath("(0).(1)", UIElement.RenderTransformProperty, RotateTransform.AngleProperty));

            int keyFrameCount = 8;
            double timeOffsetInSeconds = 0.25;
            double totalAnimationLength = keyFrameCount * timeOffsetInSeconds;
            double repeatInterval = RepeatInterval;

            // Can't be less than zero and pointless to be less than total length
            if (repeatInterval < totalAnimationLength)
                repeatInterval = totalAnimationLength;

            animation.Duration = new Duration(TimeSpan.FromSeconds(repeatInterval));

            int targetValue = 12;
            for (int i = 0; i < keyFrameCount; i++)
                animation.KeyFrames.Add(new LinearDoubleKeyFrame(i % 2 == 0 ? targetValue : -targetValue, KeyTime.FromTimeSpan(TimeSpan.FromSeconds(i * timeOffsetInSeconds))));

            animation.KeyFrames.Add(new LinearDoubleKeyFrame(0, KeyTime.FromTimeSpan(TimeSpan.FromSeconds(totalAnimationLength))));
            return animation;
        }
    }
}

以下是如何在Xaml中使用它。

<Button>
    <Image Source="myImage.png">
        <i:Interaction.Behaviors>
            <local:ShakeBehavior RepeatInterval="30" SpeedRatio="3.0"/>
        </i:Interaction.Behaviors>
    </Image>
</Button>

有关附加行为的明确定义,您可以查看System.Windows.Interactivity.Behavior类备注。行为可以选择使用附加属性,使它们非常有用。

要获得附加属性的明确定义,您可以阅读MSDN中的Attached Properties Overview。附加属性可以做任何事情,并且可以将它们视为附加行为,因为它们可以触发发生的行为,从而导致有效行为,但从技术上讲,它们仍然只是附加属性。

由于附加属性可以像行为一样,人们也会将这些类型的附加属性称为附加行为,而实际上它并不是真正的附加行为,除非您从行为派生并附加到附加属性{ {3}}收集。

任何附加行为或附加属性都不需要混合,就像WPF / Silverlight中的大多数内容一样。

2 个答案:

答案 0 :(得分:1)

这是附加行为。请注意,如果您的控件具有现有渲染变换,则可能具有破坏性

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;
    }
}

以下是如何使用它:

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

答案 1 :(得分:0)

通过在VS中添加新项目然后导航到WPF模板来创建WPF自定义控件。这将允许您选择“自定义控件(WPF)”。将其命名为“ShakyImageControl”。这将创建一个包含generic.xaml的Themes文件夹和一个“ShakyImageControl.cs”类文件。在generic.xaml中,使用以下内容替换现有样式:

<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>

在ShakyImageControl类中添加依赖项属性,如下所示:

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));

要在按钮中使用shakyImage,请执行以下操作:

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

local是一个xml命名空间,如“xmlns:local =”clr-namespace:WpfApplication6“

注意:如果需要,您的自定义控件可以处于单独的程序集中