自定义ToggleButton动画与远程控制的实现
本文档解析了一段实现自定义ToggleButton动画与样式的代码,以及通过远程信号控制ToggleButton的示例。这段代码展示了如何使用WPF中的XAML和C#结合动画、样式和事件触发器来实现交互式控件,ToggleButton主要用来关联远程通信的状态,使用RemoteToggleButton取消OnToggle的状态变化,防止通信状态更新前的状态跳变提升用户体验。以下是详细的解析与实现笔记。
1. 资源字典与样式定义
资源字典(ResourceDictionary)
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style x:Key="WithSateHoldToggle" TargetType="ToggleButton">
<Setter Property="MinWidth" Value="60" />
<Setter Property="MinHeight" Value="40" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToggleButton">
<Border
x:Name="PART_border"
Background="{TemplateBinding Background}"
BorderBrush="Black"
BorderThickness="1">
<ContentPresenter
x:Name="PART_Presenter"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Border>
<ControlTemplate.Triggers>
<EventTrigger RoutedEvent="PreviewMouseLeftButtonDown">
<BeginStoryboard x:Name="Storyboard_Down">
<Storyboard>
<ColorAnimation
Storyboard.TargetName="PART_border"
Storyboard.TargetProperty="Background.Color"
To="#1b6aa5"
Duration="0:0:0.2" />
<ColorAnimation
Storyboard.TargetProperty="Foreground.Color"
To="White"
Duration="0:0:0.2" />
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="FontWeight">
<DiscreteObjectKeyFrame KeyTime="0:0:0.1">
<DiscreteObjectKeyFrame.Value>
<FontWeight>Bold</FontWeight>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<EventTrigger RoutedEvent="PreviewMouseLeftButtonUp">
<BeginStoryboard x:Name="Storyboard_Up">
<Storyboard>
<ColorAnimation
Storyboard.TargetName="PART_border"
Storyboard.TargetProperty="Background.Color"
To="{x:Null}"
Duration="0:0:0.2" />
<ColorAnimation
Storyboard.TargetProperty="Foreground.Color"
To="{x:Null}"
Duration="0:0:0.2" />
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="FontWeight">
<DiscreteObjectKeyFrame KeyTime="0:0:0.1">
<DiscreteObjectKeyFrame.Value>
<FontWeight>Normal</FontWeight>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<Trigger Property="IsChecked" Value="True">
<Setter Property="Background" Value="#1b6aa5" />
<Setter Property="Foreground" Value="White" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
该资源字典定义了一个名为WithSateHoldToggle
的样式,应用于ToggleButton
控件。主要特性包括:
- 设置控件的最小宽度和高度。
- 定义控件模板,包含一个边框和内容呈现器。
- 使用事件触发器实现点击动画效果。
- 使用触发器在
ToggleButton
被选中时改变背景色和前景色。
事件触发器与动画
事件触发器实现了ToggleButton
在鼠标左键按下和抬起时的动画效果。具体动画包括背景颜色、前景颜色和字体粗细的变化。
2. 远程控制ToggleButton
自定义控件RemoteToggleButton
public class RemoteToggleButton: ToggleButton
{
protected override void OnClick()
{
base.OnClick();
}
protected override void OnToggle()
{
// Do nothing
}
}
RemoteToggleButton
继承自ToggleButton
,通过重写OnClick
和OnToggle
方法,实现自定义点击行为。特别地,OnToggle
方法被重写为空,以禁用默认的切换行为,防止通信状态更新前的状态跳变提升用户体验。
数据模板与绑定
<ItemsControl.ItemTemplate>
<DataTemplate>
<control:RemoteToggleButton
Margin="2,0"
Checked="ToggleButton_Checked"
Command="{Binding DataContext.JogWithStateHoldCommand, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
CommandParameter="{Binding}"
Content="{Binding DisplayName}"
IsChecked="{Binding Status}"
Loaded="ToggleButton_Loaded"
Style="{StaticResource WithSateHoldToggle}"
Unchecked="ToggleButton_Unchecked" />
</DataTemplate>
</ItemsControl.ItemTemplate>
数据模板定义了RemoteToggleButton
的绑定属性,包括命令、命令参数、内容、状态等。使用Style
属性将先前定义的样式应用到RemoteToggleButton
。
3. ManualControl
用户控件
事件处理
public partial class ManualControl : UserControl
{
public ManualControl()
{
InitializeComponent();
}
private void ToggleButton_Checked(object sender, RoutedEventArgs e)
{
var btn = sender as ToggleButton;
var layter = AdornerLayer.GetAdornerLayer(btn);
if (layter == null) return;
var adorners = layter.GetAdorners(btn);
if (adorners != null)
{
ToggleButtonAdorner adorner = adorners.OfType<ToggleButtonAdorner>().FirstOrDefault();
if (adorner != null) layter.Remove(adorner);
}
layter.Add(new ToggleButtonAdorner(btn, "ON", "OFF"));
}
private void ToggleButton_Unchecked(object sender, RoutedEventArgs e)
{
var btn = sender as ToggleButton;
var layter = AdornerLayer.GetAdornerLayer(btn);
if (layter == null) return;
var adorners = layter.GetAdorners(btn);
if (adorners != null)
{
ToggleButtonAdorner adorner = adorners.OfType<ToggleButtonAdorner>().FirstOrDefault();
if (adorner != null) layter.Remove(adorner);
}
layter.Add(new ToggleButtonAdorner(btn, "ON", "OFF"));
}
private void ToggleButton_Loaded(object sender, RoutedEventArgs e)
{
var btn = sender as ToggleButton;
var layter = AdornerLayer.GetAdornerLayer(btn);
if (layter == null) return;
var adorners = layter.GetAdorners(btn);
if (adorners != null)
{
ToggleButtonAdorner adorner = adorners.OfType<ToggleButtonAdorner>().FirstOrDefault();
if (adorner != null) layter.Remove(adorner);
}
layter.Add(new ToggleButtonAdorner(btn, "ON", "OFF"));
}
}
ManualControl
类实现了ToggleButton的加载、选中和取消选中的事件处理。在这些事件中,使用AdornerLayer
动态添加或移除自定义的ToggleButtonAdorner
装饰器。
4. 自定义装饰器ToggleButtonAdorner
类定义与动画实现
public class ToggleButtonAdorner : Adorner
{
private double _animationProgress;
private AnimationClock _animationClock;
private readonly string _checkedStr;
private readonly string _uncheckedStr;
private readonly ToggleButton _toggleBtn;
public ToggleButtonAdorner(ToggleButton toggleBtn, string checkedStr, string uncheckedStr) : base(toggleBtn)
{
_toggleBtn = toggleBtn;
_checkedStr = checkedStr;
_uncheckedStr = uncheckedStr;
StartAnimation();
}
private void StartAnimation()
{
DoubleAnimation animation = new DoubleAnimation(0, 1, new Duration(TimeSpan.FromSeconds(2)));
animation.RepeatBehavior = RepeatBehavior.Forever;
animation.AutoReverse = false;
_animationClock = animation.CreateClock();
_animationClock.CurrentTimeInvalidated += (s, e) =>
{
if (_animationClock.CurrentProgress.HasValue)
{
_animationProgress = _animationClock.CurrentProgress.Value;
InvalidateVisual();
}
ApplyAnimationClock(AnimationProgressProperty, _animationClock);
};
}
public double AnimationProgress
{
get { return (double)GetValue(AnimationProgressProperty); }
set { SetValue(AnimationProgressProperty, value); }
}
public static readonly DependencyProperty AnimationProgressProperty =
DependencyProperty.Register("AnimationProgress", typeof(double), typeof(ToggleButtonAdorner), new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsRender));
protected override void OnRender(DrawingContext drawingContext)
{
base.OnRender(drawingContext);
Rect adornedElementRect = new Rect(this.AdornedElement.RenderSize);
if (_toggleBtn.IsChecked.Value)
{
double dashOffset = -adornedElementRect.Width * _animationProgress;
Pen pen = new Pen(Brushes.LawnGreen, 2);
FormattedText ft = new FormattedText(_checkedStr,
CultureInfo.CurrentCulture,
FlowDirection.RightToLeft,
new Typeface(new FontFamily("微软雅黑"), FontStyles.Normal, FontWeights.Bold, FontStretches.Normal),
8,
Brushes.White,
96);
drawingContext.DrawText(ft, new Point(adornedElementRect.TopRight.X - 2, adornedElementRect.TopRight.Y));
pen.DashStyle = new DashStyle(new double[] { adornedElementRect.Width / 4, adornedElementRect.Width / 4 }, dashOffset);
drawingContext.DrawRectangle(null, pen, adornedElementRect);
}
else
{
Pen pen = new Pen(new SolidColorBrush(Colors.Red), 2);
drawingContext.DrawRectangle(null, pen, adornedElementRect);
FormattedText ft = new FormattedText(_uncheckedStr,
CultureInfo.CurrentCulture,
FlowDirection.RightToLeft,
new Typeface(new FontFamily("微软雅黑"), FontStyles.Normal, FontWeights.Bold, FontStretches.Normal),
8,
Brushes.Red,
96);
drawingContext.DrawText(ft, new Point(adornedElementRect.TopRight.X - 2, adornedElementRect.TopRight.Y));
}
}
}
ToggleButtonAdorner
类继承自Adorner
,实现了对ToggleButton
的自定义装饰。主要功能包括:
- 启动一个双动画,循环改变动画进度。
- 在
OnRender
方法中,根据ToggleButton
的选中状态绘制不同的边框和文本。
依赖属性与动画时钟
通过定义依赖属性AnimationProgressProperty
,实现动画进度的绑定与更新。动画时钟控制动画的进度,并在每次进度更新时请求重绘。