2009-01-19 10 views
38

Das integrierte WPF-TreeView-Steuerelement ermöglicht keine Mehrfachauswahl, wie dies bei einer ListBox der Fall ist. Wie kann ich das TreeView anpassen, um eine Mehrfachauswahl zu ermöglichen, ohne es neu zu schreiben?Anpassen der Strukturansicht, um Mehrfachauswahl zuzulassen

+1

Sie können sich das Beispiel [TreeViewEx] (http://treevievex.codeplex.com/) ansehen. –

+0

Ein anderes CodeProject-Projekt, das Ihre Frage direkter adressiert, lautet: [WPF MultiSelect TreeView-Beispiel] (http://www.codeproject.com/KB/WPF/WPFMultiSelectTreeView.aspx). – Govert

+0

@ Govert Der Code in diesem Artikel ist wirklich schlecht geschrieben. Ich würde es niemandem empfehlen. Es ist fast so, als ob der Autor mehr Zeit damit verbringt, seinen Code zu entschuldigen als zu kodieren. –

Antwort

4

Wenn ich überlege, das grundlegende Verhalten eines Steuerelements zu überschreiben, z. B. eine Treeview, möchte ich immer die Verwendbarkeit und den Aufwand meiner Entscheidung in Betracht ziehen.

Im speziellen Fall einer Treeview finde ich, dass der Wechsel zu einem ListView in Kombination mit null, einer oder mehreren Controls zu einer besser nutzbaren Lösung führt, die oft einfacher zu implementieren ist.

Betrachten Sie als Beispiel den allgemeinen Dialogfeld Öffnen oder Windows Explorer-Anwendung.

+1

+1 für die Empfehlung, das Design zu überdenken - Don 'diskutiert Ich denke, ich bin bereit, das noch zu akzeptieren - aber es ist schön, einen Plausibilitätscheck zu haben. – BrainSlugs83

2

Ich habe diese Aufgabe vereinfacht, indem ich ein Kontrollkästchen vor dem Text für jedes TreeView-Element hinzufüge.

Also habe ich ein Dockpanel mit 2 Elementen im Inneren erstellt: Checkbox + Textblock.

So ...

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

Und dann können Sie Checkbox Wert zugreifen:

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

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

Dies ist eine einfache Methode, wenn Sie nichts Komplexes benötigen.

1

Ich beendete schließlich die Codierung meiner eigenen CustomControl mit einem TreeView innerhalb. Basierend auf der Arbeit anderer der Schlüssel der Funktionalität alle auf die Herstellung der Einzelteile des Modells der TreeView die Schnittstelle ISelectable erben befindet:

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

Auf diese Weise werden wir ein neues ‚IsSelected‘ Eigenschaft haben, dass nichts zu tun Sie mit dem TreeViewItem IsSelected. Wir müssen nur unseren Baum so stylen, dass er die Model IsSelected-Eigenschaft behandelt. Hier ist der Code (es ist mit Hilfe der Drag & Drop-Bibliotheken bei 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

Konstruktive Kritik? Das Wrappen der TreeView in einem UserControl ist eine schlechte Übung. Es wäre besser, ein Steuerelement zu erstellen, das TreeView erbt und eine neue Vorlage dafür definiert. Das Problem mit dem Wrapping ist, dass die TreeView-Eigenschaften nicht offen gelegt werden und einige (wie ich) damit nicht arbeiten können. Gute Lösung. –

+0

@ThyArtIsCode: Ich stimme zu. Das war, als mein WPF etwas eingeschränkt war, jetzt würde ich es tun, wie du sagst. Wie auch immer, Sie können die Eigenschaften immer nur umhüllen, nicht witzig, aber funktioniert. –

2

Ich habe eine Variation Somos-Implementierung, die verwendet Eine angefügte Eigenschaft, die für eine Ableitung des TreeView-Basissteuerelements deklariert wurde, um den Auswahlstatus der TreeViewItems zu verfolgen. Dadurch wird die Auswahlverfolgung für das TreeViewItem-Element selbst und für das Modellobjekt, das von der Baumansicht dargestellt wird, beibehalten.

Dies ist die neue TreeView-Klassenableitung.

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

Und hier ist der XAML.Beachten Sie, dass der hervorstechende Teil die Ersetzung der beiden Auslöser, die die Singular-Eigenschaft "IsSelected" verwenden, durch die neue angefügte Eigenschaft "IsItemSelected" im MultiSelectTreeViewItemStyle ersetzt, um den visuellen Status zu erzielen.

Beachten Sie auch, dass ich das neue TreeView-Steuerelement nicht zu einem UserControl zusammenfasse.

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

Und hier ist ein käsiges View-Modell, um es zu fahren (für Demozwecke).

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

Und hier ist der Button-Klick-Handler auf der Mainwindow Code-behind, dass zeigt die Auswahl in einer 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); 
    } 

Hoffe das hilft.

Verwandte Themen