在我们开发一些复杂信息的时候,由于需要动态展示一些相关信息,因此我们需要考虑一些控件内容的动态展示,可以通过动态构建控件的方式进行显示,如动态选项卡展示不同的信息,或者动态展示一个自定义控件的内容等等,目的就是能够减少一些硬编码的处理方式,以及能够灵活的展示数据。本篇随笔通过实际案例介绍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应用开发之控件动态内容展示的更多相关文章

  1. WPF编程,将控件所呈现的内容保存成图像的一种方法。

    原文:WPF编程,将控件所呈现的内容保存成图像的一种方法. 版权声明:我不生产代码,我只是代码的搬运工. https://blog.csdn.net/qq_43307934/article/detai ...

  2. CYQ.Data 支持WPF相关的数据控件绑定(2013-08-09)

    事件的结果 经过多天的思考及忙碌的开发及测试,CYQ.Data 终于在UI上全面支持WPF,至此,CYQ.Data 已经可以方便支持wpf的开发,同时,框架仍保留最低.net framework2.0 ...

  3. 使用C#开发ActiveX控件(新)

    前言 ActiveX控件以前也叫做OLE控件,它是微软IE支持的一种软件组件或对象,可以将其插入到Web页面中,实现在浏览器端执行动态程序功能,以增强浏览器端的动态处理能力.通常ActiveX控件都是 ...

  4. WPF后台设置xaml控件的样式System.Windows.Style

    WPF后台设置xaml控件的样式System.Windows.Style 摘-自 :感谢 作者: IT小兵   http://3w.suchso.com/projecteac-tual/wpf-zhi ...

  5. WPF Step By Step 控件介绍

    WPF Step By Step 控件介绍 回顾 上一篇,我们主要讨论了WPF的几个重点的基本知识的介绍,本篇,我们将会简单的介绍几个基本控件的简单用法,本文会举几个项目中的具体的例子,结合这些 例子 ...

  6. 使用C#开发ActiveX控件(新) 转 http://www.cnblogs.com/yilin/p/csharp-activex.html

    前言 ActiveX控件以前也叫做OLE控件,它是微软IE支持的一种软件组件或对象,可以将其插入到Web页面中,实现在浏览器端执行动态程序功能,以增强浏览器端的动态处理能力.通常ActiveX控件都是 ...

  7. C#开发ActiveX控件

    昨天写了篇博客<Winform 程序嵌入WPF程序 并发送消息>,没有说明为什么要嵌入WPF程序,那么今天就来唠叨唠叨其中的一个使用场景,开发ActiveX控件 首先,新建一个类库工程Hu ...

  8. CYQ.Data 支持WPF相关的数据控件绑定.Net获取iis版本

    CYQ.Data 支持WPF相关的数据控件绑定(2013-08-09) 事件的结果 经过多天的思考及忙碌的开发及测试,CYQ.Data 终于在UI上全面支持WPF,至此,CYQ.Data 已经可以方便 ...

  9. 使用C#开发ActiveX控件

    使用C#开发ActiveX控件(新) 前言 ActiveX控件以前也叫做OLE控件,它是微软IE支持的一种软件组件或对象,可以将其插入到Web页面中,实现在浏览器端执行动态程序功能,以增强浏览器端的动 ...

  10. 一些基于jQuery开发的控件

    基于jQuery开发,非常简单的水平方向折叠控件.主页:http://letmehaveblog.blogspot.com/2007/10/haccordion-simple-horizontal-a ...

随机推荐

  1. CAP 7.2 版本发布通告

    前言 今天,我们很高兴宣布 CAP 发布 7.2 版本正式版,我们在这个版本中主要致力于 Dashboard 对 k8s 服务发现的支持. 从 7.1 版本以来,我们发布了4个小版本,在这些版本中我们 ...

  2. 让 GPT-4 来修复 Golang “数据竞争”问题 - 每天5分钟玩转 GPT 编程系列(6)

    目录 1. Golang 中的"数据竞争" 2. GoPool 中的数据竞争问题 3. 让 GPT-4 来修复数据竞争问题 3.1 和 GPT-4 的第一轮沟通 3.2 和 GPT ...

  3. 《深入理解Java虚拟机》读书笔记:Class类文件的结构

    Class类文件的结构 Sun公司以及其他虚拟机提供商发布了许多可以运行在各种不同平台上的虚拟机,这些虚拟机都可以载入和执行同一种平台无关的的程序存储格式--字节码(ByteCode),从而实现了程序 ...

  4. Visual Studio Code(vscode)下载慢 插件安装失败解决方案

    目录 一.系统环境 二.前言 三.Visual Studio Code(vscode)简介 四.解决Visual Studio Code(vscode)下载慢的问题 4.1 问题描述 4.2 解决方案 ...

  5. 论文解读(WDGRL)《Wasserstein Distance Guided Representation Learning for Domain Adaptation》

    Note:[ wechat:Y466551 | 可加勿骚扰,付费咨询 ] 论文信息 论文标题:Wasserstein Distance Guided Representation Learning f ...

  6. Go,从命名开始!Go的关键字和标识符全列表手册和代码示例!

    关注TechLeadCloud,分享互联网架构.云服务技术的全维度知识.作者拥有10+年互联网服务架构.AI产品研发经验.团队管理经验,同济本复旦硕,复旦机器人智能实验室成员,阿里云认证的资深架构师, ...

  7. 微信Native支付(扫码支付)商户配置

    0.需要从商户平台获取/设置的配置 公众号appId 商户号 APIv3密钥 证书序列号 证书密钥 1.扫码登录商户平台 网址:https://pay.weixin.qq.com/ 2.确认已开通Na ...

  8. strimzi实战之二:部署和消息功能初体验

    欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 本篇概览 本篇是<strimzi实战>系列 ...

  9. answer answerdev/answer:latest 开源问答平台

    freedidi.com/10294.html   https://youtu.be/A2GUgvPlTBE?si=jdhqXL1WttLrLgiQ     docker依赖 yum install ...

  10. MySQL误删恢复方法1

    MySQL不同于oracle,没有闪回查询这类概念,但网上流传几个闪回的开源工具如 binglog2sql.MyFlash,可以使用binglog日志进行误操作数据的恢复. 笔者以前测试过 bingl ...