自定义TreeView以允许多选

内置的WPF TreeView控件不允许多选,就像ListBox一样。 我如何定制TreeView以允许多重select而不重写它。

当我考虑重写控件的基本行为,比如树视图时,我总是喜欢考虑与我的决定相关的可用性和努力。

在treeview的特定情况下,我发现切换到一个listview与零个,一个或多个控件的组合使得更易于实现的更有用的解决scheme。

作为一个例子,考虑一下常见的打开对话框或Windows资源pipe理器应用程序

我有一个SoMoS实现的变体,它使用在基础TreeView控件的派生上声明的附加属性来跟踪TreeViewItems的select状态。 这将保持TreeViewItem元素本身的select跟踪,并且保持树视图呈现的模型对象的closures。

这是新的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代码隐藏的button点击处理程序,它显示了MessageBox中的select。

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

希望这可以帮助。

我简化了这个任务,在每个treeviewitem的文本前添加一个checkbox。

所以,我创build了一个有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); 

然后你可以访问checkbox值:

 void item_Selected(object sender, RoutedEventArgs e) { selectedTVI = ((TreeViewItem)sender); CheckBox cb = (Checkbox)((DockPanel)selectedTVI.Header).Children[0]; } 

如果你不需要任何复杂的东西,这是一个简单的方法。

最后我终于编写了自己的包含TreeView的CustomControl。 基于他人的工作,function的关键在于使TreeView的模型的所有项都inheritance接口ISelectable:

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

这样我们将有一个新的“IsSelected”属性与TreeViewItem IsSelected无关。 我们只需要对我们的树进行devise,以便处理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 } }