在涉及风格和动画的任何事情时,我都不好。
我希望能够获得一些帮助,使按钮可见性设置为Visibility.Visible时,每隔30秒制作一次按钮抖动的唯一内容。
要引起用户注意,鼓励他们点击按钮。
我更愿意将此作为图像上的附加行为,或者如果可能的话甚至是UIControl,使其易于重复使用而不是弄乱样式,因为我已经使用了来自我的控件供应商的样式,而且我不喜欢我想编辑它。
以下是我使用的解决方案
这是可以应用于任何Attached Behavior的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中的Attached Properties Overview。附加属性可以做任何事情,并且可以将它们视为附加行为,因为它们可以触发发生的行为,从而导致有效行为,但从技术上讲,它们仍然只是附加属性。
由于附加属性可以像行为一样,人们也会将这些类型的附加属性称为附加行为,而实际上它并不是真正的附加行为,除非您从行为派生并附加到附加属性{ {3}}收集。
任何附加行为或附加属性都不需要混合,就像WPF / Silverlight中的大多数内容一样。
答案 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“
注意:如果需要,您的自定义控件可以处于单独的程序集中