当前位置: 首页 > news >正文

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 资源下载链接:自定义开关控件封装实现

相关文章:

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

文章目录 1、案例运行效果2、项目准备2、功能实现1、控件模板实现2、控件封装1、目录与文件创建2、各文件功能实现 3、开关界面与主窗体菜单实现1、开关界面实现2、主窗体菜单实现 4、源代码获取 1、案例运行效果 2、项目准备 打开项目 Wpf_Examples&#xff0c;新建ToggleBut…...

单机kafka性能需要高性能的硬件做支撑

一般来说&#xff0c;单机kafka在硬件支持的情况下&#xff0c;能支持每秒100万写入&#xff0c;如果硬件没有那么好的话(机械硬盘&#xff0c;容器内给内存8G&#xff0c; CPU也不是很好)&#xff0c;就只能减少每秒的写入量&#xff0c;每秒写入5万都比较不错了。 如果强行每…...

Spark 的 Http Broadcast 和 Torrent Broadcast 广播实现类的对比

在 Apache Spark 中&#xff0c;广播机制用于高效地将小型只读数据分发到集群中的各个执行器&#xff08;Executor&#xff09;。Spark 中主要有两种不同的广播实现方式&#xff1a;Http Broadcast 和 Torrent Broadcast。这两种方式的核心目标都是将数据高效地分发给所有工作节…...

030_Subplot_In_Matlab中多图绘制之subplot函数

基于子图的多图方法 专业的论文中通常涉及到多个有逻辑关系的图拼接在一起&#xff0c;构成相互支持或者对照。所以很早之前&#xff0c;Matlab就有这个子图的函数subplot。 这个函数的基本语义有三类&#xff1a; 在图窗上划分出一个矩形区域建立一个坐标系&#xff0c;并指…...

免费云服务器有什么使用限制和注意事项?

在数字化时代&#xff0c;云计算已经成为许多企业和个人用户的重要工具。对于初创企业、开发者和学生来说&#xff0c;免费的云服务器提供了一个低成本的解决方案&#xff0c;使他们能够进行项目开发、学习和实验。但在使用过程中也存在一些限制和注意事项。以下是主要的使用限…...

3-ZYNQ 折腾记录 -PS_PL AXI Interfaces

Zynq UltraScale MPSoC集成了功能丰富的四核或双核Arm Cortex-A53 MPCore基于处理系统(Processing System, PS)和可编程逻辑(Programmable Logic, PL)的单一设备。 PS和PL可以使用多个接口和其他信号进行紧密或松散的耦合。这使设计人员能够有效地将用户创建的硬件加速器和其他…...

总结test

1.IO流 |-- 字节流操作任何类型文件|-- 字符流操作纯字符类文件|-- BIO 传统IO流&#xff0c;阻塞型的&#xff0c;也就是BIO&#xff0c;当执行IO流时&#xff0c;CPU只能等待执行完当前任务&#xff0c;才能去执行其他线程任务|-- NIO非阻塞型IO流&#xff0c;CPU可以同时执行…...

在 On hold 期刊 eLife 上发表一篇生信文章需要什么工作量?

生信碱移 科研圈动态 根据弗雷赛斯以及相关媒体最新消息&#xff0c;中科院一区TOP&#xff0c;著名生命科学期刊 eLife [IF: 6.4]已被科睿唯安官方 On hold&#xff01; ▲ 官网截图。图片来源&#xff1a;https://mjl.clarivate.com/home eLife是一本专注于生物医学和生命科…...

使用Django框架开发企业级Web应用

&#x1f496; 博客主页&#xff1a;瑕疵的CSDN主页 &#x1f4bb; Gitee主页&#xff1a;瑕疵的gitee主页 &#x1f680; 文章专栏&#xff1a;《热点资讯》 使用Django框架开发企业级Web应用 1 引言 2 Django简介 3 安装Python与Django 4 创建Django项目 5 设计应用结构 6 创…...

认识线程 — JavaEE

目录 认识线程&#xff08;Thread&#xff09; 1 线程是什么? 2 为什么要有线程 3 进程和线程的区别 区别一 区别二 区别三 区别四 4. Java的线程和操作系统线程的关系 认识线程&#xff08;Thread&#xff09; 1 线程是什么? 一个线程就是一个 "执行流"。…...

