WPF入门到跪下 第九章 用户控件与自定义控件

2024-01-10 14:28

本文主要是介绍WPF入门到跪下 第九章 用户控件与自定义控件,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

在WPF中想要进行个性化处理,主要可以通过三个方面来实现:控件模板(控件模板、数据模板、数据容器模板)、用户控件(UserControl)、自定义控件(CustomControl)。

用户控件-UserControl

在这里插入图片描述

一、简单使用

创建用户控件

<UserControl ....>......
</UserControl>

创建完成后会出现用户控件的xaml文件,打开后看到这个xaml文件中的顶级元素为UserControl

UserControl的用法与Window是一样的,区别在于UserControl元素可以被Window元素包含,而Window元素只能作为顶级元素存在。

用户控件数据的常规处理

MyUserControl.xaml中


<Grid><StackPanel><Button Height="30"/><TextBox Text="{Binding Value, RelativeSource={RelativeSource AncestorType=UserControl, Mode=FindAncestor} ,FallbackValue=0}" Height="30"/></StackPanel>
</Grid>

MyUserControl.xaml.cs中

public partial class MyUserControl : UserControl
{public string Value{get { return (string)GetValue(ValueProperty); }set { SetValue(ValueProperty, value); }}public static readonly DependencyProperty ValueProperty =DependencyProperty.Register("Value", typeof(string), typeof(MyUserControl), new PropertyMetadata(default));public MyUserControl(){InitializeComponent();}
}

MainWindow.xaml中

<Window.DataContext><local:MainViewModel/>
</Window.DataContext>
<Grid><StackPanel><local:MyUserControl Value="{Binding Value}"/></StackPanel>
</Grid>

上面这一段用户控件的编写及使用是想说明一下对于用户控件中的数据,应该通过这种相对关系,找到自身的依赖属性进行数据绑定,从而让数据从外界传递到用户控件的内部。用户控件的内部应该保持完整的封装,就像C#的函数定义一样,对外提供接口,隐藏内部细节。


再看看下面这种做法:

UserControl.xaml中

<TextBox Text="{Binding Value}" Height="30"/>

MainWindow.xaml中

<local:MyUserControl"/>

这种做法,用户控件跟外界耦合了,万一在窗体使用时DataContext中没有Value属性,就出问题了。

特点

用户控件注重复合控件即控件的组合使用,可以根据控件开发人员自己的意愿进行功能处理,非常灵活。

用户控件主要有如下特点:

  • 多个现有控件的组合,组成一个可复用的控件组。
  • Xaml和后台代码组成,绑定非常紧密。
  • 窗体使用用户控件后,不支持对用户控件模板、样式的重写。
  • 继承自UserControl类型

二、用户控件的资源调用

UserControl是一个内容控件,可以通过Template属性的ControlTemplate对控件内容进行,同时在UserControl元素中的内容都会统一存放到ContentPresenter元素中,可以利用其进行模板样式的复用,具体用法如下。

创建资源字典

创建资源字典DefaultToolBarTemplate.xaml,编写ControlTemplate,其重点在于利用ContentPresenter元素来对UserControl的内容进行布局。

<ResourceDictionary ......><ControlTemplate TargetType="UserControl" x:Key="ToolBarTemplate"><Grid><Grid.RowDefinitions><RowDefinition Height="50"/><RowDefinition/></Grid.RowDefinitions><TextBlock Text="这里是复用内容,哪个UserControl使用都是OK的"/><!--下面就是内容布局--><ContentPresenter Grid.Row="1"/></Grid></ControlTemplate>
</ResourceDictionary>

在UserControl中引用资源

<UserControl ......><UserControl.Resources><ResourceDictionary Source="/Assets;component/Styles/DefaultToolBarTemplate.xaml"/></UserControl.Resources><UserControl.Template><!--引用资源,复用模板--><StaticResource ResourceKey="ToolBarTemplate"/></UserControl.Template><Grid><!--这里就是用户控件自己的内容了--></Grid>
</UserControl>

三、继承处理

在实际项目中,很多时候会创建多个用户控件来完成不同的功能,但是这些控件中往往有部分功能是冗余的,如下列示例代码:

TestUserControlA

    <UserControl ......><Grid Background="Green"><Button Content="删除" Width="100" Height="50" Click="Button_Click"/></Grid></UserControl>
public partial class TestUserControlA : UserControl
{public TestUserControlA(){InitializeComponent();}public ICommand DeleteCommand{get { return (ICommand)GetValue(DeleteCommandProperty); }set { SetValue(DeleteCommandProperty, value); }}public static readonly DependencyProperty DeleteCommandProperty =DependencyProperty.Register("DeleteCommand", typeof(ICommand), typeof(TestUserControlA), new PropertyMetadata(default));public object CommandParameter{get { return (object)GetValue(CommandParameterProperty); }set { SetValue(CommandParameterProperty, value); }}public static readonly DependencyProperty CommandParameterProperty =DependencyProperty.Register("CommandParameter", typeof(object), typeof(TestUserControlA), new PropertyMetadata(default));private void Button_Click(object sender, RoutedEventArgs e){DeleteCommand?.Execute(CommandParameter);}
}

