DataGridViewSpanningTextBoxCell
Introduction
The DataGridViewSpanningTextBoxCell is C# widget that, when added to a DataGridView, allows the content of the cell to spill into the cells following it to the right.
Tim Van Wassenhove provided a concept implementation of a DataGridView cell type that would span multiple cells (see DataGridViewLargeTextBoxCell). The DataGridViewSpanningTextBoxCell borrows his work, bringing it closer to production-worthy code, adding:
- Handling columns of different sizes
- Handling hidden columns
- Handling horizontal scrolling painting issues
- Draws lines in the containing grid view's grid colour
What is still missing:
- Correct handling of cell selection when not using row selection
- Right-to-left languages
- Vertical or horizontal alignment of content
Implementation
using System.Drawing; using System.Windows.Forms; namespace CustomControls.DataGridViewSpanningTextBoxCell { /// <summary> /// This class represents a TextBoxCell that spans multiple columns. /// </summary> public class DataGridViewSpanningTextBoxCell : DataGridViewTextBoxCell { #region Fields private int columnSpan; #endregion #region Properties /// <summary> /// Gets and sets the number of columns this DataGridViewCell spans. /// </summary> public int ColumnSpan { get { return this.columnSpan; } set { this.columnSpan = value; } } #endregion #region Constructors /// <summary> /// Default constructor. /// </summary> public DataGridViewSpanningTextBoxCell() : base() { this.columnSpan = 1; } /// <summary> /// Constructor. /// </summary> /// <param name="columnSpan">The number of columns this DataGridViewCell spans.</param> public DataGridViewSpanningTextBoxCell(int columnSpan) : base() { this.columnSpan = columnSpan; } #endregion #region overrides /// <summary> /// Override of OnDataGridViewChanged sets the CellPainting event /// </summary> protected override void OnDataGridViewChanged() { base.OnDataGridViewChanged(); if (this.DataGridView != null) { this.DataGridView.CellPainting += this.DataGridView_CellPainting; } } /// <summary> /// Override PositionEditingControl sets the cell clipping region /// </summary> /// <param name="setLocation">true to have the control placed as specified by the other arguments; false to allow the control to place itself.</param> /// <param name="setSize">true to specify the size; false to allow the control to size itself.</param> /// <param name="cellBounds">A System.Drawing.Rectangle that defines the cell bounds.</param> /// <param name="cellClip">The area that will be used to paint the editing control.</param> /// <param name="cellStyle">A System.Windows.Forms.DataGridViewCellStyle that represents the style of the cell being edited.</param> /// <param name="singleVerticalBorderAdded">true to add a vertical border to the cell; otherwise, false.</param> /// <param name="singleHorizontalBorderAdded">true to add a horizontal border to the cell; otherwise, false.</param> /// <param name="isFirstDisplayedColumn">true if the hosting cell is in the first visible column; otherwise, false.</param> /// <param name="isFirstDisplayedRow">true if the hosting cell is in the first visible row; otherwise, false.</param> public override void PositionEditingControl(bool setLocation, bool setSize, Rectangle cellBounds, Rectangle cellClip, DataGridViewCellStyle cellStyle, bool singleVerticalBorderAdded, bool singleHorizontalBorderAdded, bool isFirstDisplayedColumn, bool isFirstDisplayedRow) { cellBounds.Width = 0; cellClip.Width = 0; for (int i = 0; i < this.columnSpan; i++) { if (this.DataGridView.Columns[this.ColumnIndex + i].Visible) { cellBounds.Width += this.DataGridView.Columns[this.ColumnIndex + i].Width; cellClip.Width += this.DataGridView.Columns[this.ColumnIndex + i].Width; } } base.PositionEditingControl(setLocation, setSize, cellBounds, cellClip, cellStyle, singleVerticalBorderAdded, singleHorizontalBorderAdded, isFirstDisplayedColumn, isFirstDisplayedRow); } /// <summary> /// Override DetachEditingControl invalidates cells /// </summary> public override void DetachEditingControl() { base.DetachEditingControl(); this.DataGridView.InvalidateCell(this); } #endregion #region EventHandlers /// <summary> /// Handles the cell painting event for the DataGridView /// </summary> /// <param name="sender">The sending object</param> /// <param name="e">The argument parameters</param> protected virtual void DataGridView_CellPainting(object sender, DataGridViewCellPaintingEventArgs e) { if ((e.RowIndex == this.RowIndex) && (this.RowIndex != -1)) { if ((e.ColumnIndex >= this.ColumnIndex) && (this.DataGridView[this.ColumnIndex, e.RowIndex] is DataGridViewSpanningTextBoxCell)) { /* * Note the difference between the terms "displayed" and "visible". * A visible column is one that has its Visible property set to true. * A displayed column is one that is visible and is not hidden from view by horizontal scrolling. */ //The total width of visible columns included in the span. int spanWidth = 0; //The total width of columns in the span that are to the left of the first displayed column. //(This does not include the part of the width of a the first displayed column that may be partially displayed.) int totalScrolledColumnWidth = 0; //The offset from the first column (visible or not) in the span to the first displayed column in the span. int offsetToFirstDisplayedColumnInSpan = this.DataGridView.FirstDisplayedCell.ColumnIndex - this.ColumnIndex; if (offsetToFirstDisplayedColumnInSpan < 0) { offsetToFirstDisplayedColumnInSpan = 0; } //Calculate the total span and scrolled span. for (int i = 0; i < this.columnSpan; i++) { if (this.DataGridView.Columns[this.ColumnIndex + i].Visible) { spanWidth += this.DataGridView.Columns[this.ColumnIndex + i].Width; if (i < offsetToFirstDisplayedColumnInSpan) { totalScrolledColumnWidth += this.DataGridView.Columns[this.ColumnIndex + i].Width; } } } //Get a rectangle that defines the area on the screen that we want to draw to. Rectangle spanningDisplayRectangle = this.DataGridView.GetCellDisplayRectangle(this.ColumnIndex + offsetToFirstDisplayedColumnInSpan, e.RowIndex, false); spanningDisplayRectangle.Width = spanWidth; //Adjust the rectangle to compensate for visible row headers. if (this.DataGridView.RowHeadersVisible) { if (spanningDisplayRectangle.X < this.DataGridView.RowHeadersWidth) { spanningDisplayRectangle.Width -= (this.DataGridView.RowHeadersWidth - spanningDisplayRectangle.X); spanningDisplayRectangle.X = this.DataGridView.RowHeadersWidth + 1; } } //Set clipping to the calculated rectangle RectangleF oldClip = e.Graphics.ClipBounds; e.Graphics.SetClip(spanningDisplayRectangle); //If the Horizontal Scrolling offset is non-zero, the display rectangle's X property is always set to 1 (at //least for Visual Studio 2008 9.0.21022.8 RTM), which is the correct value for clipping, so we need to start //drawing further left than the start of the rectagle. if (this.DataGridView.HorizontalScrollingOffset > 0) { Rectangle rootCellDisplayRectangle = this.DataGridView.GetCellDisplayRectangle(this.ColumnIndex + offsetToFirstDisplayedColumnInSpan, e.RowIndex, false); spanningDisplayRectangle.X += rootCellDisplayRectangle.Width - this.DataGridView.Columns[this.ColumnIndex + offsetToFirstDisplayedColumnInSpan].Width; spanningDisplayRectangle.X -= totalScrolledColumnWidth; } //Move the text a pixel away from the cell border int stringX = spanningDisplayRectangle.X + 1; int stringY = spanningDisplayRectangle.Y + 1; //Draw the cell and border Brush foreBrush = null; Brush backBrush = null; try { if (this.Selected) { foreBrush = new SolidBrush(this.InheritedStyle.SelectionForeColor); backBrush = new SolidBrush(this.InheritedStyle.SelectionBackColor); } else { foreBrush = new SolidBrush(this.InheritedStyle.ForeColor); backBrush = new SolidBrush(this.InheritedStyle.BackColor); } e.Graphics.FillRectangle(backBrush, spanningDisplayRectangle); e.Graphics.DrawString(this.FormattedValue.ToString(), this.DataGridView.Columns[this.ColumnIndex].InheritedStyle.Font, foreBrush, stringX, stringY); Pen pen = new Pen(this.DataGridView.GridColor); e.Graphics.DrawLine(pen, spanningDisplayRectangle.X, spanningDisplayRectangle.Top, spanningDisplayRectangle.Right, spanningDisplayRectangle.Top); e.Graphics.DrawLine(pen, spanningDisplayRectangle.X, spanningDisplayRectangle.Y - 1, spanningDisplayRectangle.X, spanningDisplayRectangle.Bottom + 1); } finally { if (foreBrush != null) { foreBrush.Dispose(); } if (backBrush != null) { backBrush.Dispose(); } } //Reset the clipping to its original value e.Graphics.SetClip(oldClip); e.Handled = true; } } } #endregion } }
Usage
{ //Create a new row to add as a child to the expanding row DataGridViewRow newRow = new DataGridViewRow(); //Add our spanning column to the row CustomControls.DataGridViewSpanningTextBoxCell.DataGridViewSpanningTextBoxCell widenode = new CustomControls.DataGridViewSpanningTextBoxCell.DataGridViewSpanningTextBoxCell(); widenode.ColumnSpan = expandingRow.Cells.Count; //Spans all the columns widenode.Value = "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz\n" + "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz\n" + "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz"; newRow.Cells.Add(widenode); //Add the new row to the CollapsibleDataGridView someDataGridView.Rows.Add(newRow); //Set the new row's height to accomodate the multiple lines of text newRow.Height = newRow.InheritedStyle.Font.Height * widenode.Value.ToString().Split('\n').Length; }
page revision: 2, last edited: 06 Aug 2008 21:15