|
//------------------------------------------------------------------------------
// <copyright file="TreeNode.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
/*
*/
namespace System.Windows.Forms {
using System.Text;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters;
using System.Runtime.InteropServices;
using System.Runtime.Remoting;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Security;
using System.Security.Permissions;
using System;
using System.Drawing.Design;
using System.Collections;
using System.Globalization;
using System.Windows.Forms;
using System.ComponentModel;
using System.IO;
using System.Drawing;
using Microsoft.Win32;
/// <include file='doc\TreeNode.uex' path='docs/doc[@for="TreeNode"]/*' />
/// <devdoc>
/// <para>
/// Implements a node of a <see cref='System.Windows.Forms.TreeView'/>.
///
/// </para>
/// </devdoc>
[
TypeConverterAttribute(typeof(TreeNodeConverter)), Serializable,
DefaultProperty("Text"),
SuppressMessage("Microsoft.Usage", "CA2240:ImplementISerializableCorrectly")
]
public class TreeNode : MarshalByRefObject, ICloneable, ISerializable {
private const int SHIFTVAL = 12;
private const int CHECKED = 2 << SHIFTVAL;
private const int UNCHECKED = 1 << SHIFTVAL;
private const int ALLOWEDIMAGES = 14;
//the threshold value used to optimize AddRange and Clear operations for a big number of nodes
internal const int MAX_TREENODES_OPS = 200;
// we use it to store font and color data in a minimal-memory-cost manner
// ie. nodes which don't use fancy fonts or colors (ie. that use the TreeView settings for these)
// will take up less memory than those that do.
internal OwnerDrawPropertyBag propBag = null;
internal IntPtr handle;
internal string text;
internal string name;
// note: as the checked state of a node is user controlled, and this variable is simply for
// state caching when a node hasn't yet been realized, you should use the Checked property to
// find out the check state of a node, and not this member variable.
//private bool isChecked = false;
private const int TREENODESTATE_isChecked = 0x00000001;
private System.Collections.Specialized.BitVector32 treeNodeState;
private TreeNodeImageIndexer imageIndexer;
private TreeNodeImageIndexer selectedImageIndexer;
private TreeNodeImageIndexer stateImageIndexer;
private string toolTipText = "";
private ContextMenu contextMenu = null;
private ContextMenuStrip contextMenuStrip = null;
internal bool nodesCleared = false;
// We need a special way to defer to the TreeView's image
// list for indexing purposes.
internal class TreeNodeImageIndexer : ImageList.Indexer {
private TreeNode owner;
/// <include file='doc\TreeNode.uex' path='docs/doc[@for="ImageListType"]/*' />
public enum ImageListType {
/// <include file='doc\TreeNode.uex' path='docs/doc[@for="ImageListType.Default"]/*' />
Default,
/// <include file='doc\TreeNode.uex' path='docs/doc[@for="ImageListType.State"]/*' />
State
}
private ImageListType imageListType;
/// <include file='doc\TreeNode.uex' path='docs/doc[@for="ImageListType.TreeNodeImageIndexer"]/*' />
public TreeNodeImageIndexer(TreeNode node, ImageListType imageListType) {
owner = node;
this.imageListType = imageListType;
}
/// <include file='doc\TreeNode.uex' path='docs/doc[@for="ImageListType.ImageList"]/*' />
public override ImageList ImageList {
get {
if (owner.TreeView != null) {
if (imageListType == ImageListType.State) {
return owner.TreeView.StateImageList;
}
else {
return owner.TreeView.ImageList;
}
}
else {
return null;
}
}
set { Debug.Assert(false, "We should never set the image list"); }
}
}
internal TreeNodeImageIndexer ImageIndexer {
get {
//Demand create the imageIndexer
if (imageIndexer == null) {
imageIndexer = new TreeNodeImageIndexer(this, TreeNodeImageIndexer.ImageListType.Default);
}
return imageIndexer;
}
}
internal TreeNodeImageIndexer SelectedImageIndexer {
get {
//Demand create the imageIndexer
if (selectedImageIndexer == null) {
selectedImageIndexer = new TreeNodeImageIndexer(this, TreeNodeImageIndexer.ImageListType.Default);
}
return selectedImageIndexer;
}
}
internal TreeNodeImageIndexer StateImageIndexer {
get {
//Demand create the imageIndexer
if (stateImageIndexer == null) {
stateImageIndexer = new TreeNodeImageIndexer(this, TreeNodeImageIndexer.ImageListType.State);
}
return stateImageIndexer;
}
}
internal int index; // our index into our parents child array
internal int childCount;
internal TreeNode[] children;
internal TreeNode parent;
internal TreeView treeView;
private bool expandOnRealization = false;
private bool collapseOnRealization = false;
private TreeNodeCollection nodes = null;
object userData;
private readonly static int insertMask =
NativeMethods.TVIF_TEXT
| NativeMethods.TVIF_IMAGE
| NativeMethods.TVIF_SELECTEDIMAGE;
/// <include file='doc\TreeNode.uex' path='docs/doc[@for="TreeNode.TreeNode"]/*' />
/// <devdoc>
/// Creates a TreeNode object.
/// </devdoc>
public TreeNode() {
treeNodeState = new System.Collections.Specialized.BitVector32();
}
internal TreeNode(TreeView treeView) : this() {
this.treeView = treeView;
}
/// <include file='doc\TreeNode.uex' path='docs/doc[@for="TreeNode.TreeNode1"]/*' />
/// <devdoc>
/// Creates a TreeNode object.
/// </devdoc>
public TreeNode(string text) : this() {
this.text = text;
}
/// <include file='doc\TreeNode.uex' path='docs/doc[@for="TreeNode.TreeNode2"]/*' />
/// <devdoc>
/// Creates a TreeNode object.
/// </devdoc>
public TreeNode(string text, TreeNode[] children) : this() {
this.text = text;
this.Nodes.AddRange(children);
}
/// <include file='doc\TreeNode.uex' path='docs/doc[@for="TreeNode.TreeNode3"]/*' />
/// <devdoc>
/// Creates a TreeNode object.
/// </devdoc>
public TreeNode(string text, int imageIndex, int selectedImageIndex) : this() {
this.text = text;
this.ImageIndexer.Index = imageIndex;
this.SelectedImageIndexer.Index = selectedImageIndex;
}
/// <include file='doc\TreeNode.uex' path='docs/doc[@for="TreeNode.TreeNode4"]/*' />
/// <devdoc>
/// Creates a TreeNode object.
/// </devdoc>
public TreeNode(string text, int imageIndex, int selectedImageIndex, TreeNode[] children) : this() {
this.text = text;
this.ImageIndexer.Index = imageIndex;
this.SelectedImageIndexer.Index = selectedImageIndex;
this.Nodes.AddRange(children);
}
/**
* Constructor used in deserialization
*/
/// <include file='doc\TreeNode.uex' path='docs/doc[@for="TreeNode.TreeNode5"]/*' />
// PM team has reviewed and decided on naming changes already
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")]
[
SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors") // Changing Deserialize to be non-virtual
// would be a breaking change.
]
protected TreeNode(SerializationInfo serializationInfo, StreamingContext context) : this() {
Deserialize(serializationInfo, context);
}
/// <include file='doc\TreeNode.uex' path='docs/doc[@for="TreeNode.BackColor"]/*' />
/// <devdoc>
/// The background color of this node.
/// If null, the color used will be the default color from the TreeView control that this
/// node is attached to
/// </devdoc>
[
SRCategory(SR.CatAppearance),
SRDescription(SR.TreeNodeBackColorDescr)
]
public Color BackColor {
get {
if (propBag==null) return Color.Empty;
return propBag.BackColor;
}
set {
// get the old value
Color oldbk = this.BackColor;
// If we're setting the color to the default again, delete the propBag if it doesn't contain
// useful data.
if (value.IsEmpty) {
if (propBag!=null) {
propBag.BackColor = Color.Empty;
RemovePropBagIfEmpty();
}
if (!oldbk.IsEmpty) InvalidateHostTree();
return;
}
// Not the default, so if necessary create a new propBag, and fill it with the backcolor
if (propBag==null) propBag = new OwnerDrawPropertyBag();
propBag.BackColor = value;
if (!value.Equals(oldbk)) InvalidateHostTree();
}
}
/// <include file='doc\TreeNode.uex' path='docs/doc[@for="TreeNode.Bounds"]/*' />
/// <devdoc>
/// The bounding rectangle for the node (text area only). The coordinates
/// are relative to the upper left corner of the TreeView control.
/// </devdoc>
[Browsable(false)]
public Rectangle Bounds {
get {
TreeView tv = this.TreeView;
if (tv == null || tv.IsDisposed) {
return Rectangle.Empty;
}
NativeMethods.RECT rc = new NativeMethods.RECT();
unsafe { *((IntPtr *) &rc.left) = Handle; }
// wparam: 1=include only text, 0=include entire line
if ((int)UnsafeNativeMethods.SendMessage(new HandleRef(tv, tv.Handle), NativeMethods.TVM_GETITEMRECT, 1, ref rc) == 0) {
// This means the node is not visible
//
return Rectangle.Empty;
}
return Rectangle.FromLTRB(rc.left, rc.top, rc.right, rc.bottom);
}
}
/// <include file='doc\TreeNode.uex' path='docs/doc[@for="TreeNode.Bounds"]/*' />
/// <devdoc>
/// The bounding rectangle for the node (full row). The coordinates
/// are relative to the upper left corner of the TreeView control.
/// </devdoc>
internal Rectangle RowBounds {
get {
TreeView tv = this.TreeView;
NativeMethods.RECT rc = new NativeMethods.RECT();
unsafe { *((IntPtr *) &rc.left) = Handle; }
// wparam: 1=include only text, 0=include entire line
if (tv == null || tv.IsDisposed) {
return Rectangle.Empty;
}
if ((int)UnsafeNativeMethods.SendMessage(new HandleRef(tv, tv.Handle), NativeMethods.TVM_GETITEMRECT, 0, ref rc) == 0) {
// This means the node is not visible
//
return Rectangle.Empty;
}
return Rectangle.FromLTRB(rc.left, rc.top, rc.right, rc.bottom);
}
}
internal bool CheckedStateInternal {
get {
return treeNodeState[TREENODESTATE_isChecked];
}
set {
treeNodeState[TREENODESTATE_isChecked] = value;
}
}
// Checked does sanity checking and fires Before/AfterCheck events, then forwards to this
// property to get/set the actual checked value.
internal bool CheckedInternal {
get {
return CheckedStateInternal;
}
set {
CheckedStateInternal = value;
if (handle == IntPtr.Zero)
return;
TreeView tv = this.TreeView;
if (tv == null || !tv.IsHandleCreated || tv.IsDisposed)
return;
NativeMethods.TV_ITEM item = new NativeMethods.TV_ITEM();
item.mask = NativeMethods.TVIF_HANDLE | NativeMethods.TVIF_STATE;
item.hItem = handle;
item.stateMask = NativeMethods.TVIS_STATEIMAGEMASK;
item.state |= value ? CHECKED : UNCHECKED;
UnsafeNativeMethods.SendMessage(new HandleRef(tv, tv.Handle), NativeMethods.TVM_SETITEM, 0, ref item);
}
}
/// <include file='doc\TreeNode.uex' path='docs/doc[@for="TreeNode.Checked"]/*' />
/// <devdoc>
/// Indicates whether the node's checkbox is checked.
/// </devdoc>
[
SRCategory(SR.CatBehavior),
SRDescription(SR.TreeNodeCheckedDescr),
DefaultValue(false)
]
public bool Checked {
get {
#if DEBUG
if(handle != IntPtr.Zero) {
NativeMethods.TV_ITEM item = new NativeMethods.TV_ITEM();
item.mask = NativeMethods.TVIF_HANDLE | NativeMethods.TVIF_STATE;
item.hItem = handle;
item.stateMask = NativeMethods.TVIS_STATEIMAGEMASK;
UnsafeNativeMethods.SendMessage(new HandleRef(null, TreeView.Handle), NativeMethods.TVM_GETITEM, 0, ref item);
Debug.Assert(!TreeView.CheckBoxes || ((item.state >> SHIFTVAL) > 1) == CheckedInternal,
"isChecked on node '" + Name + "' did not match the state in TVM_GETITEM.");
}
#endif
return CheckedInternal;
}
set {
TreeView tv = TreeView;
if (tv != null) {
bool eventReturn = tv.TreeViewBeforeCheck(this, TreeViewAction.Unknown);
if (!eventReturn) {
CheckedInternal = value;
tv.TreeViewAfterCheck(this, TreeViewAction.Unknown);
}
}
else {
CheckedInternal = value;
}
}
}
/// <include file='doc\TreeNode.uex' path='docs/doc[@for="TreeNode.ContextMenu"]/*' />
/// <devdoc>
/// The contextMenu associated with this tree node. The contextMenu
/// will be shown when the user right clicks the mouse on the control.
/// </devdoc>
[
SRCategory(SR.CatBehavior),
DefaultValue(null),
SRDescription(SR.ControlContextMenuDescr)
]
public virtual ContextMenu ContextMenu {
get {
return contextMenu;
}
set {
contextMenu = value;
}
}
/// <include file='doc\TreeNode.uex' path='docs/doc[@for="TreeNode.ContextMenu"]/*' />
/// <devdoc>
/// </devdoc>
[
SRCategory(SR.CatBehavior),
DefaultValue(null),
SRDescription(SR.ControlContextMenuDescr)
]
public virtual ContextMenuStrip ContextMenuStrip {
get {
return contextMenuStrip;
}
set {
contextMenuStrip = value;
}
}
/// <include file='doc\TreeNode.uex' path='docs/doc[@for="TreeNode.FirstNode"]/*' />
/// <devdoc>
/// The first child node of this node.
/// </devdoc>
[Browsable(false)]
public TreeNode FirstNode {
get {
if (childCount == 0) return null;
return children[0];
}
}
private TreeNode FirstVisibleParent {
get {
TreeNode node = this;
while (node != null && node.Bounds.IsEmpty) {
node = node.Parent;
}
return node;
}
}
/// <include file='doc\TreeNode.uex' path='docs/doc[@for="TreeNode.ForeColor"]/*' />
/// <devdoc>
/// The foreground color of this node.
/// If null, the color used will be the default color from the TreeView control that this
/// node is attached to
/// </devdoc>
[
SRCategory(SR.CatAppearance),
SRDescription(SR.TreeNodeForeColorDescr)
]
public Color ForeColor {
get {
if (propBag == null) return Color.Empty;
return propBag.ForeColor;
}
set {
Color oldfc = this.ForeColor;
// If we're setting the color to the default again, delete the propBag if it doesn't contain
// useful data.
if (value.IsEmpty) {
if (propBag != null) {
propBag.ForeColor = Color.Empty;
RemovePropBagIfEmpty();
}
if (!oldfc.IsEmpty) InvalidateHostTree();
return;
}
// Not the default, so if necessary create a new propBag, and fill it with the new forecolor
if (propBag == null) propBag = new OwnerDrawPropertyBag();
propBag.ForeColor = value;
if (!value.Equals(oldfc)) InvalidateHostTree();
}
}
/// <include file='doc\TreeNode.uex' path='docs/doc[@for="TreeNode.FullPath"]/*' />
/// <devdoc>
/// Returns the full path of this node.
/// The path consists of the labels of each of the nodes from the root to this node,
/// each separated by the pathSeperator.
/// </devdoc>
[Browsable(false)]
public string FullPath {
get {
TreeView tv = TreeView;
if (tv != null) {
StringBuilder path = new StringBuilder();
GetFullPath(path, tv.PathSeparator);
return path.ToString();
}
else
throw new InvalidOperationException(SR.GetString(SR.TreeNodeNoParent));
}
}
/// <include file='doc\TreeNode.uex' path='docs/doc[@for="TreeNode.Handle"]/*' />
/// <devdoc>
/// The HTREEITEM handle associated with this node. If the handle
/// has not yet been created, this will force handle creation.
/// </devdoc>
[Browsable(false)]
public IntPtr Handle {
get {
if (handle == IntPtr.Zero) {
TreeView.CreateControl(); // force handle creation
}
return handle;
}
}
/// <include file='doc\TreeNode.uex' path='docs/doc[@for="TreeNode.ImageIndex"]/*' />
/// <devdoc>
/// The index of the image to be displayed when the node is in the unselected state.
/// The image is contained in the ImageList referenced by the imageList property.
/// </devdoc>
[
Localizable(true),
SRCategory(SR.CatBehavior),
SRDescription(SR.TreeNodeImageIndexDescr),
TypeConverterAttribute(typeof(TreeViewImageIndexConverter)),
Editor("System.Windows.Forms.Design.ImageIndexEditor, " + AssemblyRef.SystemDesign, typeof(UITypeEditor)),
RefreshProperties(RefreshProperties.Repaint),
DefaultValue(-1),
RelatedImageList("TreeView.ImageList")
]
public int ImageIndex {
get { return ImageIndexer.Index;}
set {
ImageIndexer.Index = value;
UpdateNode(NativeMethods.TVIF_IMAGE);
}
}
/// <include file='doc\TreeNode.uex' path='docs/doc[@for="TreeNode.ImageIndex"]/*' />
/// <devdoc>
/// The index of the image to be displayed when the node is in the unselected state.
/// The image is contained in the ImageList referenced by the imageList property.
/// </devdoc>
[
Localizable(true),
SRCategory(SR.CatBehavior),
SRDescription(SR.TreeNodeImageKeyDescr),
TypeConverterAttribute(typeof(TreeViewImageKeyConverter)),
DefaultValue(""),
Editor("System.Windows.Forms.Design.ImageIndexEditor, " + AssemblyRef.SystemDesign, typeof(UITypeEditor)),
RefreshProperties(RefreshProperties.Repaint),
RelatedImageList("TreeView.ImageList")
]
public string ImageKey {
get {return ImageIndexer.Key;}
set {
ImageIndexer.Key = value;
UpdateNode(NativeMethods.TVIF_IMAGE);
}
}
/// <include file='doc\TreeNode.uex' path='docs/doc[@for="TreeNode.Index"]/*' />
/// <devdoc>
/// Returns the position of this node in relation to its siblings
/// </devdoc>
[
SRCategory(SR.CatBehavior),
SRDescription(SR.TreeNodeIndexDescr),
]
public int Index {
get { return index;}
}
/// <include file='doc\TreeNode.uex' path='docs/doc[@for="TreeNode.IsEditing"]/*' />
/// <devdoc>
/// Specifies whether this node is being edited by the user.
/// </devdoc>
[Browsable(false)]
public bool IsEditing {
get {
TreeView tv = TreeView;
if (tv != null)
return tv.editNode == this;
return false;
}
}
/// <include file='doc\TreeNode.uex' path='docs/doc[@for="TreeNode.IsExpanded"]/*' />
/// <devdoc>
/// Specifies whether this node is in the expanded state.
/// </devdoc>
[Browsable(false)]
public bool IsExpanded {
get {
if (handle == IntPtr.Zero) {
return expandOnRealization;
}
return(State & NativeMethods.TVIS_EXPANDED) != 0;
}
}
/// <include file='doc\TreeNode.uex' path='docs/doc[@for="TreeNode.IsSelected"]/*' />
/// <devdoc>
/// Specifies whether this node is in the selected state.
/// </devdoc>
[Browsable(false)]
public bool IsSelected {
get {
if (handle == IntPtr.Zero) return false;
return(State & NativeMethods.TVIS_SELECTED) != 0;
}
}
/// <include file='doc\TreeNode.uex' path='docs/doc[@for="TreeNode.IsVisible"]/*' />
/// <devdoc>
/// Specifies whether this node is visible.
/// </devdoc>
[Browsable(false)]
public bool IsVisible {
get {
if (handle == IntPtr.Zero) return false;
TreeView tv = this.TreeView;
if (tv.IsDisposed) {
return false;
}
NativeMethods.RECT rc = new NativeMethods.RECT();
unsafe { *((IntPtr *) &rc.left) = Handle; }
bool visible = ((int)UnsafeNativeMethods.SendMessage(new HandleRef(tv, tv.Handle), NativeMethods.TVM_GETITEMRECT, 1, ref rc) != 0);
if (visible) {
Size size = tv.ClientSize;
visible = (rc.bottom > 0 && rc.right > 0 && rc.top < size.Height && rc.left < size.Width);
}
return visible;
}
}
/// <include file='doc\TreeNode.uex' path='docs/doc[@for="TreeNode.LastNode"]/*' />
/// <devdoc>
/// The last child node of this node.
/// </devdoc>
[Browsable(false)]
public TreeNode LastNode {
get {
if (childCount == 0) return null;
return children[childCount-1];
}
}
/// <include file='doc\TreeNode.uex' path='docs/doc[@for="TreeNode.Level"]/*' />
/// <devdoc>
/// This denotes the depth of nesting of the treenode.
/// </devdoc>
[Browsable(false)]
public int Level {
get {
if (this.Parent == null) {
return 0;
}
else {
return Parent.Level + 1;
}
}
}
/// <include file='doc\TreeNode.uex' path='docs/doc[@for="TreeNode.NextNode"]/*' />
/// <devdoc>
/// The next sibling node.
/// </devdoc>
[Browsable(false)]
public TreeNode NextNode {
get {
if (index+1 < parent.Nodes.Count) {
return parent.Nodes[index+1];
}
else {
return null;
}
}
}
/// <include file='doc\TreeNode.uex' path='docs/doc[@for="TreeNode.NextVisibleNode"]/*' />
/// <devdoc>
/// The next visible node. It may be a child, sibling,
/// or a node from another branch.
/// </devdoc>
[Browsable(false)]
public TreeNode NextVisibleNode {
get {
// TVGN_NEXTVISIBLE can only be sent if the specified node is visible.
// So before sending, we check if this node is visible. If not, we find the first visible parent.
//
TreeView tv = this.TreeView;
if (tv == null || tv.IsDisposed) {
return null;
}
TreeNode node = FirstVisibleParent;
if (node != null) {
IntPtr next = UnsafeNativeMethods.SendMessage(new HandleRef(tv, tv.Handle),
NativeMethods.TVM_GETNEXTITEM, NativeMethods.TVGN_NEXTVISIBLE, node.Handle);
if (next != IntPtr.Zero) {
return tv.NodeFromHandle(next);
}
}
return null;
}
}
/// <include file='doc\TreeNode.uex' path='docs/doc[@for="TreeNode.NodeFont"]/*' />
/// <devdoc>
/// The font that will be used to draw this node
/// If null, the font used will be the default font from the TreeView control that this
/// node is attached to.
/// NOTE: If the node font is larger than the default font from the TreeView control, then
/// the node will be clipped.
/// </devdoc>
[
Localizable(true),
SRCategory(SR.CatAppearance),
SRDescription(SR.TreeNodeNodeFontDescr),
DefaultValue(null)
]
public Font NodeFont {
get {
if (propBag==null) return null;
return propBag.Font;
}
set {
Font oldfont = this.NodeFont;
// If we're setting the font to the default again, delete the propBag if it doesn't contain
// useful data.
if (value==null) {
if (propBag!=null) {
propBag.Font = null;
RemovePropBagIfEmpty();
}
if (oldfont != null) InvalidateHostTree();
return;
}
// Not the default, so if necessary create a new propBag, and fill it with the font
if (propBag==null) propBag = new OwnerDrawPropertyBag();
propBag.Font = value;
if (!value.Equals(oldfont)) InvalidateHostTree();
}
}
/// <include file='doc\TreeNode.uex' path='docs/doc[@for="TreeNode.Nodes"]/*' />
/// <devdoc>
/// <para>[To be supplied.]</para>
/// </devdoc>
[
ListBindable(false),
Browsable(false)
]
public TreeNodeCollection Nodes {
get {
if (nodes == null) {
nodes = new TreeNodeCollection(this);
}
return nodes;
}
}
/// <include file='doc\TreeNode.uex' path='docs/doc[@for="TreeNode.Parent"]/*' />
/// <devdoc>
/// Retrieves parent node.
/// </devdoc>
[Browsable(false)]
public TreeNode Parent {
get {
TreeView tv = TreeView;
// Don't expose the virtual root publicly
if (tv != null && parent == tv.root) {
return null;
}
return parent;
}
}
/// <include file='doc\TreeNode.uex' path='docs/doc[@for="TreeNode.PrevNode"]/*' />
/// <devdoc>
/// The previous sibling node.
/// </devdoc>
[Browsable(false)]
public TreeNode PrevNode {
get {
//fixedIndex is used for perf. optimization in case of adding big ranges of nodes
int currentInd = index;
int fixedInd = parent.Nodes.FixedIndex;
if (fixedInd > 0) {
currentInd = fixedInd;
}
if (currentInd > 0 && currentInd <= parent.Nodes.Count) {
return parent.Nodes[currentInd-1];
}
else {
return null;
}
}
}
/// <include file='doc\TreeNode.uex' path='docs/doc[@for="TreeNode.PrevVisibleNode"]/*' />
/// <devdoc>
/// The next visible node. It may be a parent, sibling,
/// or a node from another branch.
/// </devdoc>
[Browsable(false)]
public TreeNode PrevVisibleNode {
get {
// TVGN_PREVIOUSVISIBLE can only be sent if the specified node is visible.
// So before sending, we check if this node is visible. If not, we find the first visible parent.
//
TreeNode node = FirstVisibleParent;
TreeView tv = this.TreeView;
if (node != null) {
if (tv == null || tv.IsDisposed) {
return null;
}
IntPtr prev = UnsafeNativeMethods.SendMessage(new HandleRef(tv, tv.Handle),
NativeMethods.TVM_GETNEXTITEM,
NativeMethods.TVGN_PREVIOUSVISIBLE, node.Handle);
if (prev != IntPtr.Zero) {
return tv.NodeFromHandle(prev);
}
}
return null;
}
}
/// <include file='doc\TreeNode.uex' path='docs/doc[@for="TreeNode.SelectedImageIndex"]/*' />
/// <devdoc>
/// The index of the image displayed when the node is in the selected state.
/// The image is contained in the ImageList referenced by the imageList property.
/// </devdoc>
[
Localizable(true),
SRCategory(SR.CatBehavior),
SRDescription(SR.TreeNodeSelectedImageIndexDescr),
TypeConverterAttribute(typeof(TreeViewImageIndexConverter)),
DefaultValue(-1),
RefreshProperties(RefreshProperties.Repaint),
Editor("System.Windows.Forms.Design.ImageIndexEditor, " + AssemblyRef.SystemDesign, typeof(UITypeEditor)),
RelatedImageList("TreeView.ImageList")
]
public int SelectedImageIndex {
get {
return SelectedImageIndexer.Index;
}
set {
SelectedImageIndexer.Index = value;
UpdateNode(NativeMethods.TVIF_SELECTEDIMAGE);
}
}
/// <include file='doc\TreeNode.uex' path='docs/doc[@for="TreeNode.SelectedImageKey"]/*' />
/// <devdoc>
/// The index of the image displayed when the node is in the selected state.
/// The image is contained in the ImageList referenced by the imageList property.
/// </devdoc>
[
Localizable(true),
SRCategory(SR.CatBehavior),
SRDescription(SR.TreeNodeSelectedImageKeyDescr),
TypeConverterAttribute(typeof(TreeViewImageKeyConverter)),
DefaultValue(""),
RefreshProperties(RefreshProperties.Repaint),
Editor("System.Windows.Forms.Design.ImageIndexEditor, " + AssemblyRef.SystemDesign, typeof(UITypeEditor)),
RelatedImageList("TreeView.ImageList")
]
public string SelectedImageKey {
get {
return SelectedImageIndexer.Key;
}
set {
SelectedImageIndexer.Key = value;
UpdateNode(NativeMethods.TVIF_SELECTEDIMAGE);
}
}
/// <include file='doc\TreeNode.uex' path='docs/doc[@for="TreeNode.State"]/*' />
/// <devdoc>
/// Retrieve state bits for this node
/// </devdoc>
/// <internalonly/>
internal int State {
get {
if (handle == IntPtr.Zero)
return 0;
TreeView tv = this.TreeView;
if (tv == null || tv.IsDisposed) {
return 0;
}
NativeMethods.TV_ITEM item = new NativeMethods.TV_ITEM();
item.hItem = Handle;
item.mask = NativeMethods.TVIF_HANDLE | NativeMethods.TVIF_STATE;
item.stateMask = NativeMethods.TVIS_SELECTED | NativeMethods.TVIS_EXPANDED;
UnsafeNativeMethods.SendMessage(new HandleRef(tv, tv.Handle), NativeMethods.TVM_GETITEM, 0, ref item);
return item.state;
}
}
/// <include file='doc\TreeNode.uex' path='docs/doc[@for="TreeNode.ImageIndex"]/*' />
/// <devdoc>
/// The key of the StateImage that the user want to display.
/// </devdoc>
[
Localizable(true),
SRCategory(SR.CatBehavior),
SRDescription(SR.TreeNodeStateImageKeyDescr),
TypeConverterAttribute(typeof(ImageKeyConverter)),
DefaultValue(""),
Editor("System.Windows.Forms.Design.ImageIndexEditor, " + AssemblyRef.SystemDesign, typeof(UITypeEditor)),
RefreshProperties(RefreshProperties.Repaint),
RelatedImageList("TreeView.StateImageList")
]
public string StateImageKey {
get {
return StateImageIndexer.Key; }
set {
if (StateImageIndexer.Key != value) {
StateImageIndexer.Key = value;
if (treeView != null && !treeView.CheckBoxes)
{
UpdateNode(NativeMethods.TVIF_STATE);
}
}
}
}
/// <include file='doc\TreeNode.uex' path='docs/doc[@for="TreeNode.StateImageIndex"]/*' />
/// <devdoc>
/// <para>[To be supplied.]</para>
/// </devdoc>
[
Localizable(true),
TypeConverterAttribute(typeof(NoneExcludedImageIndexConverter)),
DefaultValue(-1),
SRCategory(SR.CatBehavior),
SRDescription(SR.TreeNodeStateImageIndexDescr),
Editor("System.Windows.Forms.Design.ImageIndexEditor, " + AssemblyRef.SystemDesign, typeof(UITypeEditor)),
RefreshProperties(RefreshProperties.Repaint),
RelatedImageList("TreeView.StateImageList")
]
public int StateImageIndex {
get {
return (treeView == null || treeView.StateImageList == null) ? -1:StateImageIndexer.Index;
}
set {
if (value < -1 || value > ALLOWEDIMAGES) {
throw new ArgumentOutOfRangeException("StateImageIndex", SR.GetString(SR.InvalidArgument, "StateImageIndex", (value).ToString(CultureInfo.CurrentCulture)));
}
StateImageIndexer.Index = value;
if (treeView != null && !treeView.CheckBoxes)
{
UpdateNode(NativeMethods.TVIF_STATE);
}
}
}
// <include file='doc\TreeNode.uex' path='docs/doc[@for="TreeNode.Tag"]/*' />
/// <include file='doc\TreeNode.uex' path='docs/doc[@for="TreeNode.Tag"]/*' />
[
SRCategory(SR.CatData),
Localizable(false),
Bindable(true),
SRDescription(SR.ControlTagDescr),
DefaultValue(null),
TypeConverter(typeof(StringConverter)),
]
public object Tag {
get {
return userData;
}
set {
userData = value;
}
}
/// <include file='doc\TreeNode.uex' path='docs/doc[@for="TreeNode.Text"]/*' />
/// <devdoc>
/// The label text for the tree node
/// </devdoc>
[
Localizable(true),
SRCategory(SR.CatAppearance),
SRDescription(SR.TreeNodeTextDescr)
]
public string Text {
get {
return text == null ? "" : text;
}
set {
this.text = value;
UpdateNode(NativeMethods.TVIF_TEXT);
}
}
/// <include file='doc\TreeNode.uex' path='docs/doc[@for="TreeNode.ToolTipText"]/*' />
/// <devdoc>
/// The ToolTip text that will be displayed when the mouse hovers over the node.
/// </devdoc>
[
Localizable(false),
SRCategory(SR.CatAppearance),
SRDescription(SR.TreeNodeToolTipTextDescr),
DefaultValue("")
]
public string ToolTipText {
get {
return toolTipText;
}
set {
toolTipText = value;
}
}
/// <include file='doc\TreeNode.uex' path='docs/doc[@for="TreeNode.Name"]/*' />
/// <devdoc>
/// The name for the tree node - useful for indexing.
/// </devdoc>
[
SRCategory(SR.CatAppearance),
SRDescription(SR.TreeNodeNodeNameDescr)
]
public string Name {
get {
return name == null ? "" : name;
}
set {
this.name = value;
}
}
/// <include file='doc\TreeNode.uex' path='docs/doc[@for="TreeNode.TreeView"]/*' />
/// <devdoc>
/// Return the TreeView control this node belongs to.
/// </devdoc>
[Browsable(false)]
public TreeView TreeView {
get {
if (treeView == null)
treeView = FindTreeView();
return treeView;
}
}
/// <include file='doc\TreeNode.uex' path='docs/doc[@for="TreeNode.AddSorted"]/*' />
/// <devdoc>
/// Adds a new child node at the appropriate sorted position
/// </devdoc>
/// <internalonly/>
internal int AddSorted(TreeNode node) {
int index = 0;
int iMin, iLim, iT;
string nodeText = node.Text;
TreeView parentTreeView = TreeView;
if (childCount > 0) {
if (parentTreeView.TreeViewNodeSorter == null)
{
CompareInfo compare = Application.CurrentCulture.CompareInfo;
// Optimize for the case where they're already sorted
if (compare.Compare(children[childCount-1].Text, nodeText) <= 0)
index = childCount;
else {
// Insert at appropriate sorted spot
for (iMin = 0, iLim = childCount; iMin < iLim;) {
iT = (iMin + iLim) / 2;
if (compare.Compare(children[iT].Text, nodeText) <= 0)
iMin = iT + 1;
else
iLim = iT;
}
index = iMin;
}
}
else
{
IComparer sorter = parentTreeView.TreeViewNodeSorter;
// Insert at appropriate sorted spot
for (iMin = 0, iLim = childCount; iMin < iLim;) {
iT = (iMin + iLim) / 2;
if (sorter.Compare(children[iT] /*previous*/, node/*current*/) <= 0)
iMin = iT + 1;
else
iLim = iT;
}
index = iMin;
}
}
node.SortChildren(parentTreeView);
InsertNodeAt(index, node);
return index;
}
/// <include file='doc\TreeNode.uex' path='docs/doc[@for="TreeNode.FromHandle"]/*' />
/// <devdoc>
/// Returns a TreeNode object for the given HTREEITEM handle
/// </devdoc>
public static TreeNode FromHandle(TreeView tree, IntPtr handle) {
// SECREVIEW:
// Demand before we pass the TreeNode form handle.
IntSecurity.ControlFromHandleOrLocation.Demand();
return tree.NodeFromHandle(handle);
}
private void SortChildren(TreeView parentTreeView) {
//
if (childCount > 0) {
TreeNode[] newOrder = new TreeNode[childCount];
if (parentTreeView == null || parentTreeView.TreeViewNodeSorter == null)
{
CompareInfo compare = Application.CurrentCulture.CompareInfo;
for (int i = 0; i < childCount; i++) {
int min = -1;
for (int j = 0; j < childCount; j++) {
if (children[j] == null)
continue;
if (min == -1) {
min = j;
continue;
}
if (compare.Compare(children[j].Text, children[min].Text) <= 0)
min = j;
}
Debug.Assert(min != -1, "Bad sorting");
newOrder[i] = children[min];
children[min] = null;
newOrder[i].index = i;
newOrder[i].SortChildren(parentTreeView);
}
children = newOrder;
}
else
{
IComparer sorter = parentTreeView.TreeViewNodeSorter;
for (int i = 0; i < childCount; i++) {
int min = -1;
for (int j = 0; j < childCount; j++) {
if (children[j] == null)
continue;
if (min == -1) {
min = j;
continue;
}
if (sorter.Compare(children[j] /*previous*/, children[min] /*current*/) <= 0)
min = j;
}
Debug.Assert(min != -1, "Bad sorting");
newOrder[i] = children[min];
children[min] = null;
newOrder[i].index = i;
newOrder[i].SortChildren(parentTreeView);
}
children = newOrder;
}
}
}
/// <include file='doc\TreeNode.uex' path='docs/doc[@for="TreeNode.BeginEdit"]/*' />
/// <devdoc>
/// Initiate editing of the node's label.
/// Only effective if LabelEdit property is true.
/// </devdoc>
public void BeginEdit() {
if (handle != IntPtr.Zero) {
TreeView tv = TreeView;
if (tv.LabelEdit == false)
throw new InvalidOperationException(SR.GetString(SR.TreeNodeBeginEditFailed));
if (!tv.Focused)
tv.FocusInternal();
UnsafeNativeMethods.SendMessage(new HandleRef(tv, tv.Handle), NativeMethods.TVM_EDITLABEL, 0, handle);
}
}
/// <devdoc>
/// Called by the tree node collection to clear all nodes. We optimize here if
/// this is the root node.
/// </devdoc>
internal void Clear() {
// This is a node that is a child of some other node. We have
// to selectively remove children here.
//
bool isBulkOperation = false;
TreeView tv = TreeView;
try
{
if (tv != null) {
tv.nodesCollectionClear = true;
if (tv != null && childCount > MAX_TREENODES_OPS) {
isBulkOperation = true;
tv.BeginUpdate();
}
}
while(childCount > 0) {
children[childCount - 1].Remove(true);
}
children = null;
if (tv != null && isBulkOperation) {
tv.EndUpdate();
}
}
finally
{
if (tv != null) {
tv.nodesCollectionClear = false;
}
nodesCleared = true;
}
}
/// <include file='doc\TreeNode.uex' path='docs/doc[@for="TreeNode.Clone"]/*' />
/// <devdoc>
/// Clone the entire subtree rooted at this node.
/// </devdoc>
public virtual object Clone() {
Type clonedType = this.GetType();
TreeNode node = null;
if (clonedType == typeof(TreeNode)){
node = new TreeNode(text, ImageIndexer.Index, SelectedImageIndexer.Index);
}
else {
// SECREVIEW : Late-binding does not represent a security thread, see bug#411899 for more info..
//
node = (TreeNode)Activator.CreateInstance(clonedType);
}
node.Text = text;
node.Name = name;
node.ImageIndexer.Index = ImageIndexer.Index;
node.SelectedImageIndexer.Index = SelectedImageIndexer.Index;
node.StateImageIndexer.Index = StateImageIndexer.Index;
node.ToolTipText = toolTipText;
node.ContextMenu = contextMenu;
node.ContextMenuStrip = contextMenuStrip;
// only set the key if it's set to something useful
if ( ! (string.IsNullOrEmpty(ImageIndexer.Key))) {
node.ImageIndexer.Key = ImageIndexer.Key;
}
// only set the key if it's set to something useful
if (!(string.IsNullOrEmpty(SelectedImageIndexer.Key))) {
node.SelectedImageIndexer.Key = SelectedImageIndexer.Key;
}
// only set the key if it's set to something useful
if (!(string.IsNullOrEmpty(StateImageIndexer.Key))) {
node.StateImageIndexer.Key = StateImageIndexer.Key;
}
if (childCount > 0) {
node.children = new TreeNode[childCount];
for (int i = 0; i < childCount; i++)
node.Nodes.Add((TreeNode)children[i].Clone());
}
// Clone properties
//
if (propBag != null) {
node.propBag = OwnerDrawPropertyBag.Copy(propBag);
}
node.Checked = this.Checked;
node.Tag = this.Tag;
return node;
}
private void CollapseInternal(bool ignoreChildren)
{
TreeView tv = TreeView;
bool setSelection = false;
collapseOnRealization = false;
expandOnRealization = false;
if (tv == null || !tv.IsHandleCreated) {
collapseOnRealization = true;
return;
}
//terminating condition for recursion...
//
if (ignoreChildren)
{
DoCollapse(tv);
}
else {
if (!ignoreChildren && childCount > 0) {
// Virtual root should collapse all its children
for (int i = 0; i < childCount; i++) {
if (tv.SelectedNode == children[i]) {
setSelection = true;
}
children[i].DoCollapse(tv);
children[i].Collapse();
}
}
DoCollapse(tv);
}
if (setSelection)
tv.SelectedNode = this;
tv.Invalidate();
collapseOnRealization = false;
}
/// <include file='doc\TreeNode.uex' path='docs/doc[@for="TreeNode.Collapse"]/*' />
/// <devdoc>
/// Collapse the node ignoring its children while collapsing the parent
/// </devdoc>
public void Collapse(bool ignoreChildren)
{
CollapseInternal(ignoreChildren);
}
/// <include file='doc\TreeNode.uex' path='docs/doc[@for="TreeNode.Collapse"]/*' />
/// <devdoc>
/// Collapse the node.
/// </devdoc>
public void Collapse() {
CollapseInternal(false);
}
/// <include file='doc\TreeNode.uex' path='docs/doc[@for="TreeNode.DoCollapse"]/*' />
/// <devdoc>
/// Windows TreeView doesn't send the proper notifications on collapse, so we do it manually.
/// </devdoc>
private void DoCollapse(TreeView tv) {
if ((State & NativeMethods.TVIS_EXPANDED) != 0) {
TreeViewCancelEventArgs e = new TreeViewCancelEventArgs(this, false, TreeViewAction.Collapse);
tv.OnBeforeCollapse(e);
if (!e.Cancel) {
UnsafeNativeMethods.SendMessage(new HandleRef(tv, tv.Handle), NativeMethods.TVM_EXPAND, NativeMethods.TVE_COLLAPSE, Handle);
tv.OnAfterCollapse(new TreeViewEventArgs(this));
}
}
}
/// <include file='doc\TreeNode.uex' path='docs/doc[@for="TreeNode.Deserialize"]/*' />
/// <devdoc>
/// <para>[To be supplied.]</para>
/// </devdoc>
protected virtual void Deserialize(SerializationInfo serializationInfo, StreamingContext context) {
int childCount = 0;
int imageIndex = -1;
string imageKey = null;
int selectedImageIndex = -1;
string selectedImageKey = null;
int stateImageIndex = -1;
string stateImageKey = null;
foreach (SerializationEntry entry in serializationInfo) {
switch (entry.Name) {
case "PropBag":
// SEC
propBag = (OwnerDrawPropertyBag)serializationInfo.GetValue(entry.Name, typeof(OwnerDrawPropertyBag));
break;
case "Text":
Text = serializationInfo.GetString(entry.Name);
break;
case "ToolTipText":
ToolTipText = serializationInfo.GetString(entry.Name);
break;
case "Name":
Name = serializationInfo.GetString(entry.Name);
break;
case "IsChecked":
CheckedStateInternal = serializationInfo.GetBoolean(entry.Name);
break;
case "ImageIndex":
imageIndex = serializationInfo.GetInt32(entry.Name);
break;
case "SelectedImageIndex":
selectedImageIndex = serializationInfo.GetInt32(entry.Name);
break;
case "ImageKey":
imageKey = serializationInfo.GetString(entry.Name);
break;
case "SelectedImageKey":
selectedImageKey= serializationInfo.GetString(entry.Name);
break;
case "StateImageKey":
stateImageKey = serializationInfo.GetString(entry.Name);
break;
case "StateImageIndex":
stateImageIndex = serializationInfo.GetInt32(entry.Name);
break;
case "ChildCount":
childCount = serializationInfo.GetInt32(entry.Name);
break;
case "UserData":
userData = entry.Value;
break;
}
}
// let imagekey take precidence
if (imageKey != null) {
ImageKey = imageKey;
}
else if (imageIndex != -1) {
ImageIndex = imageIndex;
}
// let selectedimagekey take precidence
if (selectedImageKey != null) {
SelectedImageKey = selectedImageKey;
}
else if (selectedImageIndex != -1) {
SelectedImageIndex = selectedImageIndex;
}
// let stateimagekey take precidence
if (stateImageKey != null) {
StateImageKey = stateImageKey;
}
else if (stateImageIndex != -1) {
StateImageIndex = stateImageIndex;
}
if (childCount > 0) {
TreeNode[] childNodes = new TreeNode[childCount];
for (int i = 0; i < childCount; i++) {
// SEC
childNodes[i] = (TreeNode)serializationInfo.GetValue("children" + i, typeof(TreeNode));
}
Nodes.AddRange(childNodes);
}
}
/// <include file='doc\TreeNode.uex' path='docs/doc[@for="TreeNode.EndEdit"]/*' />
/// <devdoc>
/// Terminate the editing of any tree view item's label.
/// </devdoc>
public void EndEdit(bool cancel) {
TreeView tv = this.TreeView;
if (tv == null || tv.IsDisposed) {
return;
}
UnsafeNativeMethods.SendMessage(new HandleRef(tv, tv.Handle), NativeMethods.TVM_ENDEDITLABELNOW, cancel?1:0, 0);
}
/// <include file='doc\TreeNode.uex' path='docs/doc[@for="TreeNode.EnsureCapacity"]/*' />
/// <devdoc>
/// Makes sure there is enough room to add n children
/// </devdoc>
/// <internalonly/>
internal void EnsureCapacity(int num) {
Debug.Assert(num > 0,"required capacity can not be less than 1");
int size = num;
if (size < 4) {
size = 4;
}
if (children == null) {
children = new TreeNode[size];
}
else if (childCount + num > children.Length) {
int newSize = childCount + num;
if (num == 1) {
newSize = childCount * 2;
}
TreeNode[] bigger = new TreeNode[newSize];
System.Array.Copy(children, 0, bigger, 0, childCount);
children = bigger;
}
}
/// <include file='doc\TreeNode.uex' path='docs/doc[@for="TreeNode.EnsureStateImageValue"]/*' />
/// <devdoc>
/// Ensures the the node's StateImageIndex value is properly set.
/// </devdoc>
/// <internalonly/>
private void EnsureStateImageValue()
{
if (treeView == null) {
return;
}
if (treeView.CheckBoxes && treeView.StateImageList != null) {
if (!String.IsNullOrEmpty(this.StateImageKey)) {
this.StateImageIndex = (this.Checked) ? 1 : 0;
this.StateImageKey = treeView.StateImageList.Images.Keys[this.StateImageIndex];
}
else {
this.StateImageIndex = (this.Checked) ? 1 : 0;
}
}
}
/// <include file='doc\TreeNode.uex' path='docs/doc[@for="TreeNode.EnsureVisible"]/*' />
/// <devdoc>
/// Ensure that the node is visible, expanding nodes and scrolling the
/// TreeView control as necessary.
/// </devdoc>
public void EnsureVisible() {
TreeView tv = this.TreeView;
if (tv == null || tv.IsDisposed) {
return;
}
UnsafeNativeMethods.SendMessage(new HandleRef(tv, tv.Handle), NativeMethods.TVM_ENSUREVISIBLE, 0, Handle);
}
/// <include file='doc\TreeNode.uex' path='docs/doc[@for="TreeNode.Expand"]/*' />
/// <devdoc>
/// Expand the node.
/// </devdoc>
public void Expand() {
TreeView tv = TreeView;
if (tv == null || !tv.IsHandleCreated) {
expandOnRealization = true;
return;
}
ResetExpandedState(tv);
if (!IsExpanded) {
UnsafeNativeMethods.SendMessage(new HandleRef(tv, tv.Handle), NativeMethods.TVM_EXPAND, NativeMethods.TVE_EXPAND, Handle);
}
expandOnRealization = false;
}
/// <include file='doc\TreeNode.uex' path='docs/doc[@for="TreeNode.ExpandAll"]/*' />
/// <devdoc>
/// Expand the node.
/// </devdoc>
public void ExpandAll() {
Expand();
for (int i = 0; i < childCount; i++) {
children[i].ExpandAll();
}
}
/// <include file='doc\TreeNode.uex' path='docs/doc[@for="TreeNode.FindTreeView"]/*' />
/// <devdoc>
/// Locate this tree node's containing tree view control by scanning
/// up to the virtual root, whose treeView pointer we know to be
/// correct
/// </devdoc>
internal TreeView FindTreeView() {
TreeNode node = this;
while (node.parent != null)
node = node.parent;
return node.treeView;
}
/// <include file='doc\TreeNode.uex' path='docs/doc[@for="TreeNode.GetFullPath"]/*' />
/// <devdoc>
/// Helper function for getFullPath().
/// </devdoc>
private void GetFullPath(StringBuilder path, string pathSeparator) {
if (parent != null) {
parent.GetFullPath(path, pathSeparator);
if (parent.parent != null)
path.Append(pathSeparator);
path.Append(this.text);
}
}
/// <include file='doc\TreeNode.uex' path='docs/doc[@for="TreeNode.GetNodeCount"]/*' />
/// <devdoc>
/// Returns number of child nodes.
/// </devdoc>
public int GetNodeCount(bool includeSubTrees) {
int total = childCount;
if (includeSubTrees) {
for (int i = 0; i < childCount; i++)
total += children[i].GetNodeCount(true);
}
return total;
}
/// <include file='doc\TreeNode.uex' path='docs/doc[@for="TreeNode.InsertNodeAt"]/*' />
/// <devdoc>
/// Helper function to add node at a given index after all validation has been done
/// </devdoc>
/// <internalonly/>
internal void InsertNodeAt(int index, TreeNode node) {
EnsureCapacity(1);
node.parent = this;
node.index = index;
for (int i = childCount; i > index; --i) {
(children[i] = children[i-1]).index = i;
}
children[index] = node;
childCount++;
node.Realize(false);
if (TreeView != null && node == TreeView.selectedNode)
TreeView.SelectedNode = node; // communicate this to the handle
}
/// <include file='doc\TreeNode.uex' path='docs/doc[@for="TreeNode.InvalidateHostTree"]/*' />
/// <devdoc>
/// Invalidates the treeview control that is hosting this node
/// </devdoc>
private void InvalidateHostTree() {
if (treeView != null && treeView.IsHandleCreated) treeView.Invalidate();
}
/// <include file='doc\TreeNode.uex' path='docs/doc[@for="TreeNode.Realize"]/*' />
/// <devdoc>
/// </devdoc>
/// <internalonly/>
internal void Realize(bool insertFirst) {
// Debug.assert(handle == 0, "Node already realized");
TreeView tv = this.TreeView;
if (tv == null || !tv.IsHandleCreated || tv.IsDisposed)
return;
if (parent != null) { // Never realize the virtual root
if (tv.InvokeRequired) {
throw new InvalidOperationException(SR.GetString(SR.InvalidCrossThreadControlCall));
}
NativeMethods.TV_INSERTSTRUCT tvis = new NativeMethods.TV_INSERTSTRUCT();
tvis.item_mask = insertMask;
tvis.hParent = parent.handle;
TreeNode prev = PrevNode;
if (insertFirst || prev == null) {
tvis.hInsertAfter = (IntPtr)NativeMethods.TVI_FIRST;
}
else {
tvis.hInsertAfter = prev.handle;
// Debug.assert(tvis.hInsertAfter != 0);
}
tvis.item_pszText = Marshal.StringToHGlobalAuto(text);
tvis.item_iImage = (ImageIndexer.ActualIndex == -1) ? tv.ImageIndexer.ActualIndex : ImageIndexer.ActualIndex;
tvis.item_iSelectedImage = (SelectedImageIndexer.ActualIndex == -1) ? tv.SelectedImageIndexer.ActualIndex : SelectedImageIndexer.ActualIndex;
tvis.item_mask = NativeMethods.TVIF_TEXT;
tvis.item_stateMask = 0;
tvis.item_state = 0;
if (tv.CheckBoxes) {
tvis.item_mask |= NativeMethods.TVIF_STATE;
tvis.item_stateMask |= NativeMethods.TVIS_STATEIMAGEMASK;
tvis.item_state |= CheckedInternal ? CHECKED : UNCHECKED;
}
else if (tv.StateImageList != null && StateImageIndexer.ActualIndex >= 0) {
tvis.item_mask |= NativeMethods.TVIF_STATE;
tvis.item_stateMask = NativeMethods.TVIS_STATEIMAGEMASK;
tvis.item_state = ((StateImageIndexer.ActualIndex + 1) << SHIFTVAL);
}
if (tvis.item_iImage >= 0) tvis.item_mask |= NativeMethods.TVIF_IMAGE;
if (tvis.item_iSelectedImage >= 0) tvis.item_mask |= NativeMethods.TVIF_SELECTEDIMAGE;
// If you are editing when you add a new node, then the edit control
// gets placed in the wrong place. You must restore the edit mode
// asynchronously (PostMessage) after the add is complete
// to get the expected behavior.
//
bool editing = false;
IntPtr editHandle = UnsafeNativeMethods.SendMessage(new HandleRef(tv, tv.Handle), NativeMethods.TVM_GETEDITCONTROL, 0, 0);
if (editHandle != IntPtr.Zero) {
// currently editing...
//
editing = true;
UnsafeNativeMethods.SendMessage(new HandleRef(tv, tv.Handle), NativeMethods.TVM_ENDEDITLABELNOW, 0 /* fCancel==FALSE */, 0);
}
handle = UnsafeNativeMethods.SendMessage(new HandleRef(tv, tv.Handle), NativeMethods.TVM_INSERTITEM, 0, ref tvis);
tv.nodeTable[handle] = this;
// Lets update the Lparam to the Handle ....
UpdateNode(NativeMethods.TVIF_PARAM);
Marshal.FreeHGlobal(tvis.item_pszText);
if (editing) {
UnsafeNativeMethods.PostMessage(new HandleRef(tv, tv.Handle), NativeMethods.TVM_EDITLABEL, IntPtr.Zero, handle);
}
SafeNativeMethods.InvalidateRect(new HandleRef(tv, tv.Handle), null, false);
if (parent.nodesCleared && (insertFirst || prev == null) && !tv.Scrollable) {
// We need to Redraw the TreeView ...
// If and only If we are not scrollable ...
// and this is the FIRST NODE to get added..
// This is Comctl quirk where it just doesn't draw
// the first node after a Clear( ) if Scrollable == false.
UnsafeNativeMethods.SendMessage(new HandleRef(tv, tv.Handle), NativeMethods.WM_SETREDRAW, 1, 0);
nodesCleared = false;
}
}
for (int i = childCount - 1; i >= 0; i--)
children[i].Realize(true);
// If node expansion was requested before the handle was created,
// we can expand it now.
if (expandOnRealization) {
Expand();
}
// If node collapse was requested before the handle was created,
// we can expand it now.
if (collapseOnRealization) {
Collapse();
}
}
/// <include file='doc\TreeNode.uex' path='docs/doc[@for="TreeNode.Remove"]/*' />
/// <devdoc>
/// Remove this node from the TreeView control. Child nodes are also removed from the
/// TreeView, but are still attached to this node.
/// </devdoc>
public void Remove() {
Remove(true);
}
/// <include file='doc\TreeNode.uex' path='docs/doc[@for="TreeNode.Remove1"]/*' />
/// <devdoc>
/// </devdoc>
/// <internalonly/>
internal void Remove(bool notify) {
bool expanded = IsExpanded;
// unlink our children
//
for (int i = 0; i < childCount; i++)
children[i].Remove(false);
// children = null;
// unlink ourself
if (notify && parent != null) {
for (int i = index; i < parent.childCount-1; ++i) {
(parent.children[i] = parent.children[i+1]).index = i;
}
// Fix Dev10 Bug 473773 - TreeViewNodeCollection.AddRange adds nodes in incorrect order
// should always release the last node
parent.children[parent.childCount - 1] = null;
parent.childCount--;
parent = null;
}
// Expand when we are realized the next time.
expandOnRealization = expanded;
// unrealize ourself
TreeView tv = this.TreeView;
if (tv == null || tv.IsDisposed) {
return;
}
if (handle != IntPtr.Zero) {
if (notify && tv.IsHandleCreated)
UnsafeNativeMethods.SendMessage(new HandleRef(tv, tv.Handle), NativeMethods.TVM_DELETEITEM, 0, handle);
treeView.nodeTable.Remove(handle);
handle = IntPtr.Zero;
}
treeView = null;
}
/// <include file='doc\TreeNode.uex' path='docs/doc[@for="TreeNode.RemovePropBagIfEmpty"]/*' />
/// <devdoc>
/// Removes the propBag object if it's now devoid of useful data
/// </devdoc>
/// <internalonly/>
private void RemovePropBagIfEmpty() {
if (propBag==null) return;
if (propBag.IsEmpty()) propBag = null;
return;
}
private void ResetExpandedState(TreeView tv) {
Debug.Assert(tv.IsHandleCreated, "nonexistent handle");
NativeMethods.TV_ITEM item = new NativeMethods.TV_ITEM();
item.mask = NativeMethods.TVIF_HANDLE | NativeMethods.TVIF_STATE;
item.hItem = handle;
item.stateMask = NativeMethods.TVIS_EXPANDEDONCE;
item.state = 0;
UnsafeNativeMethods.SendMessage(new HandleRef(tv, tv.Handle), NativeMethods.TVM_SETITEM, 0, ref item);
}
private bool ShouldSerializeBackColor() {
return BackColor != Color.Empty;
}
private bool ShouldSerializeForeColor() {
return ForeColor != Color.Empty;
}
/// <include file='doc\TreeNode.uex' path='docs/doc[@for="TreeNode.Serialize"]/*' />
/// <devdoc>
/// Saves this TreeNode object to the given data stream.
/// </devdoc>
/// Review: Changing this would break VB users. so suppresing this message.
/// SECREVIEW: Since ISerializable.GetObjectData and Deserialize require SerializationFormatter - locking down.
[SecurityPermissionAttribute(SecurityAction.Demand, Flags=SecurityPermissionFlag.SerializationFormatter),
SecurityPermission(SecurityAction.InheritanceDemand, Flags=SecurityPermissionFlag.SerializationFormatter)]
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")]
protected virtual void Serialize(SerializationInfo si, StreamingContext context) {
if (propBag != null) {
si.AddValue("PropBag", propBag, typeof(OwnerDrawPropertyBag));
}
si.AddValue("Text", text);
si.AddValue("ToolTipText", toolTipText);
si.AddValue("Name", Name);
si.AddValue("IsChecked", treeNodeState[TREENODESTATE_isChecked]);
si.AddValue("ImageIndex", ImageIndexer.Index);
si.AddValue("ImageKey", ImageIndexer.Key);
si.AddValue("SelectedImageIndex", SelectedImageIndexer.Index);
si.AddValue("SelectedImageKey", SelectedImageIndexer.Key);
if (this.treeView != null && this.treeView.StateImageList != null) {
si.AddValue("StateImageIndex", StateImageIndexer.Index);
}
if (this.treeView != null && this.treeView.StateImageList != null) {
si.AddValue("StateImageKey", StateImageIndexer.Key);
}
si.AddValue("ChildCount", childCount);
if (childCount > 0) {
for (int i = 0; i < childCount; i++) {
si.AddValue("children" + i, children[i], typeof(TreeNode));
}
}
if (userData != null && userData.GetType().IsSerializable) {
si.AddValue("UserData", userData, userData.GetType());
}
}
/// <include file='doc\TreeNode.uex' path='docs/doc[@for="TreeNode.Toggle"]/*' />
/// <devdoc>
/// Toggle the state of the node. Expand if collapsed or collapse if
/// expanded.
/// </devdoc>
public void Toggle() {
Debug.Assert(parent != null, "toggle on virtual root");
// I don't use the TVE_TOGGLE message 'cuz Windows TreeView doesn't send the appropriate
// notifications when collapsing.
if (IsExpanded) {
Collapse();
}
else {
Expand();
}
}
/// <include file='doc\TreeNode.uex' path='docs/doc[@for="TreeNode.ToString"]/*' />
/// <devdoc>
/// Returns the label text for the tree node
/// </devdoc>
public override string ToString() {
return "TreeNode: " + (text == null ? "" : text);
}
/// <include file='doc\TreeNode.uex' path='docs/doc[@for="TreeNode.UpdateNode"]/*' />
/// <devdoc>
/// Tell the TreeView to refresh this node
/// </devdoc>
private void UpdateNode(int mask) {
if (handle == IntPtr.Zero) return;
TreeView tv = TreeView;
Debug.Assert(tv != null, "TreeNode has handle but no TreeView");
NativeMethods.TV_ITEM item = new NativeMethods.TV_ITEM();
item.mask = NativeMethods.TVIF_HANDLE | mask;
item.hItem = handle;
if ((mask & NativeMethods.TVIF_TEXT) != 0)
item.pszText = Marshal.StringToHGlobalAuto(text);
if ((mask & NativeMethods.TVIF_IMAGE) != 0)
item.iImage = (ImageIndexer.ActualIndex == -1) ? tv.ImageIndexer.ActualIndex : ImageIndexer.ActualIndex;
if ((mask & NativeMethods.TVIF_SELECTEDIMAGE) != 0)
item.iSelectedImage = (SelectedImageIndexer.ActualIndex == -1) ? tv.SelectedImageIndexer.ActualIndex : SelectedImageIndexer.ActualIndex;
if ((mask & NativeMethods.TVIF_STATE) != 0) {
item.stateMask = NativeMethods.TVIS_STATEIMAGEMASK;
if (StateImageIndexer.ActualIndex != -1) {
item.state = ((StateImageIndexer.ActualIndex + 1) << SHIFTVAL);
}
// VSWhidbey 143401: ActualIndex == -1 means "don't use custom image list"
// so just leave item.state set to zero, that tells the unmanaged control
// to use no state image for this node.
}
if ((mask & NativeMethods.TVIF_PARAM) != 0) {
item.lParam = handle;
}
UnsafeNativeMethods.SendMessage(new HandleRef(tv, tv.Handle), NativeMethods.TVM_SETITEM, 0, ref item);
if ((mask & NativeMethods.TVIF_TEXT) != 0) {
Marshal.FreeHGlobal(item.pszText);
if (tv.Scrollable)
tv.ForceScrollbarUpdate(false);
}
}
internal void UpdateImage ()
{
TreeView tv = this.TreeView;
if (tv.IsDisposed) {
return;
}
NativeMethods.TV_ITEM item = new NativeMethods.TV_ITEM();
item.mask = NativeMethods.TVIF_HANDLE | NativeMethods.TVIF_IMAGE;
item.hItem = Handle;
item.iImage = Math.Max(0, ((ImageIndexer.ActualIndex >= tv.ImageList.Images.Count) ? tv.ImageList.Images.Count - 1 : ImageIndexer.ActualIndex));
UnsafeNativeMethods.SendMessage(new HandleRef(tv, tv.Handle), NativeMethods.TVM_SETITEM, 0, ref item);
}
/// <include file='doc\TreeNode.uex' path='docs/doc[@for="TreeNode.ISerializable.GetObjectData"]/*' />
/// <devdoc>
/// ISerializable private implementation
/// </devdoc>
/// <internalonly/>
[SecurityPermissionAttribute(SecurityAction.LinkDemand, Flags=SecurityPermissionFlag.SerializationFormatter)]
void ISerializable.GetObjectData(SerializationInfo si, StreamingContext context) {
Serialize(si, context);
}
}
}
|