c# - 使用 TreeView 的主视图/详细 View

标签 c# wpf mvvm

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

现在 TreeView 绑定(bind)到包含所有详细信息的 View 模型对象集合,详细信息 View 绑定(bind)到 TreeView 的选定项。

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

主窗口 View 模型:

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();
    }

    // ...
}

项目 View 模型:

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

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

    // ...
}

下面是我如何绑定(bind)详细信息 View :

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

我对 WPF 和 MVVM 模式相当陌生,但我想将 TreeView 绑定(bind)到一个较小的简化对象集合,该集合仅具有显示项目所需的属性(如 Name 和ID),然后在选择它后加载所有详细信息。我将如何着手做这样的事情?

最佳答案

概览

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

如果您使用的是 vanilla WPF(我假设您是),那么我建议您使用附加行为。附加行为将绑定(bind)到主视图模型上的一个操作,当 TreeView 的选择发生变化时将调用该操作。您也可以调用命令而不是操作,但我将向您展示如何使用操作。

基本上,总体思路是使用详细 View 模型的一个实例,该实例将作为主视图模型的一个属性提供。然后,您的 RootItems 集合不再有数百个 View 模型实例,您可以使用轻量级对象,这些对象只具有节点的显示名称,并且可能在它们后面有某种 id 字段。当 TreeView 上的选择发生变化时,您希望通过调用方法或设置属性来通知详细信息 View 模型。在下面的演示代码中,我在 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 连接到它的 View 模型,如下所示:

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

这是使用直接 WPF 的 MVVM 友好解决方案的基本架构。现在,话虽如此,如果您使用的是 Caliburn.Micro 或 PRISM 之类的框架,您的方法可能与我在此处提供的方法不同。请记住这一点。

关于c# - 使用 TreeView 的主视图/详细 View ,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15206671/

相关文章:

c# - 做编译绑定(bind)(x :Bind) require the ViewModel to derive from DependencyObject?

c# - System.Drawing.Bitmap(int32,int32) 参数在 WPF 中无效但在 win 窗体中无效

c# - 如何将更改从模型获取到模型 View 中?

c# - 使用 MVVM 管理可取消的部分更新

c# - 调度线程饱和 - 寻找普通 MVVM 的设计模式变更

c# - 如何根据条件等待任务?

c# - MVVM-WPF : Dynamic view and memory leak?

c# - 为什么序列化DataContract时不能使用lambda?

c# - 基于int值的动态渐变背景

c# - DataGridTemplateColumns、AutoGenerateColumns=true 并绑定(bind)到 DataTable