TestUserControlB

<UserControl ......><Grid Background="Green"><Button Content="删除" Width="100" Height="50" Click="Button_Click"/></Grid>
</UserControl>
public partial class TestUserControlB : UserControl
{public TestUserControlB(){InitializeComponent();}public ICommand DeleteCommand{get { return (ICommand)GetValue(DeleteCommandProperty); }set { SetValue(DeleteCommandProperty, value); }}public static readonly DependencyProperty DeleteCommandProperty =DependencyProperty.Register("DeleteCommand", typeof(ICommand), typeof(TestUserControlB), new PropertyMetadata(default));public object CommandParameter{get { return (object)GetValue(CommandParameterProperty); }set { SetValue(CommandParameterProperty, value); }}public static readonly DependencyProperty CommandParameterProperty =DependencyProperty.Register("CommandParameter", typeof(object), typeof(TestUserControlB), new PropertyMetadata(default));private void Button_Click(object sender, RoutedEventArgs e){DeleteCommand?.Execute(CommandParameter);}
}

观察上述代码,可以发现TestUserControlA和TestUserControlB中的两个依赖属性其含义是相同的,这个时候可以考虑进行抽取到父类,来消除冗余,抽取之后结果如下:

父类代码

public class ComponentBase:UserControl{public ICommand DeleteCommand{get { return (ICommand)GetValue(DeleteCommandProperty); }set { SetValue(DeleteCommandProperty, value); }}public static readonly DependencyProperty DeleteCommandProperty =DependencyProperty.Register("DeleteCommand", typeof(ICommand), typeof(ComponentBase), new PropertyMetadata(default));public object CommandParameter{get { return (object)GetValue(CommandParameterProperty); }set { SetValue(CommandParameterProperty, value); }}public static readonly DependencyProperty CommandParameterProperty =DependencyProperty.Register("CommandParameter", typeof(object), typeof(ComponentBase), new PropertyMetadata(default));}

TestUserControlA

<local:ComponentBase x:Class="Components.TestUserControlA" ......><Grid Background="Green"><Button Content="删除" Width="100" Height="50" Click="Button_Click"/></Grid></local:ComponentBase>
public partial class TestUserControlA : ComponentBase
{public TestUserControlA(){InitializeComponent();}private void Button_Click(object sender, RoutedEventArgs e){DeleteCommand?.Execute(CommandParameter);}
}

TestUserControlB

<local:ComponentBase x:Class="Components.TestUserControlB" ......><Grid Background="Green"><Button Content="删除" Width="100" Height="50" Click="Button_Click"/></Grid></local:ComponentBase>
public partial class TestUserControlB : ComponentBase
{public TestUserControlB(){InitializeComponent();}private void Button_Click(object sender, RoutedEventArgs e){DeleteCommand?.Execute(CommandParameter);}
}

需要注意的是,TestUserControlA和TestUserControlB 的XAML代码中的UserConrol元素换成了父类的ComponentBase元素,这是应为在进行部分类(partial)合并时必须是相同的基类。

自定义控件-CustomControl

创建自定义控件
在这里插入图片描述
创建完成后,项目中会新增一个MyCustomControl.cs文件和一个含有Generic.xaml的Themes文件夹。

MyCustomControl.cs文件用于处理自定义控件的逻辑,Generic.xaml文件则负责界面显示。

主题文件

思考一个问题,当我们在编辑界面上右键Button按钮编辑样式-编辑副本,然后xaml文件中会出来Button的默认模板代码,这些默认模板是从哪里来的?其实就是从Button的Generic.xaml文件获取的。其实准确来说,WPF会先根据当前运行系统从对应的模板文件中获取默认模板,如果对应的系统模板文件中没有对应的控件模板,才会从控件的Generic.xaml文件中获取默认模板。

试试右键ComboBox控件->编辑样式->编辑副本,然后在xaml文件的Window元素的属性中会发现多出了一个属性,这个就是根据系统引入的模板文件,即当前系统主题。

在这里插入图片描述

需要注意的是,如果xaml文件中引入了如上主题,应用如果放到win7系统,由于win7没有这个主题文件,会导致应用崩溃。

逻辑处理

当逻辑里需要界面对象参与时,可以在后台代码中通过FindName函数来获取对应的界面对象。

FindName(string name):根据指定的元素名称获取对应的元素对象,不存在时返回null

