// Author: Bill Seddon
// Company: Lyquidity Solutions Limited
//
// This work builds on code posted to SourceForge by
// Jon Rista (jrista@hotmail.com)
//
// TreeListView extends ContainerListView to provide
// a fully-featured hybrid listview that allows the
// first column to behave as a tree.
//
// This version fixes up drawing, mouse and keyboard
// event handling issues. For example the focus row
// would not track correctly in all circumstances when
// the user used the up and down cursor keys.
//
// The way the tree is built has been changed quite a
// bit to address the performance issues Jon mentioned
// in his article. These changes allow the drawing
// routines to be more "intelligent" about the rows to
// be painted. In the original version, accommodating
// the arbitrary exapansions of nodes meant that the
// list needed to be counted each time the control was
// painted. This is no issue when the list is small
// but for large lists meant a sluggish response.
//
// In this version, nodes know about the state
// of their chilldren, information that is updated as
// new nodes are added or when existing ones are
// expanded, collapsed or removed. This requirement
// puts some additional burden on the control when nodes
// are manipulated (which is relatively infrequently)
// in exchange for lightening the load on the drawing
// routine.
//
// This code is provided "as is" and no warranty about
// it fitness for any specific task is expressed or
// implied. If you choose to use this code, you do so
// at your own risk.
//
////////////////////////////////////////////////////////
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 Lyquidity.Controls.ExtendedListViews
{
#region Enumerations
#endregion
#region TreeListView
///
/// TreeListView provides a hybrid listview whos first
/// column can behave as a treeview. This control extends
/// ContainerListView, allowing subitems to contain
/// controls.
///
[ToolboxItem(true)]
[ToolboxBitmap(typeof(Lyquidity.Controls.ExtendedListViews.TreeListView), "Resources.treeview.bmp")]
[DefaultEvent("SelectedItemChanged")]
public class TreeListView : Lyquidity.Controls.ExtendedListViews.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();
}
protected void OnSelectedItemChanged()
{
if (SelectedItemChanged != null)
{
SelectedItemChanged(this, new EventArgs());
}
}
#endregion
#region Variables
public event EventHandler SelectedItemChanged;
//private new event EventHandler SelectedIndexChanged;
protected TreeListNodeCollection nodes;
protected int indent = 19;
protected int itemheight = 20;
protected bool showlines = true, showrootlines = true, showplusminus = true;
protected bool alwaysShowPM = false;
private bool mouseActivate = false;
private bool allCollapsed = false;
protected ListDictionary pmRects;
protected ListDictionary nodeRowRects;
protected Bitmap bmpMinus, bmpPlus;
private TreeListNode curNode = null;
private TreeListNode firstSelectedNode = null;
private TreeListNode virtualParent = null;
// private SelectedTreeListNodeCollection selectedNodes;
private System.ComponentModel.Container components = null;
#endregion
#region Constructor
public TreeListView(): base()
{
virtualParent = new TreeListNode();
virtualParent.m_bIsRoot = true; // BMS 2003-05-27 - so that we know which parent is the root as this
// is special. For example, nodes are always visible
this.SelectedItemChanged = null;
//this.SelectedIndexChanged = null;
nodes = virtualParent.Nodes;
nodes.Owner = virtualParent;
nodes.MouseDown += new MouseEventHandler(OnSubControlMouseDown);
nodes.NodesChanged += new EventHandler(OnNodesChanged);
// selectedNodes = new SelectedTreeListNodeCollection();
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("Lyquidity.Controls.ExtendedListViews.TreeListView"));
Stream bitmapStream1 = myAssembly.GetManifestResourceStream("Lyquidity.Controls.ExtendedListViews.Resources.tv_minus.bmp");
bmpMinus = new Bitmap(bitmapStream1);
Stream bitmapStream2 = myAssembly.GetManifestResourceStream("Lyquidity.Controls.ExtendedListViews.Resources.tv_plus.bmp");
bmpPlus = new Bitmap(bitmapStream2);
}
#endregion
#region Properties
[
Browsable(false)
]
public SelectedTreeListNodeCollection 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)]
private new ContainerListViewItemCollection Items // Hide this method: should work with Nodes
{
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
protected new void MakeSelectedVisible()
{
if ((curNode != null) && ensureVisible)
{
TreeListNode item = this.curNode;
if (item.Selected)
{
// Need to work out what the row number of the curNode is in the whole list
int i = curNode.RowNumber(true)-1;
Rectangle r = ClientRectangle;
int pos = r.Top+(itemheight*i)+headerBuffer+2-vscrollBar.Value;
try
{
if (pos+itemheight + ((hscrollBar.Visible) ? hscrollBar.Height : 0) > r.Top+r.Height)
{
pos = Math.Abs((r.Top+r.Height)-(pos + itemheight + ((hscrollBar.Visible) ? hscrollBar.Height : 0)));
pos = (pos > itemheight/2) ? itemheight : 0;
vscrollBar.Value += pos;
}
else if (pos < r.Top+headerBuffer+2)
{
vscrollBar.Value -= Math.Abs(r.Top+headerBuffer-pos);
}
}
catch (ArgumentException)
{
if (vscrollBar.Value > vscrollBar.Maximum)
vscrollBar.Value = vscrollBar.Maximum;
else if (vscrollBar.Value < vscrollBar.Minimum)
vscrollBar.Value = vscrollBar.Minimum;
}
}
}
}
protected override void OnKeyDown(KeyEventArgs e)
{
base.OnCheckShiftState(e);
if ((e.KeyCode == Keys.Left) || (e.KeyCode == Keys.Right))
OnLeftRightKeys(e);
else
base.OnKeyDown (e);
}
protected virtual void OnLeftRightKeys(KeyEventArgs e)
{
if (nodes.Count > 0)
{
if (curNode != null)
{
switch (e.KeyCode)
{
#region Left Key
case Keys.Left: // collapse current node or move up to parent
if (curNode.IsExpanded)
{
curNode.Collapse();
AdjustScrollbars();
}
else if (curNode.ParentNode() != null)
{
TreeListNode t = (TreeListNode)curNode.ParentNode();
if (t.ParentNode() != null) // never select virtualParent node
{
if (this.multiSelectMode == MultiSelectMode.Single)
curNode = (TreeListNode)curNode.ParentNode();
}
}
break;
#endregion
#region Right
case Keys.Right: // expand current node or move down to first child
if (!curNode.IsExpanded)
{
curNode.Expand();
AdjustScrollbars();
}
else if (curNode.IsExpanded && curNode.Children > 0)
{
if (this.multiSelectMode == MultiSelectMode.Single)
curNode = (TreeListNode)curNode.FirstChild();
}
break;
#endregion
}
ShowSelectedItems();
Invalidate();
e.Handled = true;
}
}
}
protected override void OnUpDownKeys(KeyEventArgs e)
{
// The basic idea is to unselect everything, work out the new position
// and then select the nodes between the firstSelectedItem and the curNode
// When multiSelectMode == MultiSelectMode.Single, the firstSelectedItem
// will also be the curNode.
// This process takes the burden of node selection and focusing away
// from the code in each "if" statement. The code in the "if"
// statements can then focus on just locating the correct node.
switch (e.KeyCode)
{
case Keys.Up:
GetPriorNode(ref curNode);
break;
case Keys.Down:
GetNextNode(ref curNode);
break;
}
if (curNode == null) return;
if ((this.multiSelectMode == MultiSelectMode.Single) | (firstSelectedNode == null))
{
firstSelectedNode = curNode;
firstSelectedNode.Selected = true;
firstSelectedNode.Focused = true;
}
ShowSelectedItems();
OnSelectedItemChanged();
Invalidate();
e.Handled = true;
}
///
/// Gets the prior visible node.
///
///
/// Returns TRUE if already on the first node
protected bool GetPriorNode(ref TreeListNode cCurNode)
{
if (cCurNode == null) return true;
if (cCurNode.PreviousSibling() == null && cCurNode.ParentNode() != null)
{
TreeListNode t = (TreeListNode)cCurNode.ParentNode();
if (t.ParentNode() != null) // never select virtualParent node
{
cCurNode = (TreeListNode)cCurNode.ParentNode();
return false;
}
}
else if (cCurNode.PreviousSibling() != null)
{
TreeListNode t = (TreeListNode)cCurNode.PreviousSibling();
if (t.Children > 0 && t.IsExpanded)
{
do
{
t = (TreeListNode)t.LastChild();
if (!t.IsExpanded | t.Nodes.Count == 0)
{
cCurNode = t;
return false;
}
} while (t.Children > 0 && t.IsExpanded);
}
else
{
cCurNode = (TreeListNode)cCurNode.PreviousSibling();
return false;
}
}
return true;
}
///
/// Gets the next visible node.
///
///
/// Returns TRUE if already on the last node
private bool GetNextNode(ref TreeListNode cCurNode)
{
if (cCurNode == null) return true;
if (cCurNode.IsExpanded && cCurNode.Children > 0)
{
cCurNode = (TreeListNode)cCurNode.FirstChild();
return false;
}
else if (cCurNode.NextSibling() == null && cCurNode.ParentNode() != null)
{
TreeListNode t = cCurNode;
do
{
t = (TreeListNode)t.ParentNode();
if (t.NextSibling() != null)
{
cCurNode = (TreeListNode)t.NextSibling();
return false;
}
} while (t.NextSibling() == null && t.ParentNode() != null);
}
else if (cCurNode.NextSibling() != null)
{
cCurNode = (TreeListNode)cCurNode.NextSibling();
return false;
}
return true;
}
///
///
///
private void ShowSelectedItems()
{
if (this.curNode == null) return;
this.UnselectNodes(nodes);
// Figure out the path between firstSelectedItem and curNode
// then select all visible (children of expanded) nodes
if (firstSelectedNode == curNode)
{
curNode.Selected = true;
}
else
{
// Now set each node from the first to the current node selecting as we go
int iCurrentNodeIsAbove = this.FirstNodeRelativeToCurrentNode();
TreeListNode cCurNode = firstSelectedNode;
cCurNode.Selected = true;
while (cCurNode != curNode)
{
if (iCurrentNodeIsAbove == -1)
{
if (this.GetPriorNode(ref cCurNode)) break;
}
else
{
if (this.GetNextNode(ref cCurNode)) break;
}
cCurNode.Selected = true;
}
}
MakeSelectedVisible();
}
protected override void OnResize(EventArgs e)
{
base.OnResize(e);
AdjustScrollbars();
Invalidate();
}
protected override void OnMouseDown(MouseEventArgs e)
{
base.OnMouseDown(e);
#region Columns
for (int i=0; i 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;
}
}
}
#endregion
#region Body
if (MouseInRect(e, rowsRect))
{
if (e.Button == MouseButtons.Left)
{
TreeListNode cnode;
#region Expand/Collapse
// check if a nodes plus/minus has been clicked
cnode = NodePlusClicked(e);
if (cnode != null)
{
cnode.Toggle();
AdjustScrollbars();
}
else
#endregion
#region Left
{
// check if a noderow has been clicked
cnode = NodeInNodeRow(e);
if (cnode == null) return;
switch (multiSelectMode)
{
case MultiSelectMode.Single:
this.UnselectNodes(nodes);
if (e.Clicks == 2 && !mouseActivate)
{
cnode.Toggle();
AdjustScrollbars();
}
else if (e.Clicks == 2 && mouseActivate)
{} // OnItemActivate(new EventArgs());
curNode = cnode;
break;
case MultiSelectMode.Range:
this.UnselectNodes(nodes);
curNode = cnode;
break;
case MultiSelectMode.Selective:
UnfocusNodes(nodes);
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();
return;
}
}
}
#endregion
if ((this.multiSelectMode == MultiSelectMode.Single) | (firstSelectedNode == null))
{
if (curNode != null)
{
firstSelectedNode = curNode;
firstSelectedNode.Selected = true;
firstSelectedNode.Focused = true;
}
}
ShowSelectedItems();
OnSelectedItemChanged();
Invalidate();
// e.Handled = true;
}
#endregion
}
protected override void OnKeyUp(KeyEventArgs e)
{
base.OnKeyUp(e);
if (e.Handled) return;
if (e.KeyCode == Keys.F5)
{
if (allCollapsed)
ExpandAll();
else
CollapseAll();
}
}
protected override void DrawRows(Graphics g, Rectangle r)
{
// render listview item rows
// In update so no painting allowed
if (this.InUpdateTransaction) return;
if (this.nodes.Count == 0) return;
int totalRend = 0;
int maxrend = ClientRectangle.Height/itemheight+1;
int prior = vscrollBar.Value/itemheight;
if (prior < 0)
prior = 0;
TreeListNode nodeDraw;
if (prior > 0)
{
if (this.virtualParent.GetNodeAt(prior+1, 0, out nodeDraw))
{
int iRowNumber = nodeDraw.RowNumber(true);
// Console.WriteLine(" RowNumber: " + iRowNumber.ToString());
// Found node
Console.WriteLine(nodeDraw.Text);
}
else
{
try
{
prior += 1;
throw new Exception(string.Format("No node falls within the client rectangle. Node is {0}", prior.ToString()));
}
catch(Exception ex)
{
Console.WriteLine(ex.Message);
return;
}
}
}
else
{
nodeDraw = (TreeListNode)this.virtualParent.FirstChild();
}
totalRend = 0;
nodeRowRects.Clear();
pmRects.Clear();
GenerateColumnRects();
bool bNoMoreNodes = false;
while (!bNoMoreNodes && (maxrend > totalRend))
{
RenderNodeRows(nodeDraw, g, r, ref totalRend);
// increment number of rendered nodes
totalRend++;
bNoMoreNodes = this.GetNextNode(ref nodeDraw);
}
}
protected override void Dispose( bool disposing )
{
if( disposing )
{
if(components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
public void ReportStatus()
{
Console.WriteLine("Count: " + this.virtualParent.m_iDescendentsCount +
" Visible: " + this.virtualParent.m_iDescendentsVisibleCount +
" Expanded: " + this.virtualParent.m_iExpandedCount);
}
public override void AdjustScrollbars()
{
// ReportStatus();
if (nodes.Count > 0 || columns.Count > 0 && !colScaleMode)
{
allColsWidth = 0;
for (int i=0; i allRowsHeight) ? 0 : vscrollBar.Height);
vscrollBar.SmallChange = itemheight;
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.Left = this.ClientRectangle.Left+2;
hscrollBar.Top = this.ClientRectangle.Top+this.ClientRectangle.Height-hscrollBar.Height-2;
hscrollBar.Width = this.ClientRectangle.Width-vsize-4;
hscrollBar.Maximum = allColsWidth;
hscrollBar.LargeChange = (this.ClientRectangle.Width - vsize - 4 > 0 ? this.ClientRectangle.Width - vsize - 4 : 0);
if (allColsWidth > this.ClientRectangle.Width-4-vsize)
{
hscrollBar.Show();
hsize = hscrollBar.Height;
}
else
{
hscrollBar.Hide();
hscrollBar.Value = 0;
hsize = 0;
}
}
}
#endregion
#region Helper Functions
private int vsize, hsize;
private int rendcnt = 0;
private void AutoSetColWidth(TreeListNodeCollection nodes, ref int mwid, ref int twid, int i)
{
for (int j=0; j 0)
twid = (Nodes[j].SubItems[i-1] == null) ? 0 : GetStringWidth(nodes[j].SubItems[i-1].Text);
// 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);
}
}
}
private void UnfocusNodes(TreeListNodeCollection nodecol)
{
for (int i=0; i= 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, ref int totalRend)
{
// Get handy references
TreeListNodeCollection nodesParent = ((TreeListNode)node.ParentNode()).Nodes;
TreeListNode nodePreviousSibling = ((TreeListNode)node.PreviousSibling());
// Set working variables
int childCount = 0;
if (nodePreviousSibling != null) childCount = nodePreviousSibling.GetVisibleNodeCount;
int count = nodesParent.Count;
int index = nodesParent.IndexOf(node);
int level = node.Level();
if (node.IsVisible)
{
int eb = 10; // edge buffer
// only render if row is visible in viewport
if (((r.Top+itemheight*totalRend+eb/4+itemheight >= r.Top+2)
&& (r.Top+itemheight*totalRend+eb/4 < 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, 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, 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, allColsWidth-(lb+ib+eb+4), itemheight);
nodeRowRects.Add(sr, node);
int iColWidth = (columns[0].ScaleStyle == ColumnScaleStyle.Spring ? springWid : columns[0].Width); // BMS 2003-05-24
// 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, 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, iColWidth /* BMS 2003-05-24 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, iColWidth /* BMS 2003-05-23 columns[0].Width */, r.Height-hb-4));
// render root lines if visible
bool bMoreSiblingNodes = node.NextSibling() != null;
if (r.Left+eb-hscrollBar.Value > r.Left)
{
if (showrootlines && level == 0)
{
if (index == 0)
{
// Draw horizontal line with a length of eb/2
g.DrawLine(linePen, r.Left+eb/2-hscrollBar.Value, r.Top+eb/2+hb, r.Left+eb-hscrollBar.Value, r.Top+eb/2+hb);
if (bMoreSiblingNodes)
// Draw vertial line with a length of eb/2
g.DrawLine(linePen, r.Left+eb/2-hscrollBar.Value, r.Top+eb/2+hb, r.Left+eb/2-hscrollBar.Value, r.Top+eb+hb);
}
else if (index == count-1)
{
// Draw horizontal line with a length of eb/2 offset vertically by itemHeight*totalrend
g.DrawLine(linePen, r.Left+eb/2-hscrollBar.Value, r.Top+eb/2+hb+itemheight*(totalRend), r.Left+eb-hscrollBar.Value, r.Top+eb/2+hb+itemheight*(totalRend));
// if (bMoreSiblingNodes)
// Draw vertial line with a length of eb/2 offset vertically by itemHeight*totalrend
g.DrawLine(linePen, r.Left+eb/2-hscrollBar.Value, r.Top+hb+itemheight*(totalRend), r.Left+eb/2-hscrollBar.Value, r.Top+eb/2+hb+itemheight*(totalRend));
}
else
{
// Draw horizontal line with a length of eb/2 offset vertically by itemHeight*totalrend
g.DrawLine(linePen, r.Left+eb/2-hscrollBar.Value, r.Top+eb+hb+itemheight*(totalRend)-eb/2, r.Left+eb-hscrollBar.Value, r.Top+eb+hb+itemheight*(totalRend)-eb/2);
if (bMoreSiblingNodes)
// Draw vertial line with a length of eb/2 offset vertically by itemHeight*totalrend
g.DrawLine(linePen, r.Left+eb/2-hscrollBar.Value, r.Top+eb+hb+itemheight*(totalRend-1), r.Left+eb/2-hscrollBar.Value, r.Top+eb+hb+itemheight*(totalRend));
}
if (childCount > 0)
g.DrawLine(linePen, r.Left+eb/2-hscrollBar.Value, r.Top+hb+itemheight*(totalRend-childCount), r.Left+eb/2-hscrollBar.Value, r.Top+hb+itemheight*(totalRend));
}
}
// 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), r.Left+lb+eb-hscrollBar.Value, r.Top+eb/2+hb+itemheight*(totalRend));
// if (bMoreSiblingNodes)
g.DrawLine(linePen, r.Left+lb+eb/2-hscrollBar.Value, r.Top+hb+itemheight*(totalRend), r.Left+lb+eb/2-hscrollBar.Value, r.Top+eb/2+hb+itemheight*(totalRend));
}
else
{
g.DrawLine(linePen, r.Left+lb+eb/2-hscrollBar.Value, r.Top+eb/2+hb+itemheight*(totalRend), r.Left+lb+eb-hscrollBar.Value, r.Top+eb/2+hb+itemheight*(totalRend));
// if (bMoreSiblingNodes)
g.DrawLine(linePen, r.Left+lb+eb/2-hscrollBar.Value, r.Top+hb+itemheight*(totalRend), r.Left+lb+eb/2-hscrollBar.Value, r.Top+eb+hb+itemheight*(totalRend));
}
if (childCount > 0)
g.DrawLine(linePen, r.Left+lb+eb/2-hscrollBar.Value, r.Top+hb+itemheight*(totalRend-childCount), r.Left+lb+eb/2-hscrollBar.Value, r.Top+hb+itemheight*(totalRend));
}
}
// render +/- signs if visible
if (r.Left+lb+eb/2+5-hscrollBar.Value > r.Left)
{
if (showplusminus && (node.Children > 0 || alwaysShowPM))
{
if (index == 0 && level == 0)
{
RenderPlus(g, r.Left+lb+eb/2-4-hscrollBar.Value, r.Top+hb+eb/2-4, 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, 8, 8, node);
}
else
{
RenderPlus(g, r.Left+lb+eb/2-4-hscrollBar.Value, r.Top+hb+itemheight*totalRend+eb/2-4, 8, 8, node);
}
}
}
// render text if visible
if (r.Left+ iColWidth /* BMS 2003-05024 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));
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));
}
// render subitems
int j;
int last = 0;
if (columns.Count > 0)
{
for (j=0; j r.Width-6 ? r.Width-6 : iColPlus1Width /* BMS 2003-05-24 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);
c.ClientSize = new Size(iColPlus1Width /* BMS 2003-05-24 columns[j+1].Width */-6, itemheight-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, iColPlus1Width /* BMS 2003-05-24 columns[j+1].Width */, 9, g), this.Font, SystemBrushes.HighlightText, (float)(last+6-hscrollBar.Value), (float)(r.Top+(itemheight*totalRend)+headerBuffer+4));
else
g.DrawString(TruncatedString(node.SubItems[j].Text, iColPlus1Width /* BMS 2003-05-24 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));
}
else if (columns[j+1].TextAlign == HorizontalAlignment.Right)
{
sp = TruncatedString(node.SubItems[j].Text, iColPlus1Width /* BMS 2003-05-24 columns[j+1].Width */, 9, g);
if (node.Selected && isFocused)
g.DrawString(sp, this.Font, SystemBrushes.HighlightText, (float)(last+iColPlus1Width /* BMS 2003-05-24 columns[j+1].Width */-Helpers.StringTools.MeasureDisplayStringWidth(g, sp, this.Font)-4-hscrollBar.Value), (float)(r.Top+(itemheight*totalRend)+headerBuffer+4));
else
g.DrawString(sp, this.Font, (node.UseItemStyleForSubItems ? new SolidBrush(node.ForeColor) : SystemBrushes.WindowText), (float)(last+iColPlus1Width /* BMS 2003-05-24 columns[j+1].Width */-Helpers.StringTools.MeasureDisplayStringWidth(g, sp, this.Font)-4-hscrollBar.Value), (float)(r.Top+(itemheight*totalRend)+headerBuffer+4));
}
else
{
sp = TruncatedString(node.SubItems[j].Text, iColPlus1Width /* BMS 2003-05-24 columns[j+1].Width */, 9, g);
if (node.Selected && isFocused)
g.DrawString(sp, this.Font, SystemBrushes.HighlightText, (float)(last+(iColPlus1Width /* BMS 2003-05-24 columns[j+1].Width *//2)-(Helpers.StringTools.MeasureDisplayStringWidth(g, sp, this.Font)/2)-hscrollBar.Value), (float)(r.Top+(itemheight*totalRend)+headerBuffer+4));
else
g.DrawString(sp, this.Font, (node.UseItemStyleForSubItems ? new SolidBrush(node.ForeColor) : SystemBrushes.WindowText), (float)(last+(iColPlus1Width /* BMS 2003-05-24 columns[j+1].Width *//2)-(Helpers.StringTools.MeasureDisplayStringWidth(g, sp, this.Font)/2)-hscrollBar.Value), (float)(r.Top+(itemheight*totalRend)+headerBuffer+4));
}
}
}
}
}
}
}
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);
}
///
/// The stacks used in this collection could be used to return relative or
/// absolute paths from one node to another. For the time being, this
/// information is used ONLY to determine whether one node is "above" or
/// below another.
///
private int FirstNodeRelativeToCurrentNode()
{
// The return value will be TRUE if curNode falls before firstSelectedNode in
// common parents node list.
System.Collections.Stack cFirstSelectedNodeStack = new System.Collections.Stack();
System.Collections.Stack cCurNodeStack = new System.Collections.Stack();
// Find the root node for both ends of the selected range
firstSelectedNode.GetStackToVirtualParent(cFirstSelectedNodeStack);
curNode.GetStackToVirtualParent(cCurNodeStack);
// Find the point of divergence
while ((cFirstSelectedNodeStack.Count > 1) & (cCurNodeStack.Count > 1))
{
// Look at the first item in each stack and pop them off if they are the same
if (cFirstSelectedNodeStack.Peek() != cCurNodeStack.Peek()) break;
cFirstSelectedNodeStack.Pop();
cCurNodeStack.Pop();
}
// At this point there are two nodes at the same level in the tree, one at the
// top of each stack. Use this information to determine which appears first in
// its parents node collection.
// Is curNode "above" or "below" firstSelectedItem (affects the node walking direction)
int iResult = ((TreeListNode)cFirstSelectedNodeStack.Peek()).CompareTo((TreeListNode)cCurNodeStack.Peek());
// Reverse the sign because if the first node is "after" the curr node then
// then nodes above need to be selected (and vice-versa)
if (iResult != 0) return -iResult;
// If iResult = 0, it's going to be because the curNode is the parent of the firstnode
// or because the firstnode is the parent of the curNode
if (cFirstSelectedNodeStack.Count > cCurNodeStack.Count)
{
// cCurNodeStack is parent of cFirstSelectedNodeStack
return -1;
}
else
{
return 1;
}
}
#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 SelectedTreeListNodeCollection GetSelectedNodes(TreeListNode node)
{
SelectedTreeListNodeCollection list = new SelectedTreeListNodeCollection();
for (int i=0; i 0 && nodes.Count > c)
curChild = nodes[c];
else
curChild = nodes[nodes.Count];
// Update counts as the number of sub nodes and their respective
// visibilities may have changed BMS 2003-05-27
}
public void Toggle()
{
if (expanded)
this.Collapse();
else
this.Expand();
}
public bool IsNodeAt(int iFirstVisiblePixelY, int iOffsetY, int iRowHeight)
{
// iFirstVisiblePixelY is the absolute number of pixels in the Y axis to the first visible pixel in the viewport
// iOffsetY is the number of pixels attributable to ancestral and prior sibling nodes.
return (iFirstVisiblePixelY > iOffsetY) &
((iOffsetY + (this.m_iDescendentsVisibleCount * iRowHeight) /* Height consumed by sub nodes */
+ iRowHeight ) <= iFirstVisiblePixelY); /* Height consumed by this node */
}
public bool GetNodeAt(int iFirstVisiblePixelY, int iOffsetY, int iRowHeight, out TreeListNode node)
{
// iFirstVisiblePixelY is the absolute number of pixels in the Y axis to the first visible pixel in the viewport
// iOffsetY is the number of pixels attributable to ancestral and prior sibling nodes.
node = null; // Just in case
bool bResult = (iFirstVisiblePixelY > iOffsetY) &
((iOffsetY + (this.m_iDescendentsVisibleCount * iRowHeight) /* Height consumed by sub nodes */
+ iRowHeight ) <= iFirstVisiblePixelY); /* Height consumed by this node */
if (!bResult) return false;
// Is the pixel in this node?
if (iOffsetY + iRowHeight <= iFirstVisiblePixelY)
{
node = this;
return true;
}
// If not the find the node
for(int iIndex=1; iIndex <= nodes.Count; iIndex++)
{
TreeListNode tln = nodes[iIndex-1];
if (tln.GetNodeAt(iFirstVisiblePixelY, iOffsetY+iIndex*iRowHeight, iRowHeight, out node)) return true;
}
// Should never get this far!!
node = null;
return false;
}
public bool IsNodeAt(int iRow, int iPrior)
{
// iRow is the row to be found
// iPrior is the number of rows preceding this node.
return ((iRow - iPrior) <= this.m_iDescendentsVisibleCount+1);
}
public bool GetNodeAt(int iRow, int iPrior, out TreeListNode node)
{
// iRow is the row to be found
// iPrior is the number of rows preceding this node.
node = null; // Just in case
// Can't select a root node
if (!this.m_bIsRoot)
{
int iFirstRow = iRow - iPrior;
if (!IsNodeAt(iFirstRow, 0)) return false;
// Is the pixel in this node?
if (iFirstRow == 1)
{
node = this;
return true;
}
iPrior += 1;
}
// If not the find the node
for(int iIndex=0; iIndex < nodes.Count; iIndex++)
{
TreeListNode tln = nodes[iIndex];
if (tln.GetNodeAt(iRow, iPrior, out node)) return true;
iPrior += tln.GetVisibleNodeCount+1;
}
// Should never get this far!!
node = null;
return false;
}
public int Level()
{
Stack cStack = new Stack();
GetStackToVirtualParent(cStack);
return cStack.Count-2; // The stack will always contain the additional virtual node
// and since Level should return a zero-based reference, need to subtract 2.
}
public int RowNumber(bool bVisibleOnly)
{
Stack cStack = new Stack();
GetStackToVirtualParent(cStack);
int iNodes = 0;
while(cStack.Count > 1)
{
TreeListNode parent = (TreeListNode)cStack.Pop();
TreeListNode child = (TreeListNode)cStack.Peek();
// Find child in parent and add up prior nodes
foreach(TreeListNode node in parent.nodes)
{
if (node == child)
{
// Need to add on the position of child in parent
iNodes += parent.Nodes.IndexOf(child)+1;
break;
}
iNodes += node.m_iDescendentsVisibleCount;
}
}
cStack.Pop();
return iNodes;
}
#endregion
#region IParentChildList
public IParentChildList ParentNode()
{
return parent;
}
public IParentChildList PreviousSibling()
{
if (parent != null)
{
int thisIndex = parent.Nodes[this];
if (thisIndex > 0)
return parent.Nodes[thisIndex-1];
}
return null;
}
public IParentChildList NextSibling()
{
if (parent != null)
{
int thisIndex = parent.Nodes[this];
if (thisIndex < parent.Nodes.Count-1)
return parent.Nodes[thisIndex+1];
}
return null;
}
public IParentChildList FirstChild()
{
curChild = Nodes[0];
return curChild;
}
public IParentChildList LastChild()
{
curChild = Nodes[Nodes.Count-1];
return curChild;
}
public IParentChildList NextChild()
{
curChild = (TreeListNode)curChild.NextSibling();
return curChild;
}
public IParentChildList PreviousChild()
{
curChild = (TreeListNode)curChild.PreviousSibling();
return curChild;
}
public int CompareTo(TreeListNode comparisonNode)
{
if (this == comparisonNode) return 0;
if (this.IsAfter(comparisonNode)) return 1;
return -1; // Before
}
#endregion
#region Private
internal void GetStackToVirtualParent(System.Collections.Stack cStack)
{
cStack.Push(this);
if (this.ParentNode() != null) GetStackToVirtualParent(cStack, this.ParentNode());
// Got to the root
}
private void GetStackToVirtualParent(System.Collections.Stack cStack, IParentChildList cNode)
{
cStack.Push(cNode);
if (cNode.ParentNode() != null) GetStackToVirtualParent(cStack, cNode.ParentNode()); // Recursive call
// Got to the root
}
private int VisibleNodes
{
get
{
int iDescendentsVisibleCount = this.m_iDescendentsVisibleCount;
for (int i=0; i nodeIndex);
}
private bool IsBefore(TreeListNode node)
{
int thisIndex = parent.Nodes[this];
int nodeIndex = parent.Nodes[node];
return (thisIndex < nodeIndex);
}
internal void PropagateNodeChange(int iTotalCountDelta, int iDescendentsVisibleCountDelta, int iExpandedCount)
{
// This function passes the change in numbers of nodes...
this.m_iDescendentsCount += iTotalCountDelta;
this.m_iDescendentsVisibleCount += iDescendentsVisibleCountDelta;
this.m_iExpandedCount += iExpandedCount;
// ...up the chain
if (this.parent == null) return;
this.parent.PropagateNodeChange(iTotalCountDelta, iDescendentsVisibleCountDelta, iExpandedCount);
}
#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;
// internal int m_iDescendentsVisibleCount;
// internal int m_iDescendentsCount;
#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.m_iDescendentsCount;
}
return tcnt;
}
}
#region Implementation
public TreeListNode this[int index]
{
get
{
if (List.Count > 0)
{
return List[index] as TreeListNode;
}
else
return null;
}
set
{
TreeListNode tln = ((TreeListNode)List[index]);
int iOldDescendentCount = tln.m_iDescendentsCount;
int iOldVisibleCount = tln.m_iDescendentsVisibleCount;
int iOldExpandedCount = tln.m_iExpandedCount;
tln.MouseDown -= new MouseEventHandler(OnMouseDown);
tln.Nodes.NodesChanged -= new EventHandler(OnNodesChanged);
List[index] = value;
tln = null;
tln = value;
tln.MouseDown += new MouseEventHandler(OnMouseDown);
tln.Nodes.NodesChanged += new EventHandler(OnNodesChanged);
tln.Parent = owner;
// Update counts as the number of sub nodes and their respective
// visibilities may have changed BMS 2003-05-27
this.owner.PropagateNodeChange(tln.m_iDescendentsCount-iOldDescendentCount,
tln.m_iDescendentsVisibleCount-iOldVisibleCount,
tln.m_iExpandedCount-iOldExpandedCount);
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;
// Special case when the parent is null because the root is visible
if (owner.m_bIsRoot)
{
owner.m_iDescendentsVisibleCount += 1;
owner.m_iExpandedCount += 1;
}
// Update counts as the number of sub nodes and their respective
// visibilities may have changed BMS 2003-05-27
this.owner.PropagateNodeChange(item.m_iDescendentsCount+1, item.m_iDescendentsVisibleCount, item.m_iExpandedCount);
OnNodesChanged();
return item.Index = List.Add(item);
}
public void AddRange(TreeListNode[] items)
{
lock(List.SyncRoot)
{
int iDescendentsCount = 0;
int iDescendentsVisibleCount = 0;
int iExpandedCount = 0;
for (int i=0; i