Click here to Skip to main content
15,886,788 members
Articles / Programming Languages / C#

ContainerListView and TreeListView: Writing VS.NET design-surface compatible controls

Rate me:
Please Sign up or sign in to vote.
4.77/5 (144 votes)
13 Jan 2003CPOL17 min read 836.8K   22.2K   339  
Learn how to properly integrate your custom .NET control into the Visual Studio .NET design environment with TypeConverters and UITypeEditors. The article includes two useful controls, a container ListView, and a complete, feature-loaded TreeListView.
// TreeListView class
// Author: Jon Rista
// Email: jrista@hotmail.com
// 
// TreeListView extends ContainerListView to provide
// a fully-featured hybrid listview that allows the
// first column to behave as a tree.
////////////////////////////////////////////////////////

using System;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.ComponentModel.Design.Serialization;
using System.Collections;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Design;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Windows.Forms;

namespace SynapticEffect.Forms
{
	#region Enumerations
	#endregion

	#region Event Stuff
	#endregion

	#region TreeListNode
	[DesignTimeVisible(false), TypeConverter("SynapticEffect.Forms.TreeListNodeConverter")]
	public class TreeListNode: IParentChildList
	{
		#region Event Handlers
		public event MouseEventHandler MouseDown;

		private void OnSubItemsChanged(object sender, ItemsChangedEventArgs e)
		{
			subitems[e.IndexChanged].MouseDown += new MouseEventHandler(OnSubItemMouseDown);
		}

		private void OnSubItemMouseDown(object sender, MouseEventArgs e)
		{
			if (MouseDown != null)
				MouseDown(this, e);
		}

		private void OnSubNodeMouseDown(object sender, MouseEventArgs e)
		{
			if (MouseDown != null)
				MouseDown(sender, e);
		}
		#endregion

		#region Variables
		private Color backcolor = SystemColors.Window;
		private Rectangle bounds;
		private bool ischecked = false;
		private bool focused = false;
		private Font font, nodeFont;
		private Color forecolor = SystemColors.WindowText;
		private int imageindex = 0;
		private int stateimageindex = 0;
		private int index = 0;
		private TreeListView treelistview;
		private bool selected = false;
		private ContainerSubListViewItemCollection subitems;
		private object tag;
		private string text;
		private bool styleall = false;
		private bool hovered = false;

		private TreeListNode curChild = null;
		private TreeListNodeCollection nodes;
		private string fullPath = "";
		private bool expanded = false;
		private bool visible = true;

		private TreeListNode parent;
		#endregion

		#region Constructor
		public TreeListNode()
		{
			subitems = new ContainerSubListViewItemCollection();
			subitems.ItemsChanged += new ItemsChangedEventHandler(OnSubItemsChanged);

			nodes = new TreeListNodeCollection();
			nodes.Owner = this;
			nodes.MouseDown += new MouseEventHandler(OnSubNodeMouseDown);
		}
		#endregion

		#region Properties
		[
		Category("Behavior"),
		Description("Sets the parent node of this node.")
		]
		public TreeListNode Parent
		{
			set { parent = value; }
		}

		[
		Category("Data"), 
		Description("The collection of root nodes in the treelist."),
		DesignerSerializationVisibility(DesignerSerializationVisibility.Content),
		Editor(typeof(CollectionEditor), typeof(UITypeEditor))
		]
		public TreeListNodeCollection Nodes
		{
			get { return nodes; }
		}

		[Category("Behavior"), DefaultValue(false)]
		public bool IsExpanded
		{
			get { return expanded; }
			set { expanded = value; }
		}

		[Category("Behavior"), DefaultValue(true)]
		public bool IsVisible
		{
			get { return visible; }
			set { visible = value; }
		}

		[Category("Behavior")]
		public string FullPath
		{
			get { return fullPath; }
		}

		[Category("Appearance")]
		public Color BackColor
		{
			get { return backcolor;	}
			set { backcolor = value; }
		}

		[Browsable(false)]
		public Rectangle Bounds
		{
			get { return bounds; }
		}

		[Category("Behavior")]
		public bool Checked
		{
			get { return ischecked; }
			set { ischecked = value; }
		}

		[Browsable(false)]
		public bool Focused
		{
			get { return focused; }
			set { focused = value; }
		}

		[Category("Appearance")]
		public Font Font
		{
			get { return font; }
			set { font = value; }
		}

		[Category("Appearance")]
		public Color ForeColor
		{
			get { return forecolor; }
			set { forecolor = value; }
		}

		[Category("Behavior")]
		public int ImageIndex
		{
			get { return imageindex; }
			set { imageindex = value; }
		}

		[Browsable(false)]
		public int Index
		{
			get { return index; }
			set { index = value; }
		}

		public TreeListView TreeListView
		{
			get { return treelistview; }
		}

		[Browsable(false)]
		public bool Selected
		{
			get { return selected; }
			set { selected = value; }
		}

		[
		Category("Behavior"),
		Description("The items collection of sub controls."),
		DesignerSerializationVisibility(DesignerSerializationVisibility.Content),
		Editor(typeof(CollectionEditor), typeof(UITypeEditor))		 
		]
		public ContainerSubListViewItemCollection SubItems
		{
			get { return subitems; }
		}

		[Category("Behavior")]
		public int StateImageIndex
		{
			get { return stateimageindex; }
			set { stateimageindex = value; }
		}

		[Browsable(false)]
		public object Tag
		{
			get { return tag; }
			set { tag = value; }
		}

		[Category("Appearance")]
		public string Text
		{
			get { return text; }
			set { text = value; }
		}

		[Category("Behavior")]
		public bool UseItemStyleForSubItems
		{
			get { return styleall; }
			set { styleall = value; }
		}

		[Browsable(false)]
		public bool Hovered
		{
			get { return hovered; }
			set { hovered = value; }
		}
		#endregion

		#region Methods
		public void Collapse()
		{
			expanded = false;
		}

		public void CollapseAll()
		{
			for(int i=0; i<nodes.Count; i++)
			{
				nodes[i].CollapseAll();
			}
			Collapse();
		}
		public void Expand()
		{
			expanded = true;
		}

