WPF应用开发之控件动态内容展示
在我们开发一些复杂信息的时候,由于需要动态展示一些相关信息,因此我们需要考虑一些控件内容的动态展示,可以通过动态构建控件的方式进行显示,如动态选项卡展示不同的信息,或者动态展示一个自定义控件的内容等等,目的就是能够减少一些硬编码的处理方式,以及能够灵活的展示数据。本篇随笔通过实际案例介绍WPF应用开发之控件动态内容展示。
1、选项卡TabControl的动态内容展示
在我们客户关系管理模块中,往往需要展示一个客户相关的很多数据,我们可以把它们放在多个选项卡中进行统一展示,如下界面所示。

由于客户的相关模块信息比较多,因此我们通过选项卡的展示是比较合理的一种界面组织方式,这里由于不同的客户信息,他们展示的内容不同(但结构相同),因此可以考虑动态的刷新选项卡项目TabItem的内容数据进行。
因此我们这里引入一个自定义的控件AllRelatedListControl,用来承载所有需要展示的模块一个组合。
因此在主页面上,我们可以通过一个Divider分隔控件隔开,展示客户相关的数据,如下XAML 代码所示。
<hc:Divider
Margin="0"
LineStroke="{DynamicResource DarkPrimaryBrush}"
LineStrokeThickness="2" />
<Grid Margin="0,5,0,0" Background="{DynamicResource BackgroundBrush}">
<!-- 客户相关数据 -->
<local:AllRelatedListControl x:Name="allRelatedList" CustomerId="{Binding SelectedItem.Id, ElementName=grid}" />
</Grid>
这个自定义的控件,主要的作用是组合多个选项卡项目,减少主界面的代码,并增加一些共同的属性和方法来控制数据的更新显示的。
<UserControl
x:Class="WHC.SugarProject.CRM.WpfUI.Views.Pages.AllRelatedListControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WHC.SugarProject.CRM.WpfUI.Views.Pages"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
x:Name="allList"
d:DesignHeight="450"
d:DesignWidth="800"
Background="{DynamicResource RegionBrush}"
mc:Ignorable="d">
<Grid>
<!-- 客户相关数据 -->
<TabControl
x:Name="tabControl"
Height="auto"
HorizontalAlignment="Left"
HorizontalContentAlignment="Left"
Background="LightCyan"
Style="{StaticResource TabControlCapsuleSolid}"
TabStripPlacement="Top">
<TabItem Header="客户跟进" Tag="FollowControl">
<local:FollowListControl CustomerId="{Binding CustomerId, ElementName=allList}" />
</TabItem>
<TabItem Header="联系人资料" Tag="ContactControl">
<local:ContactListControl CustomerId="{Binding CustomerId, ElementName=allList}" />
</TabItem>
<TabItem Header="客户拜访" Tag="VisitControl">
<local:VisitListControl CustomerId="{Binding CustomerId, ElementName=allList}" />
</TabItem>
<TabItem Header="销售机会" Tag="ChanceControl">
<local:ChanceListControl CustomerId="{Binding CustomerId, ElementName=allList}" />
</TabItem>
<TabItem Header="客户文档" Tag="FileDataControl">
<local:FileDataListControl CustomerId="{Binding CustomerId, ElementName=allList}" />
</TabItem>
<TabItem Header="合同文档" Tag="ContractControl">
<local:ContractListControl CustomerId="{Binding CustomerId, ElementName=allList}" />
</TabItem>
<TabItem Header="产品报价" Tag="QuotationControl">
<local:QuotationListControl CustomerId="{Binding CustomerId, ElementName=allList}" />
</TabItem>
<TabItem Header="客户来电" Tag="ComingCallControl">
<local:ComingCallListControl CustomerId="{Binding CustomerId, ElementName=allList}" />
</TabItem>
<TabItem Header="发票记录" Tag="InvoiceControl">
<local:InvoiceListControl CustomerId="{Binding CustomerId, ElementName=allList}" />
</TabItem>
<TabItem Header="维护记录" Tag="SupplierControl">
<local:MaintenaceListControl CustomerId="{Binding CustomerId, ElementName=allList}" />
</TabItem>
<TabItem Header="售后服务" Tag="MaintenaceControl">
<local:AfterSellListControl CustomerId="{Binding CustomerId, ElementName=allList}" />
</TabItem>
<TabItem Header="客户投诉" Tag="ComplaintControl">
<local:ComplaintListControl CustomerId="{Binding CustomerId, ElementName=allList}" />
</TabItem>
<TabItem Header="客户活动" Tag="ActivityControl">
<local:ActivityListControl CustomerId="{Binding CustomerId, ElementName=allList}" />
</TabItem> <TabItem Header="收货地址" Tag="">
<local:ShippingListControl CustomerId="{Binding CustomerId, ElementName=allList}" />
</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="客户跟进" Tag="FollowControl">
<local:FollowListControl CustomerId="{Binding CustomerId, ElementName=allList}" />
</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);
}
}
以上就是对于复杂的客户信息内容,使用选项卡动态组合的方式,实现内容的动态展示处理操作。
2、动态自定义表单内容的展示
有时候,我们在工作流表单中,需要展示一些固定的申请单信息,以及根据不同流程的表单数据(结构也不同)进行展示,如工作流中,不同的表单数据结构是不同的。

