ASP.NET GridViewのItemTemplateタグ内で各明細毎にフッターレコードを作成する


1. はじめに

現在、ASP.NETの案件に携わっており、各明細行に対してフッターレコードを挿入した様なGridViewのレイアウト作成を依頼されました。
少し手こずってしまったので、備忘として記載します。
また、今回の案件ではGridViewを継承したカスタムコントロールを使用しなければならない規約でしたので、かなりゴリ推しで記述しています。。。。。
ご了承ください。

2. やりたいこと

列結合した行を各明細行の下に追加する様なイメージです。
通常のhtmlなら<td>にcolspan指定すれば良い、なんてことないレイアウトになります。

Sample.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8">
    <title>Sample</title>
  </head>
  <body>
    <table id="PersonTable" border="1" cellspacing="0" cellpadding="0">
        <tr style="background-color:#6088C6;">
            <th>ID</th>
            <th>Name</th>
            <th>Gender</th>
        </tr>
        <tr>
            <td rowspan="2">001</td>
            <td>Taro Tanaka</td>
            <td>Male</td>
        </tr>
        <tr>
            <td colspan="2">Remarks:Normal Employee</td>
        </tr>
        <tr>
            <td rowspan="2">002</td>
            <td>Yuki Suzuki</td>
            <td>Female</td>
        </tr>
        <tr>
            <td colspan="2">Remarks:Leader</td>
        </tr>
    </table>
  </body>
</html>

3. DataGridコントロール内で実装する

というわけで実装してみました。

Sample.aspx
<%@ Page Title="Sample Page" Language="C#" MasterPageFile="~/Site.Master" AutoEventWireup="true" CodeBehind="Sample .aspx.cs" Inherits="WebApplication1._Sample " %>

<asp:Content ID="BodyContent" ContentPlaceHolderID="MainContent" runat="server">
    <asp:GridView ID="PersonView" runat="server" 
        autoGenerateColumns="false" 
        headerStyle-BackColor="#6088C6" OnRowDataBound="PersonView_RowDataBound">
        <Columns>
            <asp:TemplateField>
                <HeaderTemplate>
                    <asp:Label runat="server" Text="ID"></asp:Label>
                </HeaderTemplate>
                <ItemTemplate>
                    <asp:Label id="IDLabel" runat="server" Text=<%# DataBinder.Eval(Container.DataItem, "ID").ToString() %>></asp:Label>
                </ItemTemplate>
            </asp:TemplateField>
            <asp:TemplateField>
                <HeaderTemplate>
                    <asp:Label runat="server" Text="Name"></asp:Label>
                </HeaderTemplate>
                <ItemTemplate>
                    <asp:Label id="NameLabel" runat="server" Text=<%# DataBinder.Eval(Container.DataItem, "Name").ToString() %>></asp:Label>
                </ItemTemplate>
            </asp:TemplateField>
            <asp:TemplateField>
                <HeaderTemplate>
                    <asp:Label runat="server" Text="Gender"></asp:Label>
                </HeaderTemplate>
                <ItemTemplate>
                    <asp:Label id="GenderLabel" runat="server" Text=<%# DataBinder.Eval(Container.DataItem, "Gender").ToString() %>></asp:Label>
                    </td></tr>
                    <tr id="DetailFooterRow">
                        <td colspan ="2">
                             <asp:Label id="RemarksCaptionLabel" runat="server" Text="Remarks:"></asp:Label>
                             <asp:Label id="RemarksLabel" runat="server" Text=<%# DataBinder.Eval(Container.DataItem, "Remarks").ToString() %>></asp:Label>
                </ItemTemplate>
            </asp:TemplateField>
        </Columns>
    </asp:GridView>
</asp:Content>
Sample.aspx.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;

