我正在使用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),然后一旦被选中,就加载了所有细节。我该如何做这样的事情?
答案 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这样的框架,您的方法可能与我在此提供的方法不同。请记住这一点。