2009-01-19 86 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); 
    } 

希望这会有所帮助。