commit a5ac9d09ed032ccbf46f1daa4c39f66882bd4834 Author: Daniel Grunwald Date: Fri Feb 4 12:52:26 2011 +0100 Squashed 'SharpTreeView/' content from commit 886d615 git-subtree-dir: SharpTreeView git-subtree-split: 886d615dc7ae9ffbe2a4f466455fd32e81547119 diff --git a/Converters.cs b/Converters.cs new file mode 100644 index 000000000..8c12e5b54 --- /dev/null +++ b/Converters.cs @@ -0,0 +1,34 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows; +using System.Windows.Markup; +using System.Windows.Data; +using System.Globalization; + +namespace ICSharpCode.TreeView +{ + public class CollapsedWhenFalse : MarkupExtension, IValueConverter + { + public static CollapsedWhenFalse Instance = new CollapsedWhenFalse(); + + public override object ProvideValue(IServiceProvider serviceProvider) + { + return Instance; + } + + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + return (bool)value ? Visibility.Visible : Visibility.Collapsed; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} diff --git a/DropEffect.cs b/DropEffect.cs new file mode 100644 index 000000000..24a16504f --- /dev/null +++ b/DropEffect.cs @@ -0,0 +1,15 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace ICSharpCode.TreeView +{ + public enum DropEffect + { + None, Move, Copy, Link + } +} diff --git a/EditTextBox.cs b/EditTextBox.cs new file mode 100644 index 000000000..fd5fcd3a7 --- /dev/null +++ b/EditTextBox.cs @@ -0,0 +1,84 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Data; +using System.Windows; + +namespace ICSharpCode.TreeView +{ + class EditTextBox : TextBox + { + static EditTextBox() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(EditTextBox), + new FrameworkPropertyMetadata(typeof(EditTextBox))); + } + + public EditTextBox() + { + Loaded += delegate { Init(); }; + } + + public SharpTreeViewItem Item { get; set; } + + public SharpTreeNode Node + { + get { return Item.Node; } + } + + void Init() + { + Text = Node.LoadEditText(); + Focus(); + SelectAll(); + } + + protected override void OnKeyDown(KeyEventArgs e) + { + if (e.Key == Key.Enter) { + Commit(); + } + else if (e.Key == Key.Escape) { + Node.IsEditing = false; + } + } + + protected override void OnLostKeyboardFocus(KeyboardFocusChangedEventArgs e) + { + if (Node.IsEditing) { + Commit(); + } + } + + bool commiting; + + void Commit() + { + if (!commiting) { + commiting = true; + + Node.IsEditing = false; + if (!Node.SaveEditText(Text)) { + Item.Focus(); + } + Node.RaisePropertyChanged("Text"); + + //if (Node.SaveEditText(Text)) { + // Node.IsEditing = false; + // Node.RaisePropertyChanged("Text"); + //} + //else { + // Init(); + //} + + commiting = false; + } + } + } +} diff --git a/ExtensionMethods.cs b/ExtensionMethods.cs new file mode 100644 index 000000000..761b206b3 --- /dev/null +++ b/ExtensionMethods.cs @@ -0,0 +1,37 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows.Media; +using System.Windows; +using System.Collections; +using System.Windows.Input; + +namespace ICSharpCode.TreeView +{ + public static class ExtensionMethods + { + public static T FindAncestor(this DependencyObject d) where T : class + { + return AncestorsAndSelf(d).OfType().FirstOrDefault(); + } + + public static IEnumerable AncestorsAndSelf(this DependencyObject d) + { + while (d != null) { + yield return d; + d = VisualTreeHelper.GetParent(d); + } + } + + public static void AddOnce(this IList list, object item) + { + if (!list.Contains(item)) { + list.Add(item); + } + } + } +} diff --git a/GeneralAdorner.cs b/GeneralAdorner.cs new file mode 100644 index 000000000..6bad75c80 --- /dev/null +++ b/GeneralAdorner.cs @@ -0,0 +1,70 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows.Documents; +using System.Windows; +using System.Windows.Media; + +namespace ICSharpCode.TreeView +{ + public class GeneralAdorner : Adorner + { + public GeneralAdorner(UIElement target) + : base(target) + { + } + + FrameworkElement child; + + public FrameworkElement Child + { + get + { + return child; + } + set + { + if (child != value) { + RemoveVisualChild(child); + RemoveLogicalChild(child); + child = value; + AddLogicalChild(value); + AddVisualChild(value); + InvalidateMeasure(); + } + } + } + + protected override int VisualChildrenCount + { + get { return child == null ? 0 : 1; } + } + + protected override Visual GetVisualChild(int index) + { + return child; + } + + protected override Size MeasureOverride(Size constraint) + { + if (child != null) { + child.Measure(constraint); + return child.DesiredSize; + } + return new Size(); + } + + protected override Size ArrangeOverride(Size finalSize) + { + if (child != null) { + child.Arrange(new Rect(finalSize)); + return finalSize; + } + return new Size(); + } + } +} diff --git a/ICSharpCode.TreeView.csproj b/ICSharpCode.TreeView.csproj new file mode 100644 index 000000000..c1877220a --- /dev/null +++ b/ICSharpCode.TreeView.csproj @@ -0,0 +1,93 @@ + + + + Debug + AnyCPU + 9.0.30729 + 2.0 + {DDE2A481-8271-4EAC-A330-8FA6A38D13D1} + library + Properties + ICSharpCode.TreeView + ICSharpCode.TreeView + v4.0 + 512 + {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 4 + ..\..\..\..\bin\ + + + + + true + full + false + ..\..\..\..\bin\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + ..\..\..\..\bin\ + TRACE + prompt + 4 + + + + + 3.5 + + + 3.5 + + + 3.5 + + + + + + + + + + + MSBuild:Compile + Designer + + + + + Properties\GlobalAssemblyInfo.cs + + + + + + + + + + Code + + + + + + + + + + + + + \ No newline at end of file diff --git a/InsertMarker.cs b/InsertMarker.cs new file mode 100644 index 000000000..4a3ff4530 --- /dev/null +++ b/InsertMarker.cs @@ -0,0 +1,21 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows.Controls; +using System.Windows; + +namespace ICSharpCode.TreeView +{ + public class InsertMarker : Control + { + static InsertMarker() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(InsertMarker), + new FrameworkPropertyMetadata(typeof(InsertMarker))); + } + } +} diff --git a/LinesRenderer.cs b/LinesRenderer.cs new file mode 100644 index 000000000..7ab857bf9 --- /dev/null +++ b/LinesRenderer.cs @@ -0,0 +1,57 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows; +using System.Windows.Media; + +namespace ICSharpCode.TreeView +{ + class LinesRenderer : FrameworkElement + { + static LinesRenderer() + { + pen = new Pen(Brushes.LightGray, 1); + pen.Freeze(); + } + + static Pen pen; + + SharpTreeNodeView NodeView + { + get { return TemplatedParent as SharpTreeNodeView; } + } + + protected override void OnRender(DrawingContext dc) + { + var indent = NodeView.CalculateIndent(); + var p = new Point(indent + 4.5, 0); + + if (!NodeView.Node.IsRoot || NodeView.ParentTreeView.ShowRootExpander) { + dc.DrawLine(pen, new Point(p.X, ActualHeight / 2), new Point(p.X + 10, ActualHeight / 2)); + } + + if (NodeView.Node.IsRoot) return; + + if (NodeView.Node.IsLast) { + dc.DrawLine(pen, p, new Point(p.X, ActualHeight / 2)); + } + else { + dc.DrawLine(pen, p, new Point(p.X, ActualHeight)); + } + + var current = NodeView.Node; + while (true) { + p.X -= 19; + current = current.Parent; + if (p.X < 0) break; + if (!current.IsLast) { + dc.DrawLine(pen, p, new Point(p.X, ActualHeight)); + } + } + } + } +} diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..71c9b636b --- /dev/null +++ b/Properties/AssemblyInfo.cs @@ -0,0 +1,42 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System.Reflection; +using System.Resources; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Windows; +using System.Windows.Markup; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("ICSharpCode.TreeView")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +//In order to begin building localizable applications, set +//CultureYouAreCodingWith in your .csproj file +//inside a . For example, if you are using US english +//in your source files, set the to en-US. Then uncomment +//the NeutralResourceLanguage attribute below. Update the "en-US" in +//the line below to match the UICulture setting in the project file. + +//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] + + +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] + + +[assembly: XmlnsPrefix("http://icsharpcode.net/sharpdevelop/treeview", "treeview")] + +[assembly: XmlnsDefinition("http://icsharpcode.net/sharpdevelop/treeview", "ICSharpCode.TreeView")] diff --git a/SharpGridView.cs b/SharpGridView.cs new file mode 100644 index 000000000..55f24e97e --- /dev/null +++ b/SharpGridView.cs @@ -0,0 +1,31 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows.Controls; +using System.Windows; + +namespace ICSharpCode.TreeView +{ + public class SharpGridView : GridView + { + static SharpGridView() + { + ItemContainerStyleKey = + new ComponentResourceKey(typeof(SharpTreeView), "GridViewItemContainerStyleKey"); + } + + public static ResourceKey ItemContainerStyleKey { get; private set; } + + protected override object ItemContainerDefaultStyleKey + { + get + { + return ItemContainerStyleKey; + } + } + } +} diff --git a/SharpTreeNode.cs b/SharpTreeNode.cs new file mode 100644 index 000000000..42d15d792 --- /dev/null +++ b/SharpTreeNode.cs @@ -0,0 +1,612 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows; +using System.ComponentModel; +using System.Collections.ObjectModel; +using System.Windows.Controls; +using System.Collections.Specialized; +using System.Windows.Input; + +namespace ICSharpCode.TreeView +{ + public class SharpTreeNode : INotifyPropertyChanged + { + #region Main + + static SharpTreeNode() + { + SelectedNodes = new List(); + ActiveNodes = new List(); + StartCuttedDataWatcher(); + } + + public static List SelectedNodes { get; private set; } + public static List ActiveNodes { get; private set; } + + static SharpTreeNode[] ActiveNodesArray + { + get + { + return ActiveNodes.ToArray(); + } + } + + public SharpTreeNode() + { + Children = new SharpTreeNodeCollection(this); + } + + public SharpTreeNodeCollection Children { get; private set; } + public SharpTreeNode Parent { get; internal set; } + + public virtual object Text + { + get { return null; } + } + + public virtual object Icon + { + get { return null; } + } + + public virtual object ToolTip + { + get { return null; } + } + + public int Level + { + get { return Parent != null ? Parent.Level + 1 : 0; } + } + + public bool IsRoot + { + get { return Parent == null; } + } + + //bool isSelected; + + //public bool IsSelected + //{ + // get { return isSelected; } + // set + // { + // isSelected = value; + // RaisePropertyChanged("IsSelected"); + // } + //} + + public virtual ContextMenu GetContextMenu() + { + return null; + } + + internal protected void OnChildrenChanged(NotifyCollectionChangedEventArgs e) + { + RaisePropertyChanged("ShowExpander"); + RaiseIsLastChangedIfNeeded(e); + } + + #endregion + + #region Expanding / LazyLoading + + public event EventHandler Collapsing; + + public virtual object ExpandedIcon + { + get { return Icon; } + } + + public virtual bool ShowExpander + { + get { return Children.Count > 0 || LazyLoading; } + } + + //public virtual bool ShowLoading + //{ + // get { return false; } + //} + + bool isExpanded; + + public bool IsExpanded + { + get { return isExpanded; } + set + { + if (isExpanded != value) { + isExpanded = value; + if (isExpanded) { + EnsureLazyChildren(); + } + else { + if (Collapsing != null) { + Collapsing(this, EventArgs.Empty); + } + } + RaisePropertyChanged("IsExpanded"); + } + } + } + + bool lazyLoading; + + public bool LazyLoading + { + get { return lazyLoading; } + set + { + lazyLoading = value; + if (lazyLoading) { + IsExpanded = false; + } + RaisePropertyChanged("LazyLoading"); + RaisePropertyChanged("ShowExpander"); + } + } + + bool showIcon; + + public bool ShowIcon + { + get { return showIcon; } + set { + showIcon = value; + RaisePropertyChanged("ShowIcon"); + } + } + + public virtual void LoadChildren() + { + } + + public void EnsureLazyChildren() + { + if (LazyLoading) { + LoadChildren(); + LazyLoading = false; + } + } + + #endregion + + #region Ancestors / Descendants + + public IEnumerable Descendants() + { + foreach (var child in Children) { + foreach (var child2 in child.DescendantsAndSelf()) { + yield return child2; + } + } + } + + public IEnumerable DescendantsAndSelf() + { + yield return this; + foreach (var child in Descendants()) { + yield return child; + } + } + + public IEnumerable ExpandedDescendants() + { + foreach (var child in Children) { + foreach (var child2 in child.ExpandedDescendantsAndSelf()) { + yield return child2; + } + } + } + + public IEnumerable ExpandedDescendantsAndSelf() + { + yield return this; + if (IsExpanded) { + foreach (var child in Children) { + foreach (var child2 in child.ExpandedDescendantsAndSelf()) { + yield return child2; + } + } + } + } + + public IEnumerable Ancestors() + { + var node = this; + while (node.Parent != null) { + yield return node.Parent; + node = node.Parent; + } + } + + public IEnumerable AncestorsAndSelf() + { + yield return this; + foreach (var node in Ancestors()) { + yield return node; + } + } + + #endregion + + #region Editing + + public virtual bool IsEditable + { + get { return false; } + } + + bool isEditing; + + public bool IsEditing + { + get { return isEditing; } + set + { + if (isEditing != value) { + isEditing = value; + RaisePropertyChanged("IsEditing"); + } + } + } + + public virtual string LoadEditText() + { + return null; + } + + public virtual bool SaveEditText(string value) + { + return true; + } + + #endregion + + #region Checkboxes + + public virtual bool IsCheckable + { + get { return false; } + } + + bool? isChecked; + + public bool? IsChecked + { + get { return isChecked; } + set + { + SetIsChecked(value, true); + } + } + + void SetIsChecked(bool? value, bool update) + { + if (isChecked != value) { + isChecked = value; + + if (update) { + if (IsChecked != null) { + foreach (var child in Descendants()) { + if (child.IsCheckable) { + child.SetIsChecked(IsChecked, false); + } + } + } + + foreach (var parent in Ancestors()) { + if (parent.IsCheckable) { + if (!parent.TryValueForIsChecked(true)) { + if (!parent.TryValueForIsChecked(false)) { + parent.SetIsChecked(null, false); + } + } + } + } + } + + RaisePropertyChanged("IsChecked"); + } + } + + bool TryValueForIsChecked(bool? value) + { + if (Children.Where(n => n.IsCheckable).All(n => n.IsChecked == value)) { + SetIsChecked(value, false); + return true; + } + return false; + } + + #endregion + + #region Cut / Copy / Paste / Delete + + static List cuttedNodes = new List(); + static IDataObject cuttedData; + static EventHandler requerySuggestedHandler; // for weak event + + static void StartCuttedDataWatcher() + { + requerySuggestedHandler = new EventHandler(CommandManager_RequerySuggested); + CommandManager.RequerySuggested += requerySuggestedHandler; + } + + static void CommandManager_RequerySuggested(object sender, EventArgs e) + { + if (cuttedData != null && !Clipboard.IsCurrent(cuttedData)) { + ClearCuttedData(); + } + } + + static void ClearCuttedData() + { + foreach (var node in cuttedNodes) { + node.IsCut = false; + } + cuttedNodes.Clear(); + cuttedData = null; + } + + //static public IEnumerable PurifyNodes(IEnumerable nodes) + //{ + // var list = nodes.ToList(); + // var array = list.ToArray(); + // foreach (var node1 in array) { + // foreach (var node2 in array) { + // if (node1.Descendants().Contains(node2)) { + // list.Remove(node2); + // } + // } + // } + // return list; + //} + + bool isCut; + + public bool IsCut + { + get { return isCut; } + private set + { + isCut = value; + RaisePropertyChanged("IsCut"); + } + } + + internal bool InternalCanCut() + { + return InternalCanCopy() && InternalCanDelete(); + } + + internal void InternalCut() + { + ClearCuttedData(); + cuttedData = Copy(ActiveNodesArray); + Clipboard.SetDataObject(cuttedData); + + foreach (var node in ActiveNodes) { + node.IsCut = true; + cuttedNodes.Add(node); + } + } + + internal bool InternalCanCopy() + { + return CanCopy(ActiveNodesArray); + } + + internal void InternalCopy() + { + Clipboard.SetDataObject(Copy(ActiveNodesArray)); + } + + internal bool InternalCanPaste() + { + return CanPaste(Clipboard.GetDataObject()); + } + + internal void InternalPaste() + { + Paste(Clipboard.GetDataObject()); + + if (cuttedData != null) { + DeleteCore(cuttedNodes.ToArray()); + ClearCuttedData(); + } + } + + internal bool InternalCanDelete() + { + return CanDelete(ActiveNodesArray); + } + + internal void InternalDelete() + { + Delete(ActiveNodesArray); + } + + public virtual bool CanDelete(SharpTreeNode[] nodes) + { + return false; + } + + public virtual void Delete(SharpTreeNode[] nodes) + { + } + + public virtual void DeleteCore(SharpTreeNode[] nodes) + { + } + + public virtual bool CanCopy(SharpTreeNode[] nodes) + { + return false; + } + + public virtual IDataObject Copy(SharpTreeNode[] nodes) + { + return null; + } + + public virtual bool CanPaste(IDataObject data) + { + return false; + } + + public virtual void Paste(IDataObject data) + { + EnsureLazyChildren(); + Drop(data, Children.Count, DropEffect.Copy); + } + + #endregion + + #region Drag and Drop + + internal bool InternalCanDrag() + { + return CanDrag(ActiveNodesArray); + } + + internal void InternalDrag(DependencyObject dragSource) + { + DragDrop.DoDragDrop(dragSource, Copy(ActiveNodesArray), DragDropEffects.All); + } + + internal bool InternalCanDrop(DragEventArgs e, int index) + { + var finalEffect = GetFinalEffect(e, index); + e.Effects = GetDragDropEffects(finalEffect); + return finalEffect != DropEffect.None; + } + + internal void InternalDrop(DragEventArgs e, int index) + { + if (LazyLoading) { + EnsureLazyChildren(); + index = Children.Count; + } + + var finalEffect = GetFinalEffect(e, index); + Drop(e.Data, index, finalEffect); + + if (finalEffect == DropEffect.Move) { + DeleteCore(ActiveNodesArray); + } + } + + DropEffect GetFinalEffect(DragEventArgs e, int index) + { + var requestedEffect = GetDropEffect(e); + var result = CanDrop(e.Data, requestedEffect); + if (result == DropEffect.Move) { + if (!CanDelete(ActiveNodesArray)) { + return DropEffect.None; + } + } + return result; + } + + static DropEffect GetDropEffect(DragEventArgs e) + { + if (e.Data != null) { + var all = DragDropKeyStates.ControlKey | DragDropKeyStates.ShiftKey | DragDropKeyStates.AltKey; + + if ((e.KeyStates & all) == DragDropKeyStates.ControlKey) { + return DropEffect.Copy; + } + if ((e.KeyStates & all) == DragDropKeyStates.AltKey) { + return DropEffect.Link; + } + if ((e.KeyStates & all) == (DragDropKeyStates.ControlKey | DragDropKeyStates.ShiftKey)) { + return DropEffect.Link; + } + return DropEffect.Move; + } + return DropEffect.None; + } + + static DragDropEffects GetDragDropEffects(DropEffect effect) + { + switch (effect) { + case DropEffect.Copy: + return DragDropEffects.Copy; + case DropEffect.Link: + return DragDropEffects.Link; + case DropEffect.Move: + return DragDropEffects.Move; + } + return DragDropEffects.None; + } + + public virtual bool CanDrag(SharpTreeNode[] nodes) + { + return false; + } + + public virtual DropEffect CanDrop(IDataObject data, DropEffect requestedEffect) + { + return DropEffect.None; + } + + public virtual void Drop(IDataObject data, int index, DropEffect finalEffect) + { + } + + #endregion + + #region IsLast (for TreeView lines) + + public bool IsLast + { + get + { + return Parent == null || + Parent.Children[Parent.Children.Count - 1] == this; + } + } + + void RaiseIsLastChangedIfNeeded(NotifyCollectionChangedEventArgs e) + { + switch (e.Action) { + case NotifyCollectionChangedAction.Add: + if (e.NewStartingIndex == Children.Count - 1) { + if (Children.Count > 1) { + Children[Children.Count - 2].RaisePropertyChanged("IsLast"); + } + Children[Children.Count - 1].RaisePropertyChanged("IsLast"); + } + break; + case NotifyCollectionChangedAction.Remove: + if (e.OldStartingIndex == Children.Count) { + if (Children.Count > 0) { + Children[Children.Count - 1].RaisePropertyChanged("IsLast"); + } + } + break; + } + } + + #endregion + + #region INotifyPropertyChanged Members + + public event PropertyChangedEventHandler PropertyChanged; + + public void RaisePropertyChanged(string name) + { + if (PropertyChanged != null) { + PropertyChanged(this, new PropertyChangedEventArgs(name)); + } + } + + #endregion + } +} diff --git a/SharpTreeNodeCollection.cs b/SharpTreeNodeCollection.cs new file mode 100644 index 000000000..9e51d94f9 --- /dev/null +++ b/SharpTreeNodeCollection.cs @@ -0,0 +1,49 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Collections.ObjectModel; +using System.Collections.Specialized; + +namespace ICSharpCode.TreeView +{ + public class SharpTreeNodeCollection : ObservableCollection + { + public SharpTreeNodeCollection(SharpTreeNode parent) + { + Parent = parent; + } + + public SharpTreeNode Parent { get; private set; } + + protected override void InsertItem(int index, SharpTreeNode node) + { + node.Parent = Parent; + base.InsertItem(index, node); + } + + protected override void RemoveItem(int index) + { + var node = this[index]; + node.Parent = null; + base.RemoveItem(index); + } + + protected override void ClearItems() + { + foreach (var node in this) { + node.Parent = null; + } + base.ClearItems(); + } + + protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) + { + base.OnCollectionChanged(e); + Parent.OnChildrenChanged(e); + } + } +} diff --git a/SharpTreeNodeView.cs b/SharpTreeNodeView.cs new file mode 100644 index 000000000..b85c1104a --- /dev/null +++ b/SharpTreeNodeView.cs @@ -0,0 +1,149 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows.Controls; +using System.Windows; +using System.Windows.Controls.Primitives; +using System.Windows.Media; +using System.Windows.Input; +using System.ComponentModel; +using System.Collections.Specialized; + +namespace ICSharpCode.TreeView +{ + public class SharpTreeNodeView : Control + { + static SharpTreeNodeView() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(SharpTreeNodeView), + new FrameworkPropertyMetadata(typeof(SharpTreeNodeView))); + } + + public static readonly DependencyProperty TextBackgroundProperty = + DependencyProperty.Register("TextBackground", typeof(Brush), typeof(SharpTreeNodeView)); + + public Brush TextBackground + { + get { return (Brush)GetValue(TextBackgroundProperty); } + set { SetValue(TextBackgroundProperty, value); } + } + + public SharpTreeNode Node + { + get { return DataContext as SharpTreeNode; } + } + + public SharpTreeViewItem ParentItem { get; private set; } + + public SharpTreeView ParentTreeView + { + get { return ParentItem.ParentTreeView; } + } + + internal LinesRenderer LinesRenderer { get; private set; } + + public override void OnApplyTemplate() + { + base.OnApplyTemplate(); + LinesRenderer = Template.FindName("linesRenderer", this) as LinesRenderer; + UpdateTemplate(); + } + + protected override void OnVisualParentChanged(DependencyObject oldParent) + { + base.OnVisualParentChanged(oldParent); + ParentItem = this.FindAncestor(); + ParentItem.NodeView = this; + } + + protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e) + { + base.OnPropertyChanged(e); + if (e.Property == DataContextProperty) { + UpdateDataContext(e.OldValue as SharpTreeNode, e.NewValue as SharpTreeNode); + } + } + + void UpdateDataContext(SharpTreeNode oldNode, SharpTreeNode newNode) + { + if (newNode != null) { + newNode.Collapsing += Node_Collapsing; + newNode.PropertyChanged += Node_PropertyChanged; + if (Template != null) { + UpdateTemplate(); + } + } + if (oldNode != null) { + oldNode.Collapsing -= Node_Collapsing; + oldNode.PropertyChanged -= Node_PropertyChanged; + } + } + + void Node_PropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == "IsEditing") { + OnIsEditingChanged(); + } + else if (e.PropertyName == "IsLast") { + if (ParentTreeView.ShowLines) { + foreach (var child in Node.ExpandedDescendantsAndSelf()) { + var container = ParentTreeView.ItemContainerGenerator.ContainerFromItem(child) as SharpTreeViewItem; + if (container != null) { + container.NodeView.LinesRenderer.InvalidateVisual(); + } + } + } + } + } + + void Node_Collapsing(object sender, EventArgs e) + { + ParentTreeView.HandleCollapsing(Node); + } + + void OnIsEditingChanged() + { + var textEditorContainer = Template.FindName("textEditorContainer", this) as Border; + if (Node.IsEditing) { + textEditorContainer.Child = new EditTextBox() { Item = ParentItem }; + } + else { + textEditorContainer.Child = null; + } + } + + void UpdateTemplate() + { + var spacer = Template.FindName("spacer", this) as FrameworkElement; + spacer.Width = CalculateIndent(); + + var expander = Template.FindName("expander", this) as ToggleButton; + if (ParentTreeView.Root == Node && !ParentTreeView.ShowRootExpander) { + expander.Visibility = Visibility.Collapsed; + } + else { + expander.ClearValue(VisibilityProperty); + } + } + + internal double CalculateIndent() + { + var result = 19 * Node.Level; + if (ParentTreeView.ShowRoot) { + if (!ParentTreeView.ShowRootExpander) { + if (ParentTreeView.Root != Node) { + result -= 15; + } + } + } + else { + result -= 19; + } + return result; + } + } +} diff --git a/SharpTreeView.cs b/SharpTreeView.cs new file mode 100644 index 000000000..2dc84d692 --- /dev/null +++ b/SharpTreeView.cs @@ -0,0 +1,458 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows.Controls; +using System.Windows; +using System.Windows.Media; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; + +namespace ICSharpCode.TreeView +{ + public class SharpTreeView : ListView + { + static SharpTreeView() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(SharpTreeView), + new FrameworkPropertyMetadata(typeof(SharpTreeView))); + + SelectionModeProperty.OverrideMetadata(typeof(SharpTreeView), + new FrameworkPropertyMetadata(SelectionMode.Extended)); + + AlternationCountProperty.OverrideMetadata(typeof(SharpTreeView), + new FrameworkPropertyMetadata(2)); + + DefaultItemContainerStyleKey = + new ComponentResourceKey(typeof(SharpTreeView), "DefaultItemContainerStyleKey"); + + VirtualizingStackPanel.VirtualizationModeProperty.OverrideMetadata(typeof(SharpTreeView), + new FrameworkPropertyMetadata(VirtualizationMode.Recycling)); + } + + public static ResourceKey DefaultItemContainerStyleKey { get; private set; } + + public SharpTreeView() + { + SetResourceReference(ItemContainerStyleProperty, DefaultItemContainerStyleKey); + } + + public static readonly DependencyProperty RootProperty = + DependencyProperty.Register("Root", typeof(SharpTreeNode), typeof(SharpTreeView)); + + public SharpTreeNode Root + { + get { return (SharpTreeNode)GetValue(RootProperty); } + set { SetValue(RootProperty, value); } + } + + public static readonly DependencyProperty ShowRootProperty = + DependencyProperty.Register("ShowRoot", typeof(bool), typeof(SharpTreeView), + new FrameworkPropertyMetadata(true)); + + public bool ShowRoot + { + get { return (bool)GetValue(ShowRootProperty); } + set { SetValue(ShowRootProperty, value); } + } + + public static readonly DependencyProperty ShowRootExpanderProperty = + DependencyProperty.Register("ShowRootExpander", typeof(bool), typeof(SharpTreeView), + new FrameworkPropertyMetadata(false)); + + public bool ShowRootExpander + { + get { return (bool)GetValue(ShowRootExpanderProperty); } + set { SetValue(ShowRootExpanderProperty, value); } + } + + public static readonly DependencyProperty AllowDropOrderProperty = + DependencyProperty.Register("AllowDropOrder", typeof(bool), typeof(SharpTreeView)); + + public bool AllowDropOrder + { + get { return (bool)GetValue(AllowDropOrderProperty); } + set { SetValue(AllowDropOrderProperty, value); } + } + + public static readonly DependencyProperty ShowLinesProperty = + DependencyProperty.Register("ShowLines", typeof(bool), typeof(SharpTreeView), + new FrameworkPropertyMetadata(true)); + + public bool ShowLines + { + get { return (bool)GetValue(ShowLinesProperty); } + set { SetValue(ShowLinesProperty, value); } + } + + public static bool GetShowAlternation(DependencyObject obj) + { + return (bool)obj.GetValue(ShowAlternationProperty); + } + + public static void SetShowAlternation(DependencyObject obj, bool value) + { + obj.SetValue(ShowAlternationProperty, value); + } + + public static readonly DependencyProperty ShowAlternationProperty = + DependencyProperty.RegisterAttached("ShowAlternation", typeof(bool), typeof(SharpTreeView), + new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.Inherits)); + + protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e) + { + base.OnPropertyChanged(e); + if (e.Property == RootProperty || + e.Property == ShowRootProperty || + e.Property == ShowRootExpanderProperty) { + Reload(); + } + } + + TreeFlattener flattener; + + void Reload() + { + if (flattener != null) { + flattener.Stop(); + } + if (Root != null) { + if (!(ShowRoot && ShowRootExpander)) { + Root.IsExpanded = true; + } + flattener = new TreeFlattener(Root, ShowRoot); + ItemsSource = flattener.List; + flattener.Start(); + } + } + + protected override DependencyObject GetContainerForItemOverride() + { + return new SharpTreeViewItem(); + } + + protected override bool IsItemItsOwnContainerOverride(object item) + { + return item is SharpTreeViewItem; + } + + protected override void PrepareContainerForItemOverride(DependencyObject element, object item) + { + base.PrepareContainerForItemOverride(element, item); + SharpTreeViewItem container = element as SharpTreeViewItem; + container.ParentTreeView = this; + } + + internal void HandleCollapsing(SharpTreeNode Node) + { + var selectedChilds = Node.Descendants().Where(n => SharpTreeNode.SelectedNodes.Contains(n)); + if (selectedChilds.Any()) { + var list = SelectedItems.Cast().Except(selectedChilds).ToList(); + list.AddOnce(Node); + SetSelectedItems(list); + } + } + + #region Track selection + + protected override void OnSelectionChanged(SelectionChangedEventArgs e) + { + foreach (SharpTreeNode node in e.RemovedItems) { + SharpTreeNode.SelectedNodes.Remove(node); + } + foreach (SharpTreeNode node in e.AddedItems) { + SharpTreeNode.SelectedNodes.AddOnce(node); + } + + if (IsKeyboardFocusWithin) { + foreach (SharpTreeNode node in e.RemovedItems) { + SharpTreeNode.ActiveNodes.Remove(node); + } + foreach (SharpTreeNode node in e.AddedItems) { + SharpTreeNode.ActiveNodes.AddOnce(node); + } + SortActiveNodes(); + } + base.OnSelectionChanged(e); + } + + protected override void OnPreviewGotKeyboardFocus(KeyboardFocusChangedEventArgs e) + { + foreach (SharpTreeNode node in SelectedItems) { + SharpTreeNode.ActiveNodes.AddOnce(node); + } + SortActiveNodes(); + } + + protected override void OnPreviewLostKeyboardFocus(KeyboardFocusChangedEventArgs e) + { + foreach (SharpTreeNode node in SelectedItems) { + SharpTreeNode.ActiveNodes.Remove(node); + } + } + + void SortActiveNodes() + { + SharpTreeNode.ActiveNodes.Sort(delegate(SharpTreeNode n1, SharpTreeNode n2) { + var index1 = Items.IndexOf(n1); + var index2 = Items.IndexOf(n2); + return index1.CompareTo(index2); + }); + } + + #endregion + + #region Drag and Drop + + protected override void OnDragEnter(DragEventArgs e) + { + OnDragOver(e); + } + + protected override void OnDragOver(DragEventArgs e) + { + e.Effects = DragDropEffects.None; + e.Handled = true; + + if (Root != null && !ShowRoot && Root.Children.Count == 0) { + Root.InternalCanDrop(e, 0); + } + } + + protected override void OnDrop(DragEventArgs e) + { + e.Effects = DragDropEffects.None; + e.Handled = true; + + if (Root != null && !ShowRoot && Root.Children.Count == 0) { + Root.InternalDrop(e, 0); + } + } + + internal void HandleDragEnter(SharpTreeViewItem item, DragEventArgs e) + { + HandleDragOver(item, e); + } + + internal void HandleDragOver(SharpTreeViewItem item, DragEventArgs e) + { + HidePreview(); + e.Handled = true; + + var target = GetDropTarget(item, e); + if (target != null) { + ShowPreview(target.Item, target.Place); + } + } + + internal void HandleDrop(SharpTreeViewItem item, DragEventArgs e) + { + HidePreview(); + e.Handled = true; + + var target = GetDropTarget(item, e); + if (target != null) { + target.Node.InternalDrop(e, target.Index); + } + } + + internal void HandleDragLeave(SharpTreeViewItem item, DragEventArgs e) + { + HidePreview(); + e.Handled = true; + } + + class DropTarget + { + public SharpTreeViewItem Item; + public DropPlace Place; + public double Y; + public SharpTreeNode Node; + public int Index; + } + + DropTarget GetDropTarget(SharpTreeViewItem item, DragEventArgs e) + { + var dropTargets = BuildDropTargets(item, e); + var y = e.GetPosition(item).Y; + foreach (var target in dropTargets) { + if (target.Y >= y) { + return target; + } + } + return null; + } + + List BuildDropTargets(SharpTreeViewItem item, DragEventArgs e) + { + var result = new List(); + var node = item.Node; + + if (AllowDropOrder) { + TryAddDropTarget(result, item, DropPlace.Before, e); + } + + TryAddDropTarget(result, item, DropPlace.Inside, e); + + if (AllowDropOrder) { + if (node.IsExpanded && node.Children.Count > 0) { + var firstChildItem = ItemContainerGenerator.ContainerFromItem(node.Children[0]) as SharpTreeViewItem; + TryAddDropTarget(result, firstChildItem, DropPlace.Before, e); + } + else { + TryAddDropTarget(result, item, DropPlace.After, e); + } + } + + var h = item.ActualHeight; + var y1 = 0.2 * h; + var y2 = h / 2; + var y3 = h - y1; + + if (result.Count == 2) { + if (result[0].Place == DropPlace.Inside && + result[1].Place != DropPlace.Inside) { + result[0].Y = y3; + } + else if (result[0].Place != DropPlace.Inside && + result[1].Place == DropPlace.Inside) { + result[0].Y = y1; + } + else { + result[0].Y = y2; + } + } + else if (result.Count == 3) { + result[0].Y = y1; + result[1].Y = y3; + } + if (result.Count > 0) { + result[result.Count - 1].Y = h; + } + return result; + } + + void TryAddDropTarget(List targets, SharpTreeViewItem item, DropPlace place, DragEventArgs e) + { + SharpTreeNode node; + int index; + + GetNodeAndIndex(item, place, out node, out index); + + if (node != null) { + e.Effects = DragDropEffects.None; + if (node.InternalCanDrop(e, index)) { + DropTarget target = new DropTarget() { + Item = item, + Place = place, + Node = node, + Index = index + }; + targets.Add(target); + } + } + } + + void GetNodeAndIndex(SharpTreeViewItem item, DropPlace place, out SharpTreeNode node, out int index) + { + node = null; + index = 0; + + if (place == DropPlace.Inside) { + node = item.Node; + index = node.Children.Count; + } + else if (place == DropPlace.Before) { + if (item.Node.Parent != null) { + node = item.Node.Parent; + index = node.Children.IndexOf(item.Node); + } + } + else { + if (item.Node.Parent != null) { + node = item.Node.Parent; + index = node.Children.IndexOf(item.Node) + 1; + } + } + } + + SharpTreeNodeView previewNodeView; + InsertMarker insertMarker; + DropPlace previewPlace; + + enum DropPlace + { + Before, Inside, After + } + + void ShowPreview(SharpTreeViewItem item, DropPlace place) + { + previewNodeView = item.NodeView; + previewPlace = place; + + if (place == DropPlace.Inside) { + previewNodeView.TextBackground = SystemColors.HighlightBrush; + previewNodeView.Foreground = SystemColors.HighlightTextBrush; + } + else { + if (insertMarker == null) { + var adornerLayer = AdornerLayer.GetAdornerLayer(this); + var adorner = new GeneralAdorner(this); + insertMarker = new InsertMarker(); + adorner.Child = insertMarker; + adornerLayer.Add(adorner); + } + + insertMarker.Visibility = Visibility.Visible; + + var p1 = previewNodeView.TransformToVisual(this).Transform(new Point()); + var p = new Point(p1.X + previewNodeView.CalculateIndent() + 4.5, p1.Y - 3); + + if (place == DropPlace.After) { + p.Y += previewNodeView.ActualHeight; + } + + insertMarker.Margin = new Thickness(p.X, p.Y, 0, 0); + + SharpTreeNodeView secondNodeView = null; + var index = flattener.List.IndexOf(item.Node); + + if (place == DropPlace.Before) { + if (index > 0) { + secondNodeView = (ItemContainerGenerator.ContainerFromIndex(index - 1) as SharpTreeViewItem).NodeView; + } + } + else if (index + 1 < flattener.List.Count) { + secondNodeView = (ItemContainerGenerator.ContainerFromIndex(index + 1) as SharpTreeViewItem).NodeView; + } + + var w = p1.X + previewNodeView.ActualWidth - p.X; + + if (secondNodeView != null) { + var p2 = secondNodeView.TransformToVisual(this).Transform(new Point()); + w = Math.Max(w, p2.X + secondNodeView.ActualWidth - p.X); + } + + insertMarker.Width = w + 10; + } + } + + void HidePreview() + { + if (previewNodeView != null) { + previewNodeView.ClearValue(SharpTreeNodeView.TextBackgroundProperty); + previewNodeView.ClearValue(SharpTreeNodeView.ForegroundProperty); + if (insertMarker != null) { + insertMarker.Visibility = Visibility.Collapsed; + } + previewNodeView = null; + } + } + + #endregion + } +} diff --git a/SharpTreeViewItem.cs b/SharpTreeViewItem.cs new file mode 100644 index 000000000..4201f1393 --- /dev/null +++ b/SharpTreeViewItem.cs @@ -0,0 +1,195 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows.Controls; +using System.Windows; +using System.Windows.Media; +using System.Windows.Input; +using System.Diagnostics; + +namespace ICSharpCode.TreeView +{ + public class SharpTreeViewItem : ListViewItem + { + static SharpTreeViewItem() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(SharpTreeViewItem), + new FrameworkPropertyMetadata(typeof(SharpTreeViewItem))); + + RegisterCommands(); + } + + public SharpTreeNode Node + { + get { return DataContext as SharpTreeNode; } + } + + public SharpTreeNodeView NodeView { get; internal set; } + public SharpTreeView ParentTreeView { get; internal set; } + + protected override void OnKeyDown(KeyEventArgs e) + { + switch (e.Key) { + case Key.F2: + if (SharpTreeNode.ActiveNodes.Count == 1 && Node.IsEditable) { + Node.IsEditing = true; + } + break; + case Key.Escape: + Node.IsEditing = false; + break; + case Key.Left: + Node.IsExpanded = false; + break; + case Key.Right: + Node.IsExpanded = true; + break; + } + } + + protected override void OnContextMenuOpening(ContextMenuEventArgs e) + { + ContextMenu = Node.GetContextMenu(); + } + + protected override void OnContextMenuClosing(ContextMenuEventArgs e) + { + ClearValue(ContextMenuProperty); + } + + #region Mouse + + Point startPoint; + bool wasSelected; + + protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e) + { + wasSelected = IsSelected; + if (!IsSelected) { + base.OnMouseLeftButtonDown(e); + } + + if (Mouse.LeftButton == MouseButtonState.Pressed) { + startPoint = e.GetPosition(null); + CaptureMouse(); + + if (e.ClickCount == 2) { + if (!Node.IsRoot || ParentTreeView.ShowRootExpander) { + Node.IsExpanded = !Node.IsExpanded; + } + } + } + } + + protected override void OnMouseMove(MouseEventArgs e) + { + if (IsMouseCaptured) { + var currentPoint = e.GetPosition(null); + if (Math.Abs(currentPoint.X - startPoint.X) >= SystemParameters.MinimumHorizontalDragDistance || + Math.Abs(currentPoint.Y - startPoint.Y) >= SystemParameters.MinimumVerticalDragDistance) { + + if (Node.InternalCanDrag()) { + Node.InternalDrag(this); + } + } + } + } + + protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e) + { + ReleaseMouseCapture(); + if (wasSelected) { + base.OnMouseLeftButtonDown(e); + } + } + + #endregion + + #region Drag and Drop + + protected override void OnDragEnter(DragEventArgs e) + { + ParentTreeView.HandleDragEnter(this, e); + } + + protected override void OnDragOver(DragEventArgs e) + { + ParentTreeView.HandleDragOver(this, e); + } + + protected override void OnDrop(DragEventArgs e) + { + ParentTreeView.HandleDrop(this, e); + } + + protected override void OnDragLeave(DragEventArgs e) + { + ParentTreeView.HandleDragLeave(this, e); + } + + #endregion + + #region Cut / Copy / Paste / Delete Commands + + static void RegisterCommands() + { + CommandManager.RegisterClassCommandBinding(typeof(SharpTreeViewItem), + new CommandBinding(ApplicationCommands.Cut, HandleExecuted_Cut, HandleCanExecute_Cut)); + + CommandManager.RegisterClassCommandBinding(typeof(SharpTreeViewItem), + new CommandBinding(ApplicationCommands.Copy, HandleExecuted_Copy, HandleCanExecute_Copy)); + + CommandManager.RegisterClassCommandBinding(typeof(SharpTreeViewItem), + new CommandBinding(ApplicationCommands.Paste, HandleExecuted_Paste, HandleCanExecute_Paste)); + + CommandManager.RegisterClassCommandBinding(typeof(SharpTreeViewItem), + new CommandBinding(ApplicationCommands.Delete, HandleExecuted_Delete, HandleCanExecute_Delete)); + } + + static void HandleExecuted_Cut(object sender, ExecutedRoutedEventArgs e) + { + (sender as SharpTreeViewItem).Node.InternalCut(); + } + + static void HandleCanExecute_Cut(object sender, CanExecuteRoutedEventArgs e) + { + e.CanExecute = (sender as SharpTreeViewItem).Node.InternalCanCut(); + } + + static void HandleExecuted_Copy(object sender, ExecutedRoutedEventArgs e) + { + (sender as SharpTreeViewItem).Node.InternalCopy(); + } + + static void HandleCanExecute_Copy(object sender, CanExecuteRoutedEventArgs e) + { + e.CanExecute = (sender as SharpTreeViewItem).Node.InternalCanCopy(); + } + + static void HandleExecuted_Paste(object sender, ExecutedRoutedEventArgs e) + { + (sender as SharpTreeViewItem).Node.InternalPaste(); + } + + static void HandleCanExecute_Paste(object sender, CanExecuteRoutedEventArgs e) + { + e.CanExecute = (sender as SharpTreeViewItem).Node.InternalCanPaste(); + } + + static void HandleExecuted_Delete(object sender, ExecutedRoutedEventArgs e) + { + (sender as SharpTreeViewItem).Node.InternalDelete(); + } + + static void HandleCanExecute_Delete(object sender, CanExecuteRoutedEventArgs e) + { + e.CanExecute = (sender as SharpTreeViewItem).Node.InternalCanDelete(); + } + + #endregion + } +} diff --git a/Themes/Generic.xaml b/Themes/Generic.xaml new file mode 100644 index 000000000..1bed1dd7c --- /dev/null +++ b/Themes/Generic.xaml @@ -0,0 +1,312 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/TreeFlattener.cs b/TreeFlattener.cs new file mode 100644 index 000000000..1c6545243 --- /dev/null +++ b/TreeFlattener.cs @@ -0,0 +1,150 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Collections.Specialized; + +namespace ICSharpCode.TreeView +{ + class TreeFlattener + { + public TreeFlattener(SharpTreeNode root, bool includeRoot) + { + this.root = root; + this.includeRoot = includeRoot; + List = new ObservableCollection(); + } + + SharpTreeNode root; + bool includeRoot; + + public ObservableCollection List { get; private set; } + + public void Start() + { + if (includeRoot) { + Add(root); + } + else { + root.Children.CollectionChanged += node_ChildrenChanged; + } + + foreach (var node in root.ExpandedDescendants()) { + Add(node); + } + } + + public void Stop() + { + while (List.Count > 0) { + RemoveAt(0); + } + } + + void Add(SharpTreeNode node) + { + Insert(List.Count, node); + } + + void Insert(int index, SharpTreeNode node) + { + List.Insert(index, node); + node.PropertyChanged += node_PropertyChanged; + if (node.IsExpanded) { + node.Children.CollectionChanged += node_ChildrenChanged; + } + } + + void RemoveAt(int index) + { + var node = List[index]; + List.RemoveAt(index); + node.PropertyChanged -= node_PropertyChanged; + if (node.IsExpanded) { + node.Children.CollectionChanged -= node_ChildrenChanged; + } + } + + void ClearDescendants(SharpTreeNode node) + { + var index = List.IndexOf(node); + while (index + 1 < List.Count && List[index + 1].Level > node.Level) { + RemoveAt(index + 1); + } + } + + void node_PropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == "IsExpanded") { + var node = sender as SharpTreeNode; + + if (node.IsExpanded) { + var index = List.IndexOf(node); + foreach (var childNode in node.ExpandedDescendants()) { + Insert(++index, childNode); + } + node.Children.CollectionChanged += node_ChildrenChanged; + } + else { + ClearDescendants(node); + node.Children.CollectionChanged -= node_ChildrenChanged; + } + } + } + + void Insert(SharpTreeNode parent, int index, SharpTreeNode node) + { + int finalIndex = 0; + if (index > 0) { + finalIndex = List.IndexOf(parent.Children[index - 1]) + 1; + while (finalIndex < List.Count && List[finalIndex].Level > node.Level) { + finalIndex++; + } + } + else { + finalIndex = List.IndexOf(parent) + 1; + } + Insert(finalIndex, node); + } + + void RemoveAt(SharpTreeNode parent, int index, SharpTreeNode node) + { + var i = List.IndexOf(node); + foreach (var child in node.ExpandedDescendantsAndSelf()) { + RemoveAt(i); + } + } + + void node_ChildrenChanged(object sender, NotifyCollectionChangedEventArgs e) + { + var collection = sender as SharpTreeNodeCollection; + var parent = collection.Parent; + var index = List.IndexOf(collection.Parent) + 1; + + switch (e.Action) { + case NotifyCollectionChangedAction.Add: + Insert(parent, e.NewStartingIndex, e.NewItems[0] as SharpTreeNode); + break; + case NotifyCollectionChangedAction.Remove: + RemoveAt(parent, e.OldStartingIndex, e.OldItems[0] as SharpTreeNode); + break; + case NotifyCollectionChangedAction.Move: + RemoveAt(parent, e.OldStartingIndex, e.OldItems[0] as SharpTreeNode); + Insert(parent, e.NewStartingIndex, e.NewItems[0] as SharpTreeNode); + break; + case NotifyCollectionChangedAction.Replace: + RemoveAt(parent, e.OldStartingIndex, e.OldItems[0] as SharpTreeNode); + Insert(parent, e.NewStartingIndex, e.NewItems[0] as SharpTreeNode); + break; + case NotifyCollectionChangedAction.Reset: + ClearDescendants(parent); + break; + } + } + } +}