GridView でセルを結合する方法
概要
この記事では、 GridView (ASP.NET) のセルを結合して表示する方法について説明します。*1
似たような記事として DataGrid コントロールの同一列内のセルを結合するには?が@IT にありますが、こちらはポストバック時に問題が発生する場合があります。そこで、@IT とは違った方法でセルを結合します。
解説
基本的な方針は、結合したいセルを見えなくするだけです。そのため、結合するかどうかの条件にマッチしたセルに対して CSS を適用します。
詳細はソースコードを参照してください。コメントを多めに記述しているため、再利用しやすいと思います。
単純な使用例は、メインソースの example 要素をご覧ください。
ソースコード
結合時に適用する CSS
/* 結合されたセル用 */ .GridJoined { display: none; }
メインソース
using System; using System.Collections.Generic; using System.Configuration; using System.Data; using System.Diagnostics; using System.Web; using System.Web.Security; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.UI.WebControls.WebParts; using System.Web.UI.HtmlControls; /// <summary> /// GridView に以下の設定を行う /// ・ 同じ項目が連続する部分を非表示 /// </summary> /// <remarks> /// 非表示になったデータは、画面のレイアウトからは見えないが /// HTML のソースとしては出力されている。 /// /// サンプルは以下の通り。 /// GridView の結合は、 DataBind() が終了した後に行う。 /// /// ページングやソートの後にも結合処理が必要になるため、 /// 以下の部分は、メソッド (たとえば JoinGridView という名称) として /// 用意すると使いやすい。 /// /// <example> /// GridViewJoin joiner = new GridViewJoin(gridRowHeader, (int)Column.番号); /// joiner.AddRelationToKey((int)Column.関連項目1); /// joiner.AddRelationToKey((int)Column.関連項目2); /// joiner.Apply(); /// </example> /// </remarks> public sealed class GridViewJoin { #region 変数 // 対象となる GridView private GridView gridView; // Key となる Column private int keyColumn; // Relation となる Column private List<int> relationColumns = new List<int>(); #endregion // 変数 #region Public /// <summary> /// コンストラクタ /// </summary> /// <param name="gridView">対象となる GridView</param> /// <param name="keyColumn">Key となる Column</param> public GridViewJoin(GridView gridView, int keyColumn) { this.gridView = gridView; this.keyColumn = keyColumn; } /// <summary> /// Key に連動して非表示にする Column を設定する /// </summary> /// <param name="relationColumn">Key に連動して非表示にする Column</param> public void AddRelationToKey(int relationColumn) { // 事前条件のチェック Debug.Assert(keyColumn != relationColumn, "事前条件: Key となる Column と同じ Column を Add することはできない"); Debug.Assert(!relationColumns.Contains(relationColumn), "事前条件: すでに Add している Column と同じ Column を Add することはできない"); relationColumns.Add(relationColumn); } /// <summary> /// 全ての設定を適用 /// </summary> public void Apply() { ApplyJoin(); } #endregion // Public #region Private /// <summary> /// GridView で同じ項目が連続する部分を非表示にする /// </summary> private void ApplyJoin() { int baseRow = 0; // 比較元の行番号 while (baseRow < gridView.Rows.Count - 1) { TableCellCollection baseCollection = gridView.Rows[baseRow].Cells; int targetRow = baseRow + 1; // 比較先の行番号 while (targetRow < gridView.Rows.Count) { TableCellCollection targetCollection = gridView.Rows[targetRow].Cells; // キーとなるテキストが等しいか判定し、値が等しければ以降のセルを非表示にする if (GridViewUtil.GetText(baseCollection[keyColumn]) == GridViewUtil.GetText(targetCollection[keyColumn])) { // キーとなる列を非表示にする AddRowSpan(baseCollection[keyColumn]); targetCollection[keyColumn].CssClass = GridViewUtil.JOINED_CSS_CLASS; // キーに付随する列を非表示にする foreach (int column in relationColumns) { AddRowSpan(baseCollection[column]); targetCollection[column].CssClass = GridViewUtil.JOINED_CSS_CLASS; } targetRow += 1; } else { // 値が同じではなかったため Break break; } } // baseRow を次の行 (targetRow) にする baseRow = targetRow; } } /// <summary> /// Cell を結合する /// </summary> /// <param name="cell">結合対象の Cell</param> private void AddRowSpan(TableCell cell) { if (cell.RowSpan == 0) { cell.RowSpan = 2; // 未結合の場合は初期値 2 } else { cell.RowSpan += 1; // 結合済みの場合は結合の値を増やす } } #endregion // Private }
ユーティリティクラス
using System; using System.Data; using System.Configuration; using System.Web; using System.Web.Security; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.UI.WebControls.WebParts; using System.Web.UI.HtmlControls; /// <summary> /// GridViewUtil の概要の説明です /// </summary> public static class GridViewUtil { public static string JOINED_CSS_CLASS = "GridJoined"; /// <summary> /// Grid 用の行比較ヘルパクラス /// </summary> public sealed class Compare { private GridViewRow prevGridRow; /// <summary> /// 前の行と今回指定した行が異なるかを比較する /// </summary> /// <param name="row">対象行</param> /// <param name="from">比較範囲の開始</param> /// <param name="to">比較範囲の終了</param> /// <returns>前の行と今回指定した行が異なれば true, そうでなければ false</returns> public bool IsNextRow(GridViewRow row, int from, int to) { bool isNext = false; if (prevGridRow == null) { isNext = true; } else { for (int columnNum = from; columnNum <= to; ++columnNum) { if (prevGridRow.Cells[columnNum].Text != row.Cells[columnNum].Text) { isNext = true; break; } } } prevGridRow = row; return isNext; } } /// <summary> /// 行を取得する /// </summary> /// <param name="gridView">対象の GridView</param> /// <param name="rowNumber">行番号</param> /// <returns>セル</returns> public static TableRow GetRow(GridView gridView, int rowNumber) { return gridView.Rows[rowNumber]; } /// <summary> /// セルを取得する /// </summary> /// <param name="gridView">対象の GridView</param> /// <param name="rowNumber">行番号</param> /// <param name="columnNumber">列番号</param> /// <returns>セル</returns> /// <remarks>非表示に設定されているセルを考慮する</remarks> public static TableCell GetCell(GridView gridView, int rowNumber, int columnNumber) { // CssClass が "joined" なら 1 行上のセルを参照する while (GetRow(gridView, rowNumber).Cells[columnNumber].CssClass == JOINED_CSS_CLASS) { rowNumber -= 1; } return GetRow(gridView, rowNumber).Cells[columnNumber]; } /// <summary> /// セルに表示されているテキストを取り出す /// </summary> /// <param name="gridView">対象の GridView</param> /// <param name="rowNumber">行番号</param> /// <param name="columnNumber">列番号</param> /// <returns>セルに表示されているテキスト</returns> public static string GetText(GridView gridView, int rowNumber, int columnNumber) { // セルに表示されているテキストを取り出す return GetText(GetCell(gridView, rowNumber, columnNumber)); } /// <summary> /// セルに表示されているテキストを取り出す /// </summary> /// <param name="cell">セル</param> /// <returns>セルに表示されているテキスト</returns> public static string GetText(TableCell cell) { foreach (Control control in cell.Controls) { // Control のテキストを返す if (control is TextBox) { return ((TextBox)control).Text; } else if (control is DropDownList) { return ((DropDownList)control).Text; } else if (control is CheckBox) { return ((CheckBox)control).Checked.ToString(); } else if (!(control is LiteralControl || control is DataBoundLiteralControl)) { throw new NotImplementedException(); // TODO //return ((object)control).Text; } } // Control が見つからなかったため、 BoundColumn の テキストを返す return cell.Text; } /// <summary> /// GridView に列を追加する /// </summary> /// <param name="gridView">対象の GridView</param> /// <param name="dataField">DataField</param> /// <param name="headerText">HeaderText</param> public static void AddBoundColumn(GridView gridView, string dataField, string headerText) { BoundField field = new BoundField(); field.DataField = dataField; field.HeaderText = headerText; gridView.Columns.Add(field); } /// <summary> /// GridView に列を追加する /// </summary> /// <param name="gridView">対象の GridView</param> /// <param name="itemTemplate">ITemplate</param> /// <param name="headerText">HeaderText</param> public static void AddTemplateColumn(GridView gridView, ITemplate itemTemplate, string headerText) { TemplateField field = new TemplateField(); field.ItemTemplate = itemTemplate; field.HeaderText = headerText; gridView.Columns.Add(field); } /// <summary> /// TableCell に CSS を追加適用する /// </summary> /// <param name="cell">対象の TableCell</param> /// <param name="cssClassName">適用する CSS のクラス名</param> public static void AddCssClass(TableCell cell, string cssClassName) { string currentCssClassName = cell.CssClass; // 複数の CSS クラスを適用するため、 // すでに適用されている CSS クラスがあった場合は、スペースで区切って追加する if (String.IsNullOrEmpty(currentCssClassName)) { cell.CssClass = cssClassName; } else { cell.CssClass = currentCssClassName + " " + cssClassName; } } /// <summary> /// GridViewRow の指定した位置に CSS を追加適用する /// </summary> /// <param name="row">対象の GridViewRow</param> /// <param name="position">指定位置</param> /// <param name="cssClassName">適用する CSS のクラス名</param> public static void AddCssClass(GridViewRow row, int position, string cssClassName) { if (0 <= position && position < row.Cells.Count) { AddCssClass(row.Cells[position], cssClassName); } } /// <summary> /// GridViewRow の指定した位置に縦線を表示する /// </summary> /// <param name="row">対象の GridViewRow</param> /// <param name="position">指定位置</param> public static void AddBorder(GridViewRow row, int position) { // TODO CSS 名を変更する if (0 <= position && position < row.Cells.Count) { AddCssClass(row.Cells[position], "GridTestBorderLeft"); } else if (position == row.Cells.Count) { AddCssClass(row.Cells[position - 1], "GridTestBorderRight"); } } /// <summary> /// デバッグ用簡易出力ヘルパメソッド /// </summary> /// <param name="grid">出力対象の GridView</param> public static void OutputForDebug(GridView grid) { System.Diagnostics.Debug.WriteLine("--[begin GridView]-----"); foreach (GridViewRow row in grid.Rows) { foreach (TableCell cell in row.Cells) { System.Diagnostics.Debug.Write(cell.Text + " | "); } System.Diagnostics.Debug.WriteLine(""); } System.Diagnostics.Debug.WriteLine("--[end GridView]------"); } }
*1:DataGrid も同様の方法で可能です。