2009-01-19 47 views
38

Điều khiển TreeView WPF tích hợp không cho phép chọn nhiều, như một ListBox. Làm thế nào tôi có thể tùy chỉnh TreeView để cho phép lựa chọn đa mà không cần viết lại nó.Tùy chỉnh TreeView để cho phép đa lựa chọn

+1

Bạn có thể xem ví dụ [TreeViewEx] (http://treeviewex.codeplex.com/). –

+0

Dự án CodeProject khác giải quyết trực tiếp câu hỏi của bạn trực tiếp hơn là: [WPF MultiSelect TreeView Sample] (http://www.codeproject.com/KB/WPF/WPFMultiSelectTreeView.aspx). – Govert

+0

@Govert Mã trong bài viết đó thực sự bị viết sai. Tôi sẽ không giới thiệu nó cho bất cứ ai. Nó gần như là nếu tác giả dành nhiều thời gian hơn để giải mã mã của anh ta hơn là viết mã. –

Trả lời

4

Khi tôi xem xét việc ghi đè hành vi cơ bản của điều khiển, như số lần xem trang, tôi luôn muốn xem xét khả năng sử dụng và nỗ lực gắn liền với quyết định của mình.

Trong trường hợp cụ thể của một lần xem trang, tôi thấy rằng việc chuyển sang chế độ xem danh sách kết hợp với 0, một hoặc nhiều điều khiển sẽ giúp giải pháp dễ sử dụng hơn.

Ví dụ, hãy xem xét hộp thoại Mở phổ biến hoặc ứng dụng Windows Explorer.

+1

+1 để đề xuất xem xét lại thiết kế - don ' Tôi nghĩ rằng tôi đã sẵn sàng chấp nhận điều này - nhưng thật tuyệt khi được kiểm tra sự tỉnh táo. – BrainSlugs83

2

Tôi đã đơn giản hóa tác vụ này thêm hộp kiểm trước văn bản cho mỗi treeviewitem.

Vì vậy, tôi đã tạo một dockpanel với 2 mục bên trong: checkbox + textblock.

Vậy ...

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

Và sau đó bạn có thể truy cập giá trị checkbox:

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

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

Đây là một cách đơn giản để làm nếu bạn không cần bất cứ điều gì phức tạp.

1

Cuối cùng tôi đã kết thúc mã hóa CustomControl của riêng mình chứa một TreeView bên trong. Dựa trên công việc của những người khác là chìa khóa của chức năng nằm trên làm cho tất cả các hạng mục Model của TreeView kế thừa giao diện ISelectable:

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

Bằng cách này chúng ta sẽ có một tài sản 'IsSelected' mới mà không có gì để làm với TreeViewItem IsSelected. Chúng ta chỉ cần tạo kiểu cho cây của chúng ta để nó xử lý thuộc tính IsSelected của mô hình. Đây mã (nó sử dụng thả các thư viện Kéo & sẵn tại 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

Phê bình xây dựng? Bao bọc TreeView trong UserControl là thực hành không tốt. Sẽ tốt hơn nếu bạn kiểm soát TreeView và xác định mẫu mới cho nó. Vấn đề với việc gói nó là các thuộc tính của TreeView không bị lộ ra và một số (giống như tôi) không thể làm việc với điều đó. Giải pháp tốt, mặc dù. –

+0

@ThyArtIsCode: Tôi đồng ý. Đây là khi WPF của tôi có phần hạn chế, bây giờ tôi sẽ làm như bạn nói. Dù sao bạn luôn có thể phơi bày các thuộc tính chỉ gói chúng, không vui nhưng làm việc. –

2

Tôi có một sự thay đổi tình hình thực hiện Somos sử dụng một thuộc tính đính kèm được khai báo trên một dẫn xuất của điều khiển TreeView cơ sở để theo dõi trạng thái lựa chọn của TreeViewItems. Điều này giúp việc theo dõi lựa chọn trên chính phần tử TreeViewItem, và tắt đối tượng mô hình được trình bày theo dạng cây.

Đây là dẫn xuất lớp TreeView mới.

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 
    } 
} 

Và đây là XAML.Hãy lưu ý rằng phần nổi bật là sự thay thế của hai bộ kích hoạt sử dụng thuộc tính 'IsSelected' số ít với thuộc tính gắn liền 'IsItemSelected' mới trong MultiSelectTreeViewItemStyle để đạt được trạng thái trực quan.

Cũng lưu ý rằng tôi không kết hợp điều khiển TreeView mới vào 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> 

Và đây là mô hình chế độ xem cheesy để thúc đẩy nó (cho mục đích demo).

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

Và đây là nút bấm xử lý trên mã MainWindow phía sau hiển thị các lựa chọn trong Hộp thư.

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

Hy vọng điều này sẽ hữu ích.

Các vấn đề liên quan