本文记录一个 WPF 已知问题,在 ObservableCollection 的 CollectionChanged 事件里面,绕过 ObservableCollection 的异常判断逻辑,强行修改集合内容,修改之后的 UI 层将不能符合预期。本文将告诉大家此问题的复现方法和修复方法
在 UI 绑定的 ObservableCollection 修改时,给此集合列表添加新的项目,此时 UI 绑定的数据是对的但是界面显示错误。简单的复现方法如下
先在后台代码定义好绑定模型 Model 类,代码如下
接着在 MainWindow 里添加一个 ObservableCollection<Model>
属性用于让 XAML 绑定,这里不加入一个 ViewModel 只是为了让代码简单
在 XAML 里代码如下
在 MainWindow 构造函数给 List 加上测试内容
以上的代码的 List.CollectionChanged += List_CollectionChanged;
是为了在集合变更时加入一项用来修改集合。监听 Loaded 用来模拟删除 ObservableCollection 的内容,用来触发 CollectionChanged 事件
先不要实现 List_CollectionChanged
和 MainWindow_Loaded
方法的内容,先看看此时界面显示,修复构建运行代码可以看到如下图
在 Loaded 事件里面,将 List 的第 1 项删除,代码如下
删除之后将会进入 List_CollectionChanged
方法,在 List_CollectionChanged
方法里面,如果直接修改 List 的内容,如以下代码,将会抛出 InvalidOperationException 异常
抛出的异常如下
一个绕过的方法是在进入 List_CollectionChanged
减等事件,但是绕过是存在坑的,原本预期的列表顺序应该是 0
2
xx
的顺序,然而实际的界面显示如下
以上就是最简单的方法让大家了解到问题
修复的方法有两个:
第一个方法是推荐的,使用 Dispatcher.InvokeAsync
延迟执行,修改 List_CollectionChanged
代码如下
以上的 _changed
字段只是让代码不会多次进入而已,因为添加元素也会触发集合变更事件,如果在集合变更事件里面再次添加元素,那就无限进入集合变更
可以看到界面显示符合预期
第二个方法是强行刷 ItemsSource 内容,强行刷不能在 List_CollectionChanged
立即调用,否则将会抛出 InvalidOperationException 异常
抛出的异常代码如下
通过以上的异常信息也可以了解到为什么 WPF 存在此已知问题,因为原本预期就是开发者不能在集合变更时修改集合,如果在每个集合变更里都需要重新处理状态,将会让 WPF 的性能很差。因此这个问题也是不会在 WPF 里面修复的,只能开发者自己修复
强行刷只能放在其他的时机,例如在界面添加一个按钮,点击按钮强行刷
运行程序,可以看到开始界面显示错误,在点击按钮之后,界面就符合预期
本文的代码放在github 和 gitee 欢迎访问
可以通过如下方式获取本文的源代码,先创建一个空文件夹,接着使用命令行 cd 命令进入此空文件夹,在命令行里面输入以下代码,即可获取到本文的代码
以上使用的是 gitee 的源,如果 gitee 不能访问,请替换为 github 的源。请在命令行继续输入以下代码
获取代码之后,进入 BekuhalnoKawairlunee 文件夹
更多博客,请参阅我的 博客导航
原文链接: http://blog.lindexi.com/post/WPF-%E5%B7%B2%E7%9F%A5%E9%97%AE%E9%A2%98-%E5%9C%A8-ObservableCollection-%E7%9A%84-CollectionChanged-%E4%BF%AE%E6%94%B9%E9%9B%86%E5%90%88%E5%86%85%E5%AE%B9%E5%B0%86%E8%AE%A9-UI-%E6%98%BE%E7%A4%BA%E9%94%99%E8%AF%AF
本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。
欢迎转载、使用、重新发布,但务必保留文章署名 林德熙 (包含链接: https://blog.lindexi.com ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请与我 联系。