1
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
2
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
5
using System.Collections.Generic;
6
using System.Collections.Specialized;
7
using System.Diagnostics;
11
using System.Windows.Controls;
12
using System.Windows.Controls.Primitives;
13
using System.Windows.Data;
14
using System.Windows.Documents;
15
using System.Windows.Input;
16
using System.Windows.Media;
17
using System.Windows.Threading;
19
namespace ICSharpCode.TreeView
21
public class SharpTreeView : ListView
23
static SharpTreeView()
25
DefaultStyleKeyProperty.OverrideMetadata(typeof(SharpTreeView),
26
new FrameworkPropertyMetadata(typeof(SharpTreeView)));
28
SelectionModeProperty.OverrideMetadata(typeof(SharpTreeView),
29
new FrameworkPropertyMetadata(SelectionMode.Extended));
31
AlternationCountProperty.OverrideMetadata(typeof(SharpTreeView),
32
new FrameworkPropertyMetadata(2));
34
DefaultItemContainerStyleKey =
35
new ComponentResourceKey(typeof(SharpTreeView), "DefaultItemContainerStyleKey");
37
VirtualizingStackPanel.VirtualizationModeProperty.OverrideMetadata(typeof(SharpTreeView),
38
new FrameworkPropertyMetadata(VirtualizationMode.Recycling));
43
public static ResourceKey DefaultItemContainerStyleKey { get; private set; }
45
public SharpTreeView()
47
SetResourceReference(ItemContainerStyleProperty, DefaultItemContainerStyleKey);
50
public static readonly DependencyProperty RootProperty =
51
DependencyProperty.Register("Root", typeof(SharpTreeNode), typeof(SharpTreeView));
53
public SharpTreeNode Root
55
get { return (SharpTreeNode)GetValue(RootProperty); }
56
set { SetValue(RootProperty, value); }
59
public static readonly DependencyProperty ShowRootProperty =
60
DependencyProperty.Register("ShowRoot", typeof(bool), typeof(SharpTreeView),
61
new FrameworkPropertyMetadata(true));
65
get { return (bool)GetValue(ShowRootProperty); }
66
set { SetValue(ShowRootProperty, value); }
69
public static readonly DependencyProperty ShowRootExpanderProperty =
70
DependencyProperty.Register("ShowRootExpander", typeof(bool), typeof(SharpTreeView),
71
new FrameworkPropertyMetadata(false));
73
public bool ShowRootExpander
75
get { return (bool)GetValue(ShowRootExpanderProperty); }
76
set { SetValue(ShowRootExpanderProperty, value); }
79
public static readonly DependencyProperty AllowDropOrderProperty =
80
DependencyProperty.Register("AllowDropOrder", typeof(bool), typeof(SharpTreeView));
82
public bool AllowDropOrder
84
get { return (bool)GetValue(AllowDropOrderProperty); }
85
set { SetValue(AllowDropOrderProperty, value); }
88
public static readonly DependencyProperty ShowLinesProperty =
89
DependencyProperty.Register("ShowLines", typeof(bool), typeof(SharpTreeView),
90
new FrameworkPropertyMetadata(true));
94
get { return (bool)GetValue(ShowLinesProperty); }
95
set { SetValue(ShowLinesProperty, value); }
98
public static bool GetShowAlternation(DependencyObject obj)
100
return (bool)obj.GetValue(ShowAlternationProperty);
103
public static void SetShowAlternation(DependencyObject obj, bool value)
105
obj.SetValue(ShowAlternationProperty, value);
108
public static readonly DependencyProperty ShowAlternationProperty =
109
DependencyProperty.RegisterAttached("ShowAlternation", typeof(bool), typeof(SharpTreeView),
110
new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.Inherits));
112
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
114
base.OnPropertyChanged(e);
115
if (e.Property == RootProperty ||
116
e.Property == ShowRootProperty ||
117
e.Property == ShowRootExpanderProperty) {
122
TreeFlattener flattener;
126
if (flattener != null) {
130
if (!(ShowRoot && ShowRootExpander)) {
131
Root.IsExpanded = true;
133
flattener = new TreeFlattener(Root, ShowRoot);
134
flattener.CollectionChanged += flattener_CollectionChanged;
135
this.ItemsSource = flattener;
139
void flattener_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
141
// Deselect nodes that are being hidden
142
if (e.Action == NotifyCollectionChangedAction.Remove) {
143
List<SharpTreeNode> selectedOldItems = null;
144
foreach (SharpTreeNode node in e.OldItems) {
145
if (node.IsSelected) {
146
if (selectedOldItems == null)
147
selectedOldItems = new List<SharpTreeNode>();
148
selectedOldItems.Add(node);
151
if (selectedOldItems != null) {
152
var list = SelectedItems.Cast<SharpTreeNode>().Except(selectedOldItems).ToList();
153
SetSelectedItems(list);
155
// reset the focus to the previous node
156
SelectedIndex = Math.Max(0, e.OldStartingIndex - 1);
157
if (SelectedItem != null)
158
FocusNode((SharpTreeNode)SelectedItem);
162
protected override DependencyObject GetContainerForItemOverride()
164
return new SharpTreeViewItem();
167
protected override bool IsItemItsOwnContainerOverride(object item)
169
return item is SharpTreeViewItem;
172
protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
174
base.PrepareContainerForItemOverride(element, item);
175
SharpTreeViewItem container = element as SharpTreeViewItem;
176
container.ParentTreeView = this;
179
bool doNotScrollOnExpanding;
182
/// Handles the node expanding event in the tree view.
183
/// This method gets called only if the node is in the visible region (a SharpTreeNodeView exists).
185
internal void HandleExpanding(SharpTreeNode node)
187
if (doNotScrollOnExpanding)
189
SharpTreeNode lastVisibleChild = node;
191
SharpTreeNode tmp = lastVisibleChild.Children.LastOrDefault(c => c.IsVisible);
193
lastVisibleChild = tmp;
198
if (lastVisibleChild != node) {
199
// Make the the expanded children are visible; but don't scroll down
200
// to much (keep node itself visible)
201
base.ScrollIntoView(lastVisibleChild);
202
// For some reason, this only works properly when delaying it...
203
Dispatcher.BeginInvoke(DispatcherPriority.Loaded, new Action(
205
base.ScrollIntoView(node);
210
protected override void OnKeyDown(KeyEventArgs e)
212
SharpTreeViewItem container = e.OriginalSource as SharpTreeViewItem;
215
if (container != null && ItemsControl.ItemsControlFromItemContainer(container) == this) {
216
if (container.Node.IsExpanded) {
217
container.Node.IsExpanded = false;
218
} else if (container.Node.Parent != null) {
219
this.FocusNode(container.Node.Parent);
225
if (container != null && ItemsControl.ItemsControlFromItemContainer(container) == this) {
226
if (!container.Node.IsExpanded && container.Node.ShowExpander) {
227
container.Node.IsExpanded = true;
228
} else if (container.Node.Children.Count > 0) {
229
// jump to first child:
230
container.MoveFocus(new TraversalRequest(FocusNavigationDirection.Down));
237
if (container != null && Keyboard.Modifiers == ModifierKeys.None && this.SelectedItems.Count == 1 && this.SelectedItem == container.Node) {
238
container.Node.ActivateItem(e);
242
if (container != null && ItemsControl.ItemsControlFromItemContainer(container) == this) {
243
container.Node.IsExpanded = true;
248
if (container != null && ItemsControl.ItemsControlFromItemContainer(container) == this) {
249
container.Node.IsExpanded = false;
254
if (container != null && ItemsControl.ItemsControlFromItemContainer(container) == this) {
255
container.Node.IsExpanded = true;
256
ExpandRecursively(container.Node);
265
void ExpandRecursively(SharpTreeNode node)
267
if (node.CanExpandRecursively) {
268
node.IsExpanded = true;
269
foreach (SharpTreeNode child in node.Children) {
270
ExpandRecursively(child);
276
/// Scrolls the specified node in view and sets keyboard focus on it.
278
public void FocusNode(SharpTreeNode node)
281
throw new ArgumentNullException("node");
282
ScrollIntoView(node);
283
// WPF's ScrollIntoView() uses the same if/dispatcher construct, so we call OnFocusItem() after the item was brought into view.
284
if (this.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated) {
287
this.Dispatcher.BeginInvoke(DispatcherPriority.Loaded, new DispatcherOperationCallback(this.OnFocusItem), node);
291
public void ScrollIntoView(SharpTreeNode node)
294
throw new ArgumentNullException("node");
295
doNotScrollOnExpanding = true;
296
foreach (SharpTreeNode ancestor in node.Ancestors())
297
ancestor.IsExpanded = true;
298
doNotScrollOnExpanding = false;
299
base.ScrollIntoView(node);
302
object OnFocusItem(object item)
304
FrameworkElement element = this.ItemContainerGenerator.ContainerFromItem(item) as FrameworkElement;
305
if (element != null) {
311
#region Track selection
313
protected override void OnSelectionChanged(SelectionChangedEventArgs e)
315
foreach (SharpTreeNode node in e.RemovedItems) {
316
node.IsSelected = false;
318
foreach (SharpTreeNode node in e.AddedItems) {
319
node.IsSelected = true;
321
base.OnSelectionChanged(e);
326
#region Drag and Drop
327
protected override void OnDragEnter(DragEventArgs e)
332
protected override void OnDragOver(DragEventArgs e)
334
e.Effects = DragDropEffects.None;
336
if (Root != null && !ShowRoot) {
338
Root.CanDrop(e, Root.Children.Count);
342
protected override void OnDrop(DragEventArgs e)
344
e.Effects = DragDropEffects.None;
346
if (Root != null && !ShowRoot) {
348
Root.InternalDrop(e, Root.Children.Count);
352
internal void HandleDragEnter(SharpTreeViewItem item, DragEventArgs e)
354
HandleDragOver(item, e);
357
internal void HandleDragOver(SharpTreeViewItem item, DragEventArgs e)
361
var target = GetDropTarget(item, e);
362
if (target != null) {
364
ShowPreview(target.Item, target.Place);
368
internal void HandleDrop(SharpTreeViewItem item, DragEventArgs e)
373
var target = GetDropTarget(item, e);
374
if (target != null) {
376
target.Node.InternalDrop(e, target.Index);
378
} catch (Exception ex) {
379
Debug.WriteLine(ex.ToString());
384
internal void HandleDragLeave(SharpTreeViewItem item, DragEventArgs e)
392
public SharpTreeViewItem Item;
393
public DropPlace Place;
395
public SharpTreeNode Node;
399
DropTarget GetDropTarget(SharpTreeViewItem item, DragEventArgs e)
401
var dropTargets = BuildDropTargets(item, e);
402
var y = e.GetPosition(item).Y;
403
foreach (var target in dropTargets) {
411
List<DropTarget> BuildDropTargets(SharpTreeViewItem item, DragEventArgs e)
413
var result = new List<DropTarget>();
414
var node = item.Node;
416
if (AllowDropOrder) {
417
TryAddDropTarget(result, item, DropPlace.Before, e);
420
TryAddDropTarget(result, item, DropPlace.Inside, e);
422
if (AllowDropOrder) {
423
if (node.IsExpanded && node.Children.Count > 0) {
424
var firstChildItem = ItemContainerGenerator.ContainerFromItem(node.Children[0]) as SharpTreeViewItem;
425
TryAddDropTarget(result, firstChildItem, DropPlace.Before, e);
428
TryAddDropTarget(result, item, DropPlace.After, e);
432
var h = item.ActualHeight;
437
if (result.Count == 2) {
438
if (result[0].Place == DropPlace.Inside &&
439
result[1].Place != DropPlace.Inside) {
442
else if (result[0].Place != DropPlace.Inside &&
443
result[1].Place == DropPlace.Inside) {
450
else if (result.Count == 3) {
454
if (result.Count > 0) {
455
result[result.Count - 1].Y = h;
460
void TryAddDropTarget(List<DropTarget> targets, SharpTreeViewItem item, DropPlace place, DragEventArgs e)
465
GetNodeAndIndex(item, place, out node, out index);
468
e.Effects = DragDropEffects.None;
469
if (node.CanDrop(e, index)) {
470
DropTarget target = new DropTarget() {
481
void GetNodeAndIndex(SharpTreeViewItem item, DropPlace place, out SharpTreeNode node, out int index)
486
if (place == DropPlace.Inside) {
488
index = node.Children.Count;
490
else if (place == DropPlace.Before) {
491
if (item.Node.Parent != null) {
492
node = item.Node.Parent;
493
index = node.Children.IndexOf(item.Node);
497
if (item.Node.Parent != null) {
498
node = item.Node.Parent;
499
index = node.Children.IndexOf(item.Node) + 1;
504
SharpTreeNodeView previewNodeView;
505
InsertMarker insertMarker;
506
DropPlace previewPlace;
510
Before, Inside, After
513
void ShowPreview(SharpTreeViewItem item, DropPlace place)
515
previewNodeView = item.NodeView;
516
previewPlace = place;
518
if (place == DropPlace.Inside) {
519
previewNodeView.TextBackground = SystemColors.HighlightBrush;
520
previewNodeView.Foreground = SystemColors.HighlightTextBrush;
523
if (insertMarker == null) {
524
var adornerLayer = AdornerLayer.GetAdornerLayer(this);
525
var adorner = new GeneralAdorner(this);
526
insertMarker = new InsertMarker();
527
adorner.Child = insertMarker;
528
adornerLayer.Add(adorner);
531
insertMarker.Visibility = Visibility.Visible;
533
var p1 = previewNodeView.TransformToVisual(this).Transform(new Point());
534
var p = new Point(p1.X + previewNodeView.CalculateIndent() + 4.5, p1.Y - 3);
536
if (place == DropPlace.After) {
537
p.Y += previewNodeView.ActualHeight;
540
insertMarker.Margin = new Thickness(p.X, p.Y, 0, 0);
542
SharpTreeNodeView secondNodeView = null;
543
var index = flattener.IndexOf(item.Node);
545
if (place == DropPlace.Before) {
547
secondNodeView = (ItemContainerGenerator.ContainerFromIndex(index - 1) as SharpTreeViewItem).NodeView;
550
else if (index + 1 < flattener.Count) {
551
secondNodeView = (ItemContainerGenerator.ContainerFromIndex(index + 1) as SharpTreeViewItem).NodeView;
554
var w = p1.X + previewNodeView.ActualWidth - p.X;
556
if (secondNodeView != null) {
557
var p2 = secondNodeView.TransformToVisual(this).Transform(new Point());
558
w = Math.Max(w, p2.X + secondNodeView.ActualWidth - p.X);
561
insertMarker.Width = w + 10;
567
if (previewNodeView != null) {
568
previewNodeView.ClearValue(SharpTreeNodeView.TextBackgroundProperty);
569
previewNodeView.ClearValue(SharpTreeNodeView.ForegroundProperty);
570
if (insertMarker != null) {
571
insertMarker.Visibility = Visibility.Collapsed;
573
previewNodeView = null;
578
#region Cut / Copy / Paste / Delete Commands
580
static void RegisterCommands()
582
CommandManager.RegisterClassCommandBinding(typeof(SharpTreeView),
583
new CommandBinding(ApplicationCommands.Cut, HandleExecuted_Cut, HandleCanExecute_Cut));
585
CommandManager.RegisterClassCommandBinding(typeof(SharpTreeView),
586
new CommandBinding(ApplicationCommands.Copy, HandleExecuted_Copy, HandleCanExecute_Copy));
588
CommandManager.RegisterClassCommandBinding(typeof(SharpTreeView),
589
new CommandBinding(ApplicationCommands.Paste, HandleExecuted_Paste, HandleCanExecute_Paste));
591
CommandManager.RegisterClassCommandBinding(typeof(SharpTreeView),
592
new CommandBinding(ApplicationCommands.Delete, HandleExecuted_Delete, HandleCanExecute_Delete));
595
static void HandleExecuted_Cut(object sender, ExecutedRoutedEventArgs e)
600
static void HandleCanExecute_Cut(object sender, CanExecuteRoutedEventArgs e)
602
e.CanExecute = false;
605
static void HandleExecuted_Copy(object sender, ExecutedRoutedEventArgs e)
610
static void HandleCanExecute_Copy(object sender, CanExecuteRoutedEventArgs e)
612
e.CanExecute = false;
615
static void HandleExecuted_Paste(object sender, ExecutedRoutedEventArgs e)
620
static void HandleCanExecute_Paste(object sender, CanExecuteRoutedEventArgs e)
622
e.CanExecute = false;
625
static void HandleExecuted_Delete(object sender, ExecutedRoutedEventArgs e)
627
SharpTreeView treeView = (SharpTreeView)sender;
628
foreach (SharpTreeNode node in treeView.GetTopLevelSelection().ToArray())
632
static void HandleCanExecute_Delete(object sender, CanExecuteRoutedEventArgs e)
634
SharpTreeView treeView = (SharpTreeView)sender;
635
e.CanExecute = treeView.GetTopLevelSelection().All(node => node.CanDelete());
639
/// Gets the selected items which do not have any of their ancestors selected.
641
public IEnumerable<SharpTreeNode> GetTopLevelSelection()
643
var selection = this.SelectedItems.OfType<SharpTreeNode>();
644
var selectionHash = new HashSet<SharpTreeNode>(selection);
645
return selection.Where(item => item.Ancestors().All(a => !selectionHash.Contains(a)));