2009-01-19 85 views
38

內置的WPF TreeView控件不允許多選,就像ListBox一樣。我如何定製TreeView以允許多重選擇而不重寫它。自定義TreeView以允許多選

+1

你可以看看[TreeViewEx](http://treeviewex.codeplex.com/)的例子。 – 2012-02-15 16:26:04

+0

另一個更直接解決您的問題的CodeProject項目是這樣的: [WPF MultiSelect TreeView示例](http://www.codeproject.com/KB/WPF/WPFMultiSelectTreeView.aspx)。 – Govert 2009-09-25 08:20:48

+0

@Govert這篇文章中的代碼寫得很糟糕。我不會推薦給任何人。就好像作者比編碼花費更多的時間來表達他的代碼。 – 2010-03-26 13:51:55

回答

4

當我認爲覆蓋控件的基本行爲,比如樹視圖時,我總是喜歡考慮與我的決定相關的可用性和努力。

在treeview的特定情況下,我發現切換到一個listview與零個,一個或多個控件的組合使得更易於實現的更有用的解決方案。

作爲示例,請考慮常見的「打開」對話框或Windows資源管理器應用程序。

2

我已經簡化了這項任務,在每個treeviewitem的文本前添加一個複選框。

所以,我創建了一個有2個項目的dockpanel:checkbox + textblock。

所以......

XAML

<TreeView x:Name="treeViewProcesso" Margin="1,30.351,1,5" BorderBrush="{x:Null}" MinHeight="250" VerticalContentAlignment="Top" BorderThickness="0" > 
    <TreeViewItem Header="Documents" x:Name="treeView" IsExpanded="True" DisplayMemberPath="DocumentsId" >    
    </TreeViewItem> 
</TreeView> 

CS

TreeViewItem treeViewItem = new TreeViewItem(); 
DockPanel dp = new DockPanel(); 
CheckBox cb = new CheckBox(); 
TextBlock tb = new TextBlock(); 
tb.Text = "Item"; 
dp.Children.Add(cb); 
dp.Children.Add(tb); 
treeViewItem.Header = dp; 
treeViewItem.Selected += new RoutedEventHandler(item_Selected); 
treeView.Items.Add(treeViewItem); 

然後你就可以訪問複選框值:

void item_Selected(object sender, RoutedEventArgs e) 
{ 
    selectedTVI = ((TreeViewItem)sender); 

    CheckBox cb = (Checkbox)((DockPanel)selectedTVI.Header).Children[0]; 
} 

如果你不需要任何複雜的東西,這是一個簡單的方法。

1

我終於編寫了我自己的包含TreeView的CustomControl裏面。基於他人的功能鍵位於上製作樹視圖模型的所有項目繼承接口ISelectable工作:

public interface ISelectable 
{ 
    public bool IsSelected {get; set} 
} 

這種方式,我們將有一個新的「IsSelected」屬性,有什麼可使用TreeViewItem IsSelected進行操作。我們只需要對我們的樹進行樣式設置,以便處理IsSelected屬性的模型。下面的代碼(它使用可在http://code.google.com/p/gong-wpf-dragdrop/拖動&降庫):

XAML

<UserControl x:Class="Picis.Wpf.Framework.ExtendedControls.TreeViewEx.TreeViewEx" 

     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
     xmlns:DragAndDrop="clr-namespace:Picis.Wpf.Framework.DragAndDrop"> 

<TreeView ItemsSource="{Binding ItemsSource, RelativeSource={RelativeSource AncestorType=UserControl}}" 
      ItemTemplate="{Binding ItemTemplate, RelativeSource={RelativeSource AncestorType=UserControl}}" 
      ItemContainerStyle="{Binding ItemContainerStyle, RelativeSource={RelativeSource AncestorType=UserControl}}" 
      DragAndDrop:DragDrop.DropHandler ="{Binding DropHandler, RelativeSource={RelativeSource AncestorType=UserControl}}" 
      PreviewMouseDown="TreeViewOnPreviewMouseDown" 
      PreviewMouseUp="TreeViewOnPreviewMouseUp" 
      x:FieldModifier="private" x:Name="InnerTreeView" > 
    <TreeView.Resources> 
     <Style TargetType="TreeViewItem"> 
      <Style.Resources> 
       <SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" Color="White" /> 
       <SolidColorBrush x:Key="{x:Static SystemColors.HighlightTextBrushKey}" Color="Black" /> 
       <SolidColorBrush x:Key="{x:Static SystemColors.ControlBrushKey}" Color="White" /> 
      </Style.Resources> 
     </Style> 
    </TreeView.Resources> 
</TreeView> 

C#:

using System.Collections.Generic; 
using System.Linq; 
using System.Windows; 
using System.Windows.Controls; 
using System.Windows.Input; 
using System.Windows.Media; 
using System.Windows.Shapes; 
using GongSolutions.Wpf.DragDrop; 
using DragDrop = GongSolutions.Wpf.DragDrop; 

namespace <yournamespace>.TreeViewEx 
{ 
public partial class TreeViewEx : UserControl 
{ 
    #region Attributes 

    private TreeViewItem _lastItemSelected; // Used in shift selections 
    private TreeViewItem _itemToCheck; // Used when clicking on a selected item to check if we want to deselect it or to drag the current selection 
    private bool _isDragEnabled; 
    private bool _isDropEnabled; 

    #endregion 

    #region Dependency Properties 

    public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register("ItemsSource", typeof(IEnumerable<ISelectable>), typeof(TreeViewEx)); 

    public IEnumerable<ISelectable> ItemsSource 
    { 
     get 
     { 
      return (IEnumerable<ISelectable>)this.GetValue(TreeViewEx.ItemsSourceProperty); 
     } 
     set 
     { 
      this.SetValue(TreeViewEx.ItemsSourceProperty, value); 
     } 
    } 

    public static readonly DependencyProperty ItemTemplateProperty = DependencyProperty.Register("ItemTemplate", typeof(DataTemplate), typeof(TreeViewEx)); 

    public DataTemplate ItemTemplate 
    { 
     get 
     { 
      return (DataTemplate)GetValue(TreeViewEx.ItemTemplateProperty); 
     } 
     set 
     { 
      SetValue(TreeViewEx.ItemTemplateProperty, value); 
     } 
    } 

    public static readonly DependencyProperty ItemContainerStyleProperty = DependencyProperty.Register("ItemContainerStyle", typeof(Style), typeof(TreeViewEx)); 

    public Style ItemContainerStyle 
    { 
     get 
     { 
      return (Style)GetValue(TreeViewEx.ItemContainerStyleProperty); 
     } 
     set 
     { 
      SetValue(TreeViewEx.ItemContainerStyleProperty, value); 
     } 
    } 

    public static readonly DependencyProperty DropHandlerProperty = DependencyProperty.Register("DropHandler", typeof(IDropTarget), typeof(TreeViewEx)); 

    public IDropTarget DropHandler 
    { 
     get 
     { 
      return (IDropTarget)GetValue(TreeViewEx.DropHandlerProperty); 
     } 
     set 
     { 
      SetValue(TreeViewEx.DropHandlerProperty, value); 
     } 
    } 

    #endregion 

    #region Properties 

    public bool IsDragEnabled 
    { 
     get 
     { 
      return _isDragEnabled; 
     } 
     set 
     { 
      if (_isDragEnabled != value) 
      { 
       _isDragEnabled = value; 
       DragDrop.SetIsDragSource(this.InnerTreeView, _isDragEnabled); 
      } 
     } 
    } 

    public bool IsDropEnabled 
    { 
     get 
     { 
      return _isDropEnabled; 
     } 
     set 
     { 
      if (_isDropEnabled != value) 
      { 
       _isDropEnabled = value; 
       DragDrop.SetIsDropTarget(this.InnerTreeView, _isDropEnabled); 
      } 
     } 
    } 

    #endregion 

    #region Public Methods 

    public TreeViewEx() 
    { 
     InitializeComponent(); 
    } 

    #endregion 

    #region Event Handlers 

    private void TreeViewOnPreviewMouseDown(object sender, MouseButtonEventArgs e) 
    { 
     if (e.OriginalSource is Shape || e.OriginalSource is Grid || e.OriginalSource is Border) // If clicking on the + of the tree 
      return; 

     TreeViewItem item = this.GetTreeViewItemClicked((FrameworkElement)e.OriginalSource); 

     if (item != null && item.Header != null) 
     { 
      this.SelectedItemChangedHandler(item); 
     } 
    } 

    // Check done to avoid deselecting everything when clicking to drag 
    private void TreeViewOnPreviewMouseUp(object sender, MouseButtonEventArgs e) 
    { 
     if (_itemToCheck != null) 
     { 
      TreeViewItem item = this.GetTreeViewItemClicked((FrameworkElement)e.OriginalSource); 

      if (item != null && item.Header != null) 
      { 
       if (!TreeViewEx.IsCtrlPressed) 
       { 
        GetTreeViewItems(true).Select(t => t.Header).Cast<ISelectable>().ToList().ForEach(f => f.IsSelected = false); 
        ((ISelectable)_itemToCheck.Header).IsSelected = true; 
        _lastItemSelected = _itemToCheck; 
       } 
       else 
       { 
        ((ISelectable)_itemToCheck.Header).IsSelected = false; 
        _lastItemSelected = null; 
       } 
      } 
     } 
    } 

    #endregion 

    #region Private Methods 

    private void SelectedItemChangedHandler(TreeViewItem item) 
    { 
     ISelectable content = (ISelectable)item.Header; 

     _itemToCheck = null; 

     if (content.IsSelected) 
     { 
      // Check it at the mouse up event to avoid deselecting everything when clicking to drag 
      _itemToCheck = item; 
     } 
     else 
     { 
      if (!TreeViewEx.IsCtrlPressed) 
      { 
       GetTreeViewItems(true).Select(t => t.Header).Cast<ISelectable>().ToList().ForEach(f => f.IsSelected = false); 
      } 

      if (TreeViewEx.IsShiftPressed && _lastItemSelected != null) 
      { 
       foreach (TreeViewItem tempItem in GetTreeViewItemsBetween(_lastItemSelected, item)) 
       { 
        ((ISelectable)tempItem.Header).IsSelected = true; 
        _lastItemSelected = tempItem; 
       } 
      } 
      else 
      { 
       content.IsSelected = true; 
       _lastItemSelected = item; 
      } 
     } 
    } 

    private static bool IsCtrlPressed 
    { 
     get 
     { 
      return Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl); 
     } 
    } 

    private static bool IsShiftPressed 
    { 
     get 
     { 
      return Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift); 
     } 
    } 

    private TreeViewItem GetTreeViewItemClicked(UIElement sender) 
    { 
     Point point = sender.TranslatePoint(new Point(0, 0), this.InnerTreeView); 
     DependencyObject visualItem = this.InnerTreeView.InputHitTest(point) as DependencyObject; 
     while (visualItem != null && !(visualItem is TreeViewItem)) 
     { 
      visualItem = VisualTreeHelper.GetParent(visualItem); 
     } 

     return visualItem as TreeViewItem; 
    } 

    private IEnumerable<TreeViewItem> GetTreeViewItemsBetween(TreeViewItem start, TreeViewItem end) 
    { 
     List<TreeViewItem> items = this.GetTreeViewItems(false); 

     int startIndex = items.IndexOf(start); 
     int endIndex = items.IndexOf(end); 

     // It's possible that the start element has been removed after it was selected, 
     // I don't find a way to happen on the end but I add the code to handle the situation just in case 
     if (startIndex == -1 && endIndex == -1) 
     { 
      return new List<TreeViewItem>(); 
     } 
     else if (startIndex == -1) 
     { 
      return new List<TreeViewItem>() {end}; 
     } 
     else if (endIndex == -1) 
     { 
      return new List<TreeViewItem>() { start }; 
     } 
     else 
     { 
      return startIndex > endIndex ? items.GetRange(endIndex, startIndex - endIndex + 1) : items.GetRange(startIndex, endIndex - startIndex + 1); 
     } 
    } 

    private List<TreeViewItem> GetTreeViewItems(bool includeCollapsedItems) 
    { 
     List<TreeViewItem> returnItems = new List<TreeViewItem>(); 

     for (int index = 0; index < this.InnerTreeView.Items.Count; index++) 
     { 
      TreeViewItem item = (TreeViewItem)this.InnerTreeView.ItemContainerGenerator.ContainerFromIndex(index); 
      returnItems.Add(item); 
      if (includeCollapsedItems || item.IsExpanded) 
      { 
       returnItems.AddRange(GetTreeViewItemItems(item, includeCollapsedItems));      
      } 
     } 

     return returnItems; 
    } 

    private static IEnumerable<TreeViewItem> GetTreeViewItemItems(TreeViewItem treeViewItem, bool includeCollapsedItems) 
    { 
     List<TreeViewItem> returnItems = new List<TreeViewItem>(); 

     for (int index = 0; index < treeViewItem.Items.Count; index++) 
     { 
      TreeViewItem item = (TreeViewItem)treeViewItem.ItemContainerGenerator.ContainerFromIndex(index); 
      if (item != null) 
      { 
       returnItems.Add(item); 
       if (includeCollapsedItems || item.IsExpanded) 
       { 
        returnItems.AddRange(GetTreeViewItemItems(item, includeCollapsedItems)); 
       } 
      } 
     } 

     return returnItems; 
    } 

    #endregion 
} 
} 
2

我有SOMOS實施的變化使用在派生基類TreeView控件上聲明的附加屬性,以跟蹤TreeViewItems的選擇狀態。這樣可以保持TreeViewItem元素本身的選擇跟蹤,並且不會影響樹視圖呈現的模型對象。

這是新的TreeView類派生。

using System.Linq; 
using System.Windows; 
using System.Windows.Input; 
using System.Windows.Media; 
using System.Windows.Shapes; 
using System.Windows.Controls; 
using System.Collections; 
using System.Collections.Generic; 

namespace MultiSelectTreeViewDemo 
{ 
    public sealed class MultiSelectTreeView : TreeView 
    { 
     #region Fields 

     // Used in shift selections 
     private TreeViewItem _lastItemSelected; 

     #endregion Fields 
     #region Dependency Properties 

     public static readonly DependencyProperty IsItemSelectedProperty = 
      DependencyProperty.RegisterAttached("IsItemSelected", typeof(bool), typeof(MultiSelectTreeView)); 

     public static void SetIsItemSelected(UIElement element, bool value) 
     { 
      element.SetValue(IsItemSelectedProperty, value); 
     } 
     public static bool GetIsItemSelected(UIElement element) 
     { 
      return (bool)element.GetValue(IsItemSelectedProperty); 
     } 

     #endregion Dependency Properties 
     #region Properties 

     private static bool IsCtrlPressed 
     { 
      get { return Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl); } 
     } 
     private static bool IsShiftPressed 
     { 
      get { return Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift); } 
     } 

     public IList SelectedItems 
     { 
      get 
      { 
       var selectedTreeViewItems = GetTreeViewItems(this, true).Where(GetIsItemSelected); 
       var selectedModelItems = selectedTreeViewItems.Select(treeViewItem => treeViewItem.Header); 

       return selectedModelItems.ToList(); 
      } 
     } 

     #endregion Properties 
     #region Event Handlers 

     protected override void OnPreviewMouseDown(MouseButtonEventArgs e) 
     { 
      base.OnPreviewMouseDown(e); 

      // If clicking on a tree branch expander... 
      if (e.OriginalSource is Shape || e.OriginalSource is Grid || e.OriginalSource is Border) 
       return; 

      var item = GetTreeViewItemClicked((FrameworkElement)e.OriginalSource); 
      if (item != null) SelectedItemChangedInternal(item); 
     } 

     #endregion Event Handlers 
     #region Utility Methods 

     private void SelectedItemChangedInternal(TreeViewItem tvItem) 
     { 
      // Clear all previous selected item states if ctrl is NOT being held down 
      if (!IsCtrlPressed) 
      { 
       var items = GetTreeViewItems(this, true); 
       foreach (var treeViewItem in items) 
        SetIsItemSelected(treeViewItem, false); 
      } 

      // Is this an item range selection? 
      if (IsShiftPressed && _lastItemSelected != null) 
      { 
       var items = GetTreeViewItemRange(_lastItemSelected, tvItem); 
       if (items.Count > 0) 
       { 
        foreach (var treeViewItem in items) 
         SetIsItemSelected(treeViewItem, true); 

        _lastItemSelected = items.Last(); 
       } 
      } 
      // Otherwise, individual selection 
      else 
      { 
       SetIsItemSelected(tvItem, true); 
       _lastItemSelected = tvItem; 
      } 
     } 
     private static TreeViewItem GetTreeViewItemClicked(DependencyObject sender) 
     { 
      while (sender != null && !(sender is TreeViewItem)) 
       sender = VisualTreeHelper.GetParent(sender); 
      return sender as TreeViewItem; 
     } 
     private static List<TreeViewItem> GetTreeViewItems(ItemsControl parentItem, bool includeCollapsedItems, List<TreeViewItem> itemList = null) 
     { 
      if (itemList == null) 
       itemList = new List<TreeViewItem>(); 

      for (var index = 0; index < parentItem.Items.Count; index++) 
      { 
       var tvItem = parentItem.ItemContainerGenerator.ContainerFromIndex(index) as TreeViewItem; 
       if (tvItem == null) continue; 

       itemList.Add(tvItem); 
       if (includeCollapsedItems || tvItem.IsExpanded) 
        GetTreeViewItems(tvItem, includeCollapsedItems, itemList); 
      } 
      return itemList; 
     } 
     private List<TreeViewItem> GetTreeViewItemRange(TreeViewItem start, TreeViewItem end) 
     { 
      var items = GetTreeViewItems(this, false); 

      var startIndex = items.IndexOf(start); 
      var endIndex = items.IndexOf(end); 
      var rangeStart = startIndex > endIndex || startIndex == -1 ? endIndex : startIndex; 
      var rangeCount = startIndex > endIndex ? startIndex - endIndex + 1 : endIndex - startIndex + 1; 

      if (startIndex == -1 && endIndex == -1) 
       rangeCount = 0; 

      else if (startIndex == -1 || endIndex == -1) 
       rangeCount = 1; 

      return rangeCount > 0 ? items.GetRange(rangeStart, rangeCount) : new List<TreeViewItem>(); 
     } 

     #endregion Utility Methods 
    } 
} 

這裏是XAML。請注意,突出部分是使用MultiSelectTreeViewItemStyle中新增的「IsItemSelected」附加屬性來替代使用單數「IsSelected」屬性的兩個觸發器,以實現可視狀態。

另請注意我沒有將新的TreeView控件聚合到UserControl中。

<Window 
    x:Class="MultiSelectTreeViewDemo.MainWindow" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:local="clr-namespace:MultiSelectTreeViewDemo" 
    Title="MultiSelect TreeView Demo" Height="350" Width="525"> 

    <Window.Resources> 
     <local:DemoViewModel x:Key="ViewModel"/> 
     <Style x:Key="TreeViewItemFocusVisual"> 
      <Setter Property="Control.Template"> 
       <Setter.Value> 
        <ControlTemplate> 
         <Rectangle/> 
        </ControlTemplate> 
       </Setter.Value> 
      </Setter> 
     </Style> 
     <SolidColorBrush x:Key="TreeViewItem.TreeArrow.Static.Checked.Fill" Color="#FF595959"/> 
     <SolidColorBrush x:Key="TreeViewItem.TreeArrow.Static.Checked.Stroke" Color="#FF262626"/> 
     <SolidColorBrush x:Key="TreeViewItem.TreeArrow.MouseOver.Stroke" Color="#FF1BBBFA"/> 
     <SolidColorBrush x:Key="TreeViewItem.TreeArrow.MouseOver.Fill" Color="Transparent"/> 
     <SolidColorBrush x:Key="TreeViewItem.TreeArrow.MouseOver.Checked.Stroke" Color="#FF262626"/> 
     <SolidColorBrush x:Key="TreeViewItem.TreeArrow.MouseOver.Checked.Fill" Color="#FF595959"/> 
     <PathGeometry x:Key="TreeArrow" Figures="M0,0 L0,6 L6,0 z"/> 
     <SolidColorBrush x:Key="TreeViewItem.TreeArrow.Static.Fill" Color="Transparent"/> 
     <SolidColorBrush x:Key="TreeViewItem.TreeArrow.Static.Stroke" Color="#FF989898"/> 
     <Style x:Key="ExpandCollapseToggleStyle" TargetType="{x:Type ToggleButton}"> 
      <Setter Property="Focusable" Value="False"/> 
      <Setter Property="Width" Value="16"/> 
      <Setter Property="Height" Value="16"/> 
      <Setter Property="Template"> 
       <Setter.Value> 
        <ControlTemplate TargetType="{x:Type ToggleButton}"> 
         <Border Background="Transparent" Height="16" Padding="5,5,5,5" Width="16"> 
          <Path x:Name="ExpandPath" Data="{StaticResource TreeArrow}" Fill="{StaticResource TreeViewItem.TreeArrow.Static.Fill}" Stroke="{StaticResource TreeViewItem.TreeArrow.Static.Stroke}"> 
           <Path.RenderTransform> 
            <RotateTransform Angle="135" CenterY="3" CenterX="3"/> 
           </Path.RenderTransform> 
          </Path> 
         </Border> 
         <ControlTemplate.Triggers> 
          <Trigger Property="IsChecked" Value="True"> 
           <Setter Property="RenderTransform" TargetName="ExpandPath"> 
            <Setter.Value> 
             <RotateTransform Angle="180" CenterY="3" CenterX="3"/> 
            </Setter.Value> 
           </Setter> 
           <Setter Property="Fill" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.Static.Checked.Fill}"/> 
           <Setter Property="Stroke" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.Static.Checked.Stroke}"/> 
          </Trigger> 
          <Trigger Property="IsMouseOver" Value="True"> 
           <Setter Property="Stroke" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.MouseOver.Stroke}"/> 
           <Setter Property="Fill" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.MouseOver.Fill}"/> 
          </Trigger> 
          <MultiTrigger> 
           <MultiTrigger.Conditions> 
            <Condition Property="IsMouseOver" Value="True"/> 
            <Condition Property="IsChecked" Value="True"/> 
           </MultiTrigger.Conditions> 
           <Setter Property="Stroke" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.MouseOver.Checked.Stroke}"/> 
           <Setter Property="Fill" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.MouseOver.Checked.Fill}"/> 
          </MultiTrigger> 
         </ControlTemplate.Triggers> 
        </ControlTemplate> 
       </Setter.Value> 
      </Setter> 
     </Style> 
     <Style x:Key="MultiSelectTreeViewItemStyle" TargetType="{x:Type TreeViewItem}"> 
      <Setter Property="Background" Value="Transparent"/> 
      <Setter Property="HorizontalContentAlignment" Value="{Binding HorizontalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/> 
      <Setter Property="VerticalContentAlignment" Value="{Binding VerticalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/> 
      <Setter Property="Padding" Value="1,0,0,0"/> 
      <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/> 
      <Setter Property="FocusVisualStyle" Value="{StaticResource TreeViewItemFocusVisual}"/> 
      <Setter Property="Template"> 
       <Setter.Value> 
        <ControlTemplate TargetType="{x:Type TreeViewItem}"> 
         <Grid> 
          <Grid.ColumnDefinitions> 
           <ColumnDefinition MinWidth="19" Width="Auto"/> 
           <ColumnDefinition Width="Auto"/> 
           <ColumnDefinition Width="*"/> 
          </Grid.ColumnDefinitions> 
          <Grid.RowDefinitions> 
           <RowDefinition Height="Auto"/> 
           <RowDefinition/> 
          </Grid.RowDefinitions> 
          <ToggleButton 
           x:Name="Expander" 
           ClickMode="Press" 
           IsChecked="{Binding IsExpanded, RelativeSource={RelativeSource TemplatedParent}}" 
           Style="{StaticResource ExpandCollapseToggleStyle}"/> 
          <Border 
           x:Name="Bd" 
           BorderBrush="{TemplateBinding BorderBrush}" 
           BorderThickness="{TemplateBinding BorderThickness}" 
           Background="{TemplateBinding Background}" 
           Grid.Column="1" 
           Padding="{TemplateBinding Padding}" 
           SnapsToDevicePixels="true"> 
           <ContentPresenter 
            x:Name="PART_Header" 
            ContentSource="Header" 
            HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" 
            SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/> 
          </Border> 
          <ItemsPresenter 
           x:Name="ItemsHost" 
           Grid.ColumnSpan="2" 
           Grid.Column="1" 
           Grid.Row="1"/> 
         </Grid> 
         <ControlTemplate.Triggers> 
          <Trigger Property="IsExpanded" Value="false"> 
           <Setter Property="Visibility" TargetName="ItemsHost" Value="Collapsed"/> 
          </Trigger> 
          <Trigger Property="HasItems" Value="false"> 
           <Setter Property="Visibility" TargetName="Expander" Value="Hidden"/> 
          </Trigger> 
          <!--Trigger Property="IsSelected" Value="true"--> 
          <Trigger Property="local:MultiSelectTreeView.IsItemSelected" Value="true"> 
           <Setter Property="Background" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}"/> 
           <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.HighlightTextBrushKey}}"/> 
          </Trigger> 
          <MultiTrigger> 
           <MultiTrigger.Conditions> 
            <!--Condition Property="IsSelected" Value="true"/--> 
            <Condition Property="local:MultiSelectTreeView.IsItemSelected" Value="true"/> 
            <Condition Property="IsSelectionActive" Value="false"/> 
           </MultiTrigger.Conditions> 
           <Setter Property="Background" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.InactiveSelectionHighlightBrushKey}}"/> 
           <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.InactiveSelectionHighlightTextBrushKey}}"/> 
          </MultiTrigger> 
          <Trigger Property="IsEnabled" Value="false"> 
           <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/> 
          </Trigger> 
         </ControlTemplate.Triggers> 
        </ControlTemplate> 
       </Setter.Value> 
      </Setter> 
      <Style.Triggers> 
       <Trigger Property="VirtualizingPanel.IsVirtualizing" Value="true"> 
        <Setter Property="ItemsPanel"> 
         <Setter.Value> 
          <ItemsPanelTemplate> 
           <VirtualizingStackPanel/> 
          </ItemsPanelTemplate> 
         </Setter.Value> 
        </Setter> 
       </Trigger> 
      </Style.Triggers> 
     </Style> 
    </Window.Resources> 

    <Grid 
     Background="WhiteSmoke" 
     DataContext="{DynamicResource ViewModel}"> 
     <Grid.RowDefinitions> 
      <RowDefinition/> 
      <RowDefinition Height="Auto"/> 
     </Grid.RowDefinitions> 
     <local:MultiSelectTreeView 
      x:Name="multiSelectTreeView" 
      ItemContainerStyle="{StaticResource MultiSelectTreeViewItemStyle}" 
      ItemsSource="{Binding FoodGroups}"> 
      <local:MultiSelectTreeView.ItemTemplate> 
       <HierarchicalDataTemplate ItemsSource="{Binding Children}"> 
        <Grid> 
         <TextBlock FontSize="14" Text="{Binding Name}"/> 
        </Grid> 
       </HierarchicalDataTemplate> 
      </local:MultiSelectTreeView.ItemTemplate> 
     </local:MultiSelectTreeView> 
     <Button 
      Grid.Row="1" 
      Margin="0,10" 
      Padding="20,2" 
      HorizontalAlignment="Center" 
      Content="Get Selections" 
      Click="GetSelectionsButton_OnClick"/> 
    </Grid> 
</Window> 

這裏是一個俗氣的視圖模型來驅動它(用於演示目的)。

using System.Collections.ObjectModel; 

namespace MultiSelectTreeViewDemo 
{ 
    public sealed class DemoViewModel 
    { 
     public ObservableCollection<FoodItem> FoodGroups { get; set; } 

     public DemoViewModel() 
     { 
      var redMeat = new FoodItem { Name = "Reds" }; 
      redMeat.Add(new FoodItem { Name = "Beef" }); 
      redMeat.Add(new FoodItem { Name = "Buffalo" }); 
      redMeat.Add(new FoodItem { Name = "Lamb" }); 

      var whiteMeat = new FoodItem { Name = "Whites" }; 
      whiteMeat.Add(new FoodItem { Name = "Chicken" }); 
      whiteMeat.Add(new FoodItem { Name = "Duck" }); 
      whiteMeat.Add(new FoodItem { Name = "Pork" }); 
      var meats = new FoodItem { Name = "Meats", Children = { redMeat, whiteMeat } }; 

      var veggies = new FoodItem { Name = "Vegetables" }; 
      veggies.Add(new FoodItem { Name = "Potato" }); 
      veggies.Add(new FoodItem { Name = "Corn" }); 
      veggies.Add(new FoodItem { Name = "Spinach" }); 

      var fruits = new FoodItem { Name = "Fruits" }; 
      fruits.Add(new FoodItem { Name = "Apple" }); 
      fruits.Add(new FoodItem { Name = "Orange" }); 
      fruits.Add(new FoodItem { Name = "Pear" }); 

      FoodGroups = new ObservableCollection<FoodItem> { meats, veggies, fruits }; 
     } 
    } 
    public sealed class FoodItem 
    { 
     public string Name { get; set; } 
     public ObservableCollection<FoodItem> Children { get; set; } 

     public FoodItem() 
     { 
      Children = new ObservableCollection<FoodItem>(); 
     } 
     public void Add(FoodItem item) 
     { 
      Children.Add(item); 
     } 
    } 
} 

這裏是MainWindow代碼隱藏的按鈕點擊處理程序,它顯示了MessageBox中的選擇。

private void GetSelectionsButton_OnClick(object sender, RoutedEventArgs e) 
    { 
     var selectedMesg = ""; 
     var selectedItems = multiSelectTreeView.SelectedItems; 

     if (selectedItems.Count > 0) 
     { 
      selectedMesg = selectedItems.Cast<FoodItem>() 
       .Where(modelItem => modelItem != null) 
       .Aggregate(selectedMesg, (current, modelItem) => current + modelItem.Name + Environment.NewLine); 
     } 
     else 
      selectedMesg = "No selected items!"; 

     MessageBox.Show(selectedMesg, "MultiSelect TreeView Demo", MessageBoxButton.OK); 
    } 

希望這會有所幫助。