【发布时间】:2018-10-02 18:38:23
【问题描述】:
我正在编写一个WPF 应用程序(MVVM 模式使用MVVM Light Toolkit)来读取和显示我公司使用的一堆内部日志文件。目标是从多个文件中读取,从每一行中提取内容,将它们放在一个类对象中并将所述对象添加到ObservableCollection。我已将GUI 上的DataGrid 的ItemsSource 设置到此列表中,以便它以整齐的行和列显示数据。我在第二个窗口中有一个ProgressBar 控件,在文件读取和显示过程中会更新进度。
设置
请注意,所有这些方法都被简化为删除所有不相关代码位的基本要素。
加载按钮
当用户选择包含日志文件的目录并单击此按钮时,该过程开始。此时我打开包含ProgressBar 的窗口。我在这个过程中使用了BackgroundWorker。
public void LoadButtonClicked()
{
_dialogService = new DialogService();
BackgroundWorker worker = new BackgroundWorker
{
WorkerReportsProgress = true
};
worker.DoWork += ProcessFiles;
worker.ProgressChanged += Worker_ProgressChanged;
worker.RunWorkerAsync();
}
ProcessFiles() 方法
这会读取所选目录中的所有文件,并一一处理。这里,在启动进度条窗口时,我使用的是Dispatcher.Invoke()。
private void ProcessFiles(object sender, DoWorkEventArgs e)
{
LogLineList = new ObservableCollection<LogLine>();
System.Windows.Application.Current.Dispatcher.Invoke(() =>
{
_dialogService.ShowProgressBarDialog();
});
var fileCount = 0;
foreach (string file in FileList)
{
fileCount++;
int currProgress = Convert.ToInt32(fileCount / (double)FileList.Length * 100);
ProcessOneFile(file);
(sender as BackgroundWorker).ReportProgress(currProgress);
}
}
ProcessOneFile() 方法
顾名思义,它读取一个文件,逐行遍历,将内容转换为我的类对象并将它们添加到列表中。
public void ProcessOneFile(string fileName)
{
if (FileIO.OpenAndReadAllLinesInFile(fileName, out List<string> strLineList))
{
foreach (string line in strLineList)
{
if (CreateLogLine(line, out LogLine logLine))
{
if (logLine.IsRobotLog)
{
LogLineList.Add(logLine);
}
}
}
}
}
所以这工作得很好,并根据我的需要显示我的日志。
问题
但是,在显示它们之后,如果我滚动我的DataGrid,GUI 会挂起并给我以下异常。
System.InvalidOperationException: '一个 ItemsControl 不一致 及其物品来源。有关更多信息,请参阅内部异常 信息。'
在 SO 上阅读此内容并在 Google 的帮助下,我发现这是因为我的 LogLineList 与 ItemsSource 不一致,从而导致冲突。
当前解决方案
我发现如果我将代码行放在ProcessOneFile 中,我在第二个Dispatcher.Invoke() 内将一个类对象添加到我的列表中,它可以解决我的问题。像这样:
if (logLine.IsRobotLog)
{
System.Windows.Application.Current.Dispatcher.Invoke(() =>
{
LogLineList.Add(logLine);
});
}
现在这又可以正常工作了,但问题是这会大大减慢处理时间。以前一个包含 10,000 行的日志文件大约需要 1 秒,而现在可能需要 5-10 倍的时间。
我做错了什么,还是可以预料到的?有没有更好的方法来处理这个问题?
【问题讨论】:
-
如果你可以批量更新而不是实时更新,那么创建一个单独的集合(非 obs)并添加日志,每 5 秒 - 将项目源更改为从创建的新 obs 集合非 obs 集合
-
LoadButtonClick方法位于View代码后面?
-
@shat90 我试试看是否可行。
-
@SirRufo,不,我使用的是
MVVM,所以它位于主窗口的ViewModel中。该方法附加到按钮的单击事件。 -
@Sach 那么,您应该在 ViewModel 中定义一个 ICommand 属性并将其绑定到按钮的 Command 属性。方法分配不是正确的 MVVM 绑定方式。仅供参考
标签: c# wpf dispatcher itemscontrol