		public void ExpandAll()
		{
			for (int i=0; i<nodes.Count; i++) 
				((TreeListNode)nodes[i]).ExpandAll();

			expanded = true;
		}

		public int GetNodeCount(bool includeSubTrees)
		{
			int c=0;

			if (includeSubTrees)
			{
				for (int n=0; n<nodes.Count; n++)
				{
					c += nodes[n].GetNodeCount(true);
				}
			}
			c += nodes.Count;
			return c;
		}

		public int GetVisibleNodeCount(bool includeSubTrees)
		{
			int c=0;

			if (expanded)
			{
				if (includeSubTrees)
				{
					for (int n=0; n<nodes.Count; n++)
					{
						if (nodes[n].IsExpanded)
							c += nodes[n].GetVisibleNodeCount(true);
					}
				}

				for (int n=0; n<nodes.Count; n++)
				{
					if (nodes[n].IsVisible)
						c++;
				}
			}		

			return c;
		}

		public void Remove()
		{
			int c = nodes.IndexOf(curChild);
			nodes.Remove(curChild);
			if (nodes.Count > 0 && nodes.Count > c)
				curChild = nodes[c];
			else
				curChild = nodes[nodes.Count];
		}

		public void Toggle()
		{
			if (expanded)
				expanded = false;
			else
				expanded = true;
		}
		#endregion

		#region IParentChildList
		public object ParentNode()
		{
			return parent;
		}

		public object PreviousSibling()
		{
			if (parent != null)
			{
				int thisIndex = parent.Nodes[this];
				if (thisIndex > 0)
					return parent.Nodes[thisIndex-1];
			}

			return null;
		}

		public object NextSibling()
		{
			if (parent != null)
			{
				int thisIndex = parent.Nodes[this];
				if (thisIndex < parent.Nodes.Count-1)
					return parent.Nodes[thisIndex+1];
			}

			return null;
		}

		public object FirstChild()
		{
			curChild = Nodes[0];
			return curChild;
		}

		public object LastChild()
		{
			curChild = Nodes[Nodes.Count-1];
			return curChild;
		}

		public object NextChild()
		{
			curChild = (TreeListNode)curChild.NextSibling();
			return curChild;
		}

		public object PreviousChild()
		{
			curChild = (TreeListNode)curChild.PreviousSibling();
			return curChild;
		}
		#endregion
	}

	public class TreeListNodeCollection: CollectionBase
	{
		#region Events
		public event MouseEventHandler MouseDown;
		public event EventHandler NodesChanged;

		private void OnMouseDown(object sender, MouseEventArgs e)
		{
			if (MouseDown != null)
				MouseDown(sender, e);
		}

		private void OnNodesChanged()
		{
			OnNodesChanged(this, new EventArgs());
		}

		private void OnNodesChanged(object sender, EventArgs e)
		{
			if (NodesChanged != null)
				NodesChanged(sender, e);
		}
		#endregion

		#region Variables
		private TreeListNode owner;
		#endregion

		public TreeListNodeCollection()
		{
		}

		public TreeListNodeCollection(TreeListNode owner)
		{
			this.owner = owner;
		}

		public TreeListNode Owner
		{
			get { return owner; }
			set { owner = value; }
		}

		public int TotalCount
		{
			get 
			{
				int tcnt = 0;
				tcnt += List.Count;
				foreach (TreeListNode n in List)
				{
					tcnt += n.Nodes.TotalCount;
				}

				return tcnt;
			}
		}

		#region Implementation
		public TreeListNode this[int index]
		{
			get 
			{ 
				if (List.Count > 0)
				{
					return List[index] as TreeListNode;
				}
				else
					return null;
			}
			set 
			{
				List[index] = value;
				TreeListNode tln = ((TreeListNode)List[index]);
				tln.MouseDown += new MouseEventHandler(OnMouseDown);
				tln.Nodes.NodesChanged += new EventHandler(OnNodesChanged);
				tln.Parent = owner;
				OnNodesChanged();
			}
		}

		public int this[TreeListNode item]
		{
			get { return List.IndexOf(item); }
		}

		public int Add(TreeListNode item)
		{
			item.MouseDown += new MouseEventHandler(OnMouseDown);
			item.Nodes.NodesChanged += new EventHandler(OnNodesChanged);
			item.Parent = owner;
			OnNodesChanged();
			return item.Index = List.Add(item);
		}

		public void AddRange(TreeListNode[] items)
		{
			lock(List.SyncRoot)
			{
				for (int i=0; i<items.Length; i++)
				{
					items[i].MouseDown += new MouseEventHandler(OnMouseDown);
					items[i].Nodes.NodesChanged+= new EventHandler(OnNodesChanged);
					items[i].Parent = owner;
					items[i].Index = List.Add(items[i]);
				}
				OnNodesChanged();
			}
		}

		public void Remove(TreeListNode item)
		{
			List.Remove(item);
		}

		public new void Clear()
		{
			for (int i=0; i<List.Count; i++)
			{
				ContainerSubListViewItemCollection col = ((TreeListNode)List[i]).SubItems;
				for (int j=0; j<col.Count; j++)
				{
					if (col[j].ItemControl != null)
					{
						col[j].ItemControl.Parent = null;
						col[j].ItemControl.Visible = false;
						col[j].ItemControl = null;
					}
				}
				((TreeListNode)List[i]).Nodes.Clear();
			}
			List.Clear();
			OnNodesChanged();
		}

		public int IndexOf(TreeListNode item)
		{
			return List.IndexOf(item);
		}
		#endregion
	}
	#endregion

