在我們開發的前端專案中,往往為了方便,都需對一些控制元件進行自定義的處理,以便實現快速的資料繫結以及便捷的使用,本篇隨筆介紹通過抽取常見字典列表,實現通用的字典型別繫結;以及通過自定義控制元件的屬性處理,實現系統字典內容的快捷繫結的操作。
在我們建立下拉選單的時候,我們一般處理方式,是在對應的資料模型中新增對應的下拉選單的集合物件,然後在控制元件繫結對應的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}來指定資料來源的位置來自靜態資源即可。如機構列表,通過列舉進行解析到的集合如下所示,當然其他資料也可以通過相應的處理實現。
通過這種把常見的字典類別集中到一個類中進行維護,除了統一處理列表的初始化外,也方便我們在介面程式碼中的統一使用。
我們框架一般都維護一個通用的字典型別和字典專案的資訊,通過維護這些常見的系統字典資訊,可以為我們的介面的一些下拉類列表提供資料支援,是指實現通用、統一的字典處理。
以前在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="客戶型別" 的類似寫法即可。可以極大的減輕我們繫結常見系統字典的下拉選單的複雜度。
介面效果如下所示。