2013-03-04 113 views
4

我正在使用TreeView和自定义详细信息视图控件在我的应用程序中实现主/细节视图。我也试图坚持MVVM模式。使用TreeView的主/详细视图

现在,TreeView被绑定到包含所有细节的视图模型对象集合,并且细节视图绑定到TreeView的选定项目。

这很好...直到其中一个TreeView节点有5,000个孩子,并且应用程序突然占用了500MB的RAM。

主窗口视图模型:

public class MainWindowViewModel 
{ 
    private readonly List<ItemViewModel> rootItems; 

    public List<ItemViewModel> RootItems { get { return rootItems; } } // TreeView is bound to this property. 

    public MainWindowViewModel() 
    { 
     rootItems = GetRootItems(); 
    } 

    // ... 
} 

项目视图模型:

public ItemViewModel 
{ 
    private readonly ModelItem item; // Has a TON of properties 
    private readonly List<ItemViewModel> children; 

    public List<ItemViewModel> Children { get { return children; } } 

    // ... 
} 

这里的细节如何,我结合查看:

<View:ItemDetails DataContext="{Binding SelectedItem, ElementName=ItemTreeView}" /> 

我是相当新的WPF和MVVM模式,但是我想将TreeView绑定到一个更小,更简单的对象的集合看起来很浪费,具有显示该项目所需的属性(如名称和ID),然后一旦选择了它,所有的细节都会被加载。我会如何去做这样的事情?

回答

2

概述

应该是TreeView的选定项属性绑定到你的源上的东西一件简单的事情。但是,由于TreeView控件的构建方式,您必须编写更多代码才能使用开箱即用的WPF来获得适用于MVVM的解决方案。

如果你使用的是香草WPF(我假设你是),那么我建议去附加行为。附加的行为将绑定到主视图模型上的一个操作,当TreeView的选择更改时,该操作将被调用。你也可以调用一个命令而不是一个动作,但是我会告诉你如何使用一个动作。

基本上,总体思路是使用您的详细信息视图模型的一个实例,该实例将作为您的主视图模型的属性提供。然后,您可以使用仅包含节点显示名称的轻量级对象,而不是使用具有数百个视图模型实例的RootItems集合,而不是使用其后面的某种id字段。当您的TreeView上的选择发生变化时,您希望通过调用方法或设置属性来通知您的详细信息视图模型。在下面的演示代码中,我在DetailsViewModel上设置了一个名为Selection的属性。

演练与代码

下面是附加的行为的代码:

public static class TreeViewBehavior 
{ 
    public static readonly DependencyProperty SelectionChangedActionProperty = 
     DependencyProperty.RegisterAttached("SelectionChangedAction", typeof (Action<object>), typeof (TreeViewBehavior), new PropertyMetadata(default(Action), OnSelectionChangedActionChanged)); 

    private static void OnSelectionChangedActionChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) 
    { 
     var treeView = sender as TreeView; 
     if (treeView == null) return; 

     var action = GetSelectionChangedAction(treeView); 

     if (action != null) 
     { 
      // Remove the next line if you don't want to invoke immediately. 
      InvokeSelectionChangedAction(treeView); 
      treeView.SelectedItemChanged += TreeViewOnSelectedItemChanged; 
     } 
     else 
     { 
      treeView.SelectedItemChanged -= TreeViewOnSelectedItemChanged; 
     } 
    } 

    private static void TreeViewOnSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e) 
    { 
     var treeView = sender as TreeView; 
     if (treeView == null) return; 

     InvokeSelectionChangedAction(treeView); 

    } 

    private static void InvokeSelectionChangedAction(TreeView treeView) 
    { 
     var action = GetSelectionChangedAction(treeView); 
     if (action == null) return; 

     var selectedItem = treeView.GetValue(TreeView.SelectedItemProperty); 

     action(selectedItem); 
    } 

    public static void SetSelectionChangedAction(TreeView treeView, Action<object> value) 
    { 
     treeView.SetValue(SelectionChangedActionProperty, value); 
    } 

    public static Action<object> GetSelectionChangedAction(TreeView treeView) 
    { 
     return (Action<object>) treeView.GetValue(SelectionChangedActionProperty); 
    } 
} 

然后,在你的TreeView元素的XAML,应用以下:local:TreeViewBehavior.SelectionChangedAction="{Binding Path=SelectionChangedAction}"。请注意,您将不得不用local替换TreeViewBehavior类的名称空间。

现在,添加以下属性到您的MainWindowViewModel:

public Action<object> SelectionChangedAction { get; private set; } 
public DetailsViewModel DetailsViewModel { get; private set; } 

在你MainWindowViewModel的构造函数,你需要设置SelectionChangedAction属性的东西。如果您的DetailsViewModel上有一个Selection属性,则您可以执行SelectionChangedAction = item => DetailsViewModel.Selection = item;。这完全取决于你。

最后,在你的XAML,线材细节查看最多的视图模型,像这样:

<View:ItemDetails DataContext="{Binding Path=DetailsViewModel}" /> 

这是采用了直板WPF的MVVM友好的解决方案的基本架构。现在,据说,如果您使用的是像Caliburn.Micro或PRISM这样的框架,您的方法可能与我在此提供的方法不同。要时刻铭记在心。