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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/902790.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

无法启动此程序win10玩游戏找不到d3dx9_43.dll缺失的五种常用有效解决方法

d3dx9_43.dll 是 DirectX 9 的一个关键组件&#xff0c;属于动态链接库&#xff08;DLL&#xff09;文件&#xff0c;由微软公司开发。DirectX 是一组用于多媒体应用的 API&#xff0c;包括 d3dx9_43.dll 在内的组件对游戏和图形应用程序至关重要。该文件主要负责提供3D图形渲染…

手机折叠屏贴膜应用

折叠手机贴膜的主要难点在于其独特的可折叠设计。折叠屏的弯曲部分对贴膜材料提出了更高要求&#xff0c;需要材料具备足够的柔韧性和耐折痕性&#xff0c;以避免在折叠过程中产生裂痕或脱落。此外&#xff0c;贴膜过程中需要确保无气泡、无褶皱&#xff0c;且能完美贴合屏幕的…

GPU 与 GPU 服务器:科技璀璨之星,开启无限未来

今天咱们要来聊聊在科技领域中闪闪发光的 GPU 和 GPU 服务器。这可真是一对厉害的 “科技搭档”&#xff0c;正以其卓越的性能成为众多行业发展的强大动力源。 先来说说 GPU 吧。它呀&#xff0c;一开始是为了满足图形处理的高要求而诞生的。但随着科技不断进步&#xff0c;人…

从零到一:打造你的专属待办事项应用,探索 Windows 11 开发新境界

✅作者简介&#xff1a;2022年博客新星 第八。热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏…

7、基于爬虫+Flask+Echarts+MySQL的网易云评论可视化大屏

基于爬虫FlaskEchartsMySQL的网易云评论可视化大屏 1、前言2、实现2.1 挑选想要采集的歌曲评论2.2 构建爬虫2.2.1 采集歌曲评论2.2.2 清洗数据入库 2.3 搭建flask框架2.4 数据传值2.5 完整代码&数据集获取 1、前言 本项目是基于requests爬虫flaskecharts搭建的网易云评论的…

WASM 使用说明23事(RUST实现)

文章目录 1. wasm是什么1.1 chatgpt定义如下:1.2 wasm关键特性&#xff1a; 2. wasm demo2.1 cargo 创建项目2.2 编写code2.3 安装wasm-pack2.4 编译 3.1 html页面引用wasm代码&#xff08;js引用&#xff09;3.2 访问页面4 导入js function4.1 编写lib.rs文件&#xff0c;内容…

应用案例 | Panorama SCADA助力巴黎奥运会:保障赛事协调与安全

谈到2024年最受关注的体育盛事&#xff0c;巴黎奥运会无疑是焦点之一。作为全球瞩目的顶级赛事&#xff0c;它不仅汇集了来自世界各地的精英运动员&#xff0c;还点燃了全球观众的热情。然而&#xff0c;组织如此大规模的活动绝非易事。从大量游客通过公共交通涌入&#xff0c;…

基于SSM的宠物猫狗商业系统设计与实现

前言 当今社会已经步入了科学技术进步和经济社会快速发展的新时期&#xff0c;国际信息和学术交流也不断加强&#xff0c;计算机技术对经济社会发展和人民生活改善的影响也日益突出&#xff0c;人类的生存和思考方式也产生了变化。传统宠物猫狗商业采取了人工的管理方法&#…

mfc之tab标签控件的使用--附TabSheet源码

TabSheet源码 TabSheet.h #if !defined(AFX_TABSHEET_H__42EE262D_D15F_46D5_8F26_28FD049E99F4__INCLUDED_) #define AFX_TABSHEET_H__42EE262D_D15F_46D5_8F26_28FD049E99F4__INCLUDED_#if _MSC_VER > 1000 #pragma once #endif // _MSC_VER > 1000 // TabSheet.h : …

华为云低代码Astro Zero技巧教学7:打造实时更新的动态数据表

数字化经营的浪潮中&#xff0c;数据管理的高效与准确成为企业持续成长的关键。Astro Zero高级页面功能通过创新的表格视图编辑模式&#xff0c;实现了前端操作与后台数据的无缝同步&#xff0c;极大地提升了数据处理的效率和可靠性。 本次技巧内容&#xff1a;使用Astro Zero制…

