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

相关文章

最好用的WPF加载动画功能

《最好用的WPF加载动画功能》当开发应用程序时,提供良好的用户体验(UX)是至关重要的,加载动画作为一种有效的沟通工具,它不仅能告知用户系统正在工作,还能够通过视觉上的吸引力来增强整体用户体验,本文给... 目录前言需求分析高级用法综合案例总结最后前言当开发应用程序时,提供良好的用户体验(UX)是至关重要

SpringBoot 自定义消息转换器使用详解

《SpringBoot自定义消息转换器使用详解》本文详细介绍了SpringBoot消息转换器的知识,并通过案例操作演示了如何进行自定义消息转换器的定制开发和使用,感兴趣的朋友一起看看吧... 目录一、前言二、SpringBoot 内容协商介绍2.1 什么是内容协商2.2 内容协商机制深入理解2.2.1 内容

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

数论入门整理(updating)

一、gcd lcm 基础中的基础,一般用来处理计算第一步什么的,分数化简之类。 LL gcd(LL a, LL b) { return b ? gcd(b, a % b) : a; } <pre name="code" class="cpp">LL lcm(LL a, LL b){LL c = gcd(a, b);return a / c * b;} 例题:

Java 创建图形用户界面(GUI)入门指南(Swing库 JFrame 类)概述

概述 基本概念 Java Swing 的架构 Java Swing 是一个为 Java 设计的 GUI 工具包,是 JAVA 基础类的一部分,基于 Java AWT 构建,提供了一系列轻量级、可定制的图形用户界面(GUI)组件。 与 AWT 相比,Swing 提供了许多比 AWT 更好的屏幕显示元素,更加灵活和可定制,具有更好的跨平台性能。 组件和容器 Java Swing 提供了许多

【IPV6从入门到起飞】5-1 IPV6+Home Assistant(搭建基本环境)

【IPV6从入门到起飞】5-1 IPV6+Home Assistant #搭建基本环境 1 背景2 docker下载 hass3 创建容器4 浏览器访问 hass5 手机APP远程访问hass6 更多玩法 1 背景 既然电脑可以IPV6入站,手机流量可以访问IPV6网络的服务,为什么不在电脑搭建Home Assistant(hass),来控制你的设备呢?@智能家居 @万物互联

poj 2104 and hdu 2665 划分树模板入门题

题意: 给一个数组n(1e5)个数,给一个范围(fr, to, k),求这个范围中第k大的数。 解析: 划分树入门。 bing神的模板。 坑爹的地方是把-l 看成了-1........ 一直re。 代码: poj 2104: #include <iostream>#include <cstdio>#include <cstdlib>#include <al

MySQL-CRUD入门1

文章目录 认识配置文件client节点mysql节点mysqld节点 数据的添加(Create)添加一行数据添加多行数据两种添加数据的效率对比 数据的查询(Retrieve)全列查询指定列查询查询中带有表达式关于字面量关于as重命名 临时表引入distinct去重order by 排序关于NULL 认识配置文件 在我们的MySQL服务安装好了之后, 会有一个配置文件, 也就

自定义类型:结构体(续)

目录 一. 结构体的内存对齐 1.1 为什么存在内存对齐? 1.2 修改默认对齐数 二. 结构体传参 三. 结构体实现位段 一. 结构体的内存对齐 在前面的文章里我们已经讲过一部分的内存对齐的知识,并举出了两个例子,我们再举出两个例子继续说明: struct S3{double a;int b;char c;};int mian(){printf("%zd\n",s