using System.Collections.ObjectModel; using System.Linq; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; namespace SourceGit.Helpers { /// /// Helper class to enable multi-selection of TreeView /// public static class TreeViewHelper { /// /// Definition of EnableMultiSelection property. /// public static readonly DependencyProperty EnableMultiSelectionProperty = DependencyProperty.RegisterAttached( "EnableMultiSelection", typeof(bool), typeof(TreeViewHelper), new FrameworkPropertyMetadata(false, OnEnableMultiSelectionChanged)); /// /// Getter of EnableMultiSelection /// /// /// public static bool GetEnableMultiSelection(DependencyObject obj) { return (bool)obj.GetValue(EnableMultiSelectionProperty); } /// /// Setter of EnableMultiSelection /// /// /// public static void SetEnableMultiSelection(DependencyObject obj, bool value) { obj.SetValue(EnableMultiSelectionProperty, value); } /// /// Definition of SelectedItems /// public static readonly DependencyProperty SelectedItemsProperty = DependencyProperty.RegisterAttached( "SelectedItems", typeof(ObservableCollection), typeof(TreeViewHelper), new FrameworkPropertyMetadata(null)); /// /// Getter of SelectedItems /// /// /// public static ObservableCollection GetSelectedItems(DependencyObject obj) { return (ObservableCollection)obj.GetValue(SelectedItemsProperty); } /// /// Setter of SelectedItems /// /// /// public static void SetSelectedItems(DependencyObject obj, ObservableCollection value) { obj.SetValue(SelectedItemsProperty, value); } /// /// Definition of IsChecked property. /// public static readonly DependencyProperty IsCheckedProperty = DependencyProperty.RegisterAttached( "IsChecked", typeof(bool), typeof(TreeViewHelper), new FrameworkPropertyMetadata(false)); /// /// Getter of IsChecked Property. /// /// /// public static bool GetIsChecked(DependencyObject obj) { return (bool)obj.GetValue(IsCheckedProperty); } /// /// Setter of IsChecked property /// /// /// public static void SetIsChecked(DependencyObject obj, bool value) { obj.SetValue(IsCheckedProperty, value); } /// /// Definition of MultiSelectionChangedEvent /// public static readonly RoutedEvent MultiSelectionChangedEvent = EventManager.RegisterRoutedEvent("MultiSelectionChanged", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(TreeViewHelper)); /// /// Add handler for MultiSelectionChanged event. /// /// /// public static void AddMultiSelectionChangedHandler(DependencyObject d, RoutedEventHandler handler) { var tree = d as TreeView; if (tree != null) tree.AddHandler(MultiSelectionChangedEvent, handler); } /// /// Remove handler for MultiSelectionChanged event. /// /// /// public static void RemoveMultiSelectionChangedHandler(DependencyObject d, RoutedEventHandler handler) { var tree = d as TreeView; if (tree != null) tree.RemoveHandler(MultiSelectionChangedEvent, handler); } /// /// Select all items in tree. /// /// public static void SelectWholeTree(TreeView tree) { var selected = GetSelectedItems(tree); selected.Clear(); SelectAll(selected, tree); tree.RaiseEvent(new RoutedEventArgs(MultiSelectionChangedEvent)); } /// /// Selected one item by DataContext /// /// /// public static void SelectOneByContext(TreeView tree, object obj) { var item = FindTreeViewItemByDataContext(tree, obj); if (item != null) { var selected = GetSelectedItems(tree); selected.Add(item); item.SetValue(IsCheckedProperty, true); tree.RaiseEvent(new RoutedEventArgs(MultiSelectionChangedEvent)); } } /// /// Unselect the whole tree. /// /// public static void UnselectTree(TreeView tree) { var selected = GetSelectedItems(tree); if (selected.Count == 0) return; foreach (var old in selected) old.SetValue(IsCheckedProperty, false); selected.Clear(); tree.RaiseEvent(new RoutedEventArgs(MultiSelectionChangedEvent)); } /// /// Hooks when EnableMultiSelection changed. /// /// /// private static void OnEnableMultiSelectionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var tree = d as TreeView; if (tree != null && (bool)e.NewValue) { tree.SetValue(SelectedItemsProperty, new ObservableCollection()); tree.PreviewMouseDown += OnTreeMouseDown; } } /// /// Preview mouse button select. /// /// /// private static void OnTreeMouseDown(object sender, MouseButtonEventArgs e) { var tree = sender as TreeView; if (tree == null) return; var hit = VisualTreeHelper.HitTest(tree, e.GetPosition(tree)); if (hit == null || hit.VisualHit is null) return; var item = FindTreeViewItem(hit.VisualHit as UIElement); if (item == null) return; var selected = GetSelectedItems(tree); if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl)) { if (GetIsChecked(item)) { selected.Remove(item); item.SetValue(IsCheckedProperty, false); } else { selected.Add(item); item.SetValue(IsCheckedProperty, true); } } else if ((Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift)) && selected.Count > 0) { var last = selected.Last(); if (last == item) return; var lastPos = last.PointToScreen(new Point(0, 0)); var curPos = item.PointToScreen(new Point(0, 0)); if (lastPos.Y > curPos.Y) { SelectRange(selected, tree, item, last); } else { SelectRange(selected, tree, last, item); } selected.Add(item); item.SetValue(IsCheckedProperty, true); } else if (e.RightButton == MouseButtonState.Pressed) { if (GetIsChecked(item)) return; foreach (var old in selected) old.SetValue(IsCheckedProperty, false); selected.Clear(); selected.Add(item); item.SetValue(IsCheckedProperty, true); } else { foreach (var old in selected) old.SetValue(IsCheckedProperty, false); selected.Clear(); selected.Add(item); item.SetValue(IsCheckedProperty, true); } tree.RaiseEvent(new RoutedEventArgs(MultiSelectionChangedEvent)); } /// /// Find TreeViewItem by child element. /// /// /// /// private static TreeViewItem FindTreeViewItem(DependencyObject child) { if (child == null) return null; if (child is TreeViewItem) return child as TreeViewItem; if (child is TreeView) return null; return FindTreeViewItem(VisualTreeHelper.GetParent(child)); } /// /// Find TreeViewItem by DataContext /// /// /// /// private static TreeViewItem FindTreeViewItemByDataContext(ItemsControl control, object obj) { if (control == null) return null; if (control.DataContext == obj) return control as TreeViewItem; for (int i = 0; i < control.Items.Count; i++) { var child = control.ItemContainerGenerator.ContainerFromIndex(i) as ItemsControl; var found = FindTreeViewItemByDataContext(child, obj); if (found != null) return found; } return null; } /// /// Select all items. /// /// /// private static void SelectAll(ObservableCollection selected, ItemsControl control) { for (int i = 0; i < control.Items.Count; i++) { var child = control.ItemContainerGenerator.ContainerFromIndex(i) as TreeViewItem; if (child == null) continue; selected.Add(child); child.SetValue(IsCheckedProperty, true); SelectAll(selected, child); } } /// /// Select range items between given. /// /// /// /// /// /// private static int SelectRange(ObservableCollection selected, ItemsControl control, TreeViewItem from, TreeViewItem to, int matches = 0) { for (int i = 0; i < control.Items.Count; i++) { var child = control.ItemContainerGenerator.ContainerFromIndex(i) as TreeViewItem; if (child == null) continue; if (matches == 1) { if (child == to) return 2; selected.Add(child); child.SetValue(IsCheckedProperty, true); if (TryEndRangeSelection(selected, child, to)) return 2; } else if (child == from) { matches = 1; if (TryEndRangeSelection(selected, child, to)) return 2; } else { matches = SelectRange(selected, child, from, to, matches); if (matches == 2) return 2; } } return matches; } private static bool TryEndRangeSelection(ObservableCollection selected, TreeViewItem control, TreeViewItem end) { for (int i = 0; i < control.Items.Count; i++) { var child = control.ItemContainerGenerator.ContainerFromIndex(i) as TreeViewItem; if (child == null) continue; if (child == end) { return true; } else { selected.Add(child); child.SetValue(IsCheckedProperty, true); var ended = TryEndRangeSelection(selected, child, end); if (ended) return true; } } return false; } } }