WPF应用实战开发指南 - 如何实现动态内容展示
在我们开发一些复杂信息的时候由于需要动态展示一些相关信息因此我们需要考虑一些控件内容的动态展示可以通过动态构建控件的方式进行显示如动态选项卡展示不同的信息或者动态展示一个自定义控件的内容等等目的就是能够减少一些硬编码的处理方式以及能够灵活的展示数据。本文主要通过实际案例介绍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的处理模式。本文转载自博客园 - 伍华聪

相关新闻

C/C++开发神器CLion v2023.3全新发布——支持AI辅助编码

C/C++开发神器CLion v2023.3全新发布——支持AI辅助编码

CLion是一款专为开发C及C所设计的跨平台IDE,它是以IntelliJ为基础设计的,包含了许多智能功能来提高开发人员的生产力。这种强大的IDE帮助开发人员在Linux、OS X和Windows上来开发C/C,同时它还使用智能编辑器来提高代码质量、自动代码重构并且…

2026/7/29 17:35:40阅读更多 →
工程化陷阱——离散化、延迟、饱和与奇点

工程化陷阱——离散化、延迟、饱和与奇点

摘要:前 6 篇我们一直在“理想世界”里打靶——理想的自动驾驶仪、无限的过载能力、精确的连续时间积分。但真实的导弹是离散的、迟缓的、受限的。当你把仿真代码从“教学示例”搬到“实时嵌入式系统”上时,会遇到一系列教科书里语焉不详的“坑”&#x…

2026/7/29 17:35:39阅读更多 →
界面控件Kendo UI for jQuery中文教程 - 如何集成全新的DockManager组件

界面控件Kendo UI for jQuery中文教程 - 如何集成全新的DockManager组件

R3 2023版本在Kendo UI for jQuery套件中引入了许多改进,其中包括多个主题上的新组件、特性和改进。其中一个重要的新增功能是DockManager组件,本文将话一些时间与大家讨论一下它的功能! P.S: Kendo UI for jQuery提供了在短时间…

2026/7/29 17:33:39阅读更多 →
抖音无水印下载终极教程:免费获取高清视频的完整解决方案

抖音无水印下载终极教程:免费获取高清视频的完整解决方案

抖音无水印下载终极教程:免费获取高清视频的完整解决方案 【免费下载链接】douyin-downloader A practical Douyin downloader for both single-item and profile batch downloads, with progress display, retries, SQLite deduplication, and browser fallback su…

2026/7/29 18:40:13阅读更多 →
如何用Pixelle-Video在3分钟内制作专业级短视频?AI全自动引擎完全指南

如何用Pixelle-Video在3分钟内制作专业级短视频?AI全自动引擎完全指南

如何用Pixelle-Video在3分钟内制作专业级短视频?AI全自动引擎完全指南 【免费下载链接】Pixelle-Video 🚀 AI 全自动短视频引擎 | AI Fully Automated Short Video Engine 项目地址: https://gitcode.com/GitHub_Trending/pi/Pixelle-Video Pixel…

2026/7/29 18:40:13阅读更多 →
揭秘Ninjabrain-Bot核心算法:贝叶斯推断如何革新末地城定位

揭秘Ninjabrain-Bot核心算法:贝叶斯推断如何革新末地城定位

揭秘Ninjabrain-Bot核心算法:贝叶斯推断如何革新末地城定位 【免费下载链接】Ninjabrain-Bot Accurate stronghold calculator for Minecraft speedrunning. 项目地址: https://gitcode.com/gh_mirrors/ni/Ninjabrain-Bot Ninjabrain-Bot是一款为Minecraft速…

2026/7/29 18:40:13阅读更多 →
浅谈智慧路灯APP开发有哪些好处

浅谈智慧路灯APP开发有哪些好处

智慧路灯是通过应用先进、高效、可靠的电力线载波通信技术和无线GPRS/CDMA 通信技术等,实现对路灯的远程集中控制与管理。它可以根据车流量自动调节亮度、远程照明控制、故障主动报警、灯具线缆防盗、远程抄表等功能,能够大幅节省电力资源,提…

2026/7/29 18:40:13阅读更多 →
G-Helper深度解析:华硕笔记本轻量化控制工具的技术实现与最佳实践

G-Helper深度解析:华硕笔记本轻量化控制工具的技术实现与最佳实践

G-Helper深度解析:华硕笔记本轻量化控制工具的技术实现与最佳实践 【免费下载链接】g-helper Lightweight Armoury Crate alternative for Asus laptops with nearly the same functionality. Works with ROG Zephyrus, Flow, TUF, Strix, Scar, ProArt, Vivobook, …

2026/7/29 18:40:13阅读更多 →
从理论到实践:Geotorch约束优化的数学原理与代码实现

从理论到实践:Geotorch约束优化的数学原理与代码实现

从理论到实践:Geotorch约束优化的数学原理与代码实现 【免费下载链接】geotorch Constrained optimization toolkit for PyTorch 项目地址: https://gitcode.com/gh_mirrors/ge/geotorch Geotorch是一个专为PyTorch设计的约束优化工具包,它提供了…

