我有一个从数据库中检索数据并在主窗口的数据网格中显示的应用程序。正在显示的最大项目数为~5000。
我不介意在显示结果时有时间延迟,但是我希望在这一过程中显示一个加载动画。然而,即使使用后台工作器更新集合视图源代码,UI也会在显示行之前冻结。
有没有可能添加所有这些行而不冻结UI?应用过滤器到集合视图源代码似乎也冻结了UI,如果可能的话,我也想避免。
先谢了!
数据网格的XAML:
<DataGrid Grid.Column="1" Name="documentDisplay" ItemsSource="{Binding Source={StaticResource cvsDocuments}, UpdateSourceTrigger=PropertyChanged, IsAsync=True}" AutoGenerateColumns="False"
Style="{StaticResource DataGridDefault}" ScrollViewer.CanContentScroll="True"
HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch" ColumnWidth="*">
集合视图源的XAML:
<Window.Resources>
<local:Documents x:Key="documents" />
<CollectionViewSource x:Key="cvsDocuments" Source="{StaticResource documents}"
Filter="DocumentFilter">
从数据库检索数据后调用的函数中的代码:
Documents _documents = (Documents)this.Resources["documents"];
BindingOperations.EnableCollectionSynchronization(_documents, _itemsLock);
if (!populateDocumentWorker.IsBusy)
{
progressBar.Visibility = Visibility.Visible;
populateDocumentWorker.RunWorkerAsync(jobId);
}
工人内部代码:
Documents _documents = (Documents)this.Resources["documents"];
lock (_itemsLock)
{
_documents.Clear();
_documents.AddRange(documentResult.documents);
}
可观察到的采集类别:
public class Documents : ObservableCollection<Document>, INotifyPropertyChanged
{
private bool _surpressNotification = false;
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (!_surpressNotification)
{
base.OnCollectionChanged(e);
}
}
public void AddRange(IEnumerable<Document> list)
{
if(list == null)
{
throw new ArgumentNullException("list");
_surpressNotification = true;
}
foreach(Document[] batch in list.Chunk(25))
{
foreach (Document item in batch)
{
Add(item);
}
_surpressNotification = false;
}
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
}
可观察集合的基类:
public class Document : INotifyPropertyChanged, IEditableObject
{
public int Id { get; set; }
public string Number { get; set; }
public string Title { get; set; }
public string Revision { get; set; }
public string Discipline { get; set; }
public string Type { get; set; }
public string Status { get; set; }
public DateTime Date { get; set; }
public string IssueDescription { get; set; }
public string Path { get; set; }
public string Extension { get; set; }
// Implement INotifyPropertyChanged interface.
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(String info)
{
if(PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
private void NotifyPropertyChanged(string propertyName)
{
}
// Implement IEditableObject interface.
public void BeginEdit()
{
}
public void CancelEdit()
{
}
public void EndEdit()
{
}
}
过滤器功能:
private void DocumentFilter(object sender, FilterEventArgs e)
{
//Create list of all selected disciplines
List<string> selectedDisciplines = new List<string>();
foreach(var item in disciplineFilters.SelectedItems)
{
selectedDisciplines.Add(item.ToString());
}
//Create list of all select document types
List<string> selectedDocumentTypes = new List<string>();
foreach(var item in docTypeFilters.SelectedItems)
{
selectedDocumentTypes.Add(item.ToString());
}
// Create list of all selected file tpyes
List<string> selectedFileTypes = new List<string>();
foreach(var item in fileTypeFilters.SelectedItems)
{
selectedFileTypes.Add(item.ToString());
}
//Cast event item as document object
Document doc = e.Item as Document;
//Apply filter to select discplines and document types
if( doc != null)
{
if (selectedDisciplines.Contains(doc.Discipline) && selectedDocumentTypes.Contains(doc.Type) && selectedFileTypes.Contains(doc.Extension))
{
e.Accepted = true;
} else
{
e.Accepted = false;
}
}
}
2条答案
按热度按时间tzxcd3kk1#
你的设计有几个问题。
collectionview的filter的工作方式是逐个迭代集合并返回true/false。
你的过滤器很复杂,每个项目都需要一段时间。
它运行了5000次。
不应使用该方法进行筛选。
重新思考和相当实质性的重构是可取的。
在作为后台线程运行的任务中执行所有处理和过滤。
忘记所有同步上下文的东西。
完成处理后,向UI线程返回最终数据的List
如果没有编辑或者排序,那么设置你的itemssource绑定的List属性,如果有,那么新建一个可观的集合
将列表作为构造函数参数传递,并设置一个与itemssource绑定的observablecollection属性。
因此,您在后台线程上执行所有昂贵的处理。
作为集合返回给UI线程的。
您可以通过绑定将itemssource设置为该值。
自定义Observablecollection不是个好主意。你应该只使用List或Observablecollection,其中t是一个视图模型。任何视图模型都应该实现intifypropertychanged。总是。
两个警告。
最小化呈现给UI的行数。
如果超过几百个,则考虑分页,也许还可以考虑中间缓存。
把这个从你的包里拿出来
在你知道它的作用之前不要再用它。
一些通用数据网格建议:
避免列虚拟化。
最小化绑定的列数。
如果可以,请使用固定的列宽。
考虑一下更简单的列表视图而不是数据网格。
omqzjyyz2#
问题出在
Filter
回调函数上,目前在事件处理程序中迭代了三个列表(为了创建用于查找的过滤 predicate 集合)。由于事件处理程序是针对筛选集合中的 * 每个项 * 调用的,因此这会为每个 * 筛选项 * 带来过多的工作量。
例如,如果三次迭代中的每次迭代涉及50个项,而筛选的集合包含5,000个项,则总共执行50 * 3 * 5000 = 750,000次迭代(每次事件处理程序调用150次)。
我建议在
Filter
事件处理程序之外维护选定项的集合,这样就不必为每个单独的项创建集合(事件处理程序调用)。这三个集合仅在相关的SelectedItems
属性更改时更新。为了进一步加快
Filter
事件处理程序中的查找速度,我还建议将List<T>
替换为HashSet<T>
。List.Contains
是一个 * O(n)* 操作,而HashSet.Contains
是 * O(1)* 操作,这会产生巨大的差异。您需要单独跟踪作为这些集合的源的
SelectedItems
,以更新它们。下面的示例应该可以显著加快过滤速度。
备注
您应该修复您的
Documents.AddRange
方法。它应该使用
NotifyCollectionChangedAction.Add
。NotifyCollectionChangedAction.Replace
将触发绑定目标完全更新自己,这是您希望避免的。使用适当的
NotifyCollectionChangedEventArgs
构造函数重载发送事件中添加项的完整范围:进一步考虑
ICollectionView
的过滤器功能将始终阻塞UI,直到集合被过滤。这是因为该过程不是异步的。这意味着您无法显示进度条,因为它不会实时更新。请考虑在从数据库获取项目时进行预过滤。当用户只能查看其中的10 - 50个项目时,加载5k个项目是没有意义的。ObservableCollection
,它公开了一个AddRange
方法,所以你可以开始了(不要忘记修复CollectionChanged
事件数据)。下面的示例扩展了上面的基本示例。
在使用数据库中的数据填充
UnfilteredDocuments
属性时,需要将DataGrid绑定到FilteredItemsSource
属性: