如何在按钮动画影像在WPF每30秒动摇?(How to animate an Image in a

2019-06-25 14:31发布

我也不好当涉及到处理与样式和动画任何东西。

我希望能够得到制造的形象是一个按钮的唯一内容撼动每30秒的时候曾经按钮可见性设置为Visibility.Visible一些帮助。

这是获得用户的关注,鼓励他们单击按钮。

我宁愿这样做,因为图片上附加的行为,或者如果可能的话甚至UIControl,使它很容易重复使用而不是与风格搞乱,因为我已经使用了风格,从我的控制供应商,我不希望编辑。

下面是我使用标记的答案得出的解决方案

这是附加的行为 ,可以适用于任何System.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。 附加属性可以做任何事情,他们可以被认为是附加的行为,因为他们可以触发一个动作发生促使有效的行为,但在技术上他们仍然只是一个附加属性。

由于附加属性可以像一个行为人都来也呼吁这些类型的附加属性附加的行为,而实际上它是不是真的一个附加的行为,除非你从行为中获得的,并在它的附加属性Interaction.Behaviors集合。

不需要掺和任何附加的行为或附加属性,与WPF / Silverlight中的大多数事情。

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


Answer 2:

创建通过添加VS新的项目,然后导航到WPF模板的WPF自定义控件。 这将允许您选择“自定义控制(WPF)”。 将它命名为“ShakyImageControl”。 这将创建一个主题文件夹中有一个generic.xaml和“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>

当地是XML命名空间等 “的xmlns:本地=” CLR-名称空间:WpfApplication6"

注意:您的自定义控制可以在一个单独的组件,如果你想



文章来源: How to animate an Image in a button to shake every 30 seconds in WPF?