Click here to Skip to main content
15,885,914 members
Articles / Programming Languages / C#

DataGridView with hierarchical data binding

,
Rate me:
Please Sign up or sign in to vote.
4.77/5 (22 votes)
31 Jul 2008Ms-PL2 min read 234.2K   15.9K   87  
The TreeGridView by Mark Rideout with data binding and sorting
//---------------------------------------------------------------------
// 
//  Copyright (c) Microsoft Corporation.  All rights reserved.
// 
//THIS CODE AND INFORMATION ARE PROVIDED AS IS WITHOUT WARRANTY OF ANY
//KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
//PARTICULAR PURPOSE.
//---------------------------------------------------------------------
using System;
using System.Windows.Forms;
using System.Drawing;
using System.Diagnostics;
using System.Windows.Forms.VisualStyles;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.ComponentModel.Design.Serialization;
using System.Drawing.Design;
using System.Data;
using System.Collections.Generic;

namespace HierarchicalGrid
{
	/// <summary>
	/// Summary description for HierarchicalGridView.
	/// </summary>
    [System.ComponentModel.DesignerCategory("code"),
    Designer(typeof(System.Windows.Forms.Design.ControlDesigner)),
	ComplexBindingProperties(),
    Docking(DockingBehavior.Ask)]
	public class HierarchicalGridView:DataGridView
	{		
		private int _indentWidth;
		private HierarchicalGridNode _root;
		private HierarchicalGridColumn _expandableColumn;
		private bool _disposing = false;
		internal ImageList _imageList;
		private bool _inExpandCollapse = false;
        internal bool _inExpandCollapseMouseCapture = false;
		private Control hideScrollBarControl;
        private bool _showLines = true;
        private bool _virtualNodes = false;
        private DataGridSource _dtSource = null;

		internal VisualStyleRenderer rOpen = new VisualStyleRenderer(VisualStyleElement.TreeView.Glyph.Opened);
		internal VisualStyleRenderer rClosed = new VisualStyleRenderer(VisualStyleElement.TreeView.Glyph.Closed);

        #region Constructor
        public HierarchicalGridView()
		{
			// Control when edit occurs because edit mode shouldn't start when expanding/collapsing
			this.EditMode = DataGridViewEditMode.EditProgrammatically;
            this.RowTemplate = new HierarchicalGridNode() as DataGridViewRow;
			// This sample does not support adding or deleting rows by the user.
			this.AllowUserToAddRows = false;
			this.AllowUserToDeleteRows = false;
			this._root = new HierarchicalGridNode(this);
			this._root.IsRoot = true;

			// Ensures that all rows are added unshared by listening to the CollectionChanged event.
			base.Rows.CollectionChanged += delegate(object sender, System.ComponentModel.CollectionChangeEventArgs e){};
        }
        #endregion

        #region Keyboard F2 to begin edit support
        protected override void OnKeyDown(KeyEventArgs e)
		{
			// Cause edit mode to begin since edit mode is disabled to support 
			// expanding/collapsing 
			base.OnKeyDown(e);
			if (!e.Handled)
			{
				if (e.KeyCode == Keys.F2 && this.CurrentCellAddress.X > -1 && this.CurrentCellAddress.Y >-1)
				{
					if (!this.CurrentCell.Displayed)
					{
						this.FirstDisplayedScrollingRowIndex = this.CurrentCellAddress.Y;
					}
					else
					{
						// TODO:calculate if the cell is partially offscreen and if so scroll into view
					}
					this.SelectionMode = DataGridViewSelectionMode.CellSelect;
					this.BeginEdit(true);
				}
				else if (e.KeyCode == Keys.Enter && !this.IsCurrentCellInEditMode)
				{
					this.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
					this.CurrentCell.OwningRow.Selected = true;
				}
			}
        }
        #endregion

        #region Shadow and hide DGV properties - NEW DATASOURCE

