在我们开发一些复杂信息的时候由于需要动态展示一些相关信息因此我们需要考虑一些控件内容的动态展示可以通过动态构建控件的方式进行显示如动态选项卡展示不同的信息或者动态展示一个自定义控件的内容等等目的就是能够减少一些硬编码的处理方式以及能够灵活的展示数据。本文主要通过实际案例介绍WPF应用开发之控件动态内容展示希望对大家有所帮助~PS给大家推荐一个C#开发可以用到的界面组件——DevExpress WPF它拥有120个控件和库将帮助您交付满足甚至超出企业需求的高性能业务应用程序。通过DevExpress WPF能创建有着强大互动功能的XAML基础应用程序这些应用程序专注于当代客户的需求和构建未来新一代支持触摸的解决方案。一、选项卡TabControl的动态内容展示在我们客户关系管理模块中往往需要展示一个客户相关的很多数据可以把它们放在多个选项卡中进行统一展示如下界面所示。由于客户的相关模块信息比较多因此我们通过选项卡的展示是比较合理的一种界面组织方式这里由于不同的客户信息他们展示的内容不同但结构相同因此可以考虑动态的刷新选项卡项目TabItem的内容数据进行。因此我们这里引入一个自定义的控件AllRelatedListControl用来承载所有需要展示的模块一个组合。在主页面上我们可以通过一个Divider分隔控件隔开展示客户相关的数据如下XAML 代码所示。hc:Divider Margin0 LineStroke{DynamicResource DarkPrimaryBrush} LineStrokeThickness2 / Grid Margin0,5,0,0 Background{DynamicResource BackgroundBrush} !-- 客户相关数据 -- local:AllRelatedListControl x:NameallRelatedList CustomerId{Binding SelectedItem.Id, ElementNamegrid} / /Grid这个自定义的控件主要的作用是组合多个选项卡项目减少主界面的代码并增加一些共同的属性和方法来控制数据的更新显示的。UserControl x:ClassWHC.SugarProject.CRM.WpfUI.Views.Pages.AllRelatedListControl xmlnshttp://schemas.microsoft.com/winfx/2006/xaml/presentation xmlns:xhttp://schemas.microsoft.com/winfx/2006/xaml xmlns:dhttp://schemas.microsoft.com/expression/blend/2008 xmlns:localclr-namespace:WHC.SugarProject.CRM.WpfUI.Views.Pages xmlns:mchttp://schemas.openxmlformats.org/markup-compatibility/2006 x:NameallList d:DesignHeight450 d:DesignWidth800 Background{DynamicResource RegionBrush} mc:Ignorabled Grid !-- 客户相关数据 -- TabControl x:NametabControl Heightauto HorizontalAlignmentLeft HorizontalContentAlignmentLeft BackgroundLightCyan Style{StaticResource TabControlCapsuleSolid} TabStripPlacementTop TabItem Header客户跟进 TagFollowControl local:FollowListControl CustomerId{Binding CustomerId, ElementNameallList} / /TabItem TabItem Header联系人资料 TagContactControl local:ContactListControl CustomerId{Binding CustomerId, ElementNameallList} / /TabItem TabItem Header客户拜访 TagVisitControl local:VisitListControl CustomerId{Binding CustomerId, ElementNameallList} / /TabItem TabItem Header销售机会 TagChanceControl local:ChanceListControl CustomerId{Binding CustomerId, ElementNameallList} / /TabItem TabItem Header客户文档 TagFileDataControl local:FileDataListControl CustomerId{Binding CustomerId, ElementNameallList} / /TabItem TabItem Header合同文档 TagContractControl local:ContractListControl CustomerId{Binding CustomerId, ElementNameallList} / /TabItem TabItem Header产品报价 TagQuotationControl local:QuotationListControl CustomerId{Binding CustomerId, ElementNameallList} / /TabItem TabItem Header客户来电 TagComingCallControl local:ComingCallListControl CustomerId{Binding CustomerId, ElementNameallList} / /TabItem TabItem Header发票记录 TagInvoiceControl local:InvoiceListControl CustomerId{Binding CustomerId, ElementNameallList} / /TabItem TabItem Header维护记录 TagSupplierControl local:MaintenaceListControl CustomerId{Binding CustomerId, ElementNameallList} / /TabItem TabItem Header售后服务 TagMaintenaceControl local:AfterSellListControl CustomerId{Binding CustomerId, ElementNameallList} / /TabItem TabItem Header客户投诉 TagComplaintControl local:ComplaintListControl CustomerId{Binding CustomerId, ElementNameallList} / /TabItem TabItem Header客户活动 TagActivityControl local:ActivityListControl CustomerId{Binding CustomerId, ElementNameallList} / /TabItem TabItem Header收货地址 Tag local:ShippingListControl CustomerId{Binding CustomerId, ElementNameallList} / /TabItem /TabControl /Grid /UserControl上面的用户控件的界面效果如下所示它的作用就是组合多个不同的页面。和其他自定义控件一样我们增加一些自定义的属性如这里是客户的ID用于赋值后刷新相关的数据的。/// summary /// 客户ID /// /summary public string? CustomerId { get { return (string?)GetValue(CustomerIdProperty); } set { SetValue(CustomerIdProperty, value); } } public static readonly DependencyProperty CustomerIdProperty DependencyProperty.Register( nameof(CustomerId), typeof(string), typeof(AllRelatedListControl), new FrameworkPropertyMetadata(-1, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, new PropertyChangedCallback(OnCustomerIdPropertyChanged))); private static async void OnCustomerIdPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (d is not AllRelatedListControl control) return; if (control ! null) { var oldValue (string?)e.OldValue; // 旧的值 var newValue (string?)e.NewValue; // 更新的新的值 //更新数据源 await control.InitData(newValue); } }而我们每个子控件里面其实也是已经根据父控件的客户ID进行了绑定属性了如下所示。TabItem Header客户跟进 TagFollowControl local:FollowListControl CustomerId{Binding CustomerId, ElementNameallList} / /TabItem如果我们要根据数据库的配置信息用来判断哪个选项卡显示或者隐藏那么可以进一步进行处理每个TabItem的Visibility即可。//判断是否显示 foreach (TabItem item in this.tabControl.Items) { if (!item.Tag.IsEmpty()) { item.Visibility CustomerTabItems.Contains(item.Tag.ToString()!) ? Visibility.Visible : Visibility.Collapsed; } }在DataGrid的鼠标键按下左键的时候我们刷新对应自定义控件的属性CustomerId就可以刷新相关的客户数据了。/// summary /// 选中每个客户记录的时候触发更新客户相关信息 /// /summary private async void grid_MouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e) { if (this.grid.SelectedItem is CustomerInfo selectItem) { allRelatedList.CustomerId selectItem.Id; await allRelatedList.InitData(selectItem.Id); } }以上就是对于复杂的客户信息内容使用选项卡动态组合的方式实现内容的动态展示处理操作。二、动态自定义表单内容的展示有时候我们在工作流表单中需要展示一些固定的申请单信息以及根据不同流程的表单数据结构也不同进行展示如工作流中不同的表单数据结构是不同的。明细展示效果如下所示。或者可以看出不同的流程类型表单它们的表单结构是不同的如果我们在一个固定的页面里面展示数据那么这块表单的数据展示就需要用到动态控件的展示方式。我们采用Grid布局排版方式grid.row0为固定表单这里不再赘述grid.row1为动态表单内容如下所示。StackPanel Grid.Row1 Margin0,20,0,0 TextBlock HorizontalAlignmentCenter Style{StaticResource TextBlockSubTitleBold} Text表单信息 / Frame x:NameformContent / /StackPanel其中里面Frame x:NameformContent 就是我们根据实际表单的内容进行动态展示的地方。/// summary /// 该事件在loaded之后执行也是在所有元素渲染结束之后执行 /// /summary /// param namee/param protected override async void OnContentRendered(EventArgs e) { base.OnContentRendered(e); //动态构建表单内容展示 formContent var formId this.ViewModel.Item.FormId; if(!formId.IsNullOrEmpty()) { var formInfo await BLLFactoryIFormService.Instance.GetAsync(formId); if(formInfo ! null !formInfo.ApplyWpfview.IsNullOrEmpty()) { var control ReflectionUtil.CreateInstance(formInfo.ApplyWpfview); var data control as IApplyInit; if(data ! null) { await data.InitData(this.ViewModel.Item.Id); } formContent.Content control; } } //初始化工具栏 await InitToolBar(); }我们根据表单ID获取对应的formInfo.ApplyWpfview属性配置他就是一个具体的控件的名称路径因此我们根据这个来进行反射构建一个实例并把它转换为 IApplyInit 的接口实例进行调用控件初始化即可。生成的控件当做Frame控件的Content从而实现动态内容的展示了。我们以第一个表单【故障维修】的自定义控件定义来看看public partial class MaintenanceViewControl : INavigableViewMaintenanceEditViewModel, IApplyInit它实现了IApplyInit接口因此我们可以在动态控件的时候把它转换为接口实例进行调用这也是约束动态控件实例的一个规则。不同的控件他们的数据模型肯定不同因此由它们自己本身实现具体的获取数据即可。/// summary /// 初始化相关业务表单数据 /// /summary /// param nameapplyId申请单Id/param /// returns/returns public async Task InitData(string applyId) { this.ViewModel.Item await BLLFactoryIMaintenanceService.Instance.FindByApplyId(applyId); this.ViewModel.NotifyChanged(); }这样我们自定义控件的XAML就可以顺利绑定对应的数据展示了这些控件的内容可以根据数据库接口使用我们的代码生成工具进行快速生成然后进行一定的裁剪调整即可不必要一个个来编写代码。StackPanel hc:TitleElement.TitleWidth100 hc:Row hc:Col Span12 TextBox x:NametxtDeviceName Margin5 hc:TitleElement.Title故障设备名称 hc:TitleElement.TitlePlacementLeft IsReadOnlyTrue Text{Binding ViewModel.Item.DeviceName} / /hc:Col hc:Col Span12 DatePicker x:NametxtRepairDate Margin5 hc:InfoElement.Title报修日期 hc:InfoElement.TitlePlacementLeft IsEnabledFalse SelectedDate{Binding ViewModel.Item.RepairDate, StringFormatyyyy-MM-dd} Style{StaticResource DatePickerExtend} / /hc:Col /hc:Row hc:Row hc:Col TextBox x:NametxtFaultDescription Margin5 hc:TitleElement.Title故障描述 hc:TitleElement.TitlePlacementLeft IsReadOnlyTrue Text{Binding ViewModel.Item.FaultDescription} / /hc:Col /hc:Row hc:Row hc:Col Span12 TextBox x:NametxtRepairFee Margin5 hc:TitleElement.Title预计维修费用 hc:TitleElement.TitlePlacementLeft IsReadOnlyTrue Text{Binding ViewModel.Item.RepairFee} / /hc:Col hc:Col Span12 / /hc:Row hc:Row hc:Col Span24 TextBox x:NametxtNote Height50 Margin5 VerticalAlignmentTop VerticalContentAlignmentTop hc:TitleElement.Title备注信息 hc:TitleElement.TitlePlacementLeft AcceptsReturnTrue IsReadOnlyTrue Text{Binding ViewModel.Item.Note} / /hc:Col /hc:Row hc:Row hc:Col control:AttachmentControl AttachmentGUID{Binding ViewModel.Item.AttachGUID} Text相关附件 / /hc:Col /hc:Row /StackPanel可以看到这部分的代码和我们生成的编辑界面的内容是相似的只是进行了适量的裁剪处理以及增加一些自定义控件统一界面效果如附件控件的展示。以上就是两种不同方式动态构建不同的内容展示的处理动态内容可以带来很好的处理便利不过也不要滥用比较反射太多还是会牺牲一些UI的性能不过总体来说肯定是值得的而且这也是一种UI的处理模式。本文转载自博客园 - 伍华聪