明细展示效果如下所示。

或者

可以看出不同的流程类型表单,它们的表单结构是不同的,如果我们在一个固定的页面里面展示数据,那么这块表单的数据展示,就需要用到动态控件的展示方式。
我们采用Grid布局排版方式,grid.row=0为固定表单(这里不再赘述),grid.row=1为动态表单内容,如下所示。
<StackPanel Grid.Row="1" Margin="0,20,0,0">
<TextBlock
HorizontalAlignment="Center"
Style="{StaticResource TextBlockSubTitleBold}"
Text="表单信息" />
<Frame x:Name="formContent" />
</StackPanel>
其中里面<Frame x:Name="formContent" /> 就是我们根据实际表单的内容进行动态展示的地方。
/// <summary>
/// 该事件在loaded之后执行,也是在所有元素渲染结束之后执行
/// </summary>
/// <param name="e"></param>
protected override async void OnContentRendered(EventArgs e)
{
base.OnContentRendered(e); //动态构建表单内容展示 formContent
var formId = this.ViewModel.Item.FormId;
if(!formId.IsNullOrEmpty())
{
var formInfo = await BLLFactory<IFormService>.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 : INavigableView<MaintenanceEditViewModel>, IApplyInit
它实现了 IApplyInit 接口,因此我们可以在动态控件的时候,把它转换为接口实例进行调用,这也是我们约束动态控件实例的一个规则。
不同的控件,他们的数据模型肯定不同,因此由它们自己本身实现具体的获取数据即可。
/// <summary>
/// 初始化相关业务表单数据
/// </summary>
/// <param name="applyId">申请单Id</param>
/// <returns></returns>
public async Task InitData(string applyId)
{
this.ViewModel.Item = await BLLFactory<IMaintenanceService>.Instance.FindByApplyId(applyId);
this.ViewModel.NotifyChanged();
}
这样我们自定义控件的XAML就可以顺利绑定对应的数据展示了,这些控件的内容,可以根据数据库接口,使用我们的代码生成工具进行快速生成,然后进行一定的裁剪调整即可,不必要一个个来编写代码。
<StackPanel hc:TitleElement.TitleWidth="100">
<hc:Row>
<hc:Col Span="12">
<TextBox
x:Name="txtDeviceName"
Margin="5"
hc:TitleElement.Title="故障设备名称"
hc:TitleElement.TitlePlacement="Left"
IsReadOnly="True"
Text="{Binding ViewModel.Item.DeviceName}" />
</hc:Col>
<hc:Col Span="12">
<DatePicker
x:Name="txtRepairDate"
Margin="5"
hc:InfoElement.Title="报修日期"
hc:InfoElement.TitlePlacement="Left"
IsEnabled="False"
SelectedDate="{Binding ViewModel.Item.RepairDate, StringFormat='yyyy-MM-dd'}"
Style="{StaticResource DatePickerExtend}" /> </hc:Col>
</hc:Row>
<hc:Row>
<hc:Col>
<TextBox
x:Name="txtFaultDescription"
Margin="5"
hc:TitleElement.Title="故障描述"
hc:TitleElement.TitlePlacement="Left"
IsReadOnly="True"
Text="{Binding ViewModel.Item.FaultDescription}" />
</hc:Col>
</hc:Row>
<hc:Row>
<hc:Col Span="12">
<TextBox
x:Name="txtRepairFee"
Margin="5"
hc:TitleElement.Title="预计维修费用"
hc:TitleElement.TitlePlacement="Left"
IsReadOnly="True"
Text="{Binding ViewModel.Item.RepairFee}" />
</hc:Col>
<hc:Col Span="12" />
</hc:Row> <hc:Row>
<hc:Col Span="24">
<TextBox
x:Name="txtNote"
Height="50"
Margin="5"
VerticalAlignment="Top"
VerticalContentAlignment="Top"
hc:TitleElement.Title="备注信息"
hc:TitleElement.TitlePlacement="Left"
AcceptsReturn="True"
IsReadOnly="True"
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的处理模式。
WPF应用开发之控件动态内容展示的更多相关文章
- WPF编程,将控件所呈现的内容保存成图像的一种方法。
原文:WPF编程,将控件所呈现的内容保存成图像的一种方法. 版权声明:我不生产代码,我只是代码的搬运工. https://blog.csdn.net/qq_43307934/article/detai ...
- CYQ.Data 支持WPF相关的数据控件绑定(2013-08-09)
事件的结果 经过多天的思考及忙碌的开发及测试,CYQ.Data 终于在UI上全面支持WPF,至此,CYQ.Data 已经可以方便支持wpf的开发,同时,框架仍保留最低.net framework2.0 ...
- 使用C#开发ActiveX控件(新)
前言 ActiveX控件以前也叫做OLE控件,它是微软IE支持的一种软件组件或对象,可以将其插入到Web页面中,实现在浏览器端执行动态程序功能,以增强浏览器端的动态处理能力.通常ActiveX控件都是 ...
- WPF后台设置xaml控件的样式System.Windows.Style
WPF后台设置xaml控件的样式System.Windows.Style 摘-自 :感谢 作者: IT小兵 http://3w.suchso.com/projecteac-tual/wpf-zhi ...
- WPF Step By Step 控件介绍
WPF Step By Step 控件介绍 回顾 上一篇,我们主要讨论了WPF的几个重点的基本知识的介绍,本篇,我们将会简单的介绍几个基本控件的简单用法,本文会举几个项目中的具体的例子,结合这些 例子 ...
- 使用C#开发ActiveX控件(新) 转 http://www.cnblogs.com/yilin/p/csharp-activex.html
前言 ActiveX控件以前也叫做OLE控件,它是微软IE支持的一种软件组件或对象,可以将其插入到Web页面中,实现在浏览器端执行动态程序功能,以增强浏览器端的动态处理能力.通常ActiveX控件都是 ...
- C#开发ActiveX控件
昨天写了篇博客<Winform 程序嵌入WPF程序 并发送消息>,没有说明为什么要嵌入WPF程序,那么今天就来唠叨唠叨其中的一个使用场景,开发ActiveX控件 首先,新建一个类库工程Hu ...
- CYQ.Data 支持WPF相关的数据控件绑定.Net获取iis版本
CYQ.Data 支持WPF相关的数据控件绑定(2013-08-09) 事件的结果 经过多天的思考及忙碌的开发及测试,CYQ.Data 终于在UI上全面支持WPF,至此,CYQ.Data 已经可以方便 ...
- 使用C#开发ActiveX控件
使用C#开发ActiveX控件(新) 前言 ActiveX控件以前也叫做OLE控件,它是微软IE支持的一种软件组件或对象,可以将其插入到Web页面中,实现在浏览器端执行动态程序功能,以增强浏览器端的动 ...
- 一些基于jQuery开发的控件
基于jQuery开发,非常简单的水平方向折叠控件.主页:http://letmehaveblog.blogspot.com/2007/10/haccordion-simple-horizontal-a ...
随机推荐
- vivo 场景下的 H5无障碍适配实践
作者:vivo 互联网前端团队- Zhang Li.Dai Wenkuan 随着信息无障碍的建设越来越受重视,开发人员在无障碍适配中也遇到了越来越多的挑战.本文是笔者在vivo开发H5项目做无障碍适配 ...
- 《SQL与数据库基础》04. SQL-DQL
目录 DQL 基础查询 条件查询 分组聚合 聚合函数 分组查询 结果排序 分页限制 总结 本文以 MySQL 为例 DQL 语法结构: SELECT 字段列表 FROM 表名列表 WHERE 条件列表 ...
- 淘宝详情api接口的使用说明
淘宝详情API接口是一种可以用来获取淘宝商品详细信息的服务,包括图片.标题.价格.销量.评论等数据.下面是淘宝详情API接口的使用说明: 1.关于申请API接口权限: 在使用淘宝详情API接口前,需要 ...
- CodeForces 1367E Necklace Assembly
题意 给定一个字符串\(s\),长度为\(n\),一根项链为一个环,定义一根项链为\(k-beautiful\),则该项链顺时针转\(k\)下后与原项链相等,给出\(k\),请构造一根最长的\(k-b ...
- 例子:统计电影类型的个数,以及用bar绘制出来表示
import pandas as pdimport numpy as npfrom matplotlib import pyplot as plt#获取各种电影类型的数量file='./IMDB-Mo ...
- 【Qt6】列表模型——便捷类型
前一篇水文中,老周演示了 QAbstractItemModel 抽象类的继承方法.其实,在 Qt 的库里面,QAbstractItemModel 类也派生了两个基类,能让开发者继承起来[稍稍]轻松一些 ...
- Python 有趣的模块之pynupt——通过pynput控制鼠标和键盘
写在前面 Python中有许多有趣和强大的模块,其中一个非常有趣的模块就是pynupt.pynupt是基于pynput模块的一个封装,用于控制鼠标和键盘.它可以实现自动化操作和游戏外挂等功能. 本文将 ...
- 谱图论:Laplacian二次型和Markov转移算子
以下部分是我学习CMU 15-751: TCS Toolkit的课堂笔记.由于只是个人笔记,因此许多地方在推导上可能不那么严谨,还望理论大佬多多包涵. 1 问题定义 1.1 无向图\(G\) 在本文中 ...
- 1111error
Allowed memo ry size of 268435456 bytes exhausted编辑的没有缓存都丢了
- torch.nn.ReLU(inplace=True)的具体含义:
首先根据源文档中的ReLU(x)=max(0,x),得出结论.大于0的数值不变,小于0的数据变成0. 补充:这里需要注意的是 ReLU并没有限制数据的大小. 这是对应的文档链接:https://pyt ...