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

1、下拉列表的数据绑定

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

/// <summary>
/// 用户列表-视图模型对象
/// </summary>
public partial class UserListViewModel : BaseListViewModel<UserInfo, int, UserPagedDto>
{
/// <summary>
/// 性别
/// </summary>
[ObservableProperty]
private List<CListItem> genderItems; /// <summary>
/// 构造函数
/// </summary>
/// <param name="service">业务服务接口</param>
public UserListViewModel(IUserService service) : base(service)
{
//初始化性别的列表
this.GenderItems = new List<CListItem>()
{
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" />

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

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

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

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

/// <summary>
/// 定义一些系统常用的字典项目,供页面参考引用
/// </summary>
public partial class DictItemsModel : ObservableObject
{
/// <summary>
/// 性别
/// </summary>
[ObservableProperty]
private List<CListItem> genderItems = new(); /// <summary>
/// 机构分类
/// </summary>
[ObservableProperty]
private List<CListItem> ouCategoryItems = new(); //******更多列表处理********** /// <summary>
/// 构造函数
/// </summary>
public DictItemsModel()
{
InitDictItem(); // 初始化字典
} /// <summary>
/// 初始化字典
/// </summary>
/// <returns></returns>
public async Task InitDictItem()
{
//初始化性别的列表
this.GenderItems = new List<CListItem>()
{
new(""),
new("男"),
new("女")
}; //机构分类
this.OuCategoryItems = EnumHelper.GetMemberNames<OUCategoryEnum>().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的属性改动一下就可以实现一致的效果了,相当于抽取了字典列表到独立的类中处理了。

<!--  ItemsSource="{Binding ViewModel.GenderItems}"  -->
<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" />
<!--  ItemsSource="{Binding ViewModel.CategoryItems}"  -->
<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的控件的处理方式类似,我们通过给他指定一个字典类型的名称,让它自己取得对应列表,进行绑定处理即可,因此我们自定义字典列表控件即可。

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

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

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

    /// <summary>
/// 自定义下拉列表,方便绑定字典类型
/// </summary>
public class ComboBox : HandyControl.Controls.ComboBox
{
/// <summary>
/// 字典类型名称
/// </summary>
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<IDictDataService>.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。

/// <summary>
/// 自定义下拉列表,方便绑定字典类型
/// </summary>
public class CheckComboBox : HandyControl.Controls.CheckComboBox
{
/// <summary>
/// 字典类型名称
/// </summary>
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="客户类型" 的类似写法即可。可以极大的减轻我们绑定常见系统字典的下拉列表的复杂度。

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

  1. 基于SqlSugar的开发框架循序渐进介绍(3)-- 实现代码生成工具Database2Sharp的整合开发

    我喜欢在一个项目开发模式成熟的时候,使用代码生成工具Database2Sharp来配套相关的代码生成,对于我介绍的基于SqlSugar的开发框架,从整体架构确定下来后,我就着手为它们量身定做相关的代码 ...

  2. 【WPF开发备忘】使用MVVM模式开发中列表控件内的按钮事件无法触发解决方法

    实际使用MVVM进行WPF开发的时候,可能会用到列表控件中每行一个编辑或删除按钮,这时直接去绑定,发现无法响应: <DataGridTemplateColumn Header="操作& ...

  3. 基于CkEditor实现.net在线开发之路(3)常用From表单控件介绍与说明

    上一章已经简单介绍了CKEditor控件可以编写C#代码,然后可以通过ajax去调用,但是要在网页上面编写所有C#后台逻辑,肯定痛苦死了,不说实现复杂的逻辑,就算实现一个简单增删改查,都会让人头痛欲裂 ...

  4. MVVM模式和在WPF中的实现(二)数据绑定

    MVVM模式解析和在WPF中的实现(二) 数据绑定 系列目录: MVVM模式解析和在WPF中的实现(一)MVVM模式简介 MVVM模式解析和在WPF中的实现(二)数据绑定 MVVM模式解析和在WPF中 ...

  5. 基于DevExpress开发的GridView如何实现一列显示不同的控件类型

    在很多DevExpress的使用例子里面,我们可以看到,基于GridView实现的不同控件展示的时候,每一列的控件类型都是一样的,如果我要某一列的一行让用户可以从下列列表选择选项,而其他行不可选择,那 ...

  6. 基于H5的移动端开发,window.location.href在IOS系统无法触发问题

    最近负责公司的微信公众号开发项目,基于H5进行开发,某些页面window.location.href在Android机上能正常运行而IOS系统上无法运行,导致无法重定向到指定页面,查了好久终于找到方法 ...

  7. 推荐一个基于Vue2.0的的一款移动端开发的UI框架,特别好用。。。

    一丶YDUI 一只注重审美,且性能高效的移动端&微信UI. 下面为地址自己研究去吧! 我的项目正在用,以前用的Mint-ui但是现在感觉还是这个好一点,官方给出的解释很清楚,很实用. 官方地址 ...

  8. 基于SqlSugar的开发框架循序渐进介绍(4)-- 在数据访问基类中对GUID主键进行自动赋值处理

    我们在设计数据库表的时候,往往为了方便,主键ID一般采用字符串类型或者GUID类型,这样对于数据库表记录的迁移非常方便,而且有时候可以在处理关联记录的时候,提前对应的ID值.但有时候进行数据记录插入的 ...

  9. 基于SqlSugar的开发框架循序渐进介绍(5)-- 在服务层使用接口注入方式实现IOC控制反转

    在前面随笔,我们介绍过这个基于SqlSugar的开发框架,我们区分Interface.Modal.Service三个目录来放置不同的内容,其中Modal是SqlSugar的映射实体,Interface ...

  10. 基于SqlSugar的开发框架循序渐进介绍(6)-- 在基类接口中注入用户身份信息接口

    在基于SqlSugar的开发框架中,我们设计了一些系统服务层的基类,在基类中会有很多涉及到相关的数据处理操作的,如果需要跟踪具体是那个用户进行操作的,那么就需要获得当前用户的身份信息,包括在Web A ...

随机推荐

  1. GC 分代回收算法

    GC 分代回收算法 1.首先了解JVM堆内存是如何分配的. 年轻代内部  生成区 和 S0 S1 的比例 默认情况下是 8:1 :1 堆内存和永久代存储的内容有区别:  堆内存主要存储的是 : 对象, ...

  2. java BigDecimal解决浮点数的精度丢失和大数计算问题

    java BigDecimal解决浮点数的精度丢失和大数计算问题 抛出浮点数问题: 先考个题,输入什么? System.out.println(0.1 + 0.2); 答案:0.30000000000 ...

  3. 简约版八股文(day2)

    Redis(内存中->非关系型数据库) redis是什么,为什么要用redis redis是基于键值对的NoSQL数据库,经常用来做缓存用户直接读取数据库中的数据效率是相对比较慢的,如果把数据读 ...

  4. Tauri-Admin通用后台管理系统|tauri+vue3+pinia桌面端后台EXE

    基于tauri+vite4+pinia2跨端后台管理系统应用实例TauriAdmin. tauri-admin 基于最新跨端技术 Tauri Rust webview2 整合 Vite4 构建桌面端通 ...

  5. 带你掌握利用Terraform不同数据源扩展应用场景

    本文分享自华为云社区<利用Terraform不同数据源扩展应用场景>,作者: kaliarch . 一 背景 在生产环境中使用Terraform进行基础设施编排,通常又一些信息是通过其他外 ...

  6. Linux 修改文本dos/unix 格式

    使用 vi 命令打开文件,使用 :set ff 查看格式 如果显示dos,可使用 :set ff=unix 修改 pem 证书,使用 cat xx.key xx.crt DigiCertCA.crt ...

  7. Cilium系列-8-绕过 IPTables 连接跟踪

    系列文章 Cilium 系列文章 前言 将 Kubernetes 的 CNI 从其他组件切换为 Cilium, 已经可以有效地提升网络的性能. 但是通过对 Cilium 不同模式的切换/功能的启用, ...

  8. 何时使用MongoDB而不是MySql

    什么是 MySQL 和 MongoDB MySQL 和 MongoDB 是两个可用于存储和管理数据的数据库管理系统.MySQL 是一个关系数据库系统,以结构化表格格式存储数据.相比之下,MongoDB ...

  9. 开源Java诊断工具Arthas:开篇之watch实战

    一.前言 还在为排查Java程序线上问题头痛吗,看我们用阿里开源的诊断神器 Arthas 来帮您 本文开篇主要介绍 阿里开源的诊断神器Arthas 3.7.0版本,watch.jad.classloa ...

  10. 继copilot之后,又一款免费帮你写代码的插件

    写在前面 在之前的文章中推荐过一款你写注释,它就能帮你写代码的插件copilot copilot写代码的能力没得说,但是呢copilot试用没几天之后就收费了 传送门:你写注释她帮你写代码 按理说这么 ...