文章目录
- 1、案例运行效果
- 2、项目准备
- 2、功能实现
- 1、控件模板实现
- 2、控件封装
- 1、目录与文件创建
- 2、各文件功能实现
- 3、开关界面与主窗体菜单实现
- 1、开关界面实现
- 2、主窗体菜单实现
- 4、源代码获取
1、案例运行效果
2、项目准备
打开项目 Wpf_Examples,新建ToggleButtonWindow.xmal 文件,这里没有 Wpf_Examples 项目的可以看下前面章节,WPF+MVVM案例实战(三)- 动态数字卡片效果实现 详细介绍了项目创建过程。
2、功能实现
1、控件模板实现
开关按钮基于CheckBox 按钮基础上修改控件模板实现。
ToggleButtonWindow.xaml 界面代码如下
<Window x:Class="Wpf_Examples.Views.ToggleButtonWindow"
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:Wpf_Examples.Views"
mc:Ignorable="d"
Title="ToggleButtonWindow" Height="450" Width="800" Background="#2B2B2B">
<Window.Resources>
<Style x:Key="ToggleSwitchStyleChangeAnimate" TargetType="CheckBox">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="CheckBox">
<StackPanel Orientation="Horizontal">
<Grid>
<Border CornerRadius="22" Width="80" Height="44" Background="LightGray"/>
<Border CornerRadius="18" x:Name="button" Width="36" Height="36" HorizontalAlignment="Left"/>
</Grid>
</StackPanel>
<ControlTemplate.Resources>
<Storyboard x:Key="toLeftStoryboard">
<ThicknessAnimation Storyboard.TargetProperty="Margin" Storyboard.TargetName="button" From="40,0,0,0" To="4,0,0,0" Duration="0:0:0:0.3">
<!--增加缓动处理,让切换效果更加平滑-->
<ThicknessAnimation.EasingFunction>
<CubicEase EasingMode="EaseInOut"/>
</ThicknessAnimation.EasingFunction>
</ThicknessAnimation>
</Storyboard>
<Storyboard x:Key="toRightStoryboard">
<ThicknessAnimation Storyboard.TargetProperty="Margin" Storyboard.TargetName="button" From="4,0,0,0" To="40,0,0,0" Duration="0:0:0:0.3">
<!--增加缓动处理,让切换效果更加平滑-->
<ThicknessAnimation.EasingFunction>
<CubicEase EasingMode="EaseInOut"/>
</ThicknessAnimation.EasingFunction>
</ThicknessAnimation>
</Storyboard>
</ControlTemplate.Resources>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="False">
<Trigger.EnterActions>
<RemoveStoryboard BeginStoryboardName="right"/>
<BeginStoryboard Storyboard="{StaticResource toLeftStoryboard}" x:Name="left"/>
</Trigger.EnterActions>
<Setter TargetName="button" Property="Background" Value="#737373"/>
</Trigger>
<Trigger Property="IsChecked" Value="True">
<Trigger.EnterActions>
<RemoveStoryboard BeginStoryboardName="left"/>
<BeginStoryboard Storyboard="{StaticResource toRightStoryboard}" x:Name="right"/>
</Trigger.EnterActions>
<Setter TargetName="button" Property="Background" Value="#25DC2D"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid HorizontalAlignment="Center">
<CheckBox Style="{StaticResource ToggleSwitchStyleChangeAnimate}"/>
</Grid>
</Window>
2、控件封装
1、目录与文件创建
首先在自定义控件库 CustomControlLib 中新建一个 ToggleSwitch.cs 文件,在创建一个 Converters 文件夹,创建 ToggleSwitchConverter.cs 文件,这里用来做数据转换使用。如下图所示:
2、各文件功能实现
首先我们先实现开关按钮的样式定义,将前面 ToggleButtonWindow.xaml 中的 Style 样式拷贝到自定义控件的 Generic.xaml 文件中修改后如下所示:
<Style TargetType="{x:Type local:ToggleSwitch}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="CheckBox">
<StackPanel Orientation="Horizontal">
<Grid>
<Border Width="{TemplateBinding Width}"
Height="{TemplateBinding Height}"
CornerRadius="{TemplateBinding Height,Converter={StaticResource ToggleSwitchConverter},ConverterParameter='OutSideCorner'}"
Background="LightGray"/>
<Border x:Name="button"
CornerRadius="{TemplateBinding Height,Converter={StaticResource ToggleSwitchConverter},ConverterParameter='InSideCorner'}"
Width="{TemplateBinding Height,Converter={StaticResource ToggleSwitchConverter},ConverterParameter='ButtonSize'}"
Height="{TemplateBinding Height,Converter={StaticResource ToggleSwitchConverter},ConverterParameter='ButtonSize'}"
HorizontalAlignment="Left"/>
</Grid>
</StackPanel>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="False">
<Setter TargetName="button" Property="Background" Value="#737373"/>
</Trigger>
<Trigger Property="IsChecked" Value="True">
<Setter TargetName="button" Property="Background" Value="{Binding Foreground,RelativeSource={RelativeSource TemplatedParent}}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
样式代码主要是把按钮的宽高数据值绑定到原有按钮宽高属性上,通过转换器完成不同属性数据值的不同赋值,然后我们实现转换器 ToggleSwitchConverter.cs 的代码,如下所示:
public class ToggleSwitchConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is double height)
{
var property = parameter as string;
switch (property)
{
case "ButtonSize":
return (height - 8);
case "OutSideCorner":
return new CornerRadius(height/2);
case "InSideCorner":
return new CornerRadius((height-8) / 2);
}
}
throw new NotImplementedException();
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
最后,我们在 ToggleSwitch.cs 实现按钮的切换和动画加载功能代码,ToggleSwitch.cs 代码实现如下:
public class ToggleSwitch:CheckBox
{
static ToggleSwitch()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(ToggleSwitch), new FrameworkPropertyMetadata(typeof(ToggleSwitch)));
}
public ToggleSwitch()
{
Checked += ToggleSwitch_Checked;
Unchecked += ToggleSwitch_Unchecked;
}
private void ToggleSwitch_Checked(object sender, RoutedEventArgs e)
{
Animatebutton(true);
}
private void ToggleSwitch_Unchecked(object sender, RoutedEventArgs e)
{
Animatebutton(false);
}
private void Animatebutton(bool isChecked)
{
if (GetTemplateChild("button") is Border button)
{
Storyboard storyboard = new Storyboard();
ThicknessAnimation animation = new ThicknessAnimation();
animation.Duration=new Duration (TimeSpan.FromSeconds(0.3));
animation.EasingFunction=new CircleEase { EasingMode = EasingMode.EaseOut };
if (isChecked)
{
animation.From = new Thickness(4, 0, 0, 0);
animation.To=new Thickness(ActualWidth-(ActualHeight-8)-4, 0, 0, 0);
}
else
{
animation.To = new Thickness(4, 0, 0, 0);
animation.From = new Thickness(ActualWidth - (ActualHeight - 8) - 4, 0, 0, 0);
}
Storyboard.SetTarget(animation,button);
Storyboard.SetTargetProperty(animation,new PropertyPath(MarginProperty));
storyboard.Children.Add(animation);
if (isChecked)
{
Resources.Remove("Left");
Resources["Right"] = storyboard;
}
else
{
Resources.Remove("Right");
Resources["Left"] = storyboard;
}
storyboard.Begin();
}
}
}
3、开关界面与主窗体菜单实现
1、开关界面实现
ToggleButtonWindow.xaml 代码如下所示:
<Window x:Class="Wpf_Examples.Views.ToggleButtonWindow"
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:cc="clr-namespace:CustomControlLib;assembly=CustomControlLib"
xmlns:local="clr-namespace:Wpf_Examples.Views"
mc:Ignorable="d"
Title="ToggleButtonWindow" Height="450" Width="800" Background="#2B2B2B">
<Window.Resources>
<Style x:Key="ToggleSwitchStyleChangeAnimate" TargetType="CheckBox">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="CheckBox">
<StackPanel Orientation="Horizontal">
<Grid>
<Border CornerRadius="22" Width="80" Height="44" Background="LightGray"/>
<Border CornerRadius="18" x:Name="button" Width="36" Height="36" HorizontalAlignment="Left"/>
</Grid>
</StackPanel>
<ControlTemplate.Resources>
<Storyboard x:Key="toLeftStoryboard">
<ThicknessAnimation Storyboard.TargetProperty="Margin" Storyboard.TargetName="button" From="40,0,0,0" To="4,0,0,0" Duration="0:0:0:0.3">
<!--增加缓动处理,让切换效果更加平滑-->
<ThicknessAnimation.EasingFunction>
<CubicEase EasingMode="EaseInOut"/>
</ThicknessAnimation.EasingFunction>
</ThicknessAnimation>
</Storyboard>
<Storyboard x:Key="toRightStoryboard">
<ThicknessAnimation Storyboard.TargetProperty="Margin" Storyboard.TargetName="button" From="4,0,0,0" To="40,0,0,0" Duration="0:0:0:0.3">
<!--增加缓动处理,让切换效果更加平滑-->
<ThicknessAnimation.EasingFunction>
<CubicEase EasingMode="EaseInOut"/>
</ThicknessAnimation.EasingFunction>
</ThicknessAnimation>
</Storyboard>
</ControlTemplate.Resources>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="False">
<Trigger.EnterActions>
<RemoveStoryboard BeginStoryboardName="right"/>
<BeginStoryboard Storyboard="{StaticResource toLeftStoryboard}" x:Name="left"/>
</Trigger.EnterActions>
<Setter TargetName="button" Property="Background" Value="#737373"/>
</Trigger>
<Trigger Property="IsChecked" Value="True">
<Trigger.EnterActions>
<RemoveStoryboard BeginStoryboardName="left"/>
<BeginStoryboard Storyboard="{StaticResource toRightStoryboard}" x:Name="right"/>
</Trigger.EnterActions>
<Setter TargetName="button" Property="Background" Value="#25DC2D"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid HorizontalAlignment="Center">
<StackPanel Orientation="Vertical">
<CheckBox Style="{StaticResource ToggleSwitchStyleChangeAnimate}" Margin="0 20 0 20"/>
<cc:ToggleSwitch Width="80" Height="44" Foreground="#3CC330" Margin="0 20 0 20"/>
<cc:ToggleSwitch Width="100" Height="60" Foreground="#E72114"/>
</StackPanel>
</Grid>
</Window>
上面我保留了原来的样式,然后也使用了封装控件的方式来展示相同效果。自定义控件可以通过改变属性设置,轻松实现不同按钮大小和选中颜色的设置,而模板样式只能实现一种效果。
2、主窗体菜单实现
MainWindow.xaml 菜单中添加新的按钮,代码如下:
<Grid>
<WrapPanel>
<Button Width="100" Height="30" FontSize="18" Content="开关按钮" Command="{Binding ButtonClickCmd}" CommandParameter="{Binding RelativeSource={RelativeSource Mode=Self},Path=Content}" Margin="8"/>
</WrapPanel>
</Grid>
MainViewModel.cs 的菜单功能按钮添加新的页面弹窗,代码如下:
case "开关按钮":
PopWindow(new ToggleButtonWindow());
break;
4、源代码获取
**** 资源下载链接:自定义开关控件封装实现