	#region TreeListView
	/// <summary>
	/// TreeListView provides a hybrid listview whos first
	/// column can behave as a treeview. This control extends
	/// ContainerListView, allowing subitems to contain 
	/// controls.
	/// </summary>
	public class TreeListView : SynapticEffect.Forms.ContainerListView
	{
		#region Events
		protected override void OnSubControlMouseDown(object sender, MouseEventArgs e)
		{
			TreeListNode node = (TreeListNode)sender;
			
			UnselectNodes(nodes);
			
			node.Focused = true;
			node.Selected = true;				
			//focusedIndex = firstSelected = i;

			if (e.Clicks >= 2)
				node.Toggle();

			// set selected items and indices collections							
			//selectedIndices.Add(i);						
			//selectedItems.Add(items[i]);
			Invalidate(ClientRectangle);
		}

		protected virtual void OnNodesChanged(object sender, EventArgs e)
		{
			AdjustScrollbars();
		}
		#endregion

		#region Variables
		protected TreeListNodeCollection nodes;
		protected int indent = 19;
		protected int itemheight = 20;
		protected bool showlines = false, showrootlines = false, showplusminus = true;

		protected ListDictionary pmRects;
		protected ListDictionary nodeRowRects;

		protected bool alwaysShowPM = false;

		protected Bitmap bmpMinus, bmpPlus;

		private TreeListNode curNode;
		private TreeListNode virtualParent;

		private TreeListNodeCollection selectedNodes;

		private bool mouseActivate = false;

		private bool allCollapsed = false;
		#endregion

		#region Constructor
		public TreeListView(): base()
		{
			virtualParent = new TreeListNode();

			nodes = virtualParent.Nodes;
			nodes.Owner = virtualParent;
			nodes.MouseDown += new MouseEventHandler(OnSubControlMouseDown);
			nodes.NodesChanged += new EventHandler(OnNodesChanged);

			selectedNodes = new TreeListNodeCollection();

			nodeRowRects = new ListDictionary();
			pmRects = new ListDictionary();	

			// Use reflection to load the
			// embedded bitmaps for the
			// styles plus and minus icons
			Assembly myAssembly = Assembly.GetAssembly(Type.GetType("SynapticEffect.Forms.TreeListView"));
			Stream bitmapStream1 = myAssembly.GetManifestResourceStream("SynapticEffect.Forms.Resources.tv_minus.bmp");
			bmpMinus = new Bitmap(bitmapStream1);

			Stream bitmapStream2 = myAssembly.GetManifestResourceStream("SynapticEffect.Forms.Resources.tv_plus.bmp");
			bmpPlus = new Bitmap(bitmapStream2);
		}
		#endregion

		#region Properties
		[
		Browsable(false)
		]
		public TreeListNodeCollection SelectedNodes
		{
			get { return GetSelectedNodes(virtualParent); }
		}

		[
		Category("Behavior"),
		Description("Determins wether an item is activated or expanded by a double click."),
		DefaultValue(false)
		]
		public bool MouseActivte
		{
			get { return mouseActivate; }
			set { mouseActivate = value; }
		}

		[
		Category("Behavior"),
		Description("Specifies wether to always show plus/minus signs next to each node."),
		DefaultValue(false)
		]
		public bool AlwaysShowPlusMinus
		{
			get { return alwaysShowPM; }
			set { alwaysShowPM = value; }
		}

		[
		Category("Data"), 
		Description("The collection of root nodes in the treelist."),
		DesignerSerializationVisibility(DesignerSerializationVisibility.Content),
		Editor(typeof(CollectionEditor), typeof(UITypeEditor))
		]
		public TreeListNodeCollection Nodes
		{
			get { return nodes; }
		}

		[Browsable(false)]
		public override ContainerListViewItemCollection Items
		{
			get { return items; }
		}

		[
		Category("Behavior"),
		Description("The indentation of child nodes in pixels."),
		DefaultValue(19)
		]
		public int Indent
		{
			get { return indent; }
			set { indent = value; }
		}

		[
		Category("Appearance"),
		Description("The height of every item in the treelistview."),
		DefaultValue(18)
		]
		public int ItemHeight
		{
			get { return itemheight; }
			set { itemheight = value; }
		}

		[
		Category("Behavior"),
		Description("Indicates wether lines are shown between sibling nodes and between parent and child nodes."),
		DefaultValue(false)
		]
		public bool ShowLines
		{
			get { return showlines; }
			set { showlines = value; }
		}

		[
		Category("Behavior"),
		Description("Indicates wether lines are shown between root nodes."),
		DefaultValue(false)
		]
		public bool ShowRootLines
		{
			get { return showrootlines; }
			set { showrootlines = value; }
		}

		[
		Category("Behavior"),
		Description("Indicates wether plus/minus signs are shown next to parent nodes."),
		DefaultValue(true)
		]
		public bool ShowPlusMinus
		{
			get { return showplusminus; }
			set { showplusminus = value; }
		}
		#endregion

		#region Overrides
		public override bool PreProcessMessage(ref Message msg)
		{
			if (msg.Msg == WM_KEYDOWN)
			{
				if (nodes.Count > 0)
				{
					if (curNode != null)
					{
						Keys keyData = ((Keys) (int) msg.WParam) | ModifierKeys;
						Keys keyCode = ((Keys) (int) msg.WParam);

						if (keyCode == Keys.Left)	// collapse current node or move up to parent
						{
							if (curNode.IsExpanded)
							{
								curNode.Collapse();
							}
							else if (curNode.ParentNode() != null)
							{
								TreeListNode t = (TreeListNode)curNode.ParentNode();
								if (t.ParentNode() != null) // never select virtualParent node
								{
									if (!multiSelect)
										curNode.Selected = false;
									//else
									curNode.Focused = false;
									curNode = (TreeListNode)curNode.ParentNode();
									if (!multiSelect)
										curNode.Selected = true;
									//else
									curNode.Focused = true;
								}
							}

							Invalidate();
							return true;
						}
						else if (keyCode == Keys.Right) // expand current node or move down to first child
						{
							if (!curNode.IsExpanded)
							{
								curNode.Expand();
							}
							else if (curNode.IsExpanded && curNode.GetNodeCount(false) > 0)
							{
								if (!multiSelect)
									curNode.Selected = false;
								//else
								curNode.Focused = false;
								curNode = (TreeListNode)curNode.FirstChild();
								if (!multiSelect)
									curNode.Selected = true;
								//else
								curNode.Focused = true;
							}

							Invalidate();
							return true;
						}

						else if (keyCode == Keys.Up)
						{
							if (curNode.PreviousSibling() == null && curNode.ParentNode() != null)
							{
								TreeListNode t = (TreeListNode)curNode.ParentNode();
								if (t.ParentNode() != null) // never select virtualParent node
								{
									if (!multiSelect)
										curNode.Selected = false;
									//else
									curNode.Focused = false;
									curNode = (TreeListNode)curNode.ParentNode();
									if (!multiSelect)
										curNode.Selected = true;
									//else
									curNode.Focused = true;
								}

								Invalidate();
								return true;
							}
							else if (curNode.PreviousSibling() != null)
							{
								TreeListNode t = (TreeListNode)curNode.PreviousSibling();
								if (t.GetNodeCount(false) > 0 && t.IsExpanded)
								{
									do
									{
										t = (TreeListNode)t.LastChild();
										if (!t.IsExpanded)
										{
											if (!multiSelect)
												curNode.Selected = false;
											//else
											curNode.Focused = false;
											curNode = t;
											if (!multiSelect)
												curNode.Selected = true;
											//else
											curNode.Focused = true;
										}
									} while (t.GetNodeCount(false) > 0 && t.IsExpanded);
								}
								else
								{
									if (!multiSelect)
										curNode.Selected = false;
									//else
									curNode.Focused = false;
									curNode = (TreeListNode)curNode.PreviousSibling();
									if (!multiSelect)
										curNode.Selected = true;
									//else
									curNode.Focused = true;
								}

								Invalidate();
								return true;
							}
						}
						else if (keyCode == Keys.Down)
						{
							if (curNode.IsExpanded && curNode.GetNodeCount(false) > 0)
							{
								if (!multiSelect)
									curNode.Selected = false;
								//else
								curNode.Focused = false;
								curNode = (TreeListNode)curNode.FirstChild();							
								if (!multiSelect)
									curNode.Selected = true;
								//else
								curNode.Focused = true;
							}
							else if (curNode.NextSibling() == null && curNode.ParentNode() != null)
							{
								TreeListNode t = curNode;
								do
								{
									t = (TreeListNode)t.ParentNode();
									if (t.NextSibling() != null)
									{
										if (!multiSelect)
											curNode.Selected = false;
										//else
										curNode.Focused = false;
										curNode = (TreeListNode)t.NextSibling();
										if (!multiSelect)
											curNode.Selected = true;
										//else
										curNode.Focused = true;

										break;
									}	
								} while (t.NextSibling() == null && t.ParentNode() != null);
							}						
							else if (curNode.NextSibling() != null)
							{
								if (!multiSelect)
									curNode.Selected = false;
								//else
								curNode.Focused = false;
								curNode = (TreeListNode)curNode.NextSibling();							
								if (!multiSelect)
									curNode.Selected = true;
								//else
								curNode.Focused = true;
							}

							Invalidate();
							return true;
						}						
					}
				}
			}

			return base.PreProcessMessage(ref msg);
		}

		protected override void OnResize(EventArgs e)
		{
			base.OnResize(e);

			AdjustScrollbars();
			
			Invalidate();
		}


		private void AutoSetColWidth(TreeListNodeCollection nodes, ref int mwid, ref int twid, int i)
		{
			for (int j=0; j<nodes.Count; j++)
			{
				if (i > 0)
					twid = GetStringWidth(nodes[j].SubItems[i-1].Text);
				else
					twid = GetStringWidth(nodes[j].Text);
				twid += 5;
				if (twid > mwid)
					mwid = twid;

				if (nodes[j].Nodes.Count > 0)
				{
					AutoSetColWidth(nodes[j].Nodes, ref mwid, ref twid, i);
				}
			}
		}

		protected override void OnMouseDown(MouseEventArgs e)
		{
			base.OnMouseDown(e);

			for (int i=0; i<columns.Count; i++)
			{
				if (columnSizeRects.Length > 0 && MouseInRect(e, columnSizeRects[i]))
				{
					// autosize column
					if (e.Clicks == 2 && e.Button == MouseButtons.Left)
					{
						int mwid = 0;
						int twid = 0;

						AutoSetColWidth(nodes, ref mwid, ref twid, i);					
								
						twid = GetStringWidth(columns[i].Text);
						if (twid > mwid)
							mwid = twid;

						mwid += 5;

						if (columns[i].Image != null)
							mwid += 18;

						columns[i].Width = mwid;
						GenerateColumnRects();
					}
						// scale column
					else
					{
						colScaleMode = true;
						colScaleWid = columnRects[i].Width;
						scaledCol = i;
					}
				}
			}

			if (MouseInRect(e, rowsRect))
			{
				TreeListNode cnode;
				// check if a nodes plus/minus has been clicked
				cnode = NodePlusClicked(e);
				if (cnode != null)
				{
					cnode.Toggle();
					AdjustScrollbars();
					Invalidate(ClientRectangle);
					return;
				}

				if (e.Button == MouseButtons.Left)
				{
					// check if a noderow has been clicked
					if (multiSelectMode == MultiSelectMode.Single)
					{
						UnselectNodes(nodes);
						//selectedNodes.Clear();
					
						cnode = NodeInNodeRow(e);
						if (cnode != null)
						{
							cnode.Focused = true;
							cnode.Selected = true;
							curNode = cnode;

							//selectedNodes.Add(curNode);

							if (e.Clicks == 2 && !mouseActivate)
							{
								cnode.Toggle();
								AdjustScrollbars();
							}
							else if (e.Clicks == 2 && mouseActivate)
								OnItemActivate(new EventArgs());					
						}
						else if (curNode != null)
						{
							curNode.Focused = false;
							curNode.Selected = false;
						}
						Invalidate();
					}
					else if (multiSelectMode == MultiSelectMode.Range)
					{
						// to be implemented at a later date
					}
					else if (multiSelectMode == MultiSelectMode.Selective)
					{
						UnfocusNodes(nodes);

						cnode = NodeInNodeRow(e);
						if (cnode != null)
						{
							if (cnode.Selected)
							{
								// remove node from collection of selected nodes
								//selectedNodes.Remove(curNode);

								cnode.Focused = false;
								cnode.Selected = false;
								curNode = null;
							}
							else
							{
								cnode.Focused = true;
								cnode.Selected = true;
								curNode = cnode;

								// add node to collection of selected nodes
								//selectedNodes.Add(curNode);
							}

							if (e.Clicks == 2 && !mouseActivate)
							{
								cnode.Toggle();
								AdjustScrollbars();
							}
							else if (e.Clicks == 2 && mouseActivate)
								OnItemActivate(new EventArgs());

							Invalidate();
						}

					}
				}
			}			
		}


