• 循序渐进介绍基于CommunityToolkit.Mvvm 和HandyControl的WPF应用端开发(11) -- 下拉列表的数据绑定以及自定义系统字典列表控件


    在我们开发的前端项目中,往往为了方便,都需对一些控件进行自定义的处理,以便实现快速的数据绑定以及便捷的使用,本篇随笔介绍通过抽取常见字典列表,实现通用的字典类型绑定;以及通过自定义控件的属性处理,实现系统字典内容的快捷绑定的操作。

    1、下拉列表的数据绑定

    在我们创建下拉列表的时候,我们一般处理方式,是在对应的数据模型中添加对应的下拉列表的集合对象,然后在控件绑定对应的ItemSource,如下所示是视图模型,我们增加一个性别的列表参考。

    复制代码
    /// 
    /// 用户列表-视图模型对象
    /// 
    public partial class UserListViewModel : BaseListViewModelint, UserPagedDto>
    {
        /// 
        /// 性别
        /// 
        [ObservableProperty]
        private List genderItems;
    
        /// 
        /// 构造函数
        /// 
        /// 业务服务接口
        public UserListViewModel(IUserService service) : base(service)
        {
            //初始化性别的列表
            this.GenderItems = new List()
            {
                new CListItem(""),
                new CListItem("")
            };
        }
    复制代码

    然后初始化后,就可以在界面上进行数据的绑定了,如下是对应控件的界面代码。

    复制代码
    <hc:ComboBox
        Margin="5"
        hc:TitleElement.Title="性别"
        hc:TitleElement.TitlePlacement="Left"
        DisplayMemberPath="Text"
        ItemsSource="{Binding ViewModel.GenderItems}"
        SelectedValue="{Binding ViewModel.PageDto.Gender, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
        SelectedValuePath="Value"
        ShowClearButton="True" />
    复制代码

     这种方式可能是经常用到的方式,随着不同界面代码的编写,我们发现很多这样下拉列表,如机构可能有一些类别(来自枚举对象)需要处理,其他页面也有类似的需求。

    复制代码
    /// 
    /// 机构(部门)信息 列表-视图模型对象
    /// 
    public partial class OuListViewModel : BaseListViewModelint, OuPagedDto>
    {
        /// 
        /// 机构分类
        /// 
        [ObservableProperty]
        private List categoryItems = new();
    
        /// 
        /// 构造函数
        /// 
        /// 业务服务接口
        public OuListViewModel(IOuService service) : base(service)
        {  
            //机构分类
            string[] enumNames = EnumHelper.GetMemberNames();
            this.CategoryItems.Clear();
            this.CategoryItems.AddRange(enumNames.Select(s => new CListItem(s)));
        }
    复制代码

    如果每次都需要在对应的视图模型上创建这些列表,则显得累赘、臃肿。因为这些下拉列表的内容,是界面中常用到的列表,我们是否可以把它作为一个公用的对象模型来使用呢。

    为了方便,我们来创建一个对象DictItemsModel ,用来初始化系统用到的所有参考列表对象,如下代码所示。

    复制代码
    /// 
    /// 定义一些系统常用的字典项目,供页面参考引用
    /// 
    public partial class DictItemsModel : ObservableObject
    {
        /// 
        /// 性别
        /// 
        [ObservableProperty]
        private  List genderItems = new();
    
        /// 
        /// 机构分类
        /// 
        [ObservableProperty]
        private List ouCategoryItems = new();
    
        //******更多列表处理**********
    
        /// 
        /// 构造函数
        /// 
        public DictItemsModel()
        {
            InitDictItem(); // 初始化字典
        }
    
        /// 
        /// 初始化字典
        /// 
        /// 
        public async Task InitDictItem()
        {
            //初始化性别的列表
            this.GenderItems = new List()
            {
                new(""),
                new(""),
                new("")
            };
    
            //机构分类
            this.OuCategoryItems = EnumHelper.GetMemberNames().Select(s => new CListItem(s)).ToList();
            this.OuCategoryItems.Insert(0, new CListItem(""));
    
            //*********************
        }
    }
    复制代码

    然后,我们在应用程序的XAML代码中,引入对应的静态资源,相当于每次使用这些的时候,是使用该对象的实例。

    复制代码
    <Application
        x:Class="WHC.SugarProject.WpfUI.App"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:helpers="clr-namespace:WHC.SugarProject.WpfUI.Helpers"
        xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
        DispatcherUnhandledException="OnDispatcherUnhandledException"
        Exit="OnExit"
        Startup="OnStartup">
        <Application.Resources>
            <ResourceDictionary>
                
                <helpers:IntToBooleanConverter x:Key="IntToBooleanConverter" />
                
                <helpers:DictItemsModel x:Key="DictItemsModel" />
                
            ResourceDictionary>
        Application.Resources>
    Application>
                
    复制代码

    有了这些定义,我们的下拉列表的数据源ItemSource的属性改动一下就可以实现一致的效果了,相当于抽取了字典列表到独立的类中处理了。

    复制代码
    
    <hc:ComboBox
        Margin="5"
        hc:TitleElement.Title="性别"
        hc:TitleElement.TitlePlacement="Left"
        DisplayMemberPath="Text"
        ItemsSource="{Binding Path=GenderItems, Source={StaticResource DictItemsModel}}"
        SelectedValue="{Binding ViewModel.PageDto.Gender, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
        SelectedValuePath="Value"
        ShowClearButton="True" />
    复制代码
    复制代码
    
    <hc:ComboBox
        Margin="5"
        hc:TitleElement.Title="机构分类"
        hc:TitleElement.TitlePlacement="Left"
        DisplayMemberPath="Text"
        ItemsSource="{Binding Path=OuCategoryItems, Source={StaticResource DictItemsModel}}"
        SelectedValue="{Binding ViewModel.PageDto.Category, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
        SelectedValuePath="Value"
        ShowClearButton="True" />
    复制代码

    上面代码中,我们通过Source={StaticResource DictItemsModel}来指定数据源的位置来自静态资源即可。如机构列表,通过枚举进行解析到的集合如下所示,当然其他数据也可以通过相应的处理实现。

      

    通过这种把常见的字典类别集中到一个类中进行维护,除了统一处理列表的初始化外,也方便我们在界面代码中的统一使用。

    2、自定义系统字典列表控件

    我们框架一般都维护一个通用的字典类型和字典项目的信息,通过维护这些常见的系统字典信息,可以为我们的界面的一些下拉类列表提供数据支持,是指实现通用、统一的字典处理。

    以前在Winform中绑定字典列表的时候,一般通过扩展函数BindDictItems就可以实现类型绑定了,有兴趣可以了解下随笔《在Winform开发框架中下拉列表绑定字典以及使用缓存提高界面显示速度》、《使用扩展函数方式,在Winform界面中快捷的绑定树形列表TreeList控件和TreeListLookUpEdit控件》、《在Winform开发中,我们使用的几种下拉列表展示字典数据的方式》、《在各种开发项目中使用公用类库的扩展方法,通过上下文方式快速调用处理函数》。

    对WPF来说,我们需要改变下思路,和Vue3的BS的控件的处理方式类似,我们通过给他指定一个字典类型的名称,让它自己取得对应列表,进行绑定处理即可,因此我们自定义字典列表控件即可。

        /// 
        /// 自定义下拉列表,方便绑定字典类型
        /// 
        public class ComboBox : HandyControl.Controls.ComboBox

    创建一个继承自所需下拉列表控件,可以使用原生控件继承,不过我这里偏向于UI更好的HandyControl的ComboBox。

    然后给它指定对应的字典类型属性,对应我们系统的字典大类名称。

    复制代码
        /// 
        /// 自定义下拉列表,方便绑定字典类型
        /// 
        public class ComboBox : HandyControl.Controls.ComboBox
        {
            /// 
            /// 字典类型名称
            /// 
            public string? DictTypeName
            {
                get { return (string?)GetValue(DictTypeNameProperty); }
                set { SetValue(DictTypeNameProperty, value); }
            }
    
            public static readonly DependencyProperty DictTypeNameProperty = DependencyProperty.Register(
                nameof(DictTypeName), typeof(string), typeof(ComboBox),
                new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, new PropertyChangedCallback(OnDictTypeNamePropertyChanged)));
    复制代码

    封装过自定义的WPF控件的话,我们知道,增加一个自定义属性,就需要同时增加一个自定义属性+Property的 DependencyProperty 属性对象,如上代码所示。

    通过PropertyChangedCallback的回调处理,实现设置字典类型值后触发控件内部数据的处理逻辑,也就是需要从字典服务中获取下拉类别数据,变为控件的ItemSource集合即可。

    一般情况下,我们实现下面的代码逻辑,获得数据源就差不过可以了。

    复制代码
    private static async void OnDictTypeNamePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (d is not ComboBox control)
            return;
    
        if (control != null)
        {
            var oldValue = (string?)e.OldValue;  // 旧的值
            var newValue = (string?)e.NewValue; // 更新的新的值
    
            //更新下拉列表的数据源
            if(!newValue.IsNullOrEmpty() && !control.IsInDesignMode())
            {
                var itemList = await BLLFactory.Instance.GetListItemByDictType(newValue);
                if (itemList != null)
                {
                    itemList.Insert(0, new CListItem(""));//CListItem具有Text、Value两个属性
                    control.ItemsSource = itemList;
                    control.ShowClearButton = true;
                    control.SelectedValuePath = control.SelectedValuePath.IsNullOrEmpty() ? "Value" : control.SelectedValuePath;
                }
            }
        }
    }
    复制代码

    同理,我们按照同样的处理方式,做一个复选框的下拉列表CheckComboBox。

    复制代码
    /// 
    /// 自定义下拉列表,方便绑定字典类型
    /// 
    public class CheckComboBox : HandyControl.Controls.CheckComboBox
    {
        /// 
        /// 字典类型名称
        /// 
        public string? DictTypeName
        {
            get { return (string?)GetValue(DictTypeNameProperty); }
            set { SetValue(DictTypeNameProperty, value); }
        }
    
        public static readonly DependencyProperty DictTypeNameProperty = DependencyProperty.Register(
            nameof(DictTypeName), typeof(string), typeof(CheckComboBox),
            new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, new PropertyChangedCallback(OnDictTypeNamePropertyChanged)));
    复制代码

    完成上面的自定义控件编写,我们需要在UI上放置控件,指定它的指定类型就可以了。

    复制代码
    <control:ComboBox
        Width="250"
        Height="32"
        VerticalAlignment="Center"
        hc:InfoElement.Title="客户类型"
        hc:InfoElement.TitlePlacement="Left"
        DictTypeName="客户类型"
        SelectedValue="{Binding ViewModel.PageDto.Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
        ShowClearButton="True"
        Style="{StaticResource ComboBoxPlusBaseStyle}" />
    <control:CheckComboBox
        Width="500"
        Height="32"
        VerticalAlignment="Center"
        hc:InfoElement.Title="客户类型"
        hc:InfoElement.TitlePlacement="Left"
        DictTypeName="客户类型"
        SelectedValue="{Binding ViewModel.PageDto.Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
        SelectedValuePath="Value"
        ShowClearButton="True"
        ShowSelectAllButton="True"
        Style="{StaticResource CheckComboBoxPlus}" />
    复制代码

    完成后,我们测试下自定义控件的处理效果。

        

    效果符合实际的期望。而且代码和普通WPF控件的使用类似,只需要增加一个 DictTypeName="客户类型" 的类似写法即可。可以极大的减轻我们绑定常见系统字典的下拉列表的复杂度。

    界面效果如下所示。

     

  • 相关阅读:
    跟艾文学编程《Python基础》(2)Python 容器
    计算机毕业设计(附源码)python疫情期间的校园防控管理系统
    IS31FL3737B 通用12×12 LED驱动器 I2C 42mA 40QFN
    qt.qpa.xcb: could not connect to display 0
    threadSafeMap代码解析
    B. Phoenix and Beauty
    【网络通信 -- WebRTC】FlexFec 基本知识点总结概述
    Bootstrap Blazor 实战 Menu 导航菜单使用(1)
    linux(centos7.9)安装部署mysql-cluster 7.6
    权限系统设计方案 RBAC模型
  • 原文地址:https://www.cnblogs.com/wuhuacong/p/17778978.html