经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 软件/图像 » unity » 查看文章
循序渐进介绍基于CommunityToolkit.Mvvm 和HandyControl的WPF应用端开发(3)--自定义用户控件
来源:cnblogs  作者:伍华聪  时间:2023/9/13 15:37:55  对本文有异议

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

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

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

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

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

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

 

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

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

  1. <UserControl
  2. x:Class="WHC.SugarProject.WpfUI.Controls.NumericRange"
  3. xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  4. xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  5. xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
  6. xmlns:hc="https://handyorg.github.io/handycontrol"
  7. xmlns:local="clr-namespace:WHC.SugarProject.WpfUI.Controls"
  8. xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
  9. Name="NumericRangeControl"
  10. d:Background="Transparent"
  11. d:DesignHeight="32"
  12. d:DesignWidth="150"
  13. d:Foreground="White"
  14. mc:Ignorable="d">
  15. <Grid MinWidth="150">
  16. <Grid.ColumnDefinitions>
  17. <ColumnDefinition Width="Auto" />
  18. <ColumnDefinition Width="*" />
  19. <ColumnDefinition Width="Auto" />
  20. <ColumnDefinition Width="*" />
  21. </Grid.ColumnDefinitions>
  22. <TextBlock
  23. Grid.Column="0"
  24. Margin="10,0,10,0"
  25. VerticalAlignment="Center"
  26. Text="{Binding Path=Text, RelativeSource={RelativeSource AncestorType={x:Type local:NumericRange}}}" />
  27. <TextBox
  28. x:Name="txtStart"
  29. Grid.Column="1"
  30. Margin="5"
  31. Text="{Binding Path=StartValue, Converter={StaticResource NumericConverter}, RelativeSource={RelativeSource AncestorType={x:Type local:NumericRange}}}" />
  32. <TextBlock
  33. Grid.Column="2"
  34. Margin="5,0,5,0"
  35. VerticalAlignment="Center"
  36. Text="~" />
  37. <TextBox
  38. x:Name="txtEnd"
  39. Grid.Column="3"
  40. Margin="5"
  41. Text="{Binding Path=EndValue, Converter={StaticResource NumericConverter}, RelativeSource={RelativeSource AncestorType={x:Type local:NumericRange}}}" />
  42. </Grid>
  43. </UserControl>

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

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

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

  1. Name="NumericRangeControl"

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

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

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

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

  1. /// <summary>
  2. /// 开始值
  3. /// </summary>
  4. public decimal? StartValue
  5. {
  6. get { return (decimal?)GetValue(StartValueProperty); }
  7. set { SetValue(StartValueProperty, value); }
  8. }
  9. public static readonly DependencyProperty StartValueProperty = DependencyProperty.Register(
  10. nameof(StartValue), typeof(decimal?), typeof(NumericRange),
  11. new FrameworkPropertyMetadata(default(decimal?), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, new PropertyChangedCallback(OnStartValuePropertyChanged)));

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

  1. private static void OnStartValuePropertyChanged(DependencyObject d,DependencyPropertyChangedEventArgs e)
  2. {
  3. if (d is not NumericRange control)
  4. return;
  5. if (control != null)
  6. {
  7. var oldValue = (decimal?)e.OldValue; // 旧的值
  8. var newValue = (decimal?)e.NewValue; // 更新的新的值
  9.  
  10. var args = new RoutedPropertyChangedEventArgs<decimal?>(oldValue, newValue);
  11. args.RoutedEvent = NumericRange.ValueChangedEvent;
  12. control.RaiseEvent(args);
  13. control.ValueChangedCommand?.Execute(null);
  14. }
  15. }

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

  1. /// <summary>
  2. /// 数量改变命令
  3. /// </summary>
  4. public static readonly DependencyProperty ValueChangedCommandProperty =
  5. DependencyProperty.Register("ValueChangedCommand", typeof(ICommand), typeof(NumericRange), new PropertyMetadata(default(ICommand)));
  6. /// <summary>
  7. /// 数量改变命令
  8. /// </summary>
  9. public ICommand ValueChangedCommand
  10. {
  11. get { return (ICommand)GetValue(ValueChangedCommandProperty); }
  12. set { SetValue(ValueChangedCommandProperty, value); }
  13. }

3、自定义控件的使用

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

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

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

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

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

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

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

  1. <TextBox
  2. x:Name="txtStart"
  3. Grid.Column="1"
  4. Margin="5"
  5. KeyDown="txtStartEndValue_KeyDown"
  6. Text="{Binding Path=StartValue, Converter={StaticResource NumericConverter}, ElementName=NumericRangeControl}" />
  7.  
  8. <TextBlock
  9. Grid.Column="2"
  10. Margin="5,0,5,0"
  11. VerticalAlignment="Center"
  12. Text="~" />
  13.  
  14. <TextBox
  15. x:Name="txtEnd"
  16. Grid.Column="3"
  17. Margin="5"
  18. KeyDown="txtStartEndValue_KeyDown"
  19. Text="{Binding Path=EndValue, Converter={StaticResource NumericConverter}, RelativeSource={RelativeSource AncestorType={x:Type local:NumericRange}}}" />

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

  1. private void txtStartEndValue_KeyDown(object sender, KeyEventArgs e)
  2. {
  3. if(e.Key == Key.Enter)
  4. {
  5. var textBox = sender as System.Windows.Controls.TextBox;
  6. if(textBox != null)
  7. {
  8. //切换焦点会触发值更新命令
  9. textBox.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
  10. }
  11. }
  12. }

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

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

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

 

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

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

 

原文链接:https://www.cnblogs.com/wuhuacong/p/17698705.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号