news 2026/10/24 7:14:38

XAF框架GridView定制化实战:从列控制到报表改造的完整指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
XAF框架GridView定制化实战:从列控制到报表改造的完整指南

做XAF开发的朋友基本都有同感:刚接触eXpressApp Framework时,列表页面开箱即用很香,但一旦业务要求上来,比如“这个列表要给财务加一列合计”“那个状态要在行首显示成徽章样式”“某些用户不能看某些列”,默认的GridView就变得非常“笨拙”。这篇内容我把这几年在XAF GridView定制化上踩过的坑、沉淀下来的套路和可以直接抄走的写法整理一遍,覆盖列字段控制、行为逻辑调整、布局持久化以及把列表改造成轻量报表这几个高频场景,适合正在做XAF项目二次开发、被列表需求缠住的开发者参考。

1. 定制GridView的第一层:搞清它和ListView的关系,别再乱改控件

XAF里的GridView不是孤立的控件,它是XAF数据模型和实际UI控件之间的表现层。很多人一上来就想直接找ASPxGridView(Web端)或GridControl(WinForms端)的属性去改,结果改了没反应,或者被XAF的机制覆盖。核心原因是没有搞清楚ListView、Editor和真正的GridView三者之间的层级关系。

1.1 ListView只是“虚拟视图”,GridView才是真正的渲染控件

在XAF里,ListView是XAF层面上的列表视图,它承载的是业务数据的集合定义、列集合定义、排序过滤规则等“元信息”。真正的可视化控件是你项目里引用平台对应的Editor,比如Web端是ASPxGridViewListEditor,WinForms端是GridListEditor。这两者在运行时生成实际的GridView控件。

所以我定制化的第一原则:能通过XAF的ListView层做的修改,绝不直接动GridView层;只有XAF没有提供对应能力时,才去操作GridView本身。这个原则能帮你避免大量的版本兼容性灾难——因为XAF的API在不同版本里变化不大,但底层控件(比如DevExpress 18.x和23.x的GridView)API差异是很大的。

1.2 拿到GridView的入口:View.ControlsCreated与Editor属性

无论Web端还是WinForms端,标准做法是在ViewController(或普通ViewController)里监听View的ControlsCreated事件,然后通过View.Editor拿到Editor,再根据平台类型转换成对应的Editor类型,最终从Editor的GridView属性取出真实的表格控件。

拿Web端举例:

public class InvoiceListViewController : ViewController<ListView> { protected override void OnActivated() { base.OnActivated(); ControlsCreated += OnControlsCreated; } private void OnControlsCreated(object sender, EventArgs e) { if (View.Editor is ASPxGridViewListEditor editor) { ASPxGridView grid = editor.GridView; // 到这里,你已经拿到真正的ASPxGridView了 grid.CustomizeDataRowTemplate += OnCustomizeTemplate; grid.ClientSideEvents.Init = "onGridInit"; } } }

这段代码里有几个容易出错的点。第一,View.Editor在ControlsCreated之前可能是null,所以必须挂在事件里处理。第二,从Editor拿GridView之后,建议立刻做一次功能开关的配置,而不是零零散散到处设置,方便后续维护。第三,如果是WinForms项目,对应的Editor类型是GridListEditor,拿到的控件是GridControl,后续的API逻辑完全不同,不要试图共用同一套代码。

1.3 常见误操作:在View.Shown里设置控件属性会时常失效

我见过不少同行把GridView的属性设置放在了View.Shown事件或自定义View的构造函数里,结果发现时灵时不灵。因为XAF的列表视图有自己的生命周期:控件创建(ControlsCreated)→ 数据绑定(SelectionChanged、DataSourceChanged)→ 显示(ViewShown)。

GridView的很多布局属性(如列宽、分组、排序)在数据绑定之后会被XAF重新应用一遍,尤其是列表的Model(Model.Columns)会强制覆盖GridView的列设置。所以如果你只设置一次布局属性,很可能被后续的模型应用阶段冲掉。

我的兜底方案是监听View.SelectionChanged或DataSourceChanged,做二次应用。或者更简单粗暴一点——每次ControlsCreated之后,用BeginUpdate包裹住属性设置,强制在模型应用之后再刷一次。

2. 列级定制:从哪里拿到列信息,怎么改字段名、列宽和显示顺序

GridView定制里最日常、最高频的就是列定制。需要显示哪些列、列的顺序、列的Caption、列宽、字段的输出格式,都属于列级操作。XAF在Model层提供了Columns节点,运行时的ViewController里也有对应的操作入口,掌握这一层,90%的列需求都能解决。

2.1 使用Model的Columns节点做静态配置——推荐优先

在XAF的Model Editor里,找到对应ListView节点,展开Columns子节点,每个字段对应一个Column。在这个界面上可以很直观地设置:

