3

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

现在 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),这似乎是一种浪费,然后一旦选择它,就会加载所有详细信息。我将如何去做这样的事情?

4

1 回答 1

2

概述

应该是一个简单的问题,将 TreeView 的 selected item 属性绑定到您的源上的某些东西。但是,由于 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 属性设置为某个值。SelectionChangedAction = item => DetailsViewModel.Selection = item;如果您的 DetailsViewModel 上有一个 Selection 属性,您可能会这样做。这完全取决于你。

最后,在您的 XAML 中,将详细信息视图连接到其视图模型,如下所示:

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

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

于 2013-03-04T20:44:39.903 回答