自定义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,通过重写OnClickOnToggle方法,实现自定义点击行为。特别地,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,实现动画进度的绑定与更新。动画时钟控制动画的进度,并在每次进度更新时请求重绘。

posted @   非法关键字  阅读(98)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 通过 API 将Deepseek响应流式内容输出到前端
· AI Agent开发,如何调用三方的API Function,是通过提示词来发起调用的吗
点击右上角即可分享
微信分享提示