		protected override void OnKeyUp(KeyEventArgs e)
		{
			base.OnKeyUp(e);

			if (e.KeyCode == Keys.F5)
			{
				if (allCollapsed)
					ExpandAll();
				else
					CollapseAll();
			}
		}


		int rendcnt = 0;
		protected override void DrawRows(Graphics g, Rectangle r)
		{
			// render listview item rows
			int i;
			int totalRend = 0, childCount = 0;

			int maxrend = ClientRectangle.Height/itemheight+1;
			int prior = vscrollBar.Value/itemheight-1;
			if (prior < 0)
				prior = 0;
			int nodesprior = 0;
			int rootstart = 0;

			if (prior > 0)
			{
				int nodescur;
				for (i=0; i<nodes.Count; i++)
				{
					nodescur = 0;
					if (nodes[i].IsExpanded)
						nodescur = nodes[i].GetVisibleNodeCount(true);
					nodesprior += nodescur;
					nodesprior++;
					if (nodesprior > prior)
					{
						nodesprior -= nodescur+1;
						rootstart = i;
						break;
					}
				}
			}

			totalRend = nodesprior;
			rendcnt = 0;

			nodeRowRects.Clear();
			pmRects.Clear();

			for (i=rootstart; i<nodes.Count && rendcnt<maxrend; i++)
			{
				RenderNodeRows(nodes[i], g, r, 0, i, ref totalRend, ref childCount, nodes.Count);
			}
		}
		#endregion

		#region Helper Functions
		private int vsize, hsize;
		public override void AdjustScrollbars()
		{
			if (nodes.Count > 0 || columns.Count > 0 && !colScaleMode)
			{
				allColsWidth = 0;
				for (int i=0; i<columns.Count; i++)
				{
					allColsWidth += columns[i].Width;
				}

				allRowsHeight = 0;
				for (int i=0; i<nodes.Count; i++)
				{
					allRowsHeight += itemheight + itemheight*((TreeListNode)nodes[i]).GetVisibleNodeCount(true);
				}

				vsize = vscrollBar.Width;
				hsize = hscrollBar.Height;

				hscrollBar.Left = this.ClientRectangle.Left+2;
				hscrollBar.Width = this.ClientRectangle.Width-vsize-4;
				hscrollBar.Top = this.ClientRectangle.Top+this.ClientRectangle.Height-hscrollBar.Height-2;
				hscrollBar.Maximum = allColsWidth;			
				if (allColsWidth > this.ClientRectangle.Width-4-vsize)
				{
					hscrollBar.Show();
					hsize = hscrollBar.Height;
				}
				else
				{
					hscrollBar.Hide();
					hscrollBar.Value = 0;
					hsize = 0;
				}

				vscrollBar.Left = this.ClientRectangle.Left+this.ClientRectangle.Width-vscrollBar.Width-2;
				vscrollBar.Top = this.ClientRectangle.Top+headerBuffer+2;
				vscrollBar.Height = this.ClientRectangle.Height-hsize-headerBuffer-4;
				vscrollBar.Maximum = allRowsHeight;
				vscrollBar.LargeChange = (this.ClientRectangle.Height-headerBuffer-hsize-4 > 0 ? this.ClientRectangle.Height-headerBuffer-hsize-4 : 0);
				if (allRowsHeight > this.ClientRectangle.Height-headerBuffer-4-hsize)
				{
					vscrollBar.Show();
					vsize = vscrollBar.Width;
				}
				else
				{
					vscrollBar.Hide();
					vscrollBar.Value = 0;
					vsize = 0;
				}

				hscrollBar.Width = this.ClientRectangle.Width-vsize-4;
				hscrollBar.LargeChange = (this.ClientRectangle.Width - vsize - 4 > 0 ? this.ClientRectangle.Width - vsize - 4 : 0);
				hscrollBar.Top = this.ClientRectangle.Top+this.ClientRectangle.Height-hscrollBar.Height-2;	
				if (allColsWidth > this.ClientRectangle.Width-4-vsize)
				{
					hscrollBar.Show();
				}
				else
				{
					hscrollBar.Hide();
					hscrollBar.Value = 0;
					hsize = 0;
				}
			}
		}

		private void UnfocusNodes(TreeListNodeCollection nodecol)
		{
			for (int i=0; i<nodecol.Count; i++)
			{
				UnfocusNodes(nodecol[i].Nodes);
				nodecol[i].Focused = false;
			}
		}

		private void UnselectNodes(TreeListNodeCollection nodecol)
		{
			for (int i=0; i<nodecol.Count; i++)
			{
				UnselectNodes(nodecol[i].Nodes);
				nodecol[i].Focused = false;
				nodecol[i].Selected = false;
			}
		}

		private TreeListNode NodeInNodeRow(MouseEventArgs e)
		{
			IEnumerator ek = nodeRowRects.Keys.GetEnumerator();
			IEnumerator ev = nodeRowRects.Values.GetEnumerator();

			while (ek.MoveNext() && ev.MoveNext())
			{
				Rectangle r = (Rectangle)ek.Current;

				if (r.Left <= e.X && r.Left+r.Width >= e.X
					&& r.Top <= e.Y && r.Top+r.Height >= e.Y)
				{
					return (TreeListNode)ev.Current;
				}
			}

			return null;
		}

