最近在做 WPF 项目时碰到一个挺典型的诉求:DataGrid 要按条件动态控制某些行的显示与隐藏。乍一听好像不难,但真上手你会发现,直接操作Row.Visibility会踩坑,绑定集合又容易把逻辑写散。我把自己实际调试通过的一套方案整理出来,顺便把牵扯到的DataTrigger、RowStyle、MVVM 绑定这些知识点一并讲透,看完你大概率能直接抄作业。
1. 动态显示行的整体设计与核心思路
1.1 理解需求:什么场景下需要动态显示行
先说说什么叫“动态显示行”。字面上看,就是 DataGrid 里的某些行,在满足特定条件时显示,不满足时隐藏或者反过来。我遇到过的典型场景包括:
- 状态筛选:表格里同时有“启用”和“停用”的数据,希望只显示当前筛选状态的行。
- 权限控制:当前登录人没有审批权限时,隐藏“待审批”的记录行,防止误操作。
- 分组切换:通过 ToggleButton 切换“显示全部”和“仅显示异常”,异常行之外的普通行全部隐藏。
- 懒加载视觉优化:先隐藏明细行,点击“展开”时才显示,实现类似 Master-Detail 的折叠效果。
这类需求如果写在后台代码里,通常的做法是遍历dataGrid.Items,把每一行转成DataGridRow,然后设置row.Visibility。不是不能跑,但问题很明显:一是和视图逻辑强耦合,二是每次数据刷新或者滚动加载时,你可能需要重新处理一遍,很容易漏。更麻烦的是,如果你在后台代码里直接改了Visibility,再和样式里的DataTrigger搭配,会有优先级冲突,行为会很诡异。这个后面我会专门解释。
1.2 三种方案选型:后台遍历、集合过滤、DataTrigger
我先把自己的踩坑经历摆出来,方便你理解为什么最终选了第三种。
方案一:后台代码遍历行设置 Visibility
foreach (var item in dataGrid.Items) { var row = dataGrid.ItemContainerGenerator.ContainerFromItem(item) as DataGridRow; if (row != null) { row.Visibility = ShouldShow(item) ? Visibility.Visible : Visibility.Collapsed; } }这个方案最直观,但坑最多。ContainerFromItem有个特性:它只对“已经生成容器”的项返回有效结果。DataGrid 是虚拟化控件,滚动后很多行根本没生成,自然拿不到容器,设置 Visibility 就失效了。而且上拉下拉时,行的容器会被回收复用,你设置的 Visibility 可能应用到了错误的数据项上。
方案二:通过集合筛选(过滤数据源)
public ICollectionView FilteredView { get; set; } FilteredView = CollectionViewSource.GetDefaultView(AllItems); FilteredView.Filter = o => ShouldShow(o);这个方案比方案一好很多,因为它是纯数据层面的,不碰控件容器。但问题在于它是“整个集合刷新”,如果你的业务要求保留行状态(比如选中态、行号),或者你的显示条件很复杂、甚至需要同时显示/隐藏“某些特定行”而不是按统一规则过滤,那Filter写起来就会比较绕。
方案三:RowStyle + DataTrigger 动态控制行可见性(最终采用)
这个方案的核心思路是:把“行是否显示”抽象成 ViewModel 里的一个布尔属性,在 DataGrid.RowStyle 里绑定该属性,再用 DataTrigger 控制 Visibility。这种方式的好处是:
- 不依赖 DataGrid 的行容器是否生成,纯样式层处理,虚拟化下也完全稳定。
- 逻辑集中在 XAML 里,后台代码极简,符合 MVVM 理念。
- 多条件组合时,通过多个 DataTrigger 或者 MultiDataTrigger 实现,可读性和扩展性都很好。
我当时最后选的就是方案三。下面我把完整的实现过程拆开讲。
2. 核心细节解析与实操要点
2.1 先搞懂 WPF 的 Visibility 和 DataTrigger 联动原理
在 WPF 里,UIElement.Visibility是个枚举:Visible、Collapsed、Hidden。其中Collapsed和Hidden的区别是:
Collapsed:元素不占布局空间,等同于直接移除。Hidden:元素占空间但不可见。
对 DataGridRow 来说,要隐藏行,应该用Collapsed。否则该行虽然看不见,但行高还在,视觉效果如同一个空白间隙,很容易被误认为 bug。
而DataTrigger是 WPF 样式触发器的一种,它能监听绑定值的状态变化。注意,DataTrigger 的触发条件是“值相等”判断,默认走Equals。所以当你绑定bool类型时,可以直接写Value="True"或Value="False"。
它的优先级规则是这样的:
本地赋值(Local Value) > Style 中的 Setter > Style 中的 Trigger/DataTrigger。
这句话很关键。如果你在RowStyle里用DataTrigger设置了Visibility,然后在Loaded事件或者按钮点击事件里直接给row.Visibility赋值,那个赋值是LocalValue,会覆盖掉 Style 的 Trigger 效果,导致条件再怎么变化,行也“固定”在错误状态。我第一次就是栽在这儿。
所以正确做法是:不要试图通过代码给 DataGridRow 设置 Visibility,而是通过更新 ViewModel 中绑定的属性值来触发样式更新。
2.2 RowStyle 的绑定路径设计:直接绑 ViewModel,还是绑数据项?
到了真正写 XAML 的时候,第一个问题就是:RowStyle里应该绑定什么?
DataGrid 的每一行对应一个数据项,DataGridRow.DataContext就是这个数据项。所以如果你希望在“行”层面控制显隐,最直接的方式是给数据项增加一个IsVisible属性:
public class ItemModel : INotifyPropertyChanged { public string Name { get; set; } public bool IsVisible { get; set; } }然后在 RowStyle 里:
<Style TargetType="DataGridRow"> <Setter Property="Visibility" Value="Visible" /> <Style.Triggers> <DataTrigger Binding="{Binding IsVisible}" Value="False"> <Setter Property="Visibility" Value="Collapsed" /> </DataTrigger> </Style.Triggers> </Style>这种写法简单直观。但问题也来了——如果“行是否显示”不是数据项本身固有的属性,而是由页面状态(比如用户勾选了一个“只显示异常”的 CheckBox)动态决定的,那把它塞进 ItemModel 里就很别扭。比如你十几个地方都要用这个表格,每个使用方都要给数据项额外加一个IsVisible,维护成本一下就上去了。
我当时的选择是:把行的显示状态做成一个独立属性,放在当前界面的 ViewModel 里,然后通过RelativeSource向上绑定。
<Style TargetType="DataGridRow"> <Setter Property="Visibility" Value="Visible" /> <Style.Triggers> <DataTrigger Value="False"> <DataTrigger.Binding> <MultiBinding Converter="{StaticResource RowVisibilityConverter}"> <Binding RelativeSource="{RelativeSource AncestorType=DataGrid}" Path="DataContext.ShowOnlyError" /> <Binding Path="IsError" /> </MultiBinding> </DataTrigger.Binding> <Setter Property="Visibility" Value="Collapsed" /> </DataTrigger> </Style.Triggers> </Style>这里我用了一个MultiBinding + Converter的方式,把“页面状态”和“当前行数据状态”组合起来,再判断是否显示。如果你的逻辑更简单,比如只看当前行数据里的某个字段,那用单值DataTrigger绑定当前行的属性就足够了;但如果涉及到外部状态联动,用上面的 MultiBinding 方案会优雅很多。
2.3 用 Converter 还是用多个 DataTrigger?优先级和可读性的权衡
有的人喜欢用BooleanToVisibilityConverter配合DataTrigger:
<DataTrigger Binding="{Binding IsVisible}" Value="False"> <Setter Property="Visibility" Value="Collapsed" /> </DataTrigger>但实际业务中,你未必只有一个条件。比如我需要:
- 当
IsVisible == false时隐藏; - 当
IsSpecialFlag == true时强制显示,无论 IsVisible 是否为 false。
如果只用单个 DataTrigger,就无法表达“强制显示”逻辑。这时候有两种选型:
- 写一个
MultiConverter,把多个条件揉成一个转换结果返回 Visibility。 - 用多个 DataTrigger,通过 Setter 里的值覆盖,后声明的 Trigger 优先级更高。
我建议简单场景用多个 DataTrigger,复杂场景用 MultiConverter。
多个 DataTrigger 的好处是逻辑声明式,一眼能看懂,而且不需要额外写转换器代码。例如:
<Style TargetType="DataGridRow"> <Setter Property="Visibility" Value="Visible" /> <Style.Triggers> <DataTrigger Binding="{Binding IsVisible}" Value="False"> <Setter Property="Visibility" Value="Collapsed" /> </DataTrigger> <DataTrigger Binding="{Binding IsForcedVisible}" Value="True"> <Setter Property="Visibility" Value="Visible" /> </DataTrigger> </Style.Triggers> </Style>注意,这里 “强制显示” 的DataTrigger写在后面,它对应的 Setter 会覆盖前面设置的Collapsed。WPF 中同一属性的多个 Setter 在后声明者胜出。但如果反过来,后面的触发器又把它设成 Collapsed,那就需要你自己认真排顺序。
所以我的经验是:如果逻辑没有“强制覆盖”的需求,直接在 ViewModel 里算好显示结果,绑定一个 bool 属性,再用一个 DataTrigger 就完事;如果有强制覆盖需求,才用多 DataTrigger 或 MultiConverter。
3. 实操过程与核心环节实现
3.1 准备一个可运行的实体类和 ViewModel
我用一个常见的“订单列表”来演示。假设有个订单类,我给它加了两个属性:OrderId、Status、IsHidden。
public class OrderInfo : INotifyPropertyChanged { private string _orderId; public string OrderId { get => _orderId; set { _orderId = value; OnPropertyChanged(); } } private string _status; public string Status { get => _status; set { _status = value; OnPropertyChanged(); } } private bool _isHidden; public bool IsHidden { get => _isHidden; set { _isHidden = value; OnPropertyChanged(); } } public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged([CallerMemberName] string name = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); } }然后在主 ViewModel 中:
public class MainViewModel { public ObservableCollection<OrderInfo> Orders { get; set; } private bool _showHidden; public bool ShowHidden { get => _showHidden; set { _showHidden = value; OnPropertyChanged(nameof(ShowHidden)); } } }如果你的“动态显示”就是“根据筛选类型显示符合条件的数据”,那还有一个更干脆的思路:给 ViewModel 暴露不同的集合视图。但这又会回到“刷新集合”的问题上。我这里选择的是把ShowHidden作为页面层的开关,结合每行的IsHidden属性做判断。
3.2 设置 DataGrid.RowStyle,用 DataTrigger 控制行显示
这是核心 XAML 部分:
<DataGrid ItemsSource="{Binding Orders}" AutoGenerateColumns="False" SelectionMode="Single" GridLinesVisibility="All" RowHeaderWidth="0"> <DataGrid.RowStyle> <Style TargetType="DataGridRow"> <Setter Property="Visibility" Value="Visible" /> <Style.Triggers> <DataTrigger Binding="{Binding IsHidden}" Value="True"> <Setter Property="Visibility" Value="Collapsed" /> </DataTrigger> <DataTrigger Binding="{Binding DataContext.ShowHidden, RelativeSource={RelativeSource AncestorType=DataGrid}}" Value="True"> <Setter Property="Visibility" Value="Visible" /> </DataTrigger> </Style.Triggers> </Style> </DataGrid.RowStyle> </DataGrid>我来解释一下这里的关键点:
- 第一个
DataTrigger:如果某一行数据的IsHidden为True,就把行折叠。 - 第二个
DataTrigger:如果页面开关ShowHidden为True,就强制显示所有行。 - 第二个触发器必须写在第一个后面,这样当
ShowHidden为 True 时,无论IsHidden是什么,后写的 Setter 会把 Visibility 覆盖为 Visible,从而实现“显示隐藏行”的功能。
有读者会问:如果ShowHidden为 False,第二个触发器不生效,那IsHidden=True的行会被第一个触发器折叠。这不就是我们想要的吗?
对,逻辑没问题。但这里有个“隐藏行高占位还是折叠”的细节要提——Collapsed让行完全不占空间,不显示行分隔线,视觉上很干净。如果因为某些原因你用了Visibility.Hidden,行会白占地方,还可能出现选中空白区域的问题,非常误导人。
3.3 完整示例:结合选中高亮和单元格点击的实现细节
热词里有“wpf datagrid 点单元格”和“选中默认是背景颜色”,这其实也是很多人在做 DataGrid 动态行时容易忽略的点。因为你动态隐藏了某些行后,用户点选行的行为、选中后的背景色,都可能出现异常。
先说点单元格。默认情况下,DataGrid 点到单元格只会有 Cell 的焦点框,不会自动选中整行。如果你希望“点任意单元格选中整行”,有两个做法:
- 设置
SelectionUnit="FullRow",这是标准做法。 - 在
CellStyle里把FocusVisualStyle置空,避免点击单元格后出现虚线框。
然后是选中行的背景色。很多人会发现,自己给 DataGridRow 加了背景色,但选中行时颜色不生效。这是因为 DataGrid 的默认选中样式是通过DataGridRow模板里的Trigger控制的,而你的 RowStyle Setter 很可能被系统样式覆盖。
解决方式是为DataGridRow重写模板或使用RowStyle里的 Trigger 来设置选中背景:
<DataGrid.RowStyle> <Style TargetType="DataGridRow"> <Setter Property="Background" Value="Transparent" /> <Setter Property="Visibility" Value="Visible" /> <Style.Triggers> <Trigger Property="IsSelected" Value="True"> <Setter Property="Background" Value="#FFD5E8FF" /> <Setter Property="Foreground" Value="Black" /> </Trigger> <DataTrigger Binding="{Binding IsHidden}" Value="True"> <Setter Property="Visibility" Value="Collapsed" /> </DataTrigger> <DataTrigger Binding="{Binding DataContext.ShowHidden, RelativeSource={RelativeSource AncestorType=DataGrid}}" Value="True"> <Setter Property="Visibility" Value="Visible" /> </DataTrigger> </Style.Triggers> </Style> </DataGrid.RowStyle>注意,我把IsSelected的 Trigger 也写在同一个 Style 里,这样它和 Visibility 的 DataTrigger 属于同一优先级层,不存在互相覆盖的问题。
3.4 后台代码如何配合:动态刷新行显示
有人会问:如果我不想把显示逻辑写在 ViewModel,而是想在按钮点击时动态改怎么办?
推荐做法是改 ViewModel 属性,而不是操作 UI 容器。
private void OnToggleHiddenRows(bool showHidden) { ShowHidden = showHidden; }只要ShowHidden是实现了INotifyPropertyChanged的属性,XAML 中的DataTrigger会自动刷新。
如果你一定要在后台代码里操作行,那至少也要这样处理:
private void ForceRefreshRowVisibility() { foreach (var item in dataGrid.Items) { var row = dataGrid.ItemContainerGenerator.ContainerFromItem(item) as DataGridRow; if (row == null) continue; var order = item as OrderInfo; if (order == null) continue; if (order.IsHidden && !vm.ShowHidden) row.Visibility = Visibility.Collapsed; else row.Visibility = Visibility.Visible; } }但这种方法在 DataGrid 虚拟化下很容易出问题:滚到新区域时,新生成的行不会主动调用这个刷新方法,你可能还要监听ScrollViewer.ScrollChanged或ItemContainerGenerator.StatusChanged,越写越复杂。所以我强烈不建议作为主方案,最多作为辅助手段。
3.5 使用集合视图过滤的替代实现(适合不需要保留行状态的场景)
如果你只是简单地想做“动态显示行”,而且不希望依赖 DataTrigger 的优先级逻辑,我更推荐先考虑CollectionViewSource过滤。特别是当你只在“前端一个页面”临时展示数据,并且不需要保留选中状态时,这个方案最好使。
先在 XAML 里定义:
<Window.Resources> <CollectionViewSource x:Key="FilteredOrders" Source="{Binding Orders}" Filter="CollectionViewSource_Filter" /> </Window.Resources>后台代码:
private void CollectionViewSource_Filter(object sender, FilterEventArgs e) { if (e.Item is OrderInfo order) { e.Accepted = !order.IsHidden || vm.ShowHidden; } }然后按钮点击或 CheckBox 勾选后:
CollectionViewSource.GetDefaultView(dataGrid.ItemsSource).Refresh();这种方案的优点是简单粗暴,不涉及样式优先级,代码容易理解。缺点是每次筛选都是整个视图刷新,数据量大时会有轻微卡顿;而且如果你用了 DataGrid 的行选中,刷新后选中状态会丢失。所以它更适合中小数据量的过滤场景。
动态显示行的需求本身就是多种多样的,没有一个方案能通吃所有情况。我把两个方案的坑都列一下,你自己按业务取舍。
4. 常见问题与排查技巧实录
4.1 DataTrigger 不生效,行死活不显示/不隐藏
这是遇到最多的问题。排查顺序我希望你按顺序来:
- 先看绑定路径是否正确。在
DataTrigger里,如果没有指定RelativeSource,绑定上下文默认是DataGridRow.DataContext,也就是该行对应的数据项。如果数据项没有IsHidden属性,绑定会静默失败,视觉上就是没反应。 - 再看 ViewModel 的属性是否实现了
INotifyPropertyChanged。很多人改的是普通属性public bool IsHidden { get; set; },然后刷新数据后,界面不更新。因为绑定根本不知道属性变了。 - 检查是否被其他 UI 操作覆盖了本地值。如果你之前给某个行设置了
row.Visibility = Visible,那么这个本地值优先级会压过 DataTrigger,之后你更新 ViewModel 属性也没用了。 - 用 Snoop 或 Visual Studio Live Property Explorer 检查实际 Visibility。这个最直接,能立刻看出行的 Visibility 到底是哪一层设置的。
我曾在一次项目中排查了一个多小时,最后发现是同事在 ListView 的Loaded事件里给所有行手动设了Visibility,导致 DataTrigger 永远“不生效”。所以说,本地值覆盖样式触发值是个大坑。
4.2 隐藏行后,DataGrid 出现空白区域或者选中错乱
有时候你用了Visibility.Collapsed,行确实消失了,但 DataGrid 底部还是多出一块空白,或者滚动条的位置不对劲。这个现象多见于 DataGrid 处于虚拟化模式时,因为行容器并没有全部生成,有些行虽然 Visibility 是 Collapsed,但容器被复用时可能还残留之前的布局信息。
排查办法:
- 确认你设置的是
Collapsed而不是Hidden。 - 尝试给 DataGrid 设置
EnableRowVirtualization="False"看看是否复现,如果关掉虚拟化后正常,就说明是虚拟化下的容器复用问题。 - 终极方案是:不要在 RowStyle 之外手动设置行 Visibility,让数据触发统一控制。
另外,如果你遇到“选中行背景色错乱”,比如点第一行,结果第二行高亮了,这多半是行容器复用后 IsSelected 状态没有正确同步。在开启虚拟化时,尽量避免依赖行的本地状态,而应该在样式中用IsSelectedTrigger 控制背景。
4.3 点了单元格后选不中整行
这个也是被问烂的问题。DataGrid 默认的SelectionUnit是FullRow,但如果你把SelectionUnit设置为Cell或者CellOrRowHeader,点单元格时整行就不会被选中。恢复方法:
<DataGrid SelectionUnit="FullRow" />如果你还想禁用单元格点击时的虚线焦点框:
<DataGrid.CellStyle> <Style TargetType="DataGridCell"> <Setter Property="FocusVisualStyle" Value="{x:Null}" /> <Setter Property="BorderThickness" Value="0" /> </Style> </DataGrid.CellStyle>4.4 读取 DataGrid 表格内容时,拿不到隐藏行
热词里有“wpf读取datagrid表格内容”,这个和动态显示行结合起来,经常发生的问题就是:你隐藏了一些行,然后想“导出当前可见行”,却发现导出的是全部数据。
原因是DataGrid.ItemsSource指向的是原始集合,而不是“经过行样式隐藏后的可见子集”。行样式只是控制了 UI 表现,并没有改变数据源。
解决方案有两个方向:
- 如果你用的是
CollectionViewSource过滤,直接从ICollectionView里取OfType<OrderInfo>().Where(...)就行。 - 如果你用的是 RowStyle + DataTrigger 这种“假隐藏”,那导出时要自己按下发的筛选条件过滤一遍,不能只依赖 ItemsSource。
实际项目中我一般会建议:如果这个表格以后要做导出、统计,从一开始就使用 CollectionView 过滤,而不是纯 UI 隐藏。这样可以保证数据和 UI 状态始终一致,避免“导出的数据比看到的多”这种尴尬。
5. 扩展技巧与踩坑心得
5.1 用 MultiDataTrigger 处理多条件联合控制
如果行显隐条件不是一个,而是“状态为关闭 且 没有权限”时隐藏,可以用MultiDataTrigger:
<Style TargetType="DataGridRow"> <Setter Property="Visibility" Value="Visible" /> <Style.Triggers> <MultiDataTrigger> <MultiDataTrigger.Conditions> <Condition Binding="{Binding Status}" Value="Closed" /> <Condition Binding="{Binding HasPermission}" Value="False" /> </MultiDataTrigger.Conditions> <Setter Property="Visibility" Value="Collapsed" /> </MultiDataTrigger> </Style.Triggers> </Style>这种方式的好处是逻辑集中且可读性强,条件再多也一目了然。缺点是如果某一个条件需要“或”逻辑(比如状态为 Closed 或者 Cancelled 时都隐藏),MultiDataTrigger 的多个 Condition 之间是“与”的关系,实现“或”就得写多个 DataTrigger 或者改用 Converter。
5.2 给隐藏行加一点过度动画?效果可以,但要留意取舍
有人问过我能不能给行的显示/隐藏加上动画,比如淡入淡出、高度收缩。理论上你可以通过自定义DataGridRow的模板,在 Visibility 变化时播放DoubleAnimation控制 Height 或 Opacity。
但我的建议是:如果数据量小,可以做;数据量大,千万别做。因为动画会强制 DataGrid 停止行虚拟化,每一行都会保留容器和动画状态,性能急剧下降。而且动画过程中,如果用户快速滚动或切换条件,容易出现“幽灵行”闪烁。我在一个小型配置页面做过一次,当时只有几十行,体验挺好;后来同样代码放到一个上千行的日志表格里,卡到鼠标拖动都费劲。最后我只保留状态切换,动画全部砍掉。
5.3 结合动态行显示做行号刷新
如果你在 DataGrid 第一列显示行号,那隐藏行后行号往往不会自动重排,因为行号是在加载时计算的,或者用的是AlternationIndex。隐藏行后会出现“1、2、4、5”这种间断的序号。
解决方案是在LoadingRow事件里根据“当前可见行序号”重新赋值:
private void OnLoadingRow(object sender, DataGridRowEventArgs e) { var items = dataGrid.Items.Cast<object>() .Where(item => !(item is OrderInfo o) || !o.IsHidden || vm.ShowHidden) .ToList(); e.Row.Header = items.IndexOf(e.Row.DataContext) + 1; }注意,这个思路只在“从数据层面知道哪些行是隐藏的”时有效。如果你用纯 DataTrigger 隐藏,那你在LoadingRow里还要自己写一遍相同的过滤逻辑,相当于逻辑写了两遍。这也是我后来放弃“UI 假隐藏”改用“集合过滤”的原因之一。
5.4 页面初始化时怎么避免行样式闪烁
有时窗口加载时,DataGrid 会先显示一行行的数据,然后 DataTrigger 条件生效,又把一部分行隐藏掉,视觉效果上就是“闪了一下”。
解决方式:
- 在 ViewModel 构造时就把
IsHidden和ShowHidden初始化正确,不要等视图加载后再改。 - 如果必须在 View 加载后触发,可以先用
IsVisible把表格遮住,等数据绑定完再放开。 - 或者给
DataGrid加Initialization延迟绑定,让样式先应用再渲染。
我在实际开发中一般用第一种方式,把筛选条件的初值在 ViewModel 构造函数里设置好,几乎不会出现闪烁。
5.5 从性能角度:几千行大数据量的动态显示优化
最后说说性能。如果你只是几十行的小表格,用 RowStyle + DataTrigger 没问题,代码简洁直观。但如果数据量几千甚至上万,再加上动态显示行,有几个坑必须提前规避:
- 不要频繁更新 ViewModel 里的行属性。每改一个
IsHidden就会触发该行的PropertyChanged,如果一次更新几百个属性,UI 会反复 layout,很卡。 - 如果筛选条件变化是整体性的,优先用 CollectionViewSource.Filter,一次 Refresh 高效完成。
- 尽量开启行虚拟化,不要在 RowStyle 里用复杂的 Template 或者大量 Trigger。DataGrid 的默认虚拟化在“仅控制 Visibility”时表现还可以,但如果模板复杂度高,性能下降明显。
- 避免在 LoadingRow 里做聚合计算。比如上面提到的行号刷新,每次滚动加载新行都会触发,如果在里面遍历整个 ItemsSource,会非常影响滚动流畅度。
我的经验值是:500 行以内随便玩,500 到 2000 行建议用 CollectionView + LoadingRow 简单处理,2000 行以上还是好好设计ViewModel 的筛选逻辑,必要时用分页。
结语:一些个人体会
这个需求看起来简单,但真正做下来,你会发现它牵扯到 WPF 的样式优先级、DataGrid 的虚拟化机制、数据绑定刷新时机,甚至在 MVVM 架构下还有职责划分的问题。我个人的建议是:优先通过数据源层面解决“哪些行可见”,界面样式只负责“怎么呈现”,不要把业务判断散落在 UI 代码里。
如果你是刚开始用 WPF 做表格相关功能,建议先在简单项目里把 RowStyle 和 DataTrigger 玩熟,再逐步接触 CollectionView 过滤、虚拟化性能调优这些东西。等把这些都过一遍,你会发现类似“动态显示行”这样的需求根本不算事。希望这篇文章能帮你少走点弯路。