  • Index:列的显示顺序,数值小的靠左。
  • Caption:列的标题文字,如果不设置,默认用字段本身的Caption。
  • Width:列的宽度,单位是像素,Web端还可以设置为百分比模式。
  • SortOrder:列表第一次加载时的默认排序,配合SortIndex使用。
  • GroupIndex:分组字段,把列表变成分组视图。

这里有个推荐习惯:同一个列表视图,由于XAF里的Model是多层继承的(UserModel、ApplicationModel、ModuleModel等),在Module项目里设置Model,通常是最好的位置。因为ModuleModel是模块级的,所有引用该Module的应用程序都会生效,而ApplicationModel只对当前应用生效。如果你的项目是多个应用复用同一个模块,建议把常规列表的列配置放在Module的Model里,个性化需求才放Application层。

2.2 ViewController里动态改列:按用户、按权限调整列集合

静态配置能解决80%的列需求,但还有一类需求动态性较强,比如:普通用户登录时隐藏“成本价”列,财务角色登录时显示成本价和利润列;或者在某个操作完成后,列表要自动追加一列“最近处理人”。这类需求放Model里也能做,但设计上比较绕,我一般是直接在ViewController里针对角色或业务状态动态调整View.Columns。

protected override void OnActivated() { base.OnActivated(); View.Columns.CollectionChanged += OnColumnsCollectionChanged; var currentUser = SecuritySystem.CurrentUserName; if (IsInRole(currentUser, "Finance")) { View.Columns["CostPrice"].Visible = true; View.Columns["Profit"].Visible = true; } else { View.Columns["CostPrice"].Visible = false; View.Columns["Profit"].Visible = false; } }

有一点很重要:以这种方式调整列时,优先使用View.Columns的属性,不要直接操作GridView的Columns集合。View.Columns是XAF的列模型集合,操作它之后XAF会同步到底层GridView;直接操作GridView的列,虽然一时生效,但列模型的刷新会取消你的改动,或者导致“明明设置了隐藏,刷新后又冒出来”的问题。

2.3 格式化显示:日期、金额、百分比,一个属性搞定

列显示格式的定制,Static模式用Model的DisplayFormat属性即可,动态模式则用GridView列本身的Properties:

grid.Columns["InvoiceDate"].PropertiesEdit.DisplayFormatString = "yyyy-MM-dd"; grid.Columns["TotalAmount"].PropertiesEdit.DisplayFormatString = "c2"; grid.Columns["TaxRate"].PropertiesEdit.DisplayFormatString = "p0";

注意:GridView列的PropertiesEdit.DisplayFormatString和XAF Model里的DisplayFormat并不是同一个东西。如果你既在Model里设置了格式,又在代码里设置格式,代码优先,但模型一旦发生重建,代码设置值可能会丢。所以我的习惯是:格式化显示尽量放Model里,代码只处理那些无法提前确定的动态格式。

2.4 一个小坑:Captions和ToolTips的国际化

多语言项目里,列的Caption默认拿的是业务对象的字段Caption(这个Caption往往在模型里做了翻译)。但如果你直接在代码里给GridColumn.Caption = "金额"赋值,那么切换语言后,Caption不会跟着变。

推荐的做法是自定义一个扩展方法,通过XAF的本地化服务取翻译文本:

public static void SetCaptionFromResource(this GridColumn column, string resourceKey) { var displayName = Localizer.GetString(resourceKey); if (!string.IsNullOrEmpty(displayName)) { column.Caption = displayName; } }

类似的还有ToolTip,表格列头和行内容的悬浮提示,很多业务系统要求鼠标悬停能看到字段说明、数据口径等,这类信息直接放在业务对象字段的Description属性里,然后统一绑定到GridView列的ToolTip,维护成本很低。

3. 行级行为定制:用GridView事件处理双击、行选中和自定义操作按钮

列定制只是视觉层面的调整,真正让GridView“活起来”的是行为层的定制。比如双击一行打开详情、点击行首图标触发业务操作、行状态变色等。这些需求几乎都要用到GridView的行事件,而XAF官方文档覆盖不全,需要自己摸索。

3.1 双击行打开详情视图——比默认跳转更灵活的做法

XAF的ListView默认支持双击行跳转到DetailView,但跳转逻辑是固定的:直接打开当前记录的默认DetailView。如果想控制跳转到的视图、先做权限校验、或者带上参数,则需要接管双击事件。

Web端示例:

grid.RowDblClick += (s, e) => { var rowHandle = grid.FindVisibleIndexByValue(e.VisibleIndex); var obj = grid.GetRow(rowHandle) as Invoice; if (obj != null && obj.Status == InvoiceStatus.Draft) { // 草稿单打开可编辑视图 var os = Application.CreateObjectSpace(typeof(Invoice)); var detailView = Application.CreateDetailView(os, obj, true); Application.ShowViewStrategy.ShowInNewWindow(detailView, true); } else { e.Handled = true; // 阻止默认跳转 } };

这里有个关键点:XAF的Application.CreateDetailView创建的视图不会自动管理对象空间的生命周期,如果是简单展示无所谓,如果要编辑,最好显式创建一个新的ObjectSpace来承载这个详情视图,避免在当前列表的ObjectSpace里产生脏数据。

WinForms端的做法很类似,只不过事件名是DoubleClick,行索引的获取方式也略有不同,核心思路不变。

3.2 行选中事件:在Controller里监听SelectionChanged更安全

很多人习惯直接挂GridView的SelectionChanged事件来感知当前选中行,但XAF的ListView本身已经封装了当前选中对象的概念,你在ViewController里直接重写OnSelectionChanged方法才是最稳妥的:

protected override void OnSelectionChanged() { base.OnSelectionChanged(); if (View.SelectedObjects.Count == 1) { currentInvoice = View.CurrentObject as Invoice; UpdateActionState(); } }

这样做的好处是,你的逻辑不受GridView平台差异的影响,Web端和WinForms端共用一套Controller代码。再配合Action的SelectionDependencyType属性(比如设为RequireSingleObject),可以让按钮自动在无选中时可点击状态被禁用,非常省事。

3.3 自定义操作按钮:挂在Action容器里,挂在GridView里行内

多行列表常见的需求是在每一行加一个“作废”按钮或“审批”按钮。XAF里做这个需求有几个层次:

  • 简单做法:加一个PopupWindowShowAction或SimpleAction,设置TargetViewType = ListView,工具栏上就会多出按钮,选中行后点击执行。
  • 进阶做法:把按钮直接嵌入行内,即每行末尾显示“作废”“查看日志”等小按钮。

行内按钮的实现在Web端和WinForms端差异最大。Web端可以用GridView.CustomizeDataRowTemplate往模板里注入控件;WinForms端则通常使用RepositoryItemButtonEdit放在一列里,通过ButtonClick事件触发。

private void OnCustomizeTemplate(object sender, ASPxGridViewCustomizeDataRowTemplateEventArgs e) { var button = new ASPxButton(); button.Text = "作废"; button.ClientSideEvents.Click = $"function (s, e) {{ onVoidRow('{e.KeyValue}'); }}"; e.Template = new CustomRowTemplate(button); }

这种做法的好处是视觉上直观,缺点是每行都渲染一个按钮控件,数据量大的时候性能会下降。如果你只是做少量管理操作,问题不大;如果列表动辄上万行,建议改用“目标行高亮+右键菜单”或者“选中后点击工具栏按钮”的方案,性能和体验会更好。

4. 把GridView变成轻量报表:汇总行、格式化合计、分页策略

XAF的GridView不是只有“展示明细列表”这一个用途。我经常在项目里直接把某个明细列表页面改造成“查询报表页”:要有合计行、要有分组汇总、要能自定义显示精度、还要保持分页查询的性能。这部分是GridView定制里最有“含金量”的部分,因为涉及到的不只是XAF,还有底层控件对数据源的处理方式。

4.1 开启汇总行ShowTotalSummary与自定义合计逻辑

GridView自带汇总能力,开启ShowFooter之后,可以给每个字段设置SummaryItem:

grid.TotalSummary.Clear(); grid.TotalSummary.Add(new ASPxSummaryItem("TotalAmount", DevExpress.Data.SummaryItemType.Sum) { DisplayFormat = "合计: {0:c2}", Tag = "TotalAmountSum" }); grid.TotalSummary.Add(new ASPxSummaryItem("Tax", DevExpress.Data.SummaryItemType.Sum) { DisplayFormat = "税: {0:c2}", Tag = "TaxSum" });

需要注意,在XAF里,GridView的默认数据源是XAF的BindingSource或ServerModeDataSource。如果你直接用ASPxSummaryItem,只要数据源属性名正确,加总逻辑会直接在数据源层面执行,不需要遍历所有行——这个设计对大数据量很友好。

但是有个非常容易踩的坑:如果你改了GridView的列字段名(例如在Model里把字段映射成其他名称),ASPxSummaryItem.FieldName填的不是列名,而是实体字段名。比如列Caption改成了“金额”,但FieldName必须写“TotalAmount”,否则汇总结果一直是0或者直接报字段未找到。

4.2 使用View的Criteria与ListView的Filter实现“报表参数”

既然要做报表,一般都需要接收查询条件。很多人单独建一个查询视图,然后传参数进来。其实直接在GridView列表上做过滤条件也很成熟:XAF的ListView本身支持Criteria属性,这个Criteria可以是代码设置的,也可以是用户点击快速过滤按钮动态拼出来的。

在ViewController里,可以监听过滤按钮的点击并通过View.CollectionSource.Criteria[]增加自定义过滤条件:

var collectionSource = View.CollectionSource; collectionSource.Criteria["MyReportFilter"] = CriteriaOperator.Parse( "Status = ? AND CreateDate >= ? AND TotalAmount >= ?", InvoiceStatus.Approved, dateFrom, minAmount );

这个做法的优点是可以实现“列表即报表”:用户在当前列表页面上筛选数据,汇总行跟着变化,而不用跳转页面重新加载。由于Criteria是XAF原生的过滤机制,所有数据源的权限过滤规则(比如按用户数据范围过滤记录)依然生效——这一点是直接操作GridView的FilterRow完全做不到的。

4.3 分页策略对汇总的影响:汇总行到底汇总了多少数据

很多人在列表上加了汇总行,发现汇总的数据量对不上:比如当前页有20行,但汇总出来的金额是所有满足条件记录的总金额。这个其实是GridView本身的属性决定的。

Web端的ASPxGridView默认汇总行为是:在数据源是ServerModeDataSource时,汇总行往往是对全部记录做统计;如果是普通List数据源,则是对当前数据源实例(有时数组在内存中)做统计。XAF在默认配置下可能走的是服务器模式(Server Mode),因此你的汇总行统计的是全部数据,不是当前页。这一点在业务上可能正是你想要的,但如果你只想统计当前页,则需要在数据源层面做一次分页处理后,再把当前页的数据源绑定给GridView。

WinForms的GridControl也有类似情况,默认汇总针对的是当前数据源的所有行。所以明确你的需求是“全量汇总”还是“当前页汇总”,再决定是否要对数据源做预处理,不要等到上线了才发现统计口径不对。

4.4 格式化合计文本和前缀文本的实际写法

汇总值的显示格式除了普通的{0:c2},还经常需要在合计文本前面加业务前缀,比如“本期开票合计:”。这个字符串里面混了中英文对齐问题,建议写成这样的方式:

DisplayFormat = "金额合计: {0:#,##0.00} 元"

如果你想让汇总行更醒目,可以在FooterCellStyle里设置背景色和字体:

grid.TotalSummary.Add(new ASPxSummaryItem(...) { DisplayFormat = "...", Aggregate = ... }); grid.Styles.FooterCell.BackColor = System.Drawing.Color.FromArgb(245, 245, 245); grid.Styles.FooterCell.Font.Bold = true;

注意:不要直接给TotalSummary里设置HTML编码标签,因为ASPxGridView的汇总文本默认不是HTML渲染的,写了<b>标签会被转义成普通字符串显示。如果确实要富文本效果,需要先开启RenderMode = Template或者借助FooterCellTemplate,而不是画蛇添足。

5. 布局与状态的持久化:让用户调整好的列宽、顺序不丢失

GridView定制里最容易“顺手做坏”的一件事是布局持久化。用户调整了列宽、列顺序、列隐藏显示、分组方式,结果一刷新又变回默认了。这类问题虽然不是功能缺陷,但被用户反复抱怨的杀伤力极大。XAF其实默认提供了一套布局持久化机制,但需要显式打开并配置正确的存储方式。

5.1 XAF自带的布局持久化机制与开启方法

在XAF里,针对ListView的布局持久化会保存到Model的UserModel层。用户在界面上调整GridView布局后,以XML或者二进制的形式保存到当前用户的模型差异(Model Difference)中。Web端和WinForms端都有对应的设置:

  • WinForms:在Model Editor里,找到ListView对应的节点,设置GridView.SaveLayout属性为Xml或者Registry,并指定SaveLayoutToXmlFilePath。
  • Web端:ASPxGridView本身有SettingsLoadingPanel、ClientSideEvents.EndCallback配合SaveClientLayout属性。

我的建议是:优先使用XAF原生的Model Difference持久化,而不是自己用GridView.SaveLayoutToXml单独存一份XML文件。因为XAF原生的持久化方案会和用户的权限模型、角色差异耦合在一起;而自建XML文件虽然实现简单,但在多终端上部署会很麻烦——用户在这台机器上设定好的布局,换台电脑就丢了。

XAF原生的持久化代码如下(在ViewController里设置):

protected override void OnActivated() { base.OnActivated(); ControlsCreated += (s, e) => { if (View.Editor is GridListEditor gridEditor) { gridEditor.GridView.OptionsLayout.Columns.StoreAllOptions = true; gridEditor.GridView.OptionsLayout.Columns.AddNewColumns = false; gridEditor.GridView.OptionsLayout.Columns.RemoveOldColumns = false; } }; }

5.2 权限相关的布局限制:某些列不能让用户永久隐藏

布局持久化一旦开启,用户就可以随意调整列显示隐藏。但在企业系统里,有些列是老板要求“必须显示”的,比如“合同编号”“审批状态”。如果用户把它们隐藏了,之后每次打开列表都看不到,老板就会认为是系统坏了。

针对这类需要,我实践下来最可靠的做法是:在View.Columns.CollectionChanged事件里监听列的Visible变化,如果发现被改成不可见的列属于锁定列集合,立刻改回可见:

private void OnColumnsCollectionChanged(object sender, CollectionChangeEventArgs e) { if (e.Action == CollectionChangeAction.Remove || e.Action == CollectionChangeAction.Refresh) return; var column = View.Columns[e.NewIndex]; if (lockedColumns.Contains(column.PropertyName) && !column.Visible) { column.Visible = true; } }

这种方法会有一瞬间的闪烁(用户看到列刚消失又被拉回来),但在“必须显示”这类强约束下,这个闪动反而能给用户清晰的反馈——这列你是动不了的。比起静默强制,这种可视化反馈更体验友好。

5.3 跨会话重置布局的例外场景

用户折腾出来的布局一团糟,需要“一键恢复默认布局”。实现这个功能的路径是清除当前用户的Model Difference中对应ListView的布局差异节点。

在XAF里可以通过Model节点操作实现:

var listViewModel = View.Model; listViewModel.ResetValues(true); // 重置默认值

不过这个重置的粒度比较大,可能会把View上其它非布局的设置也一起重置。更精细的版本是通过XAF的DiffStore来删除对应节点的差异:

var diff = (IModelApplication)Application.Model; var layer = diff.DifferenceStore; // 定位ListView节点后调用RemoveNode

我自己在项目里的做法是:给“恢复默认布局”单独做一个Action,并把它固定在界面显眼位置,触发时只重置当前列表View的列集合和布局相关属性,然后再调用View.Refresh()刷新界面。虽然实现起来比直接掉API多几十行代码,但这个功能在业务用户那里的满意度非常高。

6. 实战总结与几个高频问题的排查方法

最后分享一些我在GridView定制时积累的排查经验。XAF的GridView问题,大部分不是逻辑写错了,而是层级关系没理清、生命周期时机没搞对、不同平台API差异导致的表现不一致。遇到问题建议按这五步排查。

6.1 事件注册的时机问题:为什么我的GridView事件没有触发

先检查代码是否在正确的时机注册。比如RowDblClick、CustomizeDataRowTemplate这类控件级事件,必须在GridView初始化之后注册;而XAF的机制是先创建ListView的Model,再创建Editor,最后才创建GridView控件。

推荐统一使用的生命周期顺序:

  • OnActivated:注册Model层事件(View.Columns.CollectionChanged)。
  • ControlsCreated:注册GridView控件级事件。
  • DataSourceChanged:处理数据绑定之后的逻辑。

如果你已经很确定注册时机没问题,那问题多半出在事件被覆盖了——比如GridView在XAF内部被重新创建了一次,你的控件引用是旧对象。碰到这种情况,建议在ControlsCreated里重新获取Editor.GridView的最新实例,而不是把旧实例存在一个字段里长期使用。

6.2 字段名找不到、属性异常:先检查业务对象的Model

很多GridView列异常(比如某些列找不到字段名、汇总结果一直是null、编辑器无法弹出),本质原因是业务对象的XAF Model里字段定义有问题。比如Nullable类型字段没有设置Type、枚举字段没有设置Converter、关联对象没有配置Aggregated,这些都会导致列在运行时拿不到正确的元数据。

排查方式很简单:在Application调试窗口打开Model Editor,查看对应ListView.Columns节点的字段定义是否完整,字段的类型映射是否正确。不要盯着ASPxGridView的报错信息去猜,XAF的报错有时候指向的是底层控件,真正的问题在Model层。

6.3 大数据量列表:GridView直接卡死怎么办

列表数据量超过两三万行时,XAF默认的列表配置会让GridView的加载和渲染变得非常吃力,尤其是带自定义模板按钮的列表。这类问题有三个递进解决方案:

  1. 在ListView的Model里开启ServerMode,让数据源延迟加载。XAF在Web端开启后,ASPxGridView能利用服务端回调做数据分页,避免一次性把所有记录都拉到客户端。
  2. 关闭GridView的SettingsContextMenu和Settings.ShowFilterRow,减少页面渲染的计算量。
  3. 如果行内按钮导致行模板开销太大,改成Action容器的按钮,或者把行操作折叠进“右键菜单”。

记住:不要为了给每一行加一个图形按钮,牺牲掉整张列表的加载性能。这类需求的用户往往更在意列表打开的速度,而不是行内按钮的便利性。

6.4 不同角色看到的列不一样:不用复制视图,用View的Criteria和Model Layer

很多人在做“不同角色不同列表”时,第一反应是创建多个ListView,分别配置不同的列。这个方法能用,但运维成本很高,几乎每加一个字段,就要同步到所有视图里。

更合理的方案是:保持一个ListView,在运行时根据角色的不同动态修改View.Columns的Visible属性;还需要权限隔离的字段,配合XAF的PermissionPolicy规则统一控制。这样业务对象加一个字段,你只需要改一处Model默认配置,角色间的差异逻辑全部收敛在ViewController里。

6.5 移植Web端和WinForms的差异提醒

同一个XAF模块如果同时发布Web端和WinForms端,GridView定制代码里最容易出问题的就是直接操作了平台相关类型。我在项目里约定了一套平台抽象接口,例如定义IRowActionCell接口,在Web端和WinForms端分别实现“行内按钮的输出逻辑”,Controller层只和接口交互。

这样虽然刚开始要多写一些适配代码,但换来的是后续新增平台(如果哪天要出移动端)时,Controller层代码完全不用动。

6.6 我踩过一个印象最深的坑:汇总数据重复计算

有一次在财务列表中加“金额合计”,用户在界面上点了几次排序,结果合计金额翻倍了。排查到最后发现,问题不是GridView本身,而是页面用了XAF的RefreshAction(刷新当前视图),刷新时GridView重新绑定了数据源,而我在数据源绑定前又往实体集合里插入了一条临时“合计行”,导致合计行参与进了GridView的汇总计算。

从那以后我给自己定了一条规则:不要为了显示汇总行而伪造实体数据行,汇总需求一律使用GridView的汇总功能,或者单独建立一个VO对象模型展示统计数据。这条规则后来避免了很多类似事故。

6.7 最后一个实用技巧:自定义行的行背景色

列表行还需要根据业务状态标色,例如“已逾期”标红、“待审”标黄。这个需求使用ASPxGridView的HtmlRowCreated事件,判断当前行的数据对象后设置行背景色:

grid.HtmlRowCreated += (s, e) => { if (e.RowType != GridViewRowType.Data) return; var record = grid.GetRow(e.VisibleIndex) as Invoice; if (record != null && record.Status == InvoiceStatus.Overdue) { e.Row.BackColor = System.Drawing.Color.FromArgb(255, 235, 235); } };

这段逻辑里最关键的一点是判断e.RowType,只对数据行上色,避免对标题行、合计行、空行误操作,不然页面一加载就会因为空行数据判空而报错。

GridView的定制化是个越做越深的领域,很多问题不是XAF本身难,而是DevExpress底层控件和XAF的抽象层叠加之后,排查链路变长。所以我一直建议团队里写XAF的同事:先理清模型、视图、控制器之间的分工,再动代码。该放在Model里的别用代码硬写,该在Controller里动态调整的也别在Model里堆一大堆条件。这样,后续接手的人就不用靠猜来维护你的GridView定制逻辑了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/24 7:14:36

PCA9422与STM32F373RC协同电源管理实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/24 7:14:14

前端脚手架实战:用Node.js打造自动化项目初始化工具

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/24 7:14:06

PMIC+MCU架构的电源管理设计:从时序控制到低功耗策略

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/24 7:13:54

基于OMAP3x深入分析V4L2架构:从驱动到采集实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/24 7:13:05

PCA9422与TM4C1299协同的嵌入式电源管理方案实战解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/24 7:11:35

Kafka监控框架选型与落地:核心指标、采集配置与告警实践

1. 为什么Kafka监控这件事&#xff0c;越早想清楚越省心Kafka 这东西&#xff0c;用起来是真顺手&#xff0c;但一旦集群规模上去、业务链路变长&#xff0c;你就会发现“能跑”和“跑得好”完全是两码事。我见过太多团队在项目初期只关心消息能不能发出去、能不能消费到&#…

作者头像 李华