WPF+MVVM案例实战(八)- 自定义开关控件封装实现

文章目录

  • 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、源代码获取

**** 资源下载链接:自定义开关控件封装实现

上一篇:夹逼准则求数列极限(复习总结)


下一篇:纯GO语言开发RTSP流媒体服务器-RTSP推流直播、本地保存录像、录像回放、http-flv及hls协议分发