[Solved] Multiple Row per Record RadGrid

3 Answers 25 Views
Grid
David
Top achievements
Rank 1
David asked on 30 Jan 2026, 05:31 PM

I am trying to figure out how to accomplish this.  I am not sure if the RadGrid is best use

I have a data query that will return one or more records

Example - data returned may look like

UserID, User Full Name, User Status, Hire Date, Manager Name, Location, Skill Set, Last Review

And I want to format it as

Record 1

Row - User ID, User Full Name, User Status, Hire Date, Manager Name

Row - Location, Skill Set, Last Review

Next Record

Row - User ID, User Full Name, User Status, Hire Date, Manager Name

Row - Location, Skill Set, Last Review

Etc. 

I no problem filling data adapter, filling data table and then binding to grid in single row to record, but setting up multiple rows per record I am struggling. 

Thanks

3 Answers, 1 is accepted

Sort by
1
Rumen
Telerik team
answered on 30 Jan 2026, 06:35 PM

Hi,

Thank you for reaching out to Telerik Support!

Based on your requirements, RadGrid is indeed the right choice for this scenario. You can achieve the multi-row per record layout using the DetailItemTemplate property of the MasterTableView. This template automatically renders additional content below each data row, giving you the exact layout you described.

Solution Overview

Row 1 (Grid Columns): User ID, User Full Name, User Status, Hire Date, Manager Name
Row 2 (DetailItemTemplate): Location, Skill Set, Last Review

ASPX

    <style type="text/css">
        .detail-row {
            background-color: #f5f5f5;
            padding: 8px 12px;
            border-bottom: 2px solid #ccc;
        }
        .detail-row td {
            padding: 4px 15px 4px 0;
        }
        .detail-label {
            font-weight: bold;
            color: #555;
        }
    </style>
        

<telerik:RadGrid ID="RadGrid1" runat="server" 
            OnNeedDataSource="RadGrid1_NeedDataSource"
            AllowPaging="true" 
            PageSize="10"
            Skin="Bootstrap">
            <MasterTableView AutoGenerateColumns="false" DataKeyNames="UserID">
                <Columns>
                    <telerik:GridBoundColumn DataField="UserID" HeaderText="User ID" />
                    <telerik:GridBoundColumn DataField="UserFullName" HeaderText="Full Name" />
                    <telerik:GridBoundColumn DataField="UserStatus" HeaderText="Status" />
                    <telerik:GridBoundColumn DataField="HireDate" HeaderText="Hire Date" DataFormatString="{0:MM/dd/yyyy}" />
                    <telerik:GridBoundColumn DataField="ManagerName" HeaderText="Manager" />
                </Columns>
                <DetailItemTemplate>
                    <table class="detail-row" style="width: 100%;">
                        <tr>
                            <td>
                                <span class="detail-label">Location:</span> <%# Eval("Location") %>
                            </td>
                            <td>
                                <span class="detail-label">Skill Set:</span> <%# Eval("SkillSet") %>
                            </td>
                            <td>
                                <span class="detail-label">Last Review:</span> <%# Eval("LastReview", "{0:MM/dd/yyyy}") %>
                            </td>
                        </tr>
                    </table>
                </DetailItemTemplate>
            </MasterTableView>
        </telerik:RadGrid>