【C++单调栈】853. 车队|1678

本文涉及的基础知识点 C单调栈 LeetCode853. 车队 在一条单行道上&#xff0c;有 n 辆车开往同一目的地。目的地是几英里以外的 target 。 给定两个整数数组 position 和 speed &#xff0c;长度都是 n &#xff0c;其中 position[i] 是第 i 辆车的位置&#xff0c; speed[i…...

第十届文荣奖华丽开幕,郁葱以青春与努力绽放青年演员光芒

10月27日&#xff0c;第十届文荣奖在众人的期待中盛大开启&#xff0c;内地青年女演员郁葱受邀出席&#xff0c;作为国内颇具影响力的影视奖项&#xff0c;文荣奖一直以来都致力于发掘和表彰优秀的影视作品和青年影视人才&#xff0c;为影视行业的发展注入新的活力&#xff0c;…...

CMake 生成器表达式介绍

【写在前面】 生成器表达式在构建系统生成期间进行评估&#xff0c;以生成特定于每个构建配置的信息。它们的形式为 $<...>。例如&#xff1a; target_include_directories(tgt PRIVATE /opt/include/$<CXX_COMPILER_ID>) 这将扩展为 “/opt/include/GNU”、“/opt…...

ubuntu 20.04编译驱动报gcc-12 not found错误

最近在自己安装的Ubuntu 系统上编译自定义驱动&#xff0c;发现无法编译.ko,错误如下&#xff1a; 按照如下操作&#xff0c;发现可以解决&#xff0c;记录下&#xff0c;主要是Ubuntu缺少g-12的包 安装包以后发现可以正常编译...

docker sameersbn/bind dns服务器

1. 安装 #下载docker 镜像 docker pull sameersbn/bind#运行 53端口若被占用会启动失败 docker run --name dns -d --restartalways \ --publish 53:53/tcp \ --publish 53:53/udp \ --publish 10000:10000/tcp \ -v /etc/localtime:/etc/localtime \ -v /data/bind/:/data \…...

错误:无法推送一些引用到 ‘https://gitee.com/chek_kk/python-electron-app.git‘

这个错误提示说明在提交时某个文件的大小超过了 Gitee 仓库的单文件大小限制&#xff08;100MB&#xff09;。你需要从Git 历史中彻底移除这个大文件&#xff0c;否则无法推送到远程仓库。 解决步骤 1. 确认大文件信息 使用以下命令找出超过限制的大文件&#xff1a; git re…...

深度剖析美区代理IP的多元应用与优势

在当今数字时代&#xff0c;代理IP&#xff08;Proxy IP&#xff09;已成为互联网使用中的一项关键技术。尤其在美区&#xff0c;代理IP在数据采集、网络安全及在线隐私保护等领域发挥着越来越重要的作用。本文将深入探讨代理IP的基本概念、应用场景以及它带来的诸多优势&#…...

基于KV260的基础视频链路通路(MIPI+Demosaic+VDMA)

目录 1. 简介 1.1 要点 1.2 背景 1.2.1 Got stuck 1.2.2 Cant be Initialized 2. Overlay 2.1 参考 Overlay 2.1.1 KV260 Base 2.1.2 Pynq-CV-OV5640 2.2 自建 Overlay 2.2.1 IIC IP 2.2.2 MIPI CSI-2 Rx 2.2.3 AXI4-S Subset 2.2.4 Demosaic 2.2.5 Pixel Pack …...

Uni-App-04