		private TreeListNode NodePlusClicked(MouseEventArgs e)
		{
			IEnumerator ek = pmRects.Keys.GetEnumerator();
			IEnumerator ev = pmRects.Values.GetEnumerator();

			while (ek.MoveNext() && ev.MoveNext())
			{
				Rectangle r = (Rectangle)ek.Current;

				if (r.Left <= e.X && r.Left+r.Width >= e.X
					&& r.Top <= e.Y && r.Top+r.Height >= e.Y)
				{
					return (TreeListNode)ev.Current;
				}
			}

			return null;
		}

		private void RenderNodeRows(TreeListNode node, Graphics g, Rectangle r, int level, int index, ref int totalRend, ref int childCount, int count)
		{
			if (node.IsVisible)
			{
				int eb = 10;	// edge buffer				

				// only render if row is visible in viewport
				if (((r.Top+itemheight*totalRend+eb/4-vscrollBar.Value+itemheight > r.Top) 
					&& (r.Top+itemheight*totalRend+eb/4-vscrollBar.Value < r.Top+r.Height)))
				{
					rendcnt++;
					int lb = 0;		// level buffer
					int ib = 0;		// icon buffer
					int hb = headerBuffer;	// header buffer	
					Pen linePen = new Pen(SystemBrushes.ControlDark, 1.0f);
					Pen PMPen = new Pen(SystemBrushes.ControlDark, 1.0f);
					Pen PMPen2 = new Pen(new SolidBrush(Color.Black), 1.0f);

					linePen.DashStyle = DashStyle.Dot;

					// add space for plis/minus icons and/or root lines to the edge buffer
					if (showrootlines || showplusminus)
					{
						eb += 10;
					}

					// set level buffer
					lb = indent*level;

					// set icon buffer
					if ((node.Selected || node.Focused) && stateImageList != null)
					{
						if (node.ImageIndex >= 0 && node.ImageIndex < stateImageList.Images.Count)
						{
							stateImageList.Draw(g, r.Left+lb+eb+2-hscrollBar.Value, r.Top+hb+itemheight*totalRend+eb/4-2-vscrollBar.Value, 16, 16, node.ImageIndex);
							ib = 18;
						}
					}
					else
					{
						if (smallImageList != null && node.ImageIndex >= 0 && node.ImageIndex < smallImageList.Images.Count)
						{
							smallImageList.Draw(g, r.Left+lb+eb+2-hscrollBar.Value, r.Top+hb+itemheight*totalRend+eb/4-2-vscrollBar.Value, 16, 16, node.ImageIndex);
							ib = 18;
						}
					}

					// add a rectangle to the node row rectangles
					Rectangle sr = new Rectangle(r.Left+lb+ib+eb+4-hscrollBar.Value, r.Top+hb+itemheight*totalRend+2-vscrollBar.Value, allColsWidth-(lb+ib+eb+4), itemheight);
					nodeRowRects.Add(sr, node);

					// render per-item background
					if (node.BackColor != this.BackColor)
					{
						if (node.UseItemStyleForSubItems)
							g.FillRectangle(new SolidBrush(node.BackColor), r.Left+lb+ib+eb+4-hscrollBar.Value, r.Top+hb+itemheight*totalRend+2-vscrollBar.Value, allColsWidth-(lb+ib+eb+4), itemheight);
						else
							g.FillRectangle(new SolidBrush(node.BackColor), r.Left+lb+ib+eb+4-hscrollBar.Value, r.Top+hb+itemheight*totalRend+2-vscrollBar.Value, columns[0].Width-(lb+ib+eb+4), itemheight);
					}

					g.Clip = new Region(sr);

					// render selection and focus
					if (node.Selected && isFocused)
					{
						g.FillRectangle(new SolidBrush(rowSelectColor), sr);
					}
					else if (node.Selected && !isFocused && !hideSelection)
					{
						g.FillRectangle(SystemBrushes.Control, sr);
					}
					else if (node.Selected && !isFocused && hideSelection)
					{
						ControlPaint.DrawFocusRectangle(g, sr);
					}

					if (node.Focused && ((isFocused && multiSelect) || !node.Selected))
					{
						ControlPaint.DrawFocusRectangle(g, sr);
					}
			
					g.Clip = new Region(new Rectangle(r.Left+2-hscrollBar.Value, r.Top+hb+2, columns[0].Width, r.Height-hb-4));

					// render root lines if visible
					if (r.Left+eb-hscrollBar.Value > r.Left)
					{						
						if (showrootlines && level == 0)
						{
							if (index == 0)
							{
								g.DrawLine(linePen, r.Left+eb/2-hscrollBar.Value, r.Top+eb/2+hb-vscrollBar.Value, r.Left+eb-hscrollBar.Value, r.Top+eb/2+hb-vscrollBar.Value);
								g.DrawLine(linePen, r.Left+eb/2-hscrollBar.Value, r.Top+eb/2+hb-vscrollBar.Value, r.Left+eb/2-hscrollBar.Value, r.Top+eb+hb-vscrollBar.Value);
							}
							else if (index == count-1)
							{
								g.DrawLine(linePen, r.Left+eb/2-hscrollBar.Value, r.Top+eb/2+hb+itemheight*(totalRend)-vscrollBar.Value, r.Left+eb-hscrollBar.Value, r.Top+eb/2+hb+itemheight*(totalRend)-vscrollBar.Value);
								g.DrawLine(linePen, r.Left+eb/2-hscrollBar.Value, r.Top+hb+itemheight*(totalRend)-vscrollBar.Value, r.Left+eb/2-hscrollBar.Value, r.Top+eb/2+hb+itemheight*(totalRend)-vscrollBar.Value);
							}
							else
							{
								g.DrawLine(linePen, r.Left+eb/2-hscrollBar.Value, r.Top+eb+hb+itemheight*(totalRend)-eb/2-vscrollBar.Value, r.Left+eb-hscrollBar.Value, r.Top+eb+hb+itemheight*(totalRend)-eb/2-vscrollBar.Value);
								g.DrawLine(linePen, r.Left+eb/2-hscrollBar.Value, r.Top+eb+hb+itemheight*(totalRend-1)-vscrollBar.Value, r.Left+eb/2-hscrollBar.Value, r.Top+eb+hb+itemheight*(totalRend)-vscrollBar.Value);
							}

							if (childCount > 0)
								g.DrawLine(linePen, r.Left+eb/2-hscrollBar.Value, r.Top+hb+itemheight*(totalRend-childCount)-vscrollBar.Value, r.Left+eb/2-hscrollBar.Value, r.Top+hb+itemheight*(totalRend)-vscrollBar.Value);
						}
					}

					// render child lines if visible
					if (r.Left+lb+eb-hscrollBar.Value > r.Left)
					{						
						if (showlines && level > 0)
						{
							if (index == count-1)
							{
								g.DrawLine(linePen, r.Left+lb+eb/2-hscrollBar.Value, r.Top+eb/2+hb+itemheight*(totalRend)-vscrollBar.Value, r.Left+lb+eb-hscrollBar.Value, r.Top+eb/2+hb+itemheight*(totalRend)-vscrollBar.Value);
								g.DrawLine(linePen, r.Left+lb+eb/2-hscrollBar.Value, r.Top+hb+itemheight*(totalRend)-vscrollBar.Value, r.Left+lb+eb/2-hscrollBar.Value, r.Top+eb/2+hb+itemheight*(totalRend)-vscrollBar.Value);
							}
							else
							{
								g.DrawLine(linePen, r.Left+lb+eb/2-hscrollBar.Value, r.Top+eb/2+hb+itemheight*(totalRend)-vscrollBar.Value, r.Left+lb+eb-hscrollBar.Value, r.Top+eb/2+hb+itemheight*(totalRend)-vscrollBar.Value);
								g.DrawLine(linePen, r.Left+lb+eb/2-hscrollBar.Value, r.Top+hb+itemheight*(totalRend)-vscrollBar.Value, r.Left+lb+eb/2-hscrollBar.Value, r.Top+eb+hb+itemheight*(totalRend)-vscrollBar.Value);
							}

							if (childCount > 0)
								g.DrawLine(linePen, r.Left+lb+eb/2-hscrollBar.Value, r.Top+hb+itemheight*(totalRend-childCount)-vscrollBar.Value, r.Left+lb+eb/2-hscrollBar.Value, r.Top+hb+itemheight*(totalRend)-vscrollBar.Value);
						}
					}

					// render +/- signs if visible
					if (r.Left+lb+eb/2+5-hscrollBar.Value > r.Left)
					{
						if (showplusminus && (node.GetNodeCount(false) > 0 || alwaysShowPM))
						{
							if (index == 0 && level == 0)
							{
								RenderPlus(g, r.Left+lb+eb/2-4-hscrollBar.Value, r.Top+hb+eb/2-4-vscrollBar.Value, 8, 8, node);
							}
							else if (index == count-1)
							{

								RenderPlus(g, r.Left+lb+eb/2-4-hscrollBar.Value, r.Top+hb+itemheight*totalRend+eb/2-4-vscrollBar.Value, 8, 8, node);
							}
							else
							{
								RenderPlus(g, r.Left+lb+eb/2-4-hscrollBar.Value, r.Top+hb+itemheight*totalRend+eb/2-4-vscrollBar.Value, 8, 8, node);
							}
						}
					}

					// render text if visible
					if (r.Left+columns[0].Width-hscrollBar.Value > r.Left)
					{
						if (node.Selected && isFocused)
							g.DrawString(TruncatedString(node.Text, columns[0].Width, lb+eb+ib+6, g), Font, SystemBrushes.HighlightText, (float)(r.Left+lb+ib+eb+4-hscrollBar.Value), (float)(r.Top+hb+itemheight*totalRend+eb/4-vscrollBar.Value));
						else
							g.DrawString(TruncatedString(node.Text, columns[0].Width, lb+eb+ib+6, g), Font, new SolidBrush(node.ForeColor), (float)(r.Left+lb+ib+eb+4-hscrollBar.Value), (float)(r.Top+hb+itemheight*totalRend+eb/4-vscrollBar.Value));
					}

					// render subitems
					int j;
					int last = 0;
					if (columns.Count > 0)
					{
						for (j=0; j<node.SubItems.Count && j<columns.Count; j++)
						{
							last += columns[j].Width;
							g.Clip = new Region(new Rectangle(last+6-hscrollBar.Value, r.Top+headerBuffer+2, (last+columns[j+1].Width > r.Width-6 ? r.Width-6 : columns[j+1].Width-6), r.Height-5));
							if (node.SubItems[j].ItemControl != null)
							{
								Control c = node.SubItems[j].ItemControl;
								c.Location = new Point(r.Left+last+4-hscrollBar.Value, r.Top+(itemheight*totalRend)+headerBuffer+4-vscrollBar.Value);
								c.ClientSize = new Size(columns[j+1].Width-6, rowHeight-4);
								c.Parent = this;
							}						
							else
							{
								string sp = "";
								if (columns[j+1].TextAlign == HorizontalAlignment.Left)
								{
									if (node.Selected && isFocused)
										g.DrawString(TruncatedString(node.SubItems[j].Text, columns[j+1].Width, 9, g), this.Font, SystemBrushes.HighlightText, (float)(last+6-hscrollBar.Value), (float)(r.Top+(itemheight*totalRend)+headerBuffer+4-vscrollBar.Value));
									else
										g.DrawString(TruncatedString(node.SubItems[j].Text, columns[j+1].Width, 9, g), this.Font, (node.UseItemStyleForSubItems ? new SolidBrush(node.ForeColor) : SystemBrushes.WindowText), (float)(last+6-hscrollBar.Value), (float)(r.Top+(itemheight*totalRend)+headerBuffer+4-vscrollBar.Value));
								}
								else if (columns[j+1].TextAlign == HorizontalAlignment.Right)
								{
									sp = TruncatedString(node.SubItems[j].Text, columns[j+1].Width, 9, g);
									if (node.Selected && isFocused)
										g.DrawString(sp, this.Font, SystemBrushes.HighlightText, (float)(last+columns[j+1].Width-Helpers.StringTools.MeasureDisplayStringWidth(g, sp, this.Font)-4-hscrollBar.Value), (float)(r.Top+(itemheight*totalRend)+headerBuffer+4-vscrollBar.Value));
									else
										g.DrawString(sp, this.Font, (node.UseItemStyleForSubItems ? new SolidBrush(node.ForeColor) : SystemBrushes.WindowText), (float)(last+columns[j+1].Width-Helpers.StringTools.MeasureDisplayStringWidth(g, sp, this.Font)-4-hscrollBar.Value), (float)(r.Top+(itemheight*totalRend)+headerBuffer+4-vscrollBar.Value));
								}
								else
								{
									sp = TruncatedString(node.SubItems[j].Text, columns[j+1].Width, 9, g);
									if (node.Selected && isFocused)
										g.DrawString(sp, this.Font, SystemBrushes.HighlightText, (float)(last+(columns[j+1].Width/2)-(Helpers.StringTools.MeasureDisplayStringWidth(g, sp, this.Font)/2)-hscrollBar.Value), (float)(r.Top+(itemheight*totalRend)+headerBuffer+4-vscrollBar.Value));
									else
										g.DrawString(sp, this.Font, (node.UseItemStyleForSubItems ? new SolidBrush(node.ForeColor) : SystemBrushes.WindowText), (float)(last+(columns[j+1].Width/2)-(Helpers.StringTools.MeasureDisplayStringWidth(g, sp, this.Font)/2)-hscrollBar.Value), (float)(r.Top+(itemheight*totalRend)+headerBuffer+4-vscrollBar.Value));
								}
							}
						}
					}
				}

				// increment number of rendered nodes
				totalRend++;

				// render child nodes
				if (node.IsExpanded)
				{
					childCount = 0;
					for (int n=0; n<node.GetNodeCount(false); n++)
					{
						RenderNodeRows(node.Nodes[n], g, r, level+1, n, ref totalRend, ref childCount, node.Nodes.Count);
					}
				}

				childCount = node.GetVisibleNodeCount(true);
				
			}
			else
			{
				childCount = 0;
			}
		}