namespace WebApplication1
{
    /// <summary>
    /// Defaultページクラス
    /// </summary>
    public partial class _Sample : Page
    {
        /// <summary>
        /// ロードイベント
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected void Page_Load(object sender, EventArgs e)
        {
            //----------------------------------------
            // [1] バインドデータ作成
            //----------------------------------------
            DataTable personTable = new DataTable("PersonTable");

            //カラム設定
            personTable.Columns.AddRange(new DataColumn[]
            {
                 new DataColumn("ID", typeof(string))
                ,new DataColumn("Name", typeof(string))
                ,new DataColumn("Gender", typeof(string))
                ,new DataColumn("Remarks", typeof(string))
            });

            //データを追加
            personTable.Rows.Add(new string[] { "001", "Taro Tanaka", "Male", "Normal Employee" });
            personTable.Rows.Add(new string[] { "002", "Yuki Suzuki", "Female", "Leader" });

            //----------------------------------------
            // [2] GridViewにバインド
            //----------------------------------------
            this.PersonView.DataSource = new DataView(personTable);
            this.PersonView.DataBind();
        }

        /// <summary>
        /// 行バインドイベント
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected void PersonView_RowDataBound(object sender, GridViewRowEventArgs e)
        {
            //DataBind行のみを対象
            if (e.Row.RowType == DataControlRowType.DataRow)
            {
                //IDセルの行幅を設定
                e.Row.Cells[0].RowSpan = 2;
            }
        }
    }
}

上記のコーディングで下記の動的コードが生成されます。

Sample.html
<table cellspacing="0" rules="all" border="1" id="MainContent_PersonView" style="border-collapse:collapse;">
        <tr style="background-color:#6088C6;">
            <th scope="col">
                    <span>ID</span>
                </th><th scope="col">
                    <span>Name</span>
                </th><th scope="col">
                    <span>Gender</span>
                </th>
        </tr><tr>
            <td rowspan="2">
                    <span id="MainContent_PersonView_IDLabel_0">001</span>
                </td><td>
                    <span id="MainContent_PersonView_NameLabel_0">Taro Tanaka</span>
                </td><td>
                    <span id="MainContent_PersonView_GenderLabel_0">Male</span>
                    </td></tr>
                    <tr id="DetailFooterRow">
                        <td colspan ="2">
                             <span id="MainContent_PersonView_RemarksCaptionLabel_0">Remarks:</span>
                             <span id="MainContent_PersonView_RemarksLabel_0">Normal Employee</span>
                </td>
        </tr><tr>
            <td rowspan="2">
                    <span id="MainContent_PersonView_IDLabel_1">002</span>
                </td><td>
                    <span id="MainContent_PersonView_NameLabel_1">Yuki Suzuki</span>
                </td><td>
                    <span id="MainContent_PersonView_GenderLabel_1">Female</span>
                    </td></tr>
                    <tr id="DetailFooterRow">
                        <td colspan ="2">
                             <span id="MainContent_PersonView_RemarksCaptionLabel_1">Remarks:</span>
                             <span id="MainContent_PersonView_RemarksLabel_1">Leader</span>
                </td>
        </tr>
    </table>

ポイントとしては<ItemTemplate>の最終項目直下に</td></tr>を記述し、その下部に<tr><td>を記述しています。
上記によって動的ソース生成時に無理やり<tr>要素を追加しています。

そして実際に表示されるページがこちらになります。
(手抜きで申し訳ありません。。。)

4. 懸念事項

今回はStyle系のプロパティの変更がなかった為、上記のロジックで問題ありませんでした。
しかし、GridViewのAlternatingRowStyle等のプロパティを指定する場合は注意が必要です。
上記のプロパティはあくまで、動的に生成される<tr>単位に設定されます。
その為、<tr>を無理矢理閉じてしまうとプロパティに指定したStyleが適用されません。
解決策としては、イベントのタイミングで設定可能であれば、対象の<tr>をHtmlTableRowクラスかTableRowクラスで設定してあげるか、cssでクラスを定義し、JavaScript等で実装するしかなさそうです。
(※筆者は未だ検証出来ておりません。)