主页开发 保存主页数据 <script> import { indexData, base } from /serviceexport default {data() {return {base, //把服务器基础地址变量设置为数据属性carousels:[], //轮播广告条目列表menuItems:[], //当前用户选中的功能菜单列表activities:[], //最新的…...

ElasticSearch分片

本文内容参考了田雪松老师编著的《Elastic Stack应用宝典》 ElasticSearch作为一个搜索引擎&#xff0c;会存储海量的数据。而存储海量的数据&#xff0c;就要解决如何存储的问题&#xff0c;并且保证数据不会丢失&#xff0c;同时还需要保证数据检索的效率&#xff0c;尽可能…...

spring高手之路

以下是一些可以快速入门Spring的方法&#xff1a; 1. 学习基础知识 阅读官方文档&#xff1a;Spring官方文档是最权威的学习资料。它详细介绍了Spring的各个模块、概念和使用方法。从核心模块开始&#xff0c;了解如依赖注入&#xff08;DI&#xff09;和控制反转&#xff08…...

工字钢与H型钢有什么区别?90%的工程师都搞错了!

这里为大家做一个详尽的解答&#xff1a;很多人认为工字钢是国内的叫法&#xff0c;H型钢是国外的叫法&#xff0c;其实这个认知是错误的。H型钢和工字钢从形状上来说是不一样的&#xff0c;见下图&#xff1a; 工字钢 工字钢主要分为普通工字钢、轻型工字钢和宽翼缘工字钢。按…...

10个程序员可以接私活的平台(非常详细)零基础入门到精通,收藏这篇就够了

私活接的好收入不比上班少&#xff0c;一些同学靠接私活月收入也上万甚至几万了。今天老韩来分享一下有哪些接私活的网站和平台&#xff0c;转发收藏以后备用 我们先来聊聊什么样的私活不能接。。 1、没有第三方担保的个人对个人的尽量不要接&#xff0c;双方都没保障&#x…...

小程序云开发CMS新版数据模型讲解,可视化网页管理后台,内容管理对数据库进行增删改查操作,新闻小程序实战学习

一直跟着石头哥学习小程序开发的同学比较清楚cms是什么&#xff0c;cms就是可以进行可视化的管理云开发数据库的网页后台。有了cms我们可以很方便的管理云开发数据库。 但是云开发官方一直改版&#xff0c;所以现在cms功能被整合到了云开发的数据模型里&#xff0c;也就是现在想…...

undertow服务器初始化

springboot整合undertow服务器的源码从老生常谈的createWebServer方法谈起。spring会在生成所有bean后到创建web容器&#xff0c;此时会到容器找到ServletWebServerFactory接口bean&#xff0c;spring会根据引入的框架确定生成的ServletWebServerFactory&#xff0c;我们在mave…...

LeetCode9:回文数

原题地址&#xff1a;. - 力扣&#xff08;LeetCode&#xff09; 题目描述&#xff1a; 给你一个整数 x &#xff0c;如果 x 是一个回文整数&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 回文数 是指正序&#xff08;从左向右&#xff09;和倒序&#xff…...

模板语法(2)

一、循环 在模板中可以用v-for指令来循环数组&#xff0c;对象等。 1. 循环数组 <script setup name"App">import { reactive } from "vue"const books reactive([{title: 三国演义,author: 罗贯中}, {title: 水浒传,author: 施耐庵}, {title: 西…...

从头学PHP之数组输出基本函数

上期我们讲到了数组&#xff0c;数组是个特殊的变量&#xff0c;在程序中的重要程度很高&#xff0c;大部分数据处理的时候会用到这种特殊的变量&#xff0c;那么现在让我们继续深入一下吧。 上期我们打印出了数组的值&#xff0c;用print_r()或者var_dump()这俩函数&#xff0…...

基于SSM+小程序的4S店客户管理系统(汽车2)

&#x1f449;文末查看项目功能视频演示获取源码sql脚本视频导入教程视频 1、项目介绍 4S店客户管理系统主要包括管理员、用户、门店三个权限角色 1、管理员实现了首页、个人中心、用户管理、门店管理、车展管理、汽车品牌管理、新闻头条管理、预约试驾管理、我的收藏管理、…...

ZYNQ AXI_Timer 中断

REVIEW 关于ZYNQ中断&#xff1a; ZYNQ PS_GPIO中断-CSDN博客 ZYNQ AXI_GPIO_INT-CSDN博客 ZYNQ 定时器中断-CSDN博客 在一些应用场景中&#xff0c;可能需要使用到多个定时器&#xff0c;除了选择使用 PS 侧其他定时器外&#xff0c;也可以使用 PL 侧逻辑定时器。 1. 今日摸鱼…...