        // This sample does not support databinding
		[Browsable(false),
		DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden),
		EditorBrowsable(EditorBrowsableState.Never)]
		public new object DataSource
		{
            get { return _dtSource; }
			set
            {
                this.Nodes.Clear();
                this.Columns.Clear();

                if(!(value is DataGridSource)) throw new Exception("DataSource must be a DataGridSource object!");

                _dtSource = (DataGridSource)value;

                // Armazena as colunas de relacionamento
                List<String> relColList = new List<string>();
                foreach (DataRelation curRelation in _dtSource.DataSet.Relations)
                {
                    foreach (DataColumn curCol in curRelation.ParentKeyConstraint.Columns)
                    {
                        relColList.Add(curCol.ColumnName);
                    }
                }


                // Adiciona as colunas no grid
                bool relationCol;
                foreach (string curCol in _dtSource.DisplayColumns)
                {
                    relationCol = relColList.Contains(curCol);

                    if (relationCol) {
                        HierarchicalGridColumn newCol = new HierarchicalGridColumn();
                        relationCol = false;
                        newCol.Name = curCol;
                        newCol.HeaderText = curCol;
                        this.Columns.Add(newCol);
                    }
                    else { 
                        DataGridViewTextBoxColumn newCol = new DataGridViewTextBoxColumn();
                        newCol.Name = curCol;
                        newCol.HeaderText = curCol;
                        this.Columns.Add(newCol);
                    }
                }

                foreach (GroupColumn curGroup in _dtSource.GroupColumns)
                {
                    DataGridViewTextBoxColumn newCol = new DataGridViewTextBoxColumn();
                    newCol.Name = curGroup.ColName;
                    newCol.HeaderText = curGroup.ColName;
                    this.Columns.Add(newCol);
                }

                // Monta o Grid Hier�rquico
                DataRelationCollection dsRelations = _dtSource.DataSet.Relations;
                HierarchicalGridNode curNode;
                
                foreach (DataRelation curRelation in dsRelations)
                {
                    DataTable dtParent = curRelation.ParentTable;
                    DataTable dtChild = curRelation.ChildTable;
                    foreach (DataRow curRow in dtParent.Rows)
                    {
                        string childFilter = "";
                        for (int iCol = 0; iCol < curRelation.ChildKeyConstraint.Columns.Length; iCol++)
                        {
                            if (iCol > 0)
                            {
                                childFilter += " AND ";
                            }
                            childFilter += curRelation.ChildKeyConstraint.Columns[iCol].ColumnName + "='" + curRow[curRelation.ChildKeyConstraint.RelatedColumns[iCol]].ToString()+"'";
                        }

                        object[] valuesToAdd = new object[this.Columns.Count];
                        int curObj = 0;
                        foreach (string curColName in _dtSource.DisplayColumns)
                        {
                            if (dtParent.Columns.Contains(curColName))
                            {
                                valuesToAdd[curObj] = curRow[curColName];
                            }
                            else{
                                valuesToAdd[curObj] = null;
                            }
                            curObj++;
                        }

                        foreach (GroupColumn curCol in _dtSource.GroupColumns)
                        {
                            if(dtChild.Columns.Contains(curCol.ColName)) {
                                valuesToAdd[curObj] = dtChild.Compute(curCol.GroupType.ToString() + "(" + curCol.ColName + ")", childFilter);
                            }
                            else{
                                valuesToAdd[curObj] = null;
                            }
                            curObj++;
                        }

                        curNode = this.Nodes.Add(valuesToAdd);
                        DataRow[] childRows  = dtChild.Select(childFilter);
                        
                        foreach (DataRow curChildRow in childRows)
                        {
                            valuesToAdd = new object[this.Columns.Count];
                            curObj = 0;
                            foreach (DataGridViewColumn curCol in this.Columns)
                            {
                                if(dtChild.Columns.Contains(curCol.Name)) {
                                    valuesToAdd[curObj] = curChildRow[curCol.Name];
                                }
                                else{
                                    valuesToAdd[curObj] = null;
                                }
                                curObj++;
                            }
                            curNode.Nodes.Add(valuesToAdd);
                        }

                    }
                }
            }
		}

