循序渐进介绍基于CommunityToolkit.Mvvm 和HandyControl的WPF应用端开发(3)--自定义用户控件

简介: 循序渐进介绍基于CommunityToolkit.Mvvm 和HandyControl的WPF应用端开发(3)--自定义用户控件

在我们创建界面元素的时候,不管在Vue3+ElementPlus的前端上,还是Winform桌面端上,都是会利用自定义用户控件来快速重用一些自定义的界面内容,对自定义用户控件的封装处理,也是我们开发WPF应用需要熟悉的一环。本篇随笔继续深入介绍介绍基于CommunityToolkit.Mvvm 和HandyControl的WPF应用端开发,主要针对自定义用户控件的封装和使用做一些介绍。

1、自定义用户控件的应用场景

在我们使用原生的WPF控件的时候,有时候发现常规的原生控件不够好看,或者功能达不到要求,就需要进行一定程度上的二次封装处理,也就是自定义控件的开发场景。

例如我们前面介绍到的用户信息的查询界面,我们没有找到一个输入数值范围的控件,如对于年龄等类似的属性,我们需要一个区间的查询处理,可以保留为空,或者最小、最大值之间进行查询,如下界面所示。

由于WPF没有这样的原生控件,我们需要的话,就需要使用常规的数值或者文本控件来进行处理,如果多次有这样的内容,封装为自定义控件,让她简单的使用,是最为优雅的方式。

我们看到控件的外观如下所示。

 

2、自定义控件的开发代码

我们可以用Grid布局来进行处理,包括两个TextBlock和两个文本的控件界面,我们创建自定义控件后,在Xaml定义好布局信息。

<UserControl
    x:Class="WHC.SugarProject.WpfUI.Controls.NumericRange"
    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:hc="https://handyorg.github.io/handycontrol"
    xmlns:local="clr-namespace:WHC.SugarProject.WpfUI.Controls"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    Name="NumericRangeControl"
    d:Background="Transparent"
    d:DesignHeight="32"
    d:DesignWidth="150"
    d:Foreground="White"
    mc:Ignorable="d">
    <Grid MinWidth="150">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto" />
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="Auto" />
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>
        <TextBlock
            Grid.Column="0"
            Margin="10,0,10,0"
            VerticalAlignment="Center"
            Text="{Binding Path=Text, RelativeSource={RelativeSource AncestorType={x:Type local:NumericRange}}}" />
        <TextBox
            x:Name="txtStart"
            Grid.Column="1"
            Margin="5"
            Text="{Binding Path=StartValue, Converter={StaticResource NumericConverter}, RelativeSource={RelativeSource AncestorType={x:Type local:NumericRange}}}" />
        <TextBlock
            Grid.Column="2"
            Margin="5,0,5,0"
            VerticalAlignment="Center"
            Text="~" />
        <TextBox
            x:Name="txtEnd"
            Grid.Column="3"
            Margin="5"
            Text="{Binding Path=EndValue, Converter={StaticResource NumericConverter}, RelativeSource={RelativeSource AncestorType={x:Type local:NumericRange}}}" />
    </Grid>
</UserControl>

其中绑定动态属性的地方,我们使用下面代码

Text="{Binding Path=StartValue, Converter={StaticResource NumericConverter}, RelativeSource={RelativeSource AncestorType={x:Type local:NumericRange}}}"

当然也可以使用Element的标记方式,这种我们需要设置用户自定义控件名称为Name=“***”,如上面的代码设置为。

Name="NumericRangeControl"

这样我们就可以通过自定义控件的ElementName来定位绑定的属性了,等同于如下代码。

<TextBox
            x:Name="txtStart"
            Grid.Column="1"
            Margin="5"
            Text="{Binding Path=StartValue, Converter={StaticResource NumericConverter}, ElementName=NumericRangeControl}" />

前面我们介绍了该控件包含了的一些属性,如StartValue、EndValue、以及文本说明Text等,这些是在用户控件后台代码里面进行定义的自定义依赖属性的,我们来看看代码。

