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

相关文章

从入门到精通MySQL联合查询

《从入门到精通MySQL联合查询》:本文主要介绍从入门到精通MySQL联合查询,本文通过实例代码给大家介绍的非常详细,需要的朋友可以参考下... 目录摘要1. 多表联合查询时mysql内部原理2. 内连接3. 外连接4. 自连接5. 子查询6. 合并查询7. 插入查询结果摘要前面我们学习了数据库设计时要满

从入门到精通C++11 <chrono> 库特性

《从入门到精通C++11<chrono>库特性》chrono库是C++11中一个非常强大和实用的库,它为时间处理提供了丰富的功能和类型安全的接口,通过本文的介绍,我们了解了chrono库的基本概念... 目录一、引言1.1 为什么需要<chrono>库1.2<chrono>库的基本概念二、时间段(Durat

解析C++11 static_assert及与Boost库的关联从入门到精通

《解析C++11static_assert及与Boost库的关联从入门到精通》static_assert是C++中强大的编译时验证工具,它能够在编译阶段拦截不符合预期的类型或值,增强代码的健壮性,通... 目录一、背景知识:传统断言方法的局限性1.1 assert宏1.2 #error指令1.3 第三方解决

Java实现自定义table宽高的示例代码

《Java实现自定义table宽高的示例代码》在桌面应用、管理系统乃至报表工具中,表格(JTable)作为最常用的数据展示组件,不仅承载对数据的增删改查,还需要配合布局与视觉需求,而JavaSwing... 目录一、项目背景详细介绍二、项目需求详细介绍三、相关技术详细介绍四、实现思路详细介绍五、完整实现代码

一文详解Java Stream的sorted自定义排序

《一文详解JavaStream的sorted自定义排序》Javastream中的sorted方法是用于对流中的元素进行排序的方法,它可以接受一个comparator参数,用于指定排序规则,sorte... 目录一、sorted 操作的基础原理二、自定义排序的实现方式1. Comparator 接口的 Lam

从入门到精通MySQL 数据库索引(实战案例)

《从入门到精通MySQL数据库索引(实战案例)》索引是数据库的目录,提升查询速度,主要类型包括BTree、Hash、全文、空间索引,需根据场景选择,建议用于高频查询、关联字段、排序等,避免重复率高或... 目录一、索引是什么?能干嘛?核心作用:二、索引的 4 种主要类型(附通俗例子)1. BTree 索引(

Redis 配置文件使用建议redis.conf 从入门到实战

《Redis配置文件使用建议redis.conf从入门到实战》Redis配置方式包括配置文件、命令行参数、运行时CONFIG命令,支持动态修改参数及持久化,常用项涉及端口、绑定、内存策略等,版本8... 目录一、Redis.conf 是什么?二、命令行方式传参(适用于测试)三、运行时动态修改配置(不重启服务

SpringSecurity显示用户账号已被锁定的原因及解决方案

《SpringSecurity显示用户账号已被锁定的原因及解决方案》SpringSecurity中用户账号被锁定问题源于UserDetails接口方法返回值错误,解决方案是修正isAccountNon... 目录SpringSecurity显示用户账号已被锁定的解决方案1.问题出现前的工作2.问题出现原因各

MySQL 用户创建与授权最佳实践

《MySQL用户创建与授权最佳实践》在MySQL中,用户管理和权限控制是数据库安全的重要组成部分,下面详细介绍如何在MySQL中创建用户并授予适当的权限,感兴趣的朋友跟随小编一起看看吧... 目录mysql 用户创建与授权详解一、MySQL用户管理基础1. 用户账户组成2. 查看现有用户二、创建用户1. 基

MySQL DQL从入门到精通

《MySQLDQL从入门到精通》通过DQL,我们可以从数据库中检索出所需的数据,进行各种复杂的数据分析和处理,本文将深入探讨MySQLDQL的各个方面,帮助你全面掌握这一重要技能,感兴趣的朋友跟随小... 目录一、DQL 基础:SELECT 语句入门二、数据过滤:WHERE 子句的使用三、结果排序:ORDE