1 // Permission is hereby granted, free of charge, to any person obtaining
2 // a copy of this software and associated documentation files (the
3 // "Software"), to deal in the Software without restriction, including
4 // without limitation the rights to use, copy, modify, merge, publish,
5 // distribute, sublicense, and/or sell copies of the Software, and to
6 // permit persons to whom the Software is furnished to do so, subject to
7 // the following conditions:
9 // The above copyright notice and this permission notice shall be
10 // included in all copies or substantial portions of the Software.
12 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
13 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
14 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
15 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
16 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
17 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
18 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20 // Copyright (c) 2004 Novell, Inc.
23 // Jackson Harper (jackson@ximian.com)
28 using System.Drawing.Drawing2D;
29 using System.Collections;
31 namespace System.Windows.Forms {
33 public class TreeView : Control {
35 private string path_separator = "\\";
36 private int item_height = -1;
38 private TreeNode top_node;
39 internal TreeNode root_node;
40 private TreeNodeCollection nodes;
41 private int total_node_count;
43 private ImageList image_list;
44 private int image_index = -1;
45 private int selected_image_index = -1;
47 private bool full_row_select;
48 private bool hot_tracking;
49 private int indent = 19;
51 private bool checkboxes;
52 private bool label_edit;
53 private bool scrollable;
54 private bool show_lines = true;
55 private bool show_root_lines = true;
56 private bool show_plus_minus = true;
58 private int update_stack;
60 private TreeViewEventHandler on_after_check;
61 private TreeViewEventHandler on_after_collapse;
62 private TreeViewEventHandler on_after_expand;
63 private NodeLabelEditEventHandler on_after_label_edit;
64 private TreeViewEventHandler on_after_select;
65 private TreeViewCancelEventHandler on_before_check;
66 private TreeViewCancelEventHandler on_before_collapse;
67 private TreeViewCancelEventHandler on_before_expand;
68 private NodeLabelEditEventHandler on_before_label_edit;
69 private TreeViewCancelEventHandler on_before_select;
75 root_node = new TreeNode (this);
76 root_node.Text = "ROOT NODE";
77 nodes = new TreeNodeCollection (root_node);
78 root_node.SetNodes (nodes);
80 MouseDown += new MouseEventHandler (MouseDownHandler);
81 SizeChanged += new EventHandler (SizeChangedHandler);
83 SetStyle (ControlStyles.AllPaintingInWmPaint, true);
84 SetStyle (ControlStyles.UserPaint, true);
86 dash = new Pen (SystemColors.ControlLight, 1);
89 public string PathSeparator {
90 get { return path_separator; }
91 set { path_separator = value; }
95 get { return sorted; }
104 public TreeNode TopNode {
105 get { return top_node; }
108 public TreeNodeCollection Nodes {
109 get { return nodes; }
112 public int ItemHeight {
114 if (item_height == -1)
115 return FontHeight + 3;
119 if (value == item_height)
126 public int VisibleCount {
128 return ClientRectangle.Height / ItemHeight;
132 [MonoTODO ("Anything special need to be done here?")]
133 public ImageList ImageList {
134 get { return image_list; }
135 set { image_list = value; }
138 public int ImageIndex {
139 get { return image_index; }
142 throw new ArgumentException ("'" + value + "' is not a valid value for 'value'. " +
143 "'value' must be greater than or equal to 0.");
149 public int SelectedImageIndex {
150 get { return selected_image_index; }
153 throw new ArgumentException ("'" + value + "' is not a valid value for 'value'. " +
154 "'value' must be greater than or equal to 0.");
159 public override Color BackColor {
160 get { return base.BackColor;}
161 set { base.BackColor = value; }
164 public override Image BackgroundImage {
165 get { return base.BackgroundImage; }
166 set { base.BackgroundImage = value; }
170 Commented out until this is implemented in Control
171 public override BorderStyle BorderStyle {
172 get { return base.BorderStyle; }
173 set { base.BorderStyle = value; }
177 public override Color ForeColor {
178 get { return base.ForeColor; }
179 set { base.ForeColor = value; }
182 public override string Text {
183 get { return base.Text; }
184 set { base.Text = value; }
187 public bool FullRowSelect {
188 get { return full_row_select; }
190 if (value == full_row_select)
192 full_row_select = value;
197 public bool HotTracking {
198 get { return hot_tracking; }
199 set { hot_tracking = value; }
203 get { return indent; }
208 throw new ArgumentException ("'" + value + "' is not a valid value for 'Indent'. " +
209 "'Indent' must be less than or equal to 32000");
212 throw new ArgumentException ("'" + value + "' is not a valid value for 'Indent'. " +
213 "'Indent' must be greater than or equal to 0.");
220 public bool LabelEdit {
221 get { return label_edit; }
222 set { label_edit = value; }
225 public bool Scrollable {
226 get { return scrollable; }
228 if (scrollable == value)
234 public bool ShowLines {
235 get { return show_lines; }
237 if (show_lines == value)
244 public bool ShowRootLines {
245 get { return show_root_lines; }
247 if (show_root_lines == value)
249 show_root_lines = value;
254 public bool CheckBoxes {
255 get { return checkboxes; }
257 if (value == checkboxes)
264 public bool ShowPlusMinus {
265 get { return show_plus_minus; }
267 if (show_plus_minus == value)
269 show_plus_minus = value;
274 [MonoTODO ("Anything extra needed here")]
275 protected override CreateParams CreateParams {
277 CreateParams cp = base.CreateParams;
283 protected override Size DefaultSize {
284 get { return new Size (121, 97); }
287 internal int TotalNodeCount {
288 get { return total_node_count; }
289 set { total_node_count = value; }
292 protected override void CreateHandle ()
294 base.CreateHandle ();
297 protected override void Dispose (bool disposing)
300 if (image_list != null)
301 image_list.Dispose ();
303 base.Dispose (disposing);
306 [MonoTODO ("What does the state effect?")]
307 protected OwnerDrawPropertyBag GetItemRenderStyles (TreeNode node, int state)
309 return node.prop_bag;
312 [MonoTODO ("Need to know if we are editing, not if editing is enabled")]
313 protected override bool IsInputKey (Keys key_data)
315 if (label_edit && (key_data & Keys.Alt) == 0)
317 switch (key_data & Keys.KeyCode) {
331 return base.IsInputKey (key_data);
334 protected virtual void OnAfterCheck (TreeViewEventArgs e)
336 if (on_after_check != null)
337 on_after_check (this, e);
340 protected internal virtual void OnAfterCollapse (TreeViewEventArgs e)
342 if (on_after_collapse != null)
343 on_after_collapse (this, e);
346 protected internal virtual void OnAfterExpand (TreeViewEventArgs e)
348 if (on_after_expand != null)
349 on_after_expand (this, e);
352 protected virtual void OnAfterLabelEdit (NodeLabelEditEventArgs e)
354 if (on_after_label_edit != null)
355 on_after_label_edit (this, e);
358 protected virtual void OnAfterSelect (TreeViewEventArgs e)
360 if (on_after_select != null)
361 on_after_select (this, e);
364 protected virtual void OnBeforeCheck (TreeViewCancelEventArgs e)
366 if (on_before_check != null)
367 on_before_check (this, e);
370 protected internal virtual void OnBeforeCollapse (TreeViewCancelEventArgs e)
372 if (on_before_collapse != null)
373 on_before_collapse (this, e);
376 protected internal virtual void OnBeforeExpand (TreeViewCancelEventArgs e)
378 if (on_before_expand != null)
379 on_before_expand (this, e);
382 protected virtual void OnBeforeLabelEdit (NodeLabelEditEventArgs e)
384 if (on_before_label_edit != null)
385 on_before_label_edit (this, e);
388 protected virtual void OnBeforeSelect (TreeViewCancelEventArgs e)
390 if (on_before_select != null)
391 on_before_select (this, e);
394 protected override void OnHandleCreated (EventArgs e)
396 base.OnHandleCreated (e);
399 protected override void OnHandleDestroyed (EventArgs e)
401 base.OnHandleDestroyed (e);
404 public void BeginUpdate ()
406 if (!IsHandleCreated)
411 public void EndUpdate ()
413 if (!IsHandleCreated)
416 if (update_stack > 1) {
424 public void ExpandAll ()
426 root_node.ExpandAll ();
429 public void CollapseAll ()
431 root_node.CollapseAll ();
434 public TreeNode GetNodeAt (Point pt)
436 return GetNodeAt (pt.X, pt.Y);
439 public TreeNode GetNodeAt (int x, int y)
441 if (top_node == null)
442 top_node = nodes [0];
444 OpenTreeNodeEnumerator o = new OpenTreeNodeEnumerator (TopNode);
445 int move = y / ItemHeight;
447 for (int i = 0; i < move; i++) {
452 // Make sure it is in the horizontal bounding box
453 if (o.CurrentNode.Bounds.Left > x && o.CurrentNode.Bounds.Right < x)
454 return o.CurrentNode;
459 public int GetNodeCount (bool include_subtrees)
461 return root_node.GetNodeCount (include_subtrees);
464 public override string ToString ()
466 int count = Nodes.Count;
468 return String.Concat (base.ToString (), "Node Count: 0");
469 return String.Concat (base.ToString (), "Node Count: ", count, " Nodes[0]: ", Nodes [0]);
473 protected override void WndProc(ref Message m)
475 switch ((Msg) m.Msg) {
477 PaintEventArgs paint_event;
479 paint_event = XplatUI.PaintEventStart (Handle);
480 DoPaint (paint_event);
481 XplatUI.PaintEventEnd (Handle);
484 case Msg.WM_LBUTTONDBLCLK:
485 Console.WriteLine ("double click");
488 base.WndProc (ref m);
491 internal void UpdateBelow (TreeNode node)
493 // Invalidate all these nodes and the nodes below it
496 private void DoPaint (PaintEventArgs pe)
498 if (Width <= 0 || Height <= 0 || Visible == false)
502 pe.Graphics.DrawImage (ImageBuffer, 0, 0);
505 private bool add_hscroll;
506 private bool add_vscroll;
507 private int max_node_width;
511 DateTime start = DateTime.Now;
512 if (top_node == null && Nodes.Count > 0)
513 top_node = nodes [0];
514 // Decide if we need a scrollbar
515 int visible_node_count = GetVisibleNodeCount ();
516 Console.WriteLine ("time to get visible node count: " + (DateTime.Now - start));
519 Rectangle fill = ClientRectangle;
520 Rectangle vclip = Rectangle.Empty;
525 if ((visible_node_count * ItemHeight) > ClientRectangle.Height) {
528 vbar = new VScrollBar ();
529 vclip = new Rectangle (ClientRectangle.Width - vbar.Width, 0, vbar.Width, Height);
530 fill.Width -= vbar.Width;
531 DeviceContext.ExcludeClip (vclip);
534 DeviceContext.FillRectangle (new SolidBrush (Color.White), fill);
537 int item_height = ItemHeight;
539 int height = ClientRectangle.Height;
541 foreach (TreeNode node in nodes) {
542 DrawNode (node, ref depth, ref node_count, item_height,
543 font, ref visible_node_count, height);
547 if (max_node_width > ClientRectangle.Width) {
549 AddHorizontalScrollBar ();
553 AddVerticalScrollBar (node_count, vclip);
555 if (add_hscroll && add_vscroll) {
556 Rectangle grip = new Rectangle (hbar.Right, vbar.Bottom, vbar.Width, hbar.Height);
557 DeviceContext.FillRectangle (new SolidBrush (BackColor), grip);
558 ControlPaint.DrawSizeGrip (DeviceContext, BackColor, grip);
562 ControlPaint.DrawBorder3D (DeviceContext, ClientRectangle, Border3DStyle.Sunken,
563 Border3DSide.Left | Border3DSide.Right | Border3DSide.Top | Border3DSide.Bottom);
568 foreach (TreeNode node in nodes) {
569 DumpNode (node, ref depth);
574 Console.WriteLine ("treeview drawing time: " + (DateTime.Now - start));
575 Console.WriteLine ("node count: " + node_count);
576 Console.WriteLine ("total node count: " + total_node_count);
579 private void DumpNode (TreeNode node, ref int depth)
581 for (int i = 0; i < depth; i++)
582 Console.Write ("****");
583 Console.WriteLine (node.Text);
585 if (node.PrevNode != null)
586 Console.WriteLine (" -- " + node.PrevNode.Text);
588 foreach (TreeNode child in node.Nodes) {
589 DumpNode (child, ref depth);
595 private void DrawNodePlusMinus (TreeNode node, int x, int y, int middle)
597 node.UpdatePlusMinusBounds (x, middle - 4, 8, 8);
599 DeviceContext.DrawRectangle (SystemPens.ControlDark, node.plus_minus_bounds);
601 if (node.IsExpanded) {
602 DeviceContext.DrawLine (SystemPens.ControlDarkDark, x + 2, middle, x + 6, middle);
604 DeviceContext.DrawLine (SystemPens.ControlDarkDark, x + 2, middle, x + 6, middle);
605 DeviceContext.DrawLine (SystemPens.ControlDarkDark, x + 4, y + 6, x + 4, y + 10);
609 private void DrawNodeLines (TreeNode node, Pen dash, int x, int y, int middle, int item_height, int node_count)
612 if (node_count > 0 && show_plus_minus)
614 DeviceContext.DrawLine (dash, x - indent + xadjust, middle, x, middle);
617 if (node.PrevNode != null) {
618 int prevadjust = (node.Nodes.Count > 0 && show_plus_minus ? 4 : 0);
619 int myadjust = (node.Nodes.Count > 0 && show_plus_minus ? 4 : 0);
620 ly = node.PrevNode.Bounds.Bottom - (item_height / 2) + prevadjust;
621 DeviceContext.DrawLine (dash, x - indent + 9, middle - myadjust, x - indent + 9, ly);
622 } else if (node.Parent != null) {
623 int myadjust = (node.Nodes.Count > 0 && show_plus_minus ? 4 : 0);
624 ly = node.Parent.Bounds.Bottom;
625 DeviceContext.DrawLine (dash, x - indent + 9, middle - myadjust, x - indent + 9, ly);
629 private void DrawNodeImage (TreeNode node, int x, int y)
631 if (node.ImageIndex > -1 && ImageList != null && node.ImageIndex < ImageList.Images.Count) {
632 ImageList.Draw (DeviceContext, x, y + 2, ImageList.ImageSize.Width,
633 ImageList.ImageSize.Height, node.ImageIndex);
634 } else if (ImageIndex > -1 && ImageList != null && ImageIndex < ImageList.Images.Count) {
635 ImageList.Draw (DeviceContext, x, y + 2, ImageList.ImageSize.Width,
636 ImageList.ImageSize.Height, ImageIndex);
640 private void UpdateNodeBounds (TreeNode node, int x, int y, int item_height)
642 int width = (int) (node.Text.Length * Font.Size);
645 if (!show_root_lines && node.Parent == null)
648 if (image_list != null)
649 xoff += image_list.ImageSize.Width;
650 node.UpdateBounds (x + xoff, y, width, item_height);
653 private void DrawNode (TreeNode node, ref int depth, ref int node_count, int item_height,
654 Font font, ref int visible_node_count, int max_height)
657 int x = (!show_root_lines && node.Parent != null ? depth - 1 : depth) * indent;
658 int y = (item_height + 1) * (node_count - skipped_nodes - 1);
659 bool visible = (y >= 0 && y < max_height);
662 visible_node_count++;
664 int _n_count = node.nodes.Count;
665 int middle = y + (item_height / 2);
667 UpdateNodeBounds (node, x, y, item_height);
669 if (show_root_lines || node.Parent != null) {
672 if (show_plus_minus && visible) {
673 DrawNodePlusMinus (node, x, y, middle);
680 DrawNodeLines (node, dash, x, y, middle, item_height, _n_count);
683 if (ImageList != null) {
685 DrawNodeImage (node, x, y);
686 // MS leaves the space for the image if the ImageList is
687 // non null regardless of whether or not an image is drawn
688 ox += ImageList.ImageSize.Width + 3; // leave a little space so the text isn't against the image
693 DeviceContext.DrawString (node.Text, font, new SolidBrush (Color.Black), ox, y + 2);
694 y += item_height + 1;
697 if (node.Bounds.Right > max_node_width)
698 max_node_width = node.Bounds.Right;
701 if (node.IsExpanded) {
702 for (int i = 0; i < _n_count; i++) {
704 DrawNode (node.nodes [i], ref tdepth, ref node_count, item_height,
705 font, ref visible_node_count, max_height);
715 private void AddVerticalScrollBar (int total_nodes, Rectangle bounds)
717 vbar.Maximum = total_nodes;
718 int height = ClientRectangle.Height;
720 vbar.LargeChange = height / ItemHeight;
723 bounds.Height -= hbar.Height;
725 vbar.Bounds = bounds;
729 vbar.ValueChanged += new EventHandler (VScrollBarValueChanged);
738 private void AddHorizontalScrollBar ()
741 hbar = new HScrollBar ();
744 hbar.Bounds = new Rectangle (ClientRectangle.Left, ClientRectangle.Bottom - hbar.Height,
745 (add_vscroll ? Width - vbar.Width : Width), hbar.Height);
753 private void SizeChangedHandler (object sender, EventArgs e)
756 if (max_node_width > ClientRectangle.Width) {
758 AddHorizontalScrollBar ();
762 vbar.Left = Right - vbar.Width;
763 vbar.Height = Height;
767 hbar.Top = Bottom - hbar.Height;
772 private void VScrollBarValueChanged (object sender, EventArgs e)
774 skipped_nodes = vbar.Value;
778 private int GetVisibleNodeCount ()
784 OpenTreeNodeEnumerator e = new OpenTreeNodeEnumerator (root_node.Nodes [0]);
787 while (e.MoveNext ()) {
794 // TODO: Handle all sorts o stuff here
795 private void MouseDownHandler (object sender, MouseEventArgs e)
797 if (!show_plus_minus)
800 OpenTreeNodeEnumerator walk = new OpenTreeNodeEnumerator (root_node);
802 // TODO: So much optimization potential here
803 int half_height = ItemHeight / 2;
804 while (walk.MoveNext ()) {
805 TreeNode node = (TreeNode) walk.Current;
806 if (node.PlusMinusBounds.Contains (e.X, e.Y)) {
813 public event TreeViewEventHandler AfterCheck {
814 add { on_after_check += value; }
815 remove { on_after_check -= value; }
818 public event TreeViewEventHandler AfterCollapse {
819 add { on_after_collapse += value; }
820 remove { on_after_collapse -= value; }
823 public event TreeViewEventHandler AfterExpand {
824 add { on_after_expand += value; }
825 remove { on_after_expand -= value; }
828 public event NodeLabelEditEventHandler AfterLabelEdit {
829 add { on_after_label_edit += value; }
830 remove { on_after_label_edit -= value; }
833 public event TreeViewEventHandler AfterSelect {
834 add { on_after_select += value; }
835 remove { on_after_select -= value; }
838 public event TreeViewCancelEventHandler BeforeCheck {
839 add { on_before_check += value; }
840 remove { on_before_check -= value; }
843 public event TreeViewCancelEventHandler BeforeCollapse {
844 add { on_before_collapse += value; }
845 remove { on_before_collapse -= value; }
848 public event TreeViewCancelEventHandler BeforeExpand {
849 add { on_before_expand += value; }
850 remove { on_before_expand -= value; }
853 public event NodeLabelEditEventHandler BeforeLabelEdit {
854 add { on_before_label_edit += value; }
855 remove { on_before_label_edit -= value; }
858 public event TreeViewCancelEventHandler BeforeSelect {
859 add { on_before_select += value; }
860 remove { on_before_select -= value; }
863 public new event PaintEventHandler Paint {
864 add { base.Paint += value; }
865 remove { base.Paint -= value; }
868 public new event EventHandler TextChanged {
869 add { base.TextChanged += value; }
870 remove { base.TextChanged -= value; }