如我们增加一个StartValue,那么同时需要增加一个StartValueProperty的自定义依赖属性。

/// <summary>
        /// 开始值
        /// </summary>
        public decimal? StartValue
        {
            get { return (decimal?)GetValue(StartValueProperty); }
            set { SetValue(StartValueProperty, value); }
        }
        public static readonly DependencyProperty StartValueProperty = DependencyProperty.Register(
            nameof(StartValue), typeof(decimal?), typeof(NumericRange),
            new FrameworkPropertyMetadata(default(decimal?), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, new PropertyChangedCallback(OnStartValuePropertyChanged)));

同时,这个属性的变化,会触发一个控件路由的事件OnStartValuePropertyChanged ,如下所示。

private static void OnStartValuePropertyChanged(DependencyObject d,DependencyPropertyChangedEventArgs e)
        {
            if (d is not NumericRange control)
                return;
            if (control != null)
            {
                var oldValue = (decimal?)e.OldValue;  // 旧的值
                var newValue = (decimal?)e.NewValue; // 更新的新的值
                var args = new RoutedPropertyChangedEventArgs<decimal?>(oldValue, newValue);
                args.RoutedEvent = NumericRange.ValueChangedEvent;
                control.RaiseEvent(args);
                control.ValueChangedCommand?.Execute(null);
            }
        }

除了触发路由事件外,我们可以给该控件定义一个Command 命令,类似按钮的命令处理,绑定后就可以接受到相关的通知了。Command的定义如下代码所示。

/// <summary>
        /// 数量改变命令
        /// </summary>
        public static readonly DependencyProperty ValueChangedCommandProperty =
            DependencyProperty.Register("ValueChangedCommand", typeof(ICommand), typeof(NumericRange), new PropertyMetadata(default(ICommand)));
        /// <summary>
        /// 数量改变命令
        /// </summary>
        public ICommand ValueChangedCommand
        {
            get { return (ICommand)GetValue(ValueChangedCommandProperty); }
            set { SetValue(ValueChangedCommandProperty, value); }
        }

3、自定义控件的使用

自定义控件开发好后,使用也是很简单的,需要在页面或者窗口的定义部分,增加控件的命名空间,便于引用自定义控件,如下代码所示。

xmlns:Controls="clr-namespace:WHC.SugarProject.WpfUI.Controls"

这样我们在使用的时候,就和其他原生控件的使用差不多了。如下是在页面中使用的Xaml代码。

<Controls:NumericRange
      EndValue="{Binding ViewModel.PageDto.AgeEnd, UpdateSourceTrigger=PropertyChanged}"
      StartValue="{Binding ViewModel.PageDto.AgeStart, UpdateSourceTrigger=PropertyChanged}"
      Text="年龄"
      ValueChangedCommand="{Binding ViewModel.SearchCommand}" />

我们可以看到自定义控件的属性的绑定,和其他控件的属性绑定一致的,而且我们这里定义了一个Command:ValueChangedCommand

我们可以通过这个命令接收控件变化的通知。这样就可以正常的实现我们所需要的处理功能了。

另外,自定义控件的输入框,一般会在失去焦点后触发命令处理,我们也可以让文本输入框在输入后回车触发命令处理,我们增加一个KeyDown的事件处理,如下代码所示。

<TextBox
            x:Name="txtStart"
            Grid.Column="1"
            Margin="5"
            KeyDown="txtStartEndValue_KeyDown"
            Text="{Binding Path=StartValue, Converter={StaticResource NumericConverter}, ElementName=NumericRangeControl}" />
        <TextBlock
            Grid.Column="2"
            Margin="5,0,5,0"
            VerticalAlignment="Center"
            Text="~" />
        <TextBox
            x:Name="txtEnd"
            Grid.Column="3"
            Margin="5"
            KeyDown="txtStartEndValue_KeyDown"
            Text="{Binding Path=EndValue, Converter={StaticResource NumericConverter}, RelativeSource={RelativeSource AncestorType={x:Type local:NumericRange}}}" />

让回车切换到下一个焦点即可。

private void txtStartEndValue_KeyDown(object sender, KeyEventArgs e)
        {
            if(e.Key == Key.Enter)
            {
                var textBox = sender as System.Windows.Controls.TextBox;
                if(textBox != null)
                {
                    //切换焦点会触发值更新命令
                    textBox.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
                }
            }
        }

至此我们就完成了完美的控件处理事件了。

编译后,我们就可以在工具栏中看到用户自定义控件的列表了,可以直接拖动它到页面进行使用。

至此,我们就实现了自定义控件在页面上的使用了,非常简单。

当然,我们也可以组合一些面板,来实现更加复杂的控件呈现方式,可以设计一些图表、文本内容的综合展示,如下是其中的一个控件的多层展示。

根据不同的图标、内容,背景色、以及一些集合形状的叠加,就可以设计出非常好看的单个用户控件,然后动态设置,就可以很好的实现不同的内容展示。

 

专注于代码生成工具、.Net/.NetCore 框架架构及软件开发,以及各种Vue.js的前端技术应用。著有Winform开发框架/混合式开发框架、微信开发框架、Bootstrap开发框架、ABP开发框架、SqlSugar开发框架等框架产品。
 转载请注明出处:撰写人:伍华聪  http://www.iqidi.com
   

相关文章
|
3月前
|
C# 开发者 Windows
WPF 应用程序开发:一分钟入门
本文介绍 Windows Presentation Foundation (WPF),这是一种用于构建高质量、可缩放的 Windows 桌面应用程序的框架,支持 XAML 语言,方便 UI 设计与逻辑分离。文章涵盖 WPF 基础概念、代码示例,并深入探讨常见问题及解决方案,包括数据绑定、控件样式与模板、布局管理等方面,帮助开发者高效掌握 WPF 开发技巧。
166 65
|
4月前
|
C# UED 开发者
WPF与性能优化:掌握这些核心技巧,让你的应用从卡顿到丝滑,彻底告别延迟,实现响应速度质的飞跃——从布局到动画全面剖析与实例演示
【8月更文挑战第31天】本文通过对比优化前后的方法,详细探讨了提升WPF应用响应速度的策略。文章首先分析了常见的性能瓶颈,如复杂的XAML布局、耗时的事件处理、不当的数据绑定及繁重的动画效果。接着,通过具体示例展示了如何简化XAML结构、使用后台线程处理事件、调整数据绑定设置以及利用DirectX优化动画,从而有效提升应用性能。通过这些优化措施,WPF应用将更加流畅,用户体验也将得到显著改善。
252 1
|
4月前
|
安全 C# 数据安全/隐私保护
WPF安全加固全攻略:从数据绑定到网络通信,多维度防范让你的应用固若金汤,抵御各类攻击
【8月更文挑战第31天】安全性是WPF应用程序开发中不可或缺的一部分。本文从技术角度探讨了WPF应用面临的多种安全威胁及防护措施。通过严格验证绑定数据、限制资源加载来源、实施基于角色的权限管理和使用加密技术保障网络通信安全,可有效提升应用安全性,增强用户信任。例如,使用HTML编码防止XSS攻击、检查资源签名确保其可信度、定义安全策略限制文件访问权限,以及采用HTTPS和加密算法保护数据传输。这些措施有助于全面保障WPF应用的安全性。
54 0
|
4月前
|
C# 开发者 Windows
全面指南:WPF无障碍设计从入门到精通——让每一个用户都能无障碍地享受你的应用,从自动化属性到焦点导航的最佳实践
【8月更文挑战第31天】为了确保Windows Presentation Foundation (WPF) 应用程序对所有用户都具备无障碍性,开发者需关注无障碍设计原则。这不仅是法律要求,更是社会责任,旨在让技术更人性化,惠及包括视障、听障及行动受限等用户群体。
85 0
|
4月前
|
测试技术 C# 开发者
“代码守护者:详解WPF开发中的单元测试策略与实践——从选择测试框架到编写模拟对象,全方位保障你的应用程序质量”
【8月更文挑战第31天】单元测试是确保软件质量的关键实践,尤其在复杂的WPF应用中更为重要。通过为每个小模块编写独立测试用例,可以验证代码的功能正确性并在早期发现错误。本文将介绍如何在WPF项目中引入单元测试,并通过具体示例演示其实施过程。首先选择合适的测试框架如NUnit或xUnit.net,并利用Moq模拟框架隔离外部依赖。接着,通过一个简单的WPF应用程序示例,展示如何模拟`IUserRepository`接口并验证`MainViewModel`加载用户数据的正确性。这有助于确保代码质量和未来的重构与扩展。
95 0
|
4月前
|
前端开发 C# 设计模式
“深度剖析WPF开发中的设计模式应用:以MVVM为核心,手把手教你重构代码结构,实现软件工程的最佳实践与高效协作”
【8月更文挑战第31天】设计模式是在软件工程中解决常见问题的成熟方案。在WPF开发中,合理应用如MVC、MVVM及工厂模式等能显著提升代码质量和可维护性。本文通过具体案例,详细解析了这些模式的实际应用,特别是MVVM模式如何通过分离UI逻辑与业务逻辑,实现视图与模型的松耦合,从而优化代码结构并提高开发效率。通过示例代码展示了从模型定义、视图模型管理到视图展示的全过程,帮助读者更好地理解并应用这些模式。
102 0
|
4月前
|
容器 C# Docker
WPF与容器技术的碰撞:手把手教你Docker化WPF应用,实现跨环境一致性的开发与部署
【8月更文挑战第31天】容器技术简化了软件开发、测试和部署流程,尤其对Windows Presentation Foundation(WPF)应用程序而言,利用Docker能显著提升其可移植性和可维护性。本文通过具体示例代码,详细介绍了如何将WPF应用Docker化的过程,包括创建Dockerfile及构建和运行Docker镜像的步骤。借助容器技术,WPF应用能在任何支持Docker的环境下一致运行,极大地提升了开发效率和部署灵活性。
134 0
|
4月前
|
存储 C# 关系型数据库
“云端融合:WPF应用无缝对接Azure与AWS——从Blob存储到RDS数据库,全面解析跨平台云服务集成的最佳实践”
【8月更文挑战第31天】本文探讨了如何将Windows Presentation Foundation(WPF)应用与Microsoft Azure和Amazon Web Services(AWS)两大主流云平台无缝集成。通过具体示例代码展示了如何利用Azure Blob Storage存储非结构化数据、Azure Cosmos DB进行分布式数据库操作;同时介绍了如何借助Amazon S3实现大规模数据存储及通过Amazon RDS简化数据库管理。这不仅提升了WPF应用的可扩展性和可用性,还降低了基础设施成本。
89 0
|
4月前
|
vr&ar C# 图形学
WPF与AR/VR的激情碰撞:解锁Windows Presentation Foundation应用新维度,探索增强现实与虚拟现实技术在现代UI设计中的无限可能与实战应用详解
【8月更文挑战第31天】增强现实(AR)与虚拟现实(VR)技术正迅速改变生活和工作方式,在游戏、教育及工业等领域展现出广泛应用前景。本文探讨如何在Windows Presentation Foundation(WPF)环境中实现AR/VR功能,通过具体示例代码展示整合过程。尽管WPF本身不直接支持AR/VR,但借助第三方库如Unity、Vuforia或OpenVR,可实现沉浸式体验。例如,通过Unity和Vuforia在WPF中创建AR应用,或利用OpenVR在WPF中集成VR功能,从而提升用户体验并拓展应用功能边界。
73 0
|
4月前
|
区块链 C# 存储
链动未来:WPF与区块链的创新融合——从智能合约到去中心化应用,全方位解析开发安全可靠DApp的最佳路径
【8月更文挑战第31天】本文以问答形式详细介绍了区块链技术的特点及其在Windows Presentation Foundation(WPF)中的集成方法。通过示例代码展示了如何选择合适的区块链平台、创建智能合约,并在WPF应用中与其交互,实现安全可靠的消息存储和检索功能。希望这能为WPF开发者提供区块链技术应用的参考与灵感。
66 0