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
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
NeedDataSourceevent rather thanPage_Loadfor 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
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.
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
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.
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 inItemDataBound - 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
