WPF自定义控件-按钮-DoubleAnimation动画
效果展示
最近在看Wpf的书,初了解Wpf,也没做过C#和Wpf的项目。
如果下述有不妥的地方请多多包含,有错误,还请指教。
原理
自定义控件继承Button,模板为两个重叠的Border
下面为其中一个样式的代码
- 自定义控件文件(MyButton.cs),自定生成的代码先暂时什么都不用改
using System.Windows;
using System.Windows.Controls;
namespace CSDN01
{
public class MyButton : Button
{
static MyButton()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MyButton), new FrameworkPropertyMetadata(typeof(MyButton)));
}
}
}
- 项目2,资源字典文件(TemplateMyButton.xaml),我暂时先把属性设成固定值,后期再改成依赖属性
属性UseLayoutRounding=“False”
的设置是一个注意点(后续♦1处说明)
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:CSDN01">
<ControlTemplate x:Key="TemplateMyButton" TargetType="{x:Type local:MyButton}">
<Grid UseLayoutRounding="False">
<Border x:Name="bd1" Background="LightCyan">
<TextBlock Text="按钮" FontSize="12" Foreground="DarkCyan"
HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
<Border x:Name="bd2" Background="DarkCyan" Height="0">
<TextBlock Text="按钮" FontSize="12" Foreground="LightCyan"
HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
</Grid>
</ControlTemplate>
</ResourceDictionary>
- App.xaml里添加资源字典文件
<Application x:Class="CSDN01.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:CSDN01"
StartupUri="MainWindow.xaml">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="TemplateMyButton.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
- 主窗口文件(MainWindow.xaml)
属性UseLayoutRounding=“True”
的设置是一个注意点(后续♦2处说明)
<Window x:Class="CSDN01.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:CSDN01"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid UseLayoutRounding="True">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="3*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<local:MyButton Template="{StaticResource TemplateMyButton}" Grid.Column="1"/>
<local:MyButton Template="{StaticResource TemplateMyButton}" Grid.Column="2"/>
<local:MyButton Template="{StaticResource TemplateMyButton}" Grid.Column="3"/>
<local:MyButton Template="{StaticResource TemplateMyButton}" Grid.Column="4"/>
</Grid>
</Window>
-
上述步骤后,大体窗口的样子成型
-
现在重写MyButton.cs文件添加DoubleAnimation动画
属性FillBehavior
的设置是一个注意点(后续♦3处说明)
using System;
using System.Windows.Media.Animation;
/// <summary>
/// 动画对象
/// </summary>
private DoubleAnimation myAnimation = new DoubleAnimation();
/// <summary>
/// 重写MouseEnter事件
/// </summary>
/// <param name="e"></param>
protected override void OnMouseEnter(MouseEventArgs e)
{
base.OnMouseEnter(e);
// 获取MyButton内部子控件
Border bd1 = GetTemplateChild("bd1") as Border;
Border bd2 = GetTemplateChild("bd2") as Border;
// 设置属性
myAnimation.From = 0; // 动画初始值
myAnimation.To = bd1.ActualHeight; // 动画终了值
myAnimation.Duration = TimeSpan.FromSeconds(0.5); // 0.5秒内执行动画
myAnimation.FillBehavior = FillBehavior.HoldEnd; // 动画结束后,属性值保持终了状态
// 在高度属性上执行动画
bd2.BeginAnimation(HeightProperty, myAnimation);
}
/// <summary>
/// 重写MouseLeave事件
/// </summary>
/// <param name="e"></param>
protected override void OnMouseLeave(MouseEventArgs e)
{
base.OnMouseLeave(e);
// 获取MyButton内部子控件
Border bd1 = GetTemplateChild("bd1") as Border;
Border bd2 = GetTemplateChild("bd2") as Border;
// 添加并执行动画
myAnimation.From = bd1.ActualHeight; // 动画初始值
myAnimation.To = 0; // 动画终了值
myAnimation.Duration = TimeSpan.FromSeconds(0.5); // 0.5秒内执行动画
myAnimation.FillBehavior = FillBehavior.Stop; // 动画结束后,属性值还原为动画出师值
// 在高度属性上执行动画
bd2.BeginAnimation(HeightProperty, myAnimation);
}
-
上述步骤后,整个按钮算是大体全部结束了,后续要添加依赖属性,这样可以设置自己想要的背景色,文字大小颜色等属性。
-
依赖属性的设置(这里就只设置2个依赖属性做例子)
依赖属性的代码有快捷键:输入propdp,然后按2次Tab键
往MyButton.cs添加下面的代码
PropertyMetadata里设置的是初期值
using System.Windows.Media;
/// <summary>
/// 上层背景色(变动层)
/// </summary>
public SolidColorBrush UpBackground
{
get { return (SolidColorBrush)GetValue(UpBackgroundProperty); }
set { SetValue(UpBackgroundProperty, value); }
}
public static readonly DependencyProperty UpBackgroundProperty =
DependencyProperty.Register("UpBackground", typeof(SolidColorBrush), typeof(MyButton), new PropertyMetadata(new SolidColorBrush(Colors.DarkCyan)));
/// <summary>
/// 动画执行时间属性(秒)
/// </summary>
public double AnimationSeconds
{
get { return (double)GetValue(AnimationSecondsProperty); }
set { SetValue(AnimationSecondsProperty, value); }
}
public static readonly DependencyProperty AnimationSecondsProperty =
DependencyProperty.Register("AnimationSeconds", typeof(double), typeof(MyButton), new PropertyMetadata(0.2));
对MyButton.cs文件再次进行修改
// 修改前
myAnimation.Duration = TimeSpan.FromSeconds(0.5); // 0.5秒内执行动画
// 修改后
myAnimation.Duration = TimeSpan.FromSeconds(AnimationSeconds); // 制定时间内执行动画(默认0.2秒)
对TemplateMyButton.xaml文件再次进行修改
<!--变更前-->
<Border x:Name="bd2" Background="DarkCyan" Height="0">
<!--变更后-->
<Border x:Name="bd2" Background="{TemplateBinding UpBackground}" Height="0">
也可以用Button固有的属性来设置MyButton的样式
<!--变更前-->
<TextBlock Text="按钮" FontSize="12"
<!--变更后-->
<TextBlock Text="{TemplateBinding Content}"
当然要在MainWindow.xaml文件里设置Content的值
<local:MyButton Template="{StaticResource TemplateMyButton}" Grid.Column="1" Content="首页"/>
<local:MyButton Template="{StaticResource TemplateMyButton}" Grid.Column="2" Content="产品"/>
<local:MyButton Template="{StaticResource TemplateMyButton}" Grid.Column="3" Content="技术"/>
<local:MyButton Template="{StaticResource TemplateMyButton}" Grid.Column="4" Content="联系我们"/>
到此代码都结束了
补充上述3点我在做样式的注意点
- 先说注意点♦2:UseLayoutRounding=“True”
做个小例子
<Grid Grid.Row="2" Grid.Column="1">
<Border Background="Black"/>
<Border Background="White"/>
</Grid>
上述在一个单元格里放至2个Border,白色覆盖在黑色上面,下面是没有UseLayoutRounding=“True”
的运行结果
有2根比较明显的底色黑线漏出来了,我这次给的MyButton例子是不会出现这个的,因为一个是隐藏的,一个是显示的。但是如果自己想做别的动画,需要多个重叠控件的话,就可能会出现这个现象。
至于为什么,我也说不出,给2张书上的说明:
引用的是【WPF编程宝典–使用C#2012和.NET 4.5 第4版】的64,65页。
- 再说注意点♦1:UseLayoutRounding=“False”
举个小例子,去掉♦1处的:UseLayoutRounding=“False”
<Grid UseLayoutRounding="True">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="120"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<local:MyButton Template="{StaticResource TemplateMyButton}" Grid.Column="1" Content="首页" Padding="5"/>
为了防止黑线,我在最外面添加了UseLayoutRounding=“True”,如果MyButton被强制设置了宽度,又加上了Padding,MyButton的实际宽度编程110,我用慢动作看一下运行的实际动画:
文字在上下抖动,原因大概就是高度是小数,不停变化高度的时候,由于布局舍入导致居中的位置上下1像素浮动。这时候就要把布局舍入给关了。
- 再说注意点♦3:属性FillBehavior
用一个别的按钮样式说明,没有FillBehavior属性,被拉伸后的效果
拉伸前:
拉伸后:
应该很好理解,原本高宽是考Grid自动生成的,所以控件实际的Hight和Width的值是NaN,所以动画我用的都是ActualHeight和ActualWidth,动画执行的时候,改变的是Hight和Width,把实际的高宽给固定了,所以拉伸后还保持原来的Hight和Width而没有跟随Grid的变化而变化,所以要在鼠标离开后,把NaN的值还给他。