/* Copyright (c) Citrix Systems, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, * with or without modification, are permitted provided * that the following conditions are met: * * * Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ using System.Collections.Generic; using System.Windows.Forms; using System.ComponentModel; using System; using System.Drawing; using System.Diagnostics; using System.Collections.ObjectModel; namespace XenAdmin.Controls { /// /// A that only adds s when they become visible. /// public partial class VirtualTreeView : MultiSelectTreeView, IVirtualTreeNodeCollectionOwner, IHaveNodes { private readonly VirtualTreeNodeCollection _nodes; private readonly VirtualTreeSelectedNodeCollection _selectedNodes; /// /// Initializes a new instance of the class. /// public VirtualTreeView() { _nodes = new VirtualTreeNodeCollection(this); _selectedNodes = new VirtualTreeSelectedNodeCollection(this); } /// /// Gets or sets the tree node that is currently selected in the tree view control. /// /// /// /// The that is currently selected in the tree view control. /// [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] [Browsable(false)] public new VirtualTreeNode SelectedNode { get { return (VirtualTreeNode)base.SelectedNode; } set { if (value != null) { Devirtualise(value); } base.SelectedNode = value; } } /// /// Gets or sets the first fully-visible tree node in the tree view control. /// /// /// /// A that represents the first fully-visible tree node in the tree view control. /// [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] [Browsable(false)] public new VirtualTreeNode TopNode { get { return (VirtualTreeNode)base.TopNode; } set { Util.ThrowIfParameterNull(value, "value"); Devirtualise(value); base.TopNode = value; } } private static List GetParents(VirtualTreeNode node) { List parents = new List(); VirtualTreeNode nn = node.Parent; while (nn != null) { parents.Add(nn); nn = nn.Parent; } parents.Reverse(); return parents; } /// /// Populates the real nodes for every node from the root down to this node. /// private void Devirtualise(VirtualTreeNode node) { foreach (VirtualTreeNode parent in GetParents(node)) { Devirtualise(parent.Nodes); } } /// /// Ensures that all real nodes are up-to-date with the virtual ones. /// private void Devirtualise(VirtualTreeNodeCollection nodes) { if (NodeCollectionRequiresUpdate(nodes.Owner)) { nodes.Owner.RealNodes.Clear(); nodes.Owner.RealNodes.AddRange(new List(nodes).ToArray()); } } private static bool NodeCollectionRequiresUpdate(IVirtualTreeNodeCollectionOwner owner) { if (owner.RealNodes.Count == 1 && owner.RealNodes[0] is DummyTreeNode) { return true; } if (owner.RealNodes.Count != owner.Nodes.Count) { return true; } for (int i = 0; i < owner.Nodes.Count; i++) { if (owner.RealNodes[i] != owner.Nodes[i]) { return true; } } return false; } public new VirtualTreeSelectedNodeCollection SelectedNodes { get { return _selectedNodes; } } #region IVirtualTreeNodeCollectionOwner Members public new VirtualTreeNodeCollection Nodes { get { return _nodes; } } MultiSelectTreeNodeCollection IVirtualTreeNodeCollectionOwner.RealNodes { get { return base.Nodes; } } #endregion #region IHaveNodes Members VirtualTreeView.VirtualTreeNodeCollection IHaveNodes.Nodes { get { return Nodes; } } object IHaveNodes.Tag { get { return Tag; } } void IHaveNodes.Expand() { } void IHaveNodes.Collapse() { } #endregion /// /// Retrieves the tree node that is at the specified point. /// public new VirtualTreeNode GetNodeAt(Point pt) { return (VirtualTreeNode)base.GetNodeAt(pt); } /// /// Retrieves the tree node that is at the specified point. /// public new VirtualTreeNode GetNodeAt(int x, int y) { return (VirtualTreeNode)base.GetNodeAt(x, y); } /// /// Merges the specified node tree with the existing one. Automatically does a BeginUpdate /// if the nodes change. /// /// The new root nodes. public void UpdateRootNodes(IEnumerable newRootNodes) { Util.ThrowIfParameterNull(newRootNodes, "newRootNodes"); foreach (VirtualTreeNode node in newRootNodes) { if (node.TreeView != null) { throw new ArgumentException("newRootNodes should not be attached to a TreeView.", "newRootNodes"); } } bool doneBeginUpdate = false; UpdateNodes(Nodes, new List(newRootNodes), ref doneBeginUpdate); } private void UpdateNodes(VirtualTreeNodeCollection oldNodes, IList newNodes, ref bool doneBeginUpdate) { for (int i = 0; i < Math.Max(oldNodes.Count, newNodes.Count); ) { if (oldNodes.Count > i && newNodes.Count > i) { if (oldNodes[i].Nodes.Count > 0 && newNodes[i].Nodes.Count == 0 && oldNodes[i].IsExpanded) { // fix for issues CA-34486 and CA-36409 // see VirtualTreeViewTests.TestUpdateWhenRemoveAllChildNodesOfExandedParent // When merging sets of tree-nodes using UpdateRootNodes, if you remove all of // the child-nodes from a node, the node still remembers its IsExpanded state // from before the nodes were removed regardless of whether you call Collapse() // or Expand() after the nodes were removed. // This causes problems for the Virtual treeview as it // relies the BeforeExpanded event to convert DummyTreeNodes into VirtualTreeNodes // on population. oldNodes[i].Collapse(); } UpdateNode(newNodes[i], oldNodes[i], ref doneBeginUpdate); UpdateNodes(oldNodes[i].Nodes, newNodes[i].Nodes, ref doneBeginUpdate); i++; } else if (oldNodes.Count <= i && newNodes.Count > i) { LogTreeView("Adding node " + newNodes[i].Text); DoBeginUpdateIfRequired(ref doneBeginUpdate); oldNodes.Add(newNodes[i]); UpdateNodes(oldNodes[i].Nodes, newNodes[i].Nodes, ref doneBeginUpdate); i++; } else { LogTreeView("Removing node " + oldNodes[i].Text); DoBeginUpdateIfRequired(ref doneBeginUpdate); oldNodes.RemoveAt(i); } } } private void DoBeginUpdateIfRequired(ref bool doneBeginUpdate) { if (!doneBeginUpdate) { doneBeginUpdate = true; BeginUpdate(); } } [Conditional("DEBUG")] private void LogTreeView(string s) { //Debug.WriteLine(s); } private void UpdateNode(VirtualTreeNode src, VirtualTreeNode dest, ref bool doneBeginUpdate) { if (dest.ImageIndex != src.ImageIndex) { DoBeginUpdateIfRequired(ref doneBeginUpdate); dest.ImageIndex = src.ImageIndex; } if (dest.SelectedImageIndex != src.SelectedImageIndex) { DoBeginUpdateIfRequired(ref doneBeginUpdate); dest.SelectedImageIndex = src.SelectedImageIndex; } if (dest.Text != src.Text) { LogTreeView("Overwriting " + src.Text + " with " + dest.Text); DoBeginUpdateIfRequired(ref doneBeginUpdate); dest.Text = src.Text; } if (dest.Tag != src.Tag) { dest.Tag = src.Tag; } if (dest.NodeFont != src.NodeFont) { DoBeginUpdateIfRequired(ref doneBeginUpdate); dest.NodeFont = src.NodeFont; } if (dest.BackColor != src.BackColor) { DoBeginUpdateIfRequired(ref doneBeginUpdate); dest.BackColor = src.BackColor; } if (dest.ForeColor != src.ForeColor) { DoBeginUpdateIfRequired(ref doneBeginUpdate); dest.ForeColor = src.ForeColor; } if (dest.Name != src.Name) { DoBeginUpdateIfRequired(ref doneBeginUpdate); dest.Name = src.Name; } if (dest.ShowCheckBox != src.ShowCheckBox) { DoBeginUpdateIfRequired(ref doneBeginUpdate); dest.ShowCheckBox = src.ShowCheckBox; } if (dest.Checked != src.Checked) { DoBeginUpdateIfRequired(ref doneBeginUpdate); dest.Checked = src.Checked; } } /// /// Iterates through every node of this . /// public new IEnumerable AllNodes { get { foreach (VirtualTreeNode node in Nodes) { yield return node; foreach (VirtualTreeNode n in node.Descendants) { yield return n; } } } } #region Virtual overrides protected sealed override void OnAfterCheck(TreeViewEventArgs e) { VirtualTreeNode node = (VirtualTreeNode)e.Node; OnAfterCheck(new VirtualTreeViewEventArgs(node, e.Action)); } protected sealed override void OnBeforeCheck(TreeViewCancelEventArgs e) { VirtualTreeNode node = (VirtualTreeNode)e.Node; VirtualTreeViewCancelEventArgs args = new VirtualTreeViewCancelEventArgs(node, e.Cancel, e.Action); OnBeforeCheck(args); e.Cancel = args.Cancel; } protected sealed override void OnAfterCollapse(TreeViewEventArgs e) { VirtualTreeNode node = (VirtualTreeNode)e.Node; OnAfterCollapse(new VirtualTreeViewEventArgs(node, e.Action)); } protected sealed override void OnBeforeCollapse(TreeViewCancelEventArgs e) { VirtualTreeNode node = (VirtualTreeNode)e.Node; VirtualTreeViewCancelEventArgs args = new VirtualTreeViewCancelEventArgs(node, e.Cancel, e.Action); OnBeforeCollapse(args); e.Cancel = args.Cancel; } protected sealed override void OnAfterExpand(TreeViewEventArgs e) { VirtualTreeNode node = (VirtualTreeNode)e.Node; OnAfterExpand(new VirtualTreeViewEventArgs(node, e.Action)); } protected sealed override void OnBeforeExpand(TreeViewCancelEventArgs e) { VirtualTreeNode node = (VirtualTreeNode)e.Node; Devirtualise(node.Nodes); VirtualTreeViewCancelEventArgs args = new VirtualTreeViewCancelEventArgs(node, e.Cancel, e.Action); OnBeforeExpand(args); e.Cancel = args.Cancel; } protected sealed override void OnAfterDeselect(TreeViewEventArgs e) { VirtualTreeNode node = (VirtualTreeNode)e.Node; OnAfterDeselect(new VirtualTreeViewEventArgs(node)); } protected sealed override void OnBeforeDeselect(TreeViewEventArgs e) { VirtualTreeNode node = (VirtualTreeNode)e.Node; OnBeforeDeselect(new VirtualTreeViewEventArgs(node)); } protected sealed override void OnAfterSelect(TreeViewEventArgs e) { VirtualTreeNode node = (VirtualTreeNode)e.Node; OnAfterSelect(new VirtualTreeViewEventArgs(node, e.Action)); } protected sealed override void OnBeforeSelect(MultiSelectTreeViewCancelEventArgs e) { VirtualTreeNode node = (VirtualTreeNode)e.Node; VirtualTreeViewCancelEventArgs args = new VirtualTreeViewCancelEventArgs(node, e.Cancel, e.Action); OnBeforeSelect(args); e.Cancel = args.Cancel; } protected sealed override void OnNodeMouseClick(TreeNodeMouseClickEventArgs e) { VirtualTreeNode node = (VirtualTreeNode)e.Node; OnNodeMouseClick(new VirtualTreeNodeMouseClickEventArgs(node, e.Button, e.Clicks, e.X, e.Y)); } protected sealed override void OnNodeMouseDoubleClick(TreeNodeMouseClickEventArgs e) { VirtualTreeNode node = (VirtualTreeNode)e.Node; OnNodeMouseDoubleClick(new VirtualTreeNodeMouseClickEventArgs(node, e.Button, e.Clicks, e.X, e.Y)); } protected sealed override void OnAfterLabelEdit(NodeLabelEditEventArgs e) { VirtualTreeNode node = (VirtualTreeNode)e.Node; VirtualNodeLabelEditEventArgs args = new VirtualNodeLabelEditEventArgs(node, e.Label); OnAfterLabelEdit(args); e.CancelEdit = args.CancelEdit; } protected sealed override void OnBeforeLabelEdit(NodeLabelEditEventArgs e) { VirtualTreeNode node = (VirtualTreeNode)e.Node; VirtualNodeLabelEditEventArgs args = new VirtualNodeLabelEditEventArgs(node, e.Label); OnBeforeLabelEdit(args); e.CancelEdit = args.CancelEdit; } protected sealed override void OnItemDrag(MultiSelectTreeViewItemDragEventArgs e) { List nodes = new List(); foreach (MultiSelectTreeNode n in e.Nodes) { nodes.Add((VirtualTreeNode)n); } OnItemDrag(new VirtualTreeViewItemDragEventArgs(e.Button, nodes)); } protected virtual void OnAfterCheck(VirtualTreeViewEventArgs e) { EventHandler handler = AfterCheck; if (handler != null) { handler(this, e); } } protected virtual void OnBeforeCheck(VirtualTreeViewCancelEventArgs e) { EventHandler handler = BeforeCheck; if (handler != null) { handler(this, e); } } protected virtual void OnAfterCollapse(VirtualTreeViewEventArgs e) { EventHandler handler = AfterCollapse; if (handler != null) { handler(this, e); } } protected virtual void OnBeforeCollapse(VirtualTreeViewCancelEventArgs e) { EventHandler handler = BeforeCollapse; if (handler != null) { handler(this, e); } } protected virtual void OnAfterExpand(VirtualTreeViewEventArgs e) { EventHandler handler = AfterExpand; if (handler != null) { handler(this, e); } } protected virtual void OnBeforeExpand(VirtualTreeViewCancelEventArgs e) { EventHandler handler = BeforeExpand; if (handler != null) { handler(this, e); } } protected virtual void OnAfterDeselect(VirtualTreeViewEventArgs e) { EventHandler handler = AfterDeselect; if (handler != null) { handler(this, e); } } protected virtual void OnBeforeDeselect(VirtualTreeViewEventArgs e) { EventHandler handler = BeforeDeselect; if (handler != null) { handler(this, e); } } protected virtual void OnAfterSelect(VirtualTreeViewEventArgs e) { EventHandler handler = AfterSelect; if (handler != null) { handler(this, e); } } protected virtual void OnBeforeSelect(VirtualTreeViewCancelEventArgs e) { EventHandler handler = BeforeSelect; if (handler != null) { handler(this, e); } } protected virtual void OnBeforeLabelEdit(VirtualNodeLabelEditEventArgs e) { EventHandler handler = BeforeLabelEdit; if (handler != null) { handler(this, e); } } protected virtual void OnAfterLabelEdit(VirtualNodeLabelEditEventArgs e) { EventHandler handler = AfterLabelEdit; if (handler != null) { handler(this, e); } } protected virtual void OnNodeMouseClick(VirtualTreeNodeMouseClickEventArgs e) { EventHandler handler = NodeMouseClick; if (handler != null) { handler(this, e); } } protected virtual void OnNodeMouseDoubleClick(VirtualTreeNodeMouseClickEventArgs e) { EventHandler handler = NodeMouseDoubleClick; if (handler != null) { handler(this, e); } } protected virtual void OnItemDrag(VirtualTreeViewItemDragEventArgs e) { EventHandler handler = ItemDrag; if (handler != null) { handler(this, e); } } #endregion #region Events /// /// Occurs after the tree node check box is checked. /// [Category("Behavior"), Description("Occurs after the tree node check box is checked.")] public new event EventHandler AfterCheck; /// /// Occurs before the tree node check box is checked. /// [Category("Behavior"), Description("Occurs before the tree node check box is checked.")] public new event EventHandler BeforeCheck; /// /// Occurs after the tree node is collapsed. /// [Category("Behavior"), Description("Occurs after the tree node is collapsed.")] public new event EventHandler AfterCollapse; /// /// Occurs before the tree node is collapsed. /// [Category("Behavior"), Description("Occurs before the tree node is collapsed.")] public new event EventHandler BeforeCollapse; /// /// Occurs after the tree node is expanded. /// [Category("Behavior"), Description("Occurs after the tree node is expanded.")] public new event EventHandler AfterExpand; /// /// Occurs before the tree node is expanded. /// [Category("Behavior"), Description("Occurs after the tree node is expanded.")] public new event EventHandler BeforeExpand; /// /// Occurs after the tree node is deselected. /// [Category("Behavior"), Description("Occurs after the tree node is deselected.")] public new event EventHandler AfterDeselect; /// /// Occurs before the tree node is deselected. /// [Category("Behavior"), Description("Occurs after the tree node is deselected.")] public new event EventHandler BeforeDeselect; /// /// Occurs after the tree node is selected. /// [Category("Behavior"), Description("Occurs after the tree node is selected.")] public new event EventHandler AfterSelect; /// /// Occurs before the tree node is selected. /// [Category("Behavior"), Description("Occurs before the tree node is selected.")] public new event EventHandler BeforeSelect; /// /// Occurs after the node is clicked. /// [Category("Behavior"), Description("Occurs after the node is clicked.")] public new event EventHandler NodeMouseClick; /// /// Occurs after the node is double clicked. /// [Category("Behavior"), Description("Occurs after the node is double clicked.")] public new event EventHandler NodeMouseDoubleClick; /// /// Occurs after the tree node label is edited. /// [Category("Behavior"), Description("Occurs after the tree node label is edited.")] public new event EventHandler AfterLabelEdit; /// /// Occurs before the tree node label is edited. /// [Category("Behavior"), Description("Occurs before the tree node label is edited.")] public new event EventHandler BeforeLabelEdit; /// /// Occurs when a node starts being dragged. /// [Category("Behavior"), Description("Occurs when a node starts being dragged.")] public new event EventHandler ItemDrag; #endregion /// /// Used to ensure that the '+' expander thingy is visible when a node has children. /// private class DummyTreeNode : MultiSelectTreeNode { } } internal interface IVirtualTreeNodeCollectionOwner { /// /// Gets a complete collection of nodes owned. /// VirtualTreeView.VirtualTreeNodeCollection Nodes { get;} /// /// Gets a collection of the nodes which are actually added to the base . /// MultiSelectTreeNodeCollection RealNodes { get;} } internal interface IHaveNodes { VirtualTreeView.VirtualTreeNodeCollection Nodes { get; } object Tag { get; } void Expand(); void Collapse(); } #region VirtualTreeView EventArgs classes public class VirtualTreeViewEventArgs : EventArgs { private readonly TreeViewAction _action; private readonly VirtualTreeNode _node; public VirtualTreeViewEventArgs(VirtualTreeNode node) { Util.ThrowIfParameterNull(node, "node"); _node = node; } public VirtualTreeViewEventArgs(VirtualTreeNode node, TreeViewAction action) : this(node) { _action = action; } public TreeViewAction Action { get { return _action; } } public VirtualTreeNode Node { get { return _node; } } } public class VirtualTreeViewCancelEventArgs : CancelEventArgs { private readonly TreeViewAction _action; private readonly VirtualTreeNode _node; public VirtualTreeViewCancelEventArgs(VirtualTreeNode node, bool cancel, TreeViewAction action) : base(cancel) { _node = node; _action = action; } public TreeViewAction Action { get { return _action; } } public VirtualTreeNode Node { get { return _node; } } } public class VirtualTreeNodeMouseClickEventArgs : MouseEventArgs { private readonly VirtualTreeNode _node; public VirtualTreeNodeMouseClickEventArgs(VirtualTreeNode node, MouseButtons button, int clicks, int x, int y) : base(button , clicks , x , y, 0) { _node = node; } public VirtualTreeNode Node { get { return _node; } } } public class VirtualNodeLabelEditEventArgs : EventArgs { private bool _cancelEdit; private readonly string _label; private readonly VirtualTreeNode _node; public VirtualNodeLabelEditEventArgs(VirtualTreeNode node) { Util.ThrowIfParameterNull(node, "node"); _node = node; _label = null; } public VirtualNodeLabelEditEventArgs(VirtualTreeNode node, string label) : this(node) { _label = label; } public bool CancelEdit { get { return _cancelEdit; } set { _cancelEdit = value; } } public string Label { get { return _label; } } public VirtualTreeNode Node { get { return _node; } } } public class VirtualTreeViewItemDragEventArgs : EventArgs { private readonly ReadOnlyCollection _nodes; private readonly MouseButtons _button; public VirtualTreeViewItemDragEventArgs(MouseButtons button) : this(button, new VirtualTreeNode[0]) { } public VirtualTreeViewItemDragEventArgs(MouseButtons button, IEnumerable nodes) { _nodes = new ReadOnlyCollection(new List(nodes)); _button = button; } public MouseButtons Button { get { return _button; } } public ReadOnlyCollection Nodes { get { return _nodes; } } } #endregion }