【亲测】mini版centos7.9配置网络基础ssh等直接使用

1,安装好系统: 默认安装好 CentOS-7-x86_64-Minimal-2009.iso2,配置静态或动态ip: 保持root登陆操作 先配置成动态ip: vi /etc/sysconfig/network-scripts/ifcfg-eth0 重启网卡 systemctl restart network 测试网络畅通 3,配置国内yum镜像源: 先备份: cp /etc/y…

Python | Leetcode Python题解之第503题下一个更大元素II

题目&#xff1a; 题解&#xff1a; class Solution:def nextGreaterElements(self, nums: List[int]) -> List[int]:n len(nums)ret [-1] * nstk list()for i in range(n * 2 - 1):while stk and nums[stk[-1]] < nums[i % n]:ret[stk.pop()] nums[i % n]stk.appen…

排查PHP服务器CPU占用率高的问题

排查PHP服务器CPU占用率高的问题通常可以通过以下步骤进行&#xff1a; 使用top或htop命令&#xff1a;这些命令可以实时显示服务器上各个进程的CPU和内存使用情况。找到CPU使用率高的进程。 查看进程日志&#xff1a;如果PHP-FPM或Apache等服务器进程的日志记录了具体的请求…

中国书法、绘画

孙溟㠭浅析“篆刻” 什么是篆刻&#xff0c;治印一般采用篆书&#xff0c;先写后刻&#xff0c;所以称&#xff02;篆刻&#xff02;&#xff0c;也是镌刻印章的统称。博物馆常见的金属印章&#xff0c;多数是先刻印模&#xff0c;然后再浇铸&#xff0c;也有提前做好金属印坯…

Java当中的数据类型

基本数据类型和引用数据类型基本数据类型&#xff1a; 四类&#xff1a;整型、浮点型、字符型以及布尔型八种&#xff1a; 注意&#xff1a; 字符类型char&#xff1a;2字节&#xff08;1字节8位&#xff09;boolean数据类型不可以 Java当中没有所谓的&#xff1a;0是假 …

已知圆心经纬度,半径长度。算圆上点的经纬度,画圆

前言&#xff1a;经度变化1度&#xff0c;111320米.纬度变化1度&#xff0c;111130米 #define pi 3.14159265 double convertArc(double angle) {double angle_arc(angle/180.0)*pi;return angle_arc; } typedef struct{double lng;double lat;double height;}polygon_points;…

Day03罗马数字转整数

罗马数字包含以下七种字符: I&#xff0c; V&#xff0c; X&#xff0c; L&#xff0c;C&#xff0c;D 和 M。 例如&#xff0c; 罗马数字 2 写做 II &#xff0c;即为两个并列的 1 。12 写做 XII &#xff0c;即为 X II 。 27 写做 XXVII, 即为 XX V II 。 通常情况下&…

深入拆解TomcatJetty——Tomcat如何实现IO多路复用

深入拆解Tomcat&Jetty 专栏地址&#xff1a; 极客时间-深入拆解Tomcat & Jetty IO 多路复用 当用户线程发起 I/O 操作后&#xff0c;网络数据读取操作会经历两个步骤&#xff1a; 用户线程等待内核将数据从网卡拷贝到内核空间。内核将数据从内核空间拷贝到用户空间…

面试域——技术面试准备

摘要 来到技术面试这环节有两种情况&#xff0c;其一&#xff1a;这场技术面试可能就是一个面试官KPI面试&#xff08;就是面试工作量&#xff0c;这个面试你是不可能过。&#xff09;如今的就业环境下&#xff0c;人力资源部门也是有考核指标。如果遇到这样的面试你就放平心态…

RabbitMq-队列交换机绑定关系优化为枚举注册

&#x1f4da;目录 &#x1f4da;简介:&#x1f680;比较&#x1f4a8;通常注册&#x1f308;优化后注册 ✍️代码&#x1f4ab;自动注册的关键代码 &#x1f4da;简介: 该项目介绍&#xff0c;rabbitMq消息中间件&#xff0c;对队列的注册&#xff0c;交换机的注册&#xff0c…