使用TreeView的主/详细视图

时间:2013-03-04 16:50:59

标签: c# wpf mvvm

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

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

这很好用......直到其中一个TreeView节点有5000个子节点,并且应用程序突然占用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绑定到一个较小的简化对象的集合似乎是浪费,该对象只具有显示项目所需的属性(如Name和ID),然后一旦被选中,就加载了所有细节。我该如何做这样的事情?

1 个答案:

答案 0 :(得分:2)

<强>概述

这个 应该是将TreeView的选定项属性绑定到源上的内容的简单问题。但是,由于构建TreeView控件的方式,您必须使用开箱即用的WPF编写更多代码以获得MVVM友好的解决方案。

如果您正在使用vanilla 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这样的框架,您的方法可能与我在此提供的方法不同。请记住这一点。