2013-03-04 137 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這樣的框架,您的方法可能與我在此提供的方法不同。要時刻銘記在心。