如何在延迟加载 WPF MVVM TreeView 中搜索?
How to search in a lazy-loading WPF MVVM TreeView?
我需要一个 TreeView
来表示存储在 SQL Server CE 数据库中的多个表中的一些分层数据。之前,数据存储在 xml 中,并在启动时进行了简单的反序列化,一切都很好。现在我被要求将数据移动到数据库,我遇到了几个问题。
我的第一个问题是从数据库中检索许多项目并从这些项目构建 TreeView ViewModel 需要很长时间(仍然不确定什么时间更长 - 获取项目或构建此树)。所以我实现了延迟加载,现在我只在 TreeViewItem
扩展时才获取项目。
现在,我需要对所有节点执行文本搜索,但要使其正常工作,必须加载所有节点。
我试图加载所有这些,但 UI 在加载树时冻结。在 BackgroundWorker
中执行此操作对我来说也是不可能的,因为这些项目存储在 ObservableCollection
中,而我得到 "InvalidOperationException"。使用 Dispatcher
有助于解决这个问题,但它也会冻结 UI...
下面是我的 TreeViewItem
VM 的摘录,如果需要更多代码,请告诉我。也许我的设计完全错误,所以非常感谢任何评论!
public class TreeViewItemViewModel: DisplayableItem, IItemsHost
{
internal static DummyTreeViewItemViewModel _dummy = new DummyTreeViewItemViewModel();
public TreeViewItemViewModel(){}
public TreeViewItemViewModel(IDisplayableItem displayableItem)
{
Data = displayableItem;
}
public TreeViewItemViewModel(IDisplayableItem displayableItem, IDisplayableItem parent)
:this(displayableItem)
{
Parent = parent as TreeViewItemViewModel;
}
private TreeViewItemViewModel _parent;
public TreeViewItemViewModel Parent
{
get { return _parent; }
set { _parent = value; InvokePropertyChanged(new PropertyChangedEventArgs("Parent")); }
}
private IDisplayableItem _data;
public new IDisplayableItem Data
{
get { return _data; }
set { _data = value; InvokePropertyChanged(new PropertyChangedEventArgs("Data")); }
}
private bool _isSelected;
public new bool IsSelected
{
get { return _isSelected; }
set { _isSelected = value; InvokePropertyChanged(new PropertyChangedEventArgs("IsSelected")); }
}
private bool _isEnabled=true;
public new bool IsEnabled
{
get { return _isEnabled; }
set { _isEnabled = value; InvokePropertyChanged(new PropertyChangedEventArgs("IsEnabled")); }
}
private bool _isVisible = true;
public new bool IsVisible
{
get { return _isVisible; }
set { _isVisible = value; InvokePropertyChanged(new PropertyChangedEventArgs("IsVisible")); }
}
private void FillItems()
{
if (Items.Contains(_dummy))
{
Items.Remove(_dummy);
var itemshost = Data as IItemsHost;
if (itemshost != null)
{
_items = new ObservableCollection<IDisplayableItem>();
foreach (var item in itemshost.Items)//getting 'Items' actually requesting them from a database
{
var treeItem = new TreeViewItemViewModel(item, this);
_items.Add(treeItem);
}
InvokePropertyChanged(new PropertyChangedEventArgs("Items"));
}
}
}
protected bool _isExpanded;
public bool IsExpanded
{
get { return _isExpanded; }
set
{
if(value)
{
FillItems();
}
_isExpanded = value;
InvokePropertyChanged(new PropertyChangedEventArgs("IsExpanded"));
}
}
protected SObservableCollection<IDisplayableItem> _items = new SObservableCollection<IDisplayableItem>();
public SObservableCollection<IDisplayableItem> Items
{
get
{
var itemshost = Data as IItemsHost;
if (itemshost != null)
{
if (_items.Count == 0 && itemshost.Items.Count > 0)
_items.Add(_dummy);
}
return _items;
}
set { _items = value; InvokePropertyChanged(new PropertyChangedEventArgs("Items")); }
}
更新: 对于那些会搜索类似解决方案的人 - 我的问题出在我的查询方法中。我不应该在每次需要执行查询时打开一个新的 SQL Server CE 连接...
由于从数据库读取是异步完成的,因此性能瓶颈应该是从 ViewModel 构造 View。我建议采用以下方法:
- 在一次异步调用中从数据库中读取所有基本模型数据,并将它们存储在一个名为
SearchHelper
的对象中。
- 向您创建的每个 ViewModel 添加一个简单的 属性(Model.Id 或模型的哈希码),以便找到特定模型的等效视图模型。
- 只创建 个可见的 ViewModel。 (仅延迟加载 ViewModel)
- 使用
SearchHelper
查找搜索查询的匹配项,然后使用结果的 ID 或哈希码,您可以轻松找到它们的等效视图模型。
请考虑:
- 加载后,
SearchHelper
不会自行更新,因此您可能需要手动更新它。
- 为使此方法具有最佳性能,请尽量避免所有节点的迭代。相反,存储跟踪项的序列(它们的索引或 Id)以便在视图模型中找到它们。如果每个模型项都知道它的父项,那么回溯应该很容易。
新数据库 table 包含整个层次结构的扁平表示,并让您的搜索逻辑查询此 table 怎么样?当您在其他 table 中记录 insert/update/delete 时,您显然需要保持此 table 更新。
新 table 中的每条记录都需要包含一些有关该项目在层次结构中所处位置的信息,以便当您返回搜索结果时,您可以仅加载和填充那些包含"hits".
我需要一个 TreeView
来表示存储在 SQL Server CE 数据库中的多个表中的一些分层数据。之前,数据存储在 xml 中,并在启动时进行了简单的反序列化,一切都很好。现在我被要求将数据移动到数据库,我遇到了几个问题。
我的第一个问题是从数据库中检索许多项目并从这些项目构建 TreeView ViewModel 需要很长时间(仍然不确定什么时间更长 - 获取项目或构建此树)。所以我实现了延迟加载,现在我只在 TreeViewItem
扩展时才获取项目。
现在,我需要对所有节点执行文本搜索,但要使其正常工作,必须加载所有节点。
我试图加载所有这些,但 UI 在加载树时冻结。在 BackgroundWorker
中执行此操作对我来说也是不可能的,因为这些项目存储在 ObservableCollection
中,而我得到 "InvalidOperationException"。使用 Dispatcher
有助于解决这个问题,但它也会冻结 UI...
下面是我的 TreeViewItem
VM 的摘录,如果需要更多代码,请告诉我。也许我的设计完全错误,所以非常感谢任何评论!
public class TreeViewItemViewModel: DisplayableItem, IItemsHost
{
internal static DummyTreeViewItemViewModel _dummy = new DummyTreeViewItemViewModel();
public TreeViewItemViewModel(){}
public TreeViewItemViewModel(IDisplayableItem displayableItem)
{
Data = displayableItem;
}
public TreeViewItemViewModel(IDisplayableItem displayableItem, IDisplayableItem parent)
:this(displayableItem)
{
Parent = parent as TreeViewItemViewModel;
}
private TreeViewItemViewModel _parent;
public TreeViewItemViewModel Parent
{
get { return _parent; }
set { _parent = value; InvokePropertyChanged(new PropertyChangedEventArgs("Parent")); }
}
private IDisplayableItem _data;
public new IDisplayableItem Data
{
get { return _data; }
set { _data = value; InvokePropertyChanged(new PropertyChangedEventArgs("Data")); }
}
private bool _isSelected;
public new bool IsSelected
{
get { return _isSelected; }
set { _isSelected = value; InvokePropertyChanged(new PropertyChangedEventArgs("IsSelected")); }
}
private bool _isEnabled=true;
public new bool IsEnabled
{
get { return _isEnabled; }
set { _isEnabled = value; InvokePropertyChanged(new PropertyChangedEventArgs("IsEnabled")); }
}
private bool _isVisible = true;
public new bool IsVisible
{
get { return _isVisible; }
set { _isVisible = value; InvokePropertyChanged(new PropertyChangedEventArgs("IsVisible")); }
}
private void FillItems()
{
if (Items.Contains(_dummy))
{
Items.Remove(_dummy);
var itemshost = Data as IItemsHost;
if (itemshost != null)
{
_items = new ObservableCollection<IDisplayableItem>();
foreach (var item in itemshost.Items)//getting 'Items' actually requesting them from a database
{
var treeItem = new TreeViewItemViewModel(item, this);
_items.Add(treeItem);
}
InvokePropertyChanged(new PropertyChangedEventArgs("Items"));
}
}
}
protected bool _isExpanded;
public bool IsExpanded
{
get { return _isExpanded; }
set
{
if(value)
{
FillItems();
}
_isExpanded = value;
InvokePropertyChanged(new PropertyChangedEventArgs("IsExpanded"));
}
}
protected SObservableCollection<IDisplayableItem> _items = new SObservableCollection<IDisplayableItem>();
public SObservableCollection<IDisplayableItem> Items
{
get
{
var itemshost = Data as IItemsHost;
if (itemshost != null)
{
if (_items.Count == 0 && itemshost.Items.Count > 0)
_items.Add(_dummy);
}
return _items;
}
set { _items = value; InvokePropertyChanged(new PropertyChangedEventArgs("Items")); }
}
更新: 对于那些会搜索类似解决方案的人 - 我的问题出在我的查询方法中。我不应该在每次需要执行查询时打开一个新的 SQL Server CE 连接...
由于从数据库读取是异步完成的,因此性能瓶颈应该是从 ViewModel 构造 View。我建议采用以下方法:
- 在一次异步调用中从数据库中读取所有基本模型数据,并将它们存储在一个名为
SearchHelper
的对象中。 - 向您创建的每个 ViewModel 添加一个简单的 属性(Model.Id 或模型的哈希码),以便找到特定模型的等效视图模型。
- 只创建 个可见的 ViewModel。 (仅延迟加载 ViewModel)
- 使用
SearchHelper
查找搜索查询的匹配项,然后使用结果的 ID 或哈希码,您可以轻松找到它们的等效视图模型。
请考虑:
- 加载后,
SearchHelper
不会自行更新,因此您可能需要手动更新它。 - 为使此方法具有最佳性能,请尽量避免所有节点的迭代。相反,存储跟踪项的序列(它们的索引或 Id)以便在视图模型中找到它们。如果每个模型项都知道它的父项,那么回溯应该很容易。
新数据库 table 包含整个层次结构的扁平表示,并让您的搜索逻辑查询此 table 怎么样?当您在其他 table 中记录 insert/update/delete 时,您显然需要保持此 table 更新。
新 table 中的每条记录都需要包含一些有关该项目在层次结构中所处位置的信息,以便当您返回搜索结果时,您可以仅加载和填充那些包含"hits".