WPF中DataGrid绑定ItemSource后不生成行且偶现重复条目问题
解决WPF FileSystemWatcher绑定DataGrid的两个问题:界面不显示行 & 重复条目
问题回顾
我正在开发一款WPF应用,用FileSystemWatcher监控用户指定目录的变更,把变更信息输出到DataGrid中。在MainWindow构造函数里通过ItemSource把DataGrid绑定到ObservableCollection<string[]>类型的_eventList集合。当OnChanged或OnRenamed事件触发时,会把变更类型、受影响文件、路径、当前时间存入_event数组,然后调用SetThreadSafe()处理跨线程异常。
目前遇到两个异常问题:
- 调试器显示DataGrid的Items属性有正确数据,但界面上不生成行;
SetThreadSafe()方法有时会同时触发IF和ELSE分支,导致_eventList/DataGrid.Items出现重复条目。
更新:感谢ASh的帮助,问题已解决,代码已修正,当前运行正常
问题原因分析与核心修复思路
1. 界面不显示行的根源
你的XAML里所有列的Header都重复设置为"Change",这不是直接原因,但用字符串数组作为绑定项存在WPF绑定通知的潜在问题——数组是值类型集合,UI对其属性变更的感知不如强类型实体类可靠。另外,手动定义列时要确保绑定路径完全匹配数据结构,否则UI无法正确渲染。
2. 重复条目的诱因
类级别的_event字段是共享资源,当FileSystemWatcher事件高频触发时,非UI线程可能在Dispatcher.Invoke未执行完成时就覆盖了_event的值;同时SetThreadSafe()的逻辑是在非UI线程Invoke自身到UI线程,UI线程执行时又会走ELSE分支添加数据,加上字段被覆盖,就会出现重复甚至错误的条目。
修正后的完整代码
C# 后端代码(优化版)
我们改用强类型实体类存储变更信息,彻底避免共享字段的线程覆盖问题,同时简化线程安全逻辑:
using System; using System.Collections.ObjectModel; using System.IO; using System.Windows; namespace EyeInTheSky { /// <summary> /// Interaction logic for MainWindow.xaml + backend code for FileSystemWatcher project /// </summary> public partial class MainWindow : Window { #region Fields private FileSystemWatcher _watcher; private ObservableCollection<FileChangeEvent> _eventList; private bool _on; #endregion #region 强类型事件实体 public class FileChangeEvent { public string ChangeType { get; set; } public string FileName { get; set; } public string Path { get; set; } public string Time { get; set; } } #endregion #region 构造函数 public MainWindow() { InitializeComponent(); _eventList = new ObservableCollection<FileChangeEvent>(); DataGrid_DisplayOutput.ItemsSource = _eventList; // 示例:初始化FileSystemWatcher(根据需求替换路径) _watcher = new FileSystemWatcher(@"C:\Your_Target_Directory"); _watcher.Changed += OnChanged; _watcher.Renamed += OnRenamed; _watcher.EnableRaisingEvents = true; } #endregion #region FileSystemWatcher事件处理 private void OnChanged(object source, FileSystemEventArgs e) { var newEvent = new FileChangeEvent { ChangeType = e.ChangeType.ToString(), FileName = e.Name, Path = e.FullPath, Time = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") }; AddEventToUiCollection(newEvent); } private void OnRenamed(object source, RenamedEventArgs e) { var newEvent = new FileChangeEvent { ChangeType = e.ChangeType.ToString(), FileName = e.Name, Path = e.OldFullPath, Time = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") }; AddEventToUiCollection(newEvent); } private void AddEventToUiCollection(FileChangeEvent eventItem) { if (!Dispatcher.CheckAccess()) { // 非UI线程时,委托到UI线程执行 Dispatcher.Invoke(() => AddEventToUiCollection(eventItem)); } else { // UI线程直接添加,每个事件都是独立对象,不会被覆盖 _eventList.Add(eventItem); } } #endregion } }
WPF XAML(修正版)
修改列绑定路径为实体类属性,并设置清晰的表头:
<DataGrid x:Name="DataGrid_DisplayOutput" Grid.Column="1" HorizontalAlignment="Left" Margin="22,11,0,0" Grid.RowSpan="4" VerticalAlignment="Top" Width="356" Height="392" IsTextSearchEnabled="True" RowBackground="#FFFFFAE8" AlternatingRowBackground="White" HeadersVisibility="Column" AlternationCount="1" SelectionMode="Single" IsReadOnly="True" AutoGenerateColumns="False"> <DataGrid.Columns> <DataGridTextColumn Binding="{Binding ChangeType}" Header="Change Type" Width="89" IsReadOnly="True"/> <DataGridTextColumn Binding="{Binding FileName}" Header="File Name" Width="89" IsReadOnly="True"/> <DataGridTextColumn Binding="{Binding Path}" Header="Path" Width="89" IsReadOnly="True"/> <DataGridTextColumn Binding="{Binding Time}" Header="Time" Width="89" IsReadOnly="True"/> </DataGrid.Columns> </DataGrid>
关键优化点说明
- 强类型实体替代数组:用
FileChangeEvent类存储每个变更的信息,每个事件都是独立对象,彻底解决多线程下共享字段被覆盖的问题,同时让数据绑定更直观可靠。 - 移除类级共享字段:在事件处理方法中直接创建局部事件对象,确保每个事件的数据不会被后续事件篡改。
- 简化线程安全逻辑:
AddEventToUiCollection方法接收具体的事件对象,非UI线程时委托到UI线程执行,避免了原逻辑中重复触发分支的问题。 - 修正XAML绑定:每个列绑定到实体类的对应属性,设置清晰的表头,确保UI能正确识别并渲染数据。
内容的提问来源于stack exchange,提问作者Matthew Jett
相关产品推荐
相关产品推荐