public class MyCustomControl : Control
{
......private void LogicTest(){object btn = FindName("btn_Name");}
}

也可以通过重写父类ControlOnApplyTemplate函数,在函数中通过GetTemplateChild函数获取对象。

GetTemplateChild(string Name):在实例化的可视化树中获取指定名称的元素对象,不存在时返回null

public override void OnApplyTemplate()
{var buttonElement = GetTemplateChild("btn_One") as Button;base.OnApplyTemplate();
}

特点

实现自定义控件需要注重控件对象的功能,必须遵守WPF的控件规则。

自定义控件有如下特点:

  • 可以完全自定义实现一个控件或者继承现有控件进行功能扩展并添加新功能。
  • 通过后台代码(逻辑控制)和Generic.xaml(样式模板)进行组合来完成控件实现。
  • 继承自Control类型。

这篇关于WPF入门到跪下 第九章 用户控件与自定义控件的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/591111

相关文章

Spring Boot分层架构详解之从Controller到Service再到Mapper的完整流程(用户管理系统为例)

《SpringBoot分层架构详解之从Controller到Service再到Mapper的完整流程(用户管理系统为例)》本文将以一个实际案例(用户管理系统)为例,详细解析SpringBoot中Co... 目录引言:为什么学习Spring Boot分层架构?第一部分:Spring Boot的整体架构1.1

k8s admin用户生成token方式

《k8sadmin用户生成token方式》用户使用Kubernetes1.28创建admin命名空间并部署,通过ClusterRoleBinding为jenkins用户授权集群级权限,生成并获取其t... 目录k8s admin用户生成token创建一个admin的命名空间查看k8s namespace 的

C#中通过Response.Headers设置自定义参数的代码示例

《C#中通过Response.Headers设置自定义参数的代码示例》:本文主要介绍C#中通过Response.Headers设置自定义响应头的方法,涵盖基础添加、安全校验、生产实践及调试技巧,强... 目录一、基础设置方法1. 直接添加自定义头2. 批量设置模式二、高级配置技巧1. 安全校验机制2. 类型

SpringBoot AspectJ切面配合自定义注解实现权限校验的示例详解

《SpringBootAspectJ切面配合自定义注解实现权限校验的示例详解》本文章介绍了如何通过创建自定义的权限校验注解,配合AspectJ切面拦截注解实现权限校验,本文结合实例代码给大家介绍的非... 目录1. 创建权限校验注解2. 创建ASPectJ切面拦截注解校验权限3. 用法示例A. 参考文章本文

Vite 打包目录结构自定义配置小结

《Vite打包目录结构自定义配置小结》在Vite工程开发中,默认打包后的dist目录资源常集中在asset目录下,不利于资源管理,本文基于Rollup配置原理,本文就来介绍一下通过Vite配置自定义... 目录一、实现原理二、具体配置步骤1. 基础配置文件2. 配置说明(1)js 资源分离(2)非 JS 资

从入门到精通详解Python虚拟环境完全指南

《从入门到精通详解Python虚拟环境完全指南》Python虚拟环境是一个独立的Python运行环境,它允许你为不同的项目创建隔离的Python环境,下面小编就来和大家详细介绍一下吧... 目录什么是python虚拟环境一、使用venv创建和管理虚拟环境1.1 创建虚拟环境1.2 激活虚拟环境1.3 验证虚

聊聊springboot中如何自定义消息转换器

《聊聊springboot中如何自定义消息转换器》SpringBoot通过HttpMessageConverter处理HTTP数据转换,支持多种媒体类型,接下来通过本文给大家介绍springboot中... 目录核心接口springboot默认提供的转换器如何自定义消息转换器Spring Boot 中的消息

Java List 使用举例(从入门到精通)

《JavaList使用举例(从入门到精通)》本文系统讲解JavaList,涵盖基础概念、核心特性、常用实现(如ArrayList、LinkedList)及性能对比,介绍创建、操作、遍历方法,结合实... 目录一、List 基础概念1.1 什么是 List?1.2 List 的核心特性1.3 List 家族成

c++日志库log4cplus快速入门小结

《c++日志库log4cplus快速入门小结》文章浏览阅读1.1w次,点赞9次,收藏44次。本文介绍Log4cplus,一种适用于C++的线程安全日志记录API,提供灵活的日志管理和配置控制。文章涵盖... 目录简介日志等级配置文件使用关于初始化使用示例总结参考资料简介log4j 用于Java,log4c

史上最全MybatisPlus从入门到精通

《史上最全MybatisPlus从入门到精通》MyBatis-Plus是MyBatis增强工具,简化开发并提升效率,支持自动映射表名/字段与实体类,提供条件构造器、多种查询方式(等值/范围/模糊/分页... 目录1.简介2.基础篇2.1.通用mapper接口操作2.2.通用service接口操作3.进阶篇3