2026/7/29 18:38:13阅读更多 →
覆盖国产 + 海外 + 开源模型,OpenClaw 2.7.9 Windows/Mac 双端部署详解

覆盖国产 + 海外 + 开源模型,OpenClaw 2.7.9 Windows/Mac 双端部署详解

🔹 工具基础介绍 OpenClaw 是开源生态中一款实用性较强的本地智能工具,凭借本地离线运行、可视化图形操作和任务自动化三大核心特性,赢得了众多用户的青睐。与普通在线对话AI工具不同,它属于能够直接操控本机软硬件的智能数字员工…

2026/7/29 9:47:45阅读更多 →
伺服阀焊完微漏毁整机?精密激光焊接三关锁住高压

伺服阀焊完微漏毁整机?精密激光焊接三关锁住高压

所谓液压伺服阀体的精密激光焊接,是用激光束对阀座壳体(通常为不锈钢或铝合金)进行密封焊接,使阀体在21-35MPa的高压液压油或压缩气体中长期运行而不发生介质泄漏。液压伺服阀是高端液压系统的"大脑"。从航空航天飞行控…

2026/7/29 7:00:19阅读更多 →
D2DX:三步实现《暗黑破坏神2》高清宽屏体验的终极指南

D2DX:三步实现《暗黑破坏神2》高清宽屏体验的终极指南

D2DX:三步实现《暗黑破坏神2》高清宽屏体验的终极指南 【免费下载链接】d2dx D2DX is a complete solution to make Diablo II run well on modern PCs, with high fps and better resolutions. 项目地址: https://gitcode.com/gh_mirrors/d2/d2dx 你是否还在…

2026/7/29 7:58:51阅读更多 →
28. Agent 执行到一半想暂停?用 interrupt 给它设个“关卡“!

28. Agent 执行到一半想暂停?用 interrupt 给它设个“关卡“!

28. Agent 执行到一半想暂停?用 interrupt 给它设个“关卡“! 在构建复杂的 Agent 系统时,我们经常会遇到这样的场景:Agent 正在执行一个多步骤的任务,比如“下单购买商品”,但执行到一半时,我们…

2026/7/29 0:01:46阅读更多 →
自律同行,突破无界!NANK南卡正式官宣曾舜晞成为品牌代言人

自律同行,突破无界!NANK南卡正式官宣曾舜晞成为品牌代言人

近日,国际专注开放式技术研发的声学品牌Nank南卡,正式官宣实力艺人曾舜晞担任品牌代言人。消息一经发出便轰动全网。为什么耳机品牌不选择流量明星、老牌歌手?而且是选择曾舜晞?让我们一起来探索一下!比起短期的流量&a…

2026/7/29 0:01:46阅读更多 →
【RT-DETR多模态创新改进】CVPR 2025 | 独家特征融合创新改进篇 | 引入RLAB残差线性注意力模块,有效融合并强调多尺度特征,多种改进点,适合红外与可见光融合目标检测任务,有效涨点

【RT-DETR多模态创新改进】CVPR 2025 | 独家特征融合创新改进篇 | 引入RLAB残差线性注意力模块,有效融合并强调多尺度特征,多种改进点,适合红外与可见光融合目标检测任务,有效涨点

一、本文介绍 🔥本文在RT-DETR多模态融合目标检测中引入RLAB残差线性注意力模块,可在不同模态特征交互阶段进行多次残差细化,使可见光、红外等特征在尺度、语义和空间位置上更好对齐;随后将细化特征与解码器输出拼接并生成Q、K、V,通过线性注意力自适应强化关键通道、目…

2026/7/29 0:01:46阅读更多 →
YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

如果你在部署 YOLOv8 时,发现推理速度只有可怜的 1-2 FPS,而别人的演示视频却能跑到 30 FPS 以上,那么问题很可能不在模型本身,而在于你的整个处理链路。很多开发者拿到一个训练好的 YOLOv8 模型后,会直接使用官方示例…

2026/7/28 20:22:24阅读更多 →
Coze与Dify对比指南:低代码AI应用开发从入门到实战

Coze与Dify对比指南:低代码AI应用开发从入门到实战

1. 从零到一:为什么你需要了解 Coze 和 Dify?如果你对 AI 应用开发感兴趣,但一看到“大模型”、“智能体”、“工作流”这些词就头疼,觉得门槛太高,那这篇文章就是为你准备的。很多开发者,包括我自己&#…

2026/7/29 4:31:51阅读更多 →
AI生图工具怎么选?2026年6月版实测对比

AI生图工具怎么选?2026年6月版实测对比

做自媒体的朋友应该都有体会:配图一直是个让人头疼的问题。2026年,AI生图工具已经非常成熟了,但工具太多反而不知道怎么选。以下是截至2026年6月我对主流AI生图工具的实测对比。Midjourney V8.1:速度之王2026年6月11日&#xff0c…

2026/7/29 14:26:42阅读更多 →