		private void RenderPlus(Graphics g, int x, int y, int w, int h, TreeListNode node)
		{
			if (VisualStyles)
			{
				if (node.IsExpanded)
					g.DrawImage(bmpMinus, x, y);
				else
					g.DrawImage(bmpPlus, x, y);
			}
			else
			{
				g.DrawRectangle(new Pen(SystemBrushes.ControlDark),x, y, w, h);
				g.FillRectangle(new SolidBrush(Color.White), x+1, y+1, w-1, h-1);
				g.DrawLine(new Pen(new SolidBrush(Color.Black)), x+2, y+4, x+w-2, y+4);			

				if (!node.IsExpanded)
					g.DrawLine(new Pen(new SolidBrush(Color.Black)), x+4, y+2, x+4, y+h-2);
			}

			pmRects.Add(new Rectangle(x, y, w, h), node);
		}
		#endregion

		#region Methods
		public void CollapseAll()
		{
			foreach (TreeListNode node in nodes)
			{
				node.CollapseAll();
			}
			allCollapsed = true;
			AdjustScrollbars();
			Invalidate();
		}

		public void ExpandAll()
		{
			foreach (TreeListNode node in nodes)
			{
				node.ExpandAll();
			}
			allCollapsed = false;
			AdjustScrollbars();
			Invalidate();
		}

		public TreeListNode GetNodeAt(int x, int y)
		{
			// To be added
			return null;
		}

		public TreeListNode GetNodeAt(Point pt)
		{
			// To be added
			return null;
		}
		
		private TreeListNodeCollection GetSelectedNodes(TreeListNode node)
		{
			TreeListNodeCollection list = new TreeListNodeCollection();

			for (int i=0; i<node.Nodes.Count; i++)
			{
				// check if current node is selected
				if (node.Nodes[i].Selected)
				{
					list.Add(node.Nodes[i]);
				}

				// chech if node is expanded and has
				// selected children
				if (node.Nodes[i].IsExpanded)
				{
					TreeListNodeCollection list2 = GetSelectedNodes(node.Nodes[i]);
					for (int j=0; j<list2.Count; j++)
					{
						list.Add(list2[i]);
					}
				}
			}

			return list;
		}

		public int GetNodeCount()
		{
			int c = 0;

			c += nodes.Count;
			foreach (TreeListNode node in nodes)
			{
				c += GetNodeCount(node);
			}

			return c;
		}

		public int GetNodeCount(TreeListNode node)
		{
			int c = 0;
			c += node.Nodes.Count;
			foreach (TreeListNode n in node.Nodes)
			{
				c += GetNodeCount(n);
			}

			return c;
		}
		#endregion
	}
	#endregion

	#region Type Converters
	public class TreeListNodeConverter: TypeConverter
	{
		public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
		{
			if (destinationType == typeof(InstanceDescriptor))
			{
				return true;
			}
			return base.CanConvertTo(context, destinationType);
		}

		public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
		{
			if (destinationType == typeof(InstanceDescriptor) && value is TreeListNode)
			{
				TreeListNode tln = (TreeListNode)value;

				ConstructorInfo ci = typeof(TreeListNode).GetConstructor(new Type[] {});
				if (ci != null)
				{
					return new InstanceDescriptor(ci, null, false);
				}
			}
			return base.ConvertTo(context, culture, value, destinationType);
		}
	}
	#endregion
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect
United States United States
Jon Rista has been programming since the age of 8 (first Pascal program), and has been a programmer since the age of 10 (first practical program). In the last 21 years, he has learned to love C++, embrace object orientation, and finally enjoy the freedom of C#. He knows over 10 programming languages, and vows that his most important skill in programming is creativity, even more so than logic. Jon works on large-scale enterprise systems design and implementation, and employs Design Patterns, C#, .NET, and SQL Server in his daily doings.

Comments and Discussions