            [Browsable(false),
            DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden),
		EditorBrowsable(EditorBrowsableState.Never)]
		public new object DataMember
		{
			get { return null; }
			set { /*throw new NotSupportedException("The TreeGridView does not support databinding");*/ }
		}

        [Browsable(false),
        DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden),
		EditorBrowsable(EditorBrowsableState.Never)]
        public new DataGridViewRowCollection Rows
        {
            get { return base.Rows; }
        }

        //[Browsable(false),
        //DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden),
        //EditorBrowsable(EditorBrowsableState.Never)]
        public new bool VirtualMode
        {
            get { return false; }
            set { /*throw new NotSupportedException("The TreeGridView does not support virtual mode");*/ }
        }

        // none of the rows/nodes created use the row template, so it is hidden.
        [Browsable(false),
        DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden),
        EditorBrowsable(EditorBrowsableState.Never)]
        public new DataGridViewRow RowTemplate
        {
            get { return base.RowTemplate; }
            set { base.RowTemplate = value; }
        }

        #endregion

        #region Public methods
        [Description("Returns the TreeGridNode for the given DataGridViewRow")]
        public HierarchicalGridNode GetNodeForRow(DataGridViewRow row)
        {
            return row as HierarchicalGridNode;
        }

        [Description("Returns the TreeGridNode for the given DataGridViewRow")]
        public HierarchicalGridNode GetNodeForRow(int index)
        {
            return GetNodeForRow(base.Rows[index]);
        }
        #endregion

        #region Public properties
        [Category("Data"),
		Description("The collection of root nodes in the treelist."),
		DesignerSerializationVisibility(DesignerSerializationVisibility.Content),
		Editor(typeof(CollectionEditor), typeof(UITypeEditor))]
		public HierarchicalGridNodeCollection Nodes
		{
			get
			{
				return this._root.Nodes;
			}
		}

		public new HierarchicalGridNode CurrentRow
		{
			get
			{
				return base.CurrentRow as HierarchicalGridNode;
			}
		}

        [DefaultValue(false),
        Description("Causes nodes to always show as expandable. Use the NodeExpanding event to add nodes.")]
        public bool VirtualNodes
        {
            get { return _virtualNodes; }
            set { _virtualNodes = value; }
        }
	
		public HierarchicalGridNode CurrentNode
		{
			get
			{
				return this.CurrentRow;
			}
		}

        [DefaultValue(true)]
        public bool ShowLines
        {
            get { return this._showLines; }
            set { 
                if (value != this._showLines) {
                    this._showLines = value;
                    this.Invalidate();
                } 
            }
        }
	
		public ImageList ImageList
		{
			get { return this._imageList; }
			set { 
				this._imageList = value; 
				//TODO: should we invalidate cell styles when setting the image list?
			
			}
		}

        public new int RowCount
        {
            get { return this.Nodes.Count; }
            set
            {
                for (int i = 0; i < value; i++)
                    this.Nodes.Add(new HierarchicalGridNode());

            }
        }
        #endregion

        #region Site nodes and collapse/expand support
        protected override void OnRowsAdded(DataGridViewRowsAddedEventArgs e)
        {
            base.OnRowsAdded(e);

            // Notify the row when it is added to the base grid 
            int count = e.RowCount - 1;
            HierarchicalGridNode row;
            while (count >= 0)
            {
                row = base.Rows[e.RowIndex + count] as HierarchicalGridNode;
                if (row != null)
                {
                    row.Sited();
                }
                count--;
            }
        }

		internal protected void UnSiteAll()
		{
			this.UnSiteNode(this._root);
		}

        public override void Sort(DataGridViewColumn dataGridViewColumn, ListSortDirection direction)
        {

            IList<HierarchicalGridNode> nodeList = this.Nodes._list;
            IList<HierarchicalGridNode> expandedNodes = new List<HierarchicalGridNode>();
            int nodeIndex, iNode;

            for (iNode = 0; iNode < nodeList.Count; iNode++)
            {
                if (nodeList[iNode].IsExpanded)
                {
                    expandedNodes.Add(nodeList[iNode]);
                    nodeList[iNode].Collapse();
                }
            }

            base.Sort(dataGridViewColumn, direction);

            for (iNode = 0; iNode < expandedNodes.Count; iNode++) //(TreeGridNode nodeToExpand in expandedNodes)
            {
                nodeIndex = this.Nodes.IndexOf(expandedNodes[iNode]);
                this.Nodes[nodeIndex].Expand();
            }
        }

        internal protected virtual void UnSiteNode(HierarchicalGridNode node)
		{
            if (node.IsSited || node.IsRoot)
			{
				// remove child rows first
				foreach (HierarchicalGridNode childNode in node.Nodes)
				{
					this.UnSiteNode(childNode);
				}

				// now remove this row except for the root
				if (!node.IsRoot)
				{
					base.Rows.Remove(node);
					// Row isn't sited in the grid anymore after remove. Note that we cannot
					// Use the RowRemoved event since we cannot map from the row index to
					// the index of the expandable row/node.
					node.UnSited();
				}
			}
		}

		internal protected virtual bool CollapseNode(HierarchicalGridNode node)
		{
			if (node.IsExpanded)
			{
				CollapsingEventArgs exp = new CollapsingEventArgs(node);
				this.OnNodeCollapsing(exp);

				if (!exp.Cancel)
				{
					this.LockVerticalScrollBarUpdate(true);
                    this.SuspendLayout();
                    _inExpandCollapse = true;
                    node.IsExpanded = false;

					foreach (HierarchicalGridNode childNode in node.Nodes)
					{
						Debug.Assert(childNode.RowIndex != -1, "Row is NOT in the grid.");
						this.UnSiteNode(childNode);
					}

					CollapsedEventArgs exped = new CollapsedEventArgs(node);
					this.OnNodeCollapsed(exped);
					//TODO: Convert this to a specific NodeCell property
                    _inExpandCollapse = false;
                    this.LockVerticalScrollBarUpdate(false);
                    this.ResumeLayout(true);
                    this.InvalidateCell(node.Cells[0]);

				}

				return !exp.Cancel;
			}
			else
			{
				// row isn't expanded, so we didn't do anything.				
				return false;
			}
		}

		internal protected virtual void SiteNode(HierarchicalGridNode node)
		{
			//TODO: Raise exception if parent node is not the root or is not sited.
			int rowIndex = -1;
			HierarchicalGridNode currentRow;
			node._grid = this;

			if (node.Parent != null && node.Parent.IsRoot == false)
			{
				// row is a child
				Debug.Assert(node.Parent != null && node.Parent.IsExpanded == true);

				if (node.Index > 0)
				{
					currentRow = node.Parent.Nodes[node.Index - 1];
				}
				else
				{
					currentRow = node.Parent;
				}
			}
			else
			{
				// row is being added to the root
				if (node.Index > 0)
				{
					currentRow = node.Parent.Nodes[node.Index - 1];
				}
				else
				{
					currentRow = null;
				}

			}

			if (currentRow != null)
			{
				while (currentRow.Level >= node.Level)
				{
					if (currentRow.RowIndex < base.Rows.Count - 1)
					{
						currentRow = base.Rows[currentRow.RowIndex + 1] as HierarchicalGridNode;
						Debug.Assert(currentRow != null);
					}
					else
						// no more rows, site this node at the end.
						break;

				}
				if (currentRow == node.Parent)
					rowIndex = currentRow.RowIndex + 1;
				else if (currentRow.Level < node.Level)
					rowIndex = currentRow.RowIndex;
				else
					rowIndex = currentRow.RowIndex + 1;
			}
			else
				rowIndex = 0;


			Debug.Assert(rowIndex != -1);
			this.SiteNode(node, rowIndex);

			Debug.Assert(node.IsSited);
			if (node.IsExpanded)
			{
				// add all child rows to display
				foreach (HierarchicalGridNode childNode in node.Nodes)
				{
					//TODO: could use the more efficient SiteRow with index.
					this.SiteNode(childNode);
				}
			}
		}


		internal protected virtual void SiteNode(HierarchicalGridNode node, int index)
		{
			if (index < base.Rows.Count)
			{
				base.Rows.Insert(index, node);
			}
			else
			{
				// for the last item.
				base.Rows.Add(node);
			}
		}

		internal protected virtual bool ExpandNode(HierarchicalGridNode node)
		{
            if (!node.IsExpanded || this._virtualNodes)
			{
				ExpandingEventArgs exp = new ExpandingEventArgs(node);
				this.OnNodeExpanding(exp);

				if (!exp.Cancel)
				{
					this.LockVerticalScrollBarUpdate(true);
                    this.SuspendLayout();
                    _inExpandCollapse = true;
                    node.IsExpanded = true;

					//TODO Convert this to a InsertRange
					foreach (HierarchicalGridNode childNode in node.Nodes)
					{
						Debug.Assert(childNode.RowIndex == -1, "Row is already in the grid.");

						this.SiteNode(childNode);
						//this.BaseRows.Insert(rowIndex + 1, childRow);
						//TODO : remove -- just a test.
						//childNode.Cells[0].Value = "child";
					}

					ExpandedEventArgs exped = new ExpandedEventArgs(node);
					this.OnNodeExpanded(exped);
					//TODO: Convert this to a specific NodeCell property
                    _inExpandCollapse = false;
                    this.LockVerticalScrollBarUpdate(false);
                    this.ResumeLayout(true);
                    this.InvalidateCell(node.Cells[0]);
				}

				return !exp.Cancel;
			}
			else
			{
				// row is already expanded, so we didn't do anything.
				return false;
			}
        }

        protected override void OnMouseUp(MouseEventArgs e)
        {
            // used to keep extra mouse moves from selecting more rows when collapsing
            base.OnMouseUp(e);
            this._inExpandCollapseMouseCapture = false;
        }
        protected override void OnMouseMove(MouseEventArgs e)
        {
            // while we are expanding and collapsing a node mouse moves are
            // supressed to keep selections from being messed up.
            if (!this._inExpandCollapseMouseCapture)
                base.OnMouseMove(e);

        }
        #endregion

        #region Collapse/Expand events
        public event ExpandingEventHandler NodeExpanding;
        public event ExpandedEventHandler NodeExpanded;
        public event CollapsingEventHandler NodeCollapsing;
        public event CollapsedEventHandler NodeCollapsed;

        protected virtual void OnNodeExpanding(ExpandingEventArgs e)
        {
            if (this.NodeExpanding != null)
            {
                NodeExpanding(this, e);
            }
        }
        protected virtual void OnNodeExpanded(ExpandedEventArgs e)
        {
            if (this.NodeExpanded != null)
            {
                NodeExpanded(this, e);
            }
        }
        protected virtual void OnNodeCollapsing(CollapsingEventArgs e)
        {
            if (this.NodeCollapsing != null)
            {
                NodeCollapsing(this, e);
            }

        }
        protected virtual void OnNodeCollapsed(CollapsedEventArgs e)
        {
            if (this.NodeCollapsed != null)
            {
                NodeCollapsed(this, e);
            }
        }
        #endregion

        #region Helper methods
        protected override void Dispose(bool disposing)
        {
            this._disposing = true;
            base.Dispose(Disposing);
            this.UnSiteAll();
        }

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

            // this control is used to temporarly hide the vertical scroll bar
            hideScrollBarControl = new Control();
            hideScrollBarControl.Visible = false;
            hideScrollBarControl.Enabled = false;
            hideScrollBarControl.TabStop = false;
            // control is disposed automatically when the grid is disposed
            this.Controls.Add(hideScrollBarControl);
        }

        protected override void OnRowEnter(DataGridViewCellEventArgs e)
        {
            // ensure full row select
            base.OnRowEnter(e);
            if (this.SelectionMode == DataGridViewSelectionMode.CellSelect ||
                (this.SelectionMode == DataGridViewSelectionMode.FullRowSelect &&
                base.Rows[e.RowIndex].Selected == false))
            {
                this.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
                base.Rows[e.RowIndex].Selected = true;
            }
        }
        
		private void LockVerticalScrollBarUpdate(bool lockUpdate/*, bool delayed*/)
		{
            // Temporarly hide/show the vertical scroll bar by changing its parent
            if (!this._inExpandCollapse)
            {
                if (lockUpdate)
                {
                    this.VerticalScrollBar.Parent = hideScrollBarControl;
                }
                else
                {
                    this.VerticalScrollBar.Parent = this;
                }
            }
        }

        protected override void OnColumnAdded(DataGridViewColumnEventArgs e)
        {
            if (typeof(HierarchicalGridColumn).IsAssignableFrom(e.Column.GetType()))
            {
                if (_expandableColumn == null)
                {
                    // identify the expanding column.			
                    _expandableColumn = (HierarchicalGridColumn)e.Column;
                }
                else
                {
                   // this.Columns.Remove(e.Column);
                    //throw new InvalidOperationException("Only one TreeGridColumn per TreeGridView is supported.");
                }
            }

            // Expandable Grid doesn't support sorting. This is just a limitation of the sample.
            //e.Column.SortMode = DataGridViewColumnSortMode.NotSortable;

            base.OnColumnAdded(e);
        }

        private static class Win32Helper
        {
            public const int WM_SYSKEYDOWN = 0x0104,
                             WM_KEYDOWN = 0x0100,
                             WM_SETREDRAW = 0x000B;

            [System.Runtime.InteropServices.DllImport("USER32.DLL", CharSet = System.Runtime.InteropServices.CharSet.Auto)]
            public static extern IntPtr SendMessage(System.Runtime.InteropServices.HandleRef hWnd, int msg, IntPtr wParam, IntPtr lParam);

            [System.Runtime.InteropServices.DllImport("USER32.DLL", CharSet = System.Runtime.InteropServices.CharSet.Auto)]
            public static extern IntPtr SendMessage(System.Runtime.InteropServices.HandleRef hWnd, int msg, int wParam, int lParam);

            [System.Runtime.InteropServices.DllImport("USER32.DLL", CharSet = System.Runtime.InteropServices.CharSet.Auto)]
            public static extern bool PostMessage(System.Runtime.InteropServices.HandleRef hwnd, int msg, IntPtr wparam, IntPtr lparam);

        }
        #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 Microsoft Public License (Ms-PL)


Written By
Engineer
Brazil Brazil
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Written By
Brazil Brazil
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions