【问题标题】:How to search in a lazy-loading WPF MVVM TreeView?如何在延迟加载的 WPF MVVM TreeView 中搜索?
【发布时间】:2015-02-11 12:57:45
【问题描述】:

我需要一个TreeView 来表示存储在 SQL Server CE 数据库中的多个表中的一些分层数据。以前,数据存储在 xml 中,并在启动时进行简单的反序列化,一切都很好。现在我被要求将数据移动到数据库中,我遇到了几个问题。

我的第一个问题是从数据库中检索许多项目并从这些项目构建 TreeView ViewModel 需要很长时间(仍然不确定什么更长 - 获取项目或构建这棵树)。所以我实现了延迟加载,现在我只有在 TreeViewItem 扩展时才能获取项目。

现在,我需要对所有节点执行文本搜索,但要使其工作,必须加载所有节点。 我试图加载所有这些,但 UI 在树加载时冻结。在BackgroundWorker 中执行此操作对我来说也是不可能的,因为这些项目存储在ObservableCollection 中并且我得到“InvalidOperationException”。使用 Dispatcher 有助于解决此问题,但它也会冻结 UI...

我的TreeViewItem VM 的摘录如下,如果需要更多代码,请告诉我。也许我的设计完全错了,所以非常感谢任何 cmet!

  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 连接...

【问题讨论】:

    标签: wpf mvvm treeview sql-server-ce


    【解决方案1】:

    如果一个新的数据库表包含整个层次结构的扁平表示,并让您的搜索逻辑查询该表,那又如何呢?当您在其他表中插入/更新/删除记录时,您显然需要保持此表的更新。

    新表中的每条记录都需要包含有关该项目在层次结构中的位置的一些信息,这样当您返回搜索结果时,您可以仅加载和填充那些包含“命中”的树节点。

    【讨论】:

      【解决方案2】:

      由于从数据库读取是异步完成的,因此性能瓶颈应该是从 ViewModel 构造 View。我建议以下方法:

      1. 在一次异步调用中从数据库中读取所有重要的模型数据,并将它们存储在一个名为 SearchHelper 的对象中。
      2. 为您创建的每个 ViewModel 添加一个简单属性(Model.Id 或 Model 的哈希码),以便找到特定模型的等效视图模型。
      3. 创建可见的 ViewModel。 (仅适用于 ViewModel 的延迟加载)
      4. 使用 SearchHelper 查找搜索查询的匹配项,然后使用结果的 Id 或哈希码,您可以轻松找到它们的等效视图模型。

      请考虑:

      1. 加载后,SearchHelper 不会自行更新,因此您可能需要手动更新它。
      2. 要使此方法具有最佳性能,请尝试避免所有节点的迭代。相反,存储跟踪项目的序列(它们的索引或 Id)以便在视图模型中找到它们。如果每个模型项都知道其父项,那么回溯应该很容易。

      【讨论】:

      • 我使用System.Diagnostics.Stopwatch 做了一个简单的测试,发现即使不使用视图(在控制台应用程序中),也需要大约 30 秒来遍历所有树项和子项并填充它们全部。这不好,但我不知道该怎么办:(
      猜你喜欢
      • 1970-01-01
      • 2010-10-22
      • 1970-01-01
      • 2011-07-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-02-19
      • 1970-01-01
      相关资源
      最近更新 更多