ASPX.CS

    protected void RadGrid1_NeedDataSource(object sender, GridNeedDataSourceEventArgs e)
    {
        RadGrid1.DataSource = GetEmployeeData();
    }

    private DataTable GetEmployeeData()
    {
        DataTable dt = new DataTable();

        // Row 1 fields
        dt.Columns.Add("UserID", typeof(int));
        dt.Columns.Add("UserFullName", typeof(string));
        dt.Columns.Add("UserStatus", typeof(string));
        dt.Columns.Add("HireDate", typeof(DateTime));
        dt.Columns.Add("ManagerName", typeof(string));

        // Row 2 fields (displayed in DetailItemTemplate)
        dt.Columns.Add("Location", typeof(string));
        dt.Columns.Add("SkillSet", typeof(string));
        dt.Columns.Add("LastReview", typeof(DateTime));

        // Sample employee data
        string[] names = { "John Smith", "Jane Doe", "Michael Johnson", "Emily Davis", "Robert Wilson",
                           "Sarah Brown", "David Lee", "Jennifer Garcia", "William Martinez", "Lisa Anderson",
                           "James Taylor", "Maria Thomas", "Daniel Jackson", "Nancy White", "Christopher Harris" };

        string[] statuses = { "Active", "On Leave", "Active", "Probation", "Active" };
        string[] managers = { "Alice Cooper", "Bob Freeman", "Carol King", "Dan Murphy", "Eve Rogers" };
        string[] locations = { "New York", "Los Angeles", "Chicago", "Houston", "Phoenix", "Philadelphia", "San Antonio" };
        string[] skills = { "C#, ASP.NET, SQL", "JavaScript, React, Node.js", "Python, Django, PostgreSQL",
                            "Java, Spring, Oracle", "Azure, DevOps, Docker", ".NET, Angular, MongoDB" };

        Random rand = new Random(42);

        for (int i = 0; i < 25; i++)
        {
            DataRow row = dt.NewRow();
            row["UserID"] = 1000 + i;
            row["UserFullName"] = names[i % names.Length];
            row["UserStatus"] = statuses[i % statuses.Length];
            row["HireDate"] = DateTime.Today.AddDays(-rand.Next(365, 3650));
            row["ManagerName"] = managers[i % managers.Length];
            row["Location"] = locations[i % locations.Length];
            row["SkillSet"] = skills[i % skills.Length];
            row["LastReview"] = DateTime.Today.AddDays(-rand.Next(30, 365));
            dt.Rows.Add(row);
        }

        return dt;
    }

Key Points

  • DetailItemTemplate - Renders as part of each data item (no expand/collapse required), creating a seamless two-row per record appearance.
  • Data Binding - Your existing DataTable/DataAdapter approach will work perfectly. Just ensure all fields (including Location, SkillSet, LastReview) are included in your data source.
  • Use NeedDataSource - We recommend binding in the NeedDataSource event rather than Page_Load for optimal performance.
  • Styling - You can add CSS to visually distinguish the detail row (background color, borders, etc.).

Alternative Approach

If you need even more control over the layout, you can use a single GridTemplateColumn with an HTML table in the ItemTemplate to render both rows within one cell spanning all columns.

Regards,
Rumen
Progress Telerik

Stay tuned by visiting our public roadmap and feedback portal pages! Or perhaps, if you are new to our Telerik family, check out our getting started resources
David
Top achievements
Rank 1
commented on 30 Jan 2026, 07:01 PM

Fantastic and thank you.  Let me give that a shot.  Seems very straight forward
Rumen
Telerik team
commented on 30 Jan 2026, 09:23 PM

Sure, take your time!
David
Top achievements
Rank 1
commented on 02 Feb 2026, 07:33 PM

So that makes sense and I have that working for the most part with some minor other layout design tweaks.  If I want to modify or suppress a value that is the ItemDetail would that be in the OnItemDataBound event?  I have some values that may be empty for a record - so if the record has an empty value I would like to supress the label as well.

So if I took a look at the "Last Review" I would like to suppress the label as well if there is no data.
So it would be like 
If Eval("LastReview") not null 
{
    Last Review : <%# Eval("LastReview", "{0:MM/dd/yyyy}") %>
}

I am used to doing a lot of my work on standard DataGrids so this would be something I would do in the _RowDataBound 
if (e.Row.RowType == DataControlRowType.DataRow) 
Find the item, evaluate and then find the control I wanted to alter and then make the change.

Thanks.  

0
Rumen
Telerik team
answered on 02 Feb 2026, 07:47 PM

Hi David,

Great question! You're correct that this would be handled in the ItemDataBound event, similar to how you'd use RowDataBound with a standard DataGrid.

The key is to check for GridDetailTemplateItem (which represents the DetailItemTemplate rows), then use FindControl() to locate and hide the elements when the data is empty.

ASPX Markup

Wrap each field in a server-side element with an ID so you can find it in code-behind:

<DetailItemTemplate>
    <table class="detail-row" style="width: 100%;">
        <tr>
            <td runat="server" id="tdLocation">
                <span class="detail-label">Location:</span> 
                <asp:Label ID="lblLocation" runat="server" Text='<%# Eval("Location") %>' />
            </td>
            <td runat="server" id="tdSkillSet">
                <span class="detail-label">Skill Set:</span> 
                <asp:Label ID="lblSkillSet" runat="server" Text='<%# Eval("SkillSet") %>' />
            </td>
            <td runat="server" id="tdLastReview">
                <span class="detail-label">Last Review:</span> 
                <asp:Label ID="lblLastReview" runat="server" Text='<%# Eval("LastReview", "{0:MM/dd/yyyy}") %>' />
            </td>
        </tr>
    </table>
</DetailItemTemplate>

Code-Behind (C#)

Handle the ItemDataBound event to conditionally hide cells when values are empty:

protected void RadGrid1_ItemDataBound(object sender, GridItemEventArgs e)
{
    // Handle the DetailItemTemplate rows
    GridDetailTemplateItem detailItem = e.Item as GridDetailTemplateItem;
    if (detailItem != null)
    {
        DataRowView dataItem = (DataRowView)detailItem.DataItem;

        // Hide Location cell if empty
        HtmlTableCell tdLocation = (HtmlTableCell)detailItem.FindControl("tdLocation");
        if (tdLocation != null)
        {
            object location = dataItem["Location"];
            if (location == null || location == DBNull.Value || string.IsNullOrEmpty(location.ToString()))
            {
                tdLocation.Visible = false;
            }
        }

        // Hide SkillSet cell if empty
        HtmlTableCell tdSkillSet = (HtmlTableCell)detailItem.FindControl("tdSkillSet");
        if (tdSkillSet != null)
        {
            object skillSet = dataItem["SkillSet"];
            if (skillSet == null || skillSet == DBNull.Value || string.IsNullOrEmpty(skillSet.ToString()))
            {
                tdSkillSet.Visible = false;
            }
        }

        // Hide LastReview cell if empty
        HtmlTableCell tdLastReview = (HtmlTableCell)detailItem.FindControl("tdLastReview");
        if (tdLastReview != null)
        {
            object lastReview = dataItem["LastReview"];
            if (lastReview == null || lastReview == DBNull.Value)
            {
                tdLastReview.Visible = false;
            }
        }
    }
}

GridDetailTemplateItem - This is the item type for DetailItemTemplate rows, similar to checking DataControlRowType.DataRow in a standard DataGrid.

Add runat="server" and id to the <td> elements so you can find them with FindControl().

Check for DBNull.Value - Database null values come through as DBNull.Value, not C# null.

Hide the entire cell - Setting Visible = false on the <td> hides both the label and the value.

This gives you the same level of control you're used to with DataGrid's RowDataBound event. You can access the data item, find controls within the template, and modify their properties as needed.

Regards,
Rumen
Progress Telerik

Stay tuned by visiting our public roadmap and feedback portal pages! Or perhaps, if you are new to our Telerik family, check out our getting started resources
David
Top achievements
Rank 1
commented on 02 Feb 2026, 08:23 PM

That makes sense, but I am still running into a reference issue.

The ItemDataBound event is being called, but it looks like something is off in my reference.

       GridDetailTemplateItem detailItem = e.Item as GridDetailTemplateItem;
        if (detailItem != null) {

}

The detailItem looks like it is always set to null so I am never getting into the row find control section of the loop.  It is looping through, I am missing something.  

 

 

0
Rumen
Telerik team
answered on 03 Feb 2026, 10:09 AM

Hi David,

After thorough investigation, I found an important nuance with DetailItemTemplate in RadGrid: the controls inside the template are NOT available during ItemDataBound. They exist inside GridDetailTemplateItem which is only fully created after data binding completes.

The solution is to use the Page_PreRender event instead, where all controls are guaranteed to exist.

ASPX Markup

Wrap each detail field in a server-side <td> with an ID, and use asp:Label for the values:

<DetailItemTemplate>
    <table class="detail-row" style="width: 100%;">
        <tr>
            <td runat="server" id="tdLocation">
                <span class="detail-label">Location:</span> 
                <asp:Label ID="lblLocation" runat="server" Text='<%# Eval("Location") %>' />
            </td>
            <td runat="server" id="tdSkillSet">
                <span class="detail-label">Skill Set:</span> 
                <asp:Label ID="lblSkillSet" runat="server" Text='<%# Eval("SkillSet") %>' />
            </td>
            <td runat="server" id="tdLastReview">
                <span class="detail-label">Last Review:</span> 
                <asp:Label ID="lblLastReview" runat="server" Text='<%# Eval("LastReview", "{0:MM/dd/yyyy}") %>' />
            </td>
        </tr>
    </table>
</DetailItemTemplate>

Code-Behind (C#)

Use Page_PreRender with a recursive method to find and hide empty cells:

using System;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using Telerik.Web.UI;

public partial class Default : Page
{
    protected void Page_PreRender(object sender, EventArgs e)
    {
        // Process DetailItemTemplate rows to hide empty cells
        // Must be done in PreRender because DetailItemTemplate controls 
        // are not available in ItemDataBound
        HideEmptyDetailCells(RadGrid1);
    }

    private void HideEmptyDetailCells(Control container)
    {
        foreach (Control ctrl in container.Controls)
        {
            Label lbl = ctrl as Label;
            if (lbl != null)
            {
                bool shouldCheck = lbl.ID == "lblLocation" || 
                                   lbl.ID == "lblSkillSet" || 
                                   lbl.ID == "lblLastReview";
                
                if (shouldCheck)
                {
                    string text = (lbl.Text ?? "").Trim();
                    if (string.IsNullOrEmpty(text))
                    {
                        // Hide the parent table cell
                        HtmlTableCell td = lbl.Parent as HtmlTableCell;
                        if (td != null)
                        {
                            td.Visible = false;
                        }
                    }
                }
            }
            
            // Recurse into children
            HideEmptyDetailCells(ctrl);
        }
    }

    protected void RadGrid1_NeedDataSource(object sender, GridNeedDataSourceEventArgs e)
    {
        RadGrid1.DataSource = GetEmployeeData();
    }
}

  • Use Page_PreRender - controls are not available in ItemDataBound
  • Recursive search required - controls are nested inside naming containers
  • Hide the parent <td> to hide both the label text and value

At ItemDataBound time, the DetailItemTemplate content hasn't been fully created yet. The template is rendered as part of the grid's lifecycle after individual items are data-bound. By PreRender, all controls exist and have their values populated.

I've attached my test files.

Regards,
Rumen
Progress Telerik

Stay tuned by visiting our public roadmap and feedback portal pages! Or perhaps, if you are new to our Telerik family, check out our getting started resources
David
Top achievements
Rank 1
commented on 03 Feb 2026, 02:30 PM

That worked like a charm - and your answer makes perfect sense as to why it appeared that the ItemDataBound was looping properly, but that the instance was returning null.  I would not have figured this nuance out, so thanks for the tip that will also help down the road to consider when I run into similar null references, data may be bound, but the object may not be instantiated and available for that method and to look later down the life cycle as in this code.  
Rumen
Telerik team
commented on 03 Feb 2026, 02:41 PM

Glad to hear it worked well, David, and that the lifecycle detail helped clarify things.
Tags
Grid
Asked by
David
Top achievements
Rank 1
Answers by
Rumen
Telerik team
Share this question
or