Rowspan in CellRender Causes Rendering Issues with AllowVirtualization Enabled #1747

Closed
opened 2026-01-29 17:58:00 +00:00 by claunia · 3 comments
Owner

Originally created by @Abbossbek on GitHub (May 1, 2025).

Rowspan in CellRender Causes First Cell to Disappear When AllowVirtualization is Enabled

Description
When using rowspan attribute in CellRender method with AllowVirtualization=true, the first cell briefly renders and then disappears. This issue does NOT occur when:

  1. rowspan is removed from CellRender, or
  2. AllowVirtualization is set to false.

Steps to Reproduce

  1. Scroll through the virtualized grid
  2. Observe cells with applied rowspan (particularly in multi-capacity rooms)
  3. First cell flickers and gets hidden after initial render

Expected Behavior
Cells with rowspan should maintain consistent visibility and spanning behavior when virtualization is enabled.

Actual Behavior
Cells with rowspan fail to render correctly - first cell disappears after initial paint.

Code Reproduction

<RadzenDataGrid @ref="grid" 
                Data="@VisibleRoomRows" 
                LoadData="@LoadRoomRows" 
                Count="@FilteredRoomRows.Count" 
                TItem="RoomRow" 
                AllowVirtualization="true"
                VirtualizationOverscanCount="10" 
                Style="height: calc(100vh - 200px);" 
                CellRender="@CellRender">
    <Columns>
        <RadzenDataGridColumn Title="@Loc["RoomNumber"]" 
                              CssClass="rz-p-1" 
                              Property="Room.RoomNumber" 
                              SortOrder="SortOrder.Ascending" 
                              Frozen="true" 
                              MinWidth="220px" 
                              Width="220px">
            <Template Context="data">
                <RadzenStack Gap="0rem" JustifyContent="JustifyContent.SpaceBetween" Style="@($"height:{(data.Room.Capacity > 1 ? data.Room.Capacity * 28 : 56)}px")">
                    <RadzenStack Orientation="Orientation.Horizontal" JustifyContent="JustifyContent.SpaceBetween" class="rz-text-align-center" AlignItems="AlignItems.Center">
                        <RadzenText Text="@data.Room.RoomNumber" TextAlign="TextAlign.Center" Style="margin-bottom:0px" TextStyle="TextStyle.H5" />
                        <RadzenBadge Text="@Loc[data.Room.Status.ToString()]" Style="font-size:0.6rem" BadgeStyle="@data.Room.Status.GetBadgeStyleRoomStatus()" />
                    </RadzenStack>
                    <RadzenStack Orientation="Orientation.Horizontal" JustifyContent="JustifyContent.End" AlignItems="AlignItems.Center" Wrap="FlexWrap.Wrap">
                        <RadzenBadge Text="@($"{Loc["Floor"]}: {data.Room.Floor}")" Style="font-size:0.6rem" class="rz-mr-2 rz-my-1" Variant="Variant.Outlined" />
                        <RadzenBadge Text="@($"{Loc["Capacity"]}: {data.Room.Capacity}")" Style="font-size:0.6rem" class="rz-mr-2 rz-my-1" Variant="Variant.Outlined" />
                        <RadzenBadge Text="@Loc[data.Room.RoomType?.Name]" Style="font-size:0.6rem" class="rz-my-1" Variant="Variant.Outlined" />
                    </RadzenStack>
                </RadzenStack>
            </Template>
        </RadzenDataGridColumn>

        @foreach (var date in VisibleDates)
        {
            var currentDate = date;
            bool isToday = currentDate.Date == DateTimeOffset.Now.Date;
            <RadzenDataGridColumn HeaderCssClass="@("rz-text-align-center "+(isToday?"rz-background-color-info-lighter":""))" 
                                  CssClass="@(isToday? "rz-background-color-info-lighter":"")" 
                                  UniqueID="@currentDate.ToString(GlobalBase.DateTimeFormat)" 
                                  Sortable="false" 
                                  MinWidth="50px">
                <HeaderTemplate>
                    <RadzenStack AlignItems="AlignItems.Center" Style="width:100%">
                        <RadzenText Text="@currentDate.ToString("MMM")" TextAlign="TextAlign.Center" TextStyle="TextStyle.Caption" />
                        <RadzenText Text="@currentDate.ToString("dd")" TextAlign="TextAlign.Center" TextStyle="TextStyle.H5" />
                    </RadzenStack>
                </HeaderTemplate>
                <Template Context="roomRow">
                    @{
                        var reservation = GetReservationPlaceForDate(roomRow.Room, currentDate, roomRow.Index);
                        var isHighlighted = HighlightedCells.ContainsKey(currentDate) && HighlightedCells[date] == string.Join('_', roomRow.Room.Id, roomRow.Index);
                        var itemHeight = roomRow.Room.Capacity > 1 ? (reservation?.Reservation != null ? reservation.RowSpan * 25 : 25) : 50;
                        var itemStyle = $"width:100%;height:{itemHeight}px; cursor: pointer; align-items: center; display:flex; justify-content: center; text-wrap: auto";
                    }

                    @if (reservation != null && reservation.IsVisible)
                    {
                        <RadzenBadge BadgeStyle="@reservation.Reservation.GetBadgeStyle()" 
                                    Style="@itemStyle"
                                    @onclick="@(e => OnReservationClick(e, reservation.Reservation))"
                                    MouseEnter="@(args => OnReservationMouseEnter(args, reservation.Reservation))"
                                    MouseLeave="@(args => OnReservationMouseLeave())" 
                                    Text="@(reservation.ColumnSpan > 1 ? reservation.Reservation.GuestName : reservation.Reservation.GuestName.GetInitials())" />
                    }
                    else
                    {
                        <div @onmousedown="@(e => OnCellMouseDown(e, roomRow, currentDate))"
                            @onmousemove="@(e => OnCellMouseMove(e, roomRow, currentDate))"
                            @onmouseup="@(e => OnCellMouseUp(e, roomRow, currentDate))"
                            class="@(isHighlighted ? "highlighted-cell" : "")"
                            style="@itemStyle"></div>
                    }
                </Template>
            </RadzenDataGridColumn>
        }
    </Columns>
</RadzenDataGrid>

void CellRender(DataGridCellRenderEventArgs<RoomRow> args)
{
    if (string.IsNullOrWhiteSpace(args.Column.Property))
    {
        if (DateTimeOffset.TryParseExact(args.Column.UniqueID, GlobalBase.DateTimeFormat,
            CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTimeOffset currentDate))
        {
            var place = GetReservationPlaceForDate(args.Data.Room, currentDate, args.Data.Index);
            if (place == null)
                return;
            if (place.IsVisible)
            {
                if (place.ColumnSpan > 1)
                    args.Attributes.Add("colspan", place.ColumnSpan.ToString());
                if (place.RowSpan > 1)
                    args.Attributes.Add("rowspan", place.RowSpan.ToString());
            }
            else
            {
                args.Attributes.Add("hidden", "true");
            }
        }
    }
    else
    {
        if (args.Data.Index == 0)
        {
            args.Attributes.Add("rowspan", args.Data.Room.Capacity);
        }
        else
        {
            args.Attributes.Add("hidden", "true");
        }
    }
}

async Task LoadRoomRows(LoadDataArgs args)
{
    await Task.Yield();

    int skipIndex = args.Skip ?? 0;
    int takeCount = args.Top ?? FilteredRoomRows.Count();

    if (!FilteredRoomRows.Any())
    {
        VisibleRoomRows = new List<RoomRow>();
        return;
    }

    var initialChunk = FilteredRoomRows.Skip(skipIndex).Take(takeCount).ToList();

    if (initialChunk.Count == 0)
    {
        VisibleRoomRows = new List<RoomRow>();
        return;
    }

    var resultRows = new List<RoomRow>();
    var firstRoomId = initialChunk.First().Room.Id;
    var lastRoomId = initialChunk.Last().Room.Id;

    var firstRoomRows = FilteredRoomRows.Where(x => x.Room.Id == firstRoomId).ToList();
    resultRows.AddRange(firstRoomRows);

    var middleRows = initialChunk.Where(x => x.Room.Id != firstRoomId && x.Room.Id != lastRoomId).ToList();
    resultRows.AddRange(middleRows);

    if (lastRoomId != firstRoomId)
    {
        var lastRoomRows = FilteredRoomRows.Where(x => x.Room.Id == lastRoomId).ToList();
        resultRows.AddRange(lastRoomRows);
    }

    VisibleRoomRows = resultRows;
}

Environment

Radzen.Blazor Version: 6.2.4
.NET Version: 9
Browser: Edge 135.0.3179.98 (Official build) (64-bit)
OS: Windows 11

Additional Info

Screen Recording:

https://github.com/user-attachments/assets/95289620-e34e-4cfb-8e4b-94ed0d9251df

Sample project to test: https://github.com/Abbossbek/RadzenDataGridVirtualization

Console Errors: None observed

Priority
High - Breaks critical grid functionality in reservation management systems

Originally created by @Abbossbek on GitHub (May 1, 2025). ### Rowspan in CellRender Causes First Cell to Disappear When AllowVirtualization is Enabled **Description** When using `rowspan` attribute in `CellRender` method with `AllowVirtualization=true`, the first cell briefly renders and then disappears. This issue does NOT occur when: 1. `rowspan` is removed from `CellRender`, or 2. `AllowVirtualization` is set to `false`. **Steps to Reproduce** 1. Scroll through the virtualized grid 2. Observe cells with applied `rowspan` (particularly in multi-capacity rooms) 3. First cell flickers and gets hidden after initial render **Expected Behavior** Cells with `rowspan` should maintain consistent visibility and spanning behavior when virtualization is enabled. **Actual Behavior** Cells with `rowspan` fail to render correctly - first cell disappears after initial paint. **Code Reproduction** ```razor <RadzenDataGrid @ref="grid" Data="@VisibleRoomRows" LoadData="@LoadRoomRows" Count="@FilteredRoomRows.Count" TItem="RoomRow" AllowVirtualization="true" VirtualizationOverscanCount="10" Style="height: calc(100vh - 200px);" CellRender="@CellRender"> <Columns> <RadzenDataGridColumn Title="@Loc["RoomNumber"]" CssClass="rz-p-1" Property="Room.RoomNumber" SortOrder="SortOrder.Ascending" Frozen="true" MinWidth="220px" Width="220px"> <Template Context="data"> <RadzenStack Gap="0rem" JustifyContent="JustifyContent.SpaceBetween" Style="@($"height:{(data.Room.Capacity > 1 ? data.Room.Capacity * 28 : 56)}px")"> <RadzenStack Orientation="Orientation.Horizontal" JustifyContent="JustifyContent.SpaceBetween" class="rz-text-align-center" AlignItems="AlignItems.Center"> <RadzenText Text="@data.Room.RoomNumber" TextAlign="TextAlign.Center" Style="margin-bottom:0px" TextStyle="TextStyle.H5" /> <RadzenBadge Text="@Loc[data.Room.Status.ToString()]" Style="font-size:0.6rem" BadgeStyle="@data.Room.Status.GetBadgeStyleRoomStatus()" /> </RadzenStack> <RadzenStack Orientation="Orientation.Horizontal" JustifyContent="JustifyContent.End" AlignItems="AlignItems.Center" Wrap="FlexWrap.Wrap"> <RadzenBadge Text="@($"{Loc["Floor"]}: {data.Room.Floor}")" Style="font-size:0.6rem" class="rz-mr-2 rz-my-1" Variant="Variant.Outlined" /> <RadzenBadge Text="@($"{Loc["Capacity"]}: {data.Room.Capacity}")" Style="font-size:0.6rem" class="rz-mr-2 rz-my-1" Variant="Variant.Outlined" /> <RadzenBadge Text="@Loc[data.Room.RoomType?.Name]" Style="font-size:0.6rem" class="rz-my-1" Variant="Variant.Outlined" /> </RadzenStack> </RadzenStack> </Template> </RadzenDataGridColumn> @foreach (var date in VisibleDates) { var currentDate = date; bool isToday = currentDate.Date == DateTimeOffset.Now.Date; <RadzenDataGridColumn HeaderCssClass="@("rz-text-align-center "+(isToday?"rz-background-color-info-lighter":""))" CssClass="@(isToday? "rz-background-color-info-lighter":"")" UniqueID="@currentDate.ToString(GlobalBase.DateTimeFormat)" Sortable="false" MinWidth="50px"> <HeaderTemplate> <RadzenStack AlignItems="AlignItems.Center" Style="width:100%"> <RadzenText Text="@currentDate.ToString("MMM")" TextAlign="TextAlign.Center" TextStyle="TextStyle.Caption" /> <RadzenText Text="@currentDate.ToString("dd")" TextAlign="TextAlign.Center" TextStyle="TextStyle.H5" /> </RadzenStack> </HeaderTemplate> <Template Context="roomRow"> @{ var reservation = GetReservationPlaceForDate(roomRow.Room, currentDate, roomRow.Index); var isHighlighted = HighlightedCells.ContainsKey(currentDate) && HighlightedCells[date] == string.Join('_', roomRow.Room.Id, roomRow.Index); var itemHeight = roomRow.Room.Capacity > 1 ? (reservation?.Reservation != null ? reservation.RowSpan * 25 : 25) : 50; var itemStyle = $"width:100%;height:{itemHeight}px; cursor: pointer; align-items: center; display:flex; justify-content: center; text-wrap: auto"; } @if (reservation != null && reservation.IsVisible) { <RadzenBadge BadgeStyle="@reservation.Reservation.GetBadgeStyle()" Style="@itemStyle" @onclick="@(e => OnReservationClick(e, reservation.Reservation))" MouseEnter="@(args => OnReservationMouseEnter(args, reservation.Reservation))" MouseLeave="@(args => OnReservationMouseLeave())" Text="@(reservation.ColumnSpan > 1 ? reservation.Reservation.GuestName : reservation.Reservation.GuestName.GetInitials())" /> } else { <div @onmousedown="@(e => OnCellMouseDown(e, roomRow, currentDate))" @onmousemove="@(e => OnCellMouseMove(e, roomRow, currentDate))" @onmouseup="@(e => OnCellMouseUp(e, roomRow, currentDate))" class="@(isHighlighted ? "highlighted-cell" : "")" style="@itemStyle"></div> } </Template> </RadzenDataGridColumn> } </Columns> </RadzenDataGrid> void CellRender(DataGridCellRenderEventArgs<RoomRow> args) { if (string.IsNullOrWhiteSpace(args.Column.Property)) { if (DateTimeOffset.TryParseExact(args.Column.UniqueID, GlobalBase.DateTimeFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTimeOffset currentDate)) { var place = GetReservationPlaceForDate(args.Data.Room, currentDate, args.Data.Index); if (place == null) return; if (place.IsVisible) { if (place.ColumnSpan > 1) args.Attributes.Add("colspan", place.ColumnSpan.ToString()); if (place.RowSpan > 1) args.Attributes.Add("rowspan", place.RowSpan.ToString()); } else { args.Attributes.Add("hidden", "true"); } } } else { if (args.Data.Index == 0) { args.Attributes.Add("rowspan", args.Data.Room.Capacity); } else { args.Attributes.Add("hidden", "true"); } } } async Task LoadRoomRows(LoadDataArgs args) { await Task.Yield(); int skipIndex = args.Skip ?? 0; int takeCount = args.Top ?? FilteredRoomRows.Count(); if (!FilteredRoomRows.Any()) { VisibleRoomRows = new List<RoomRow>(); return; } var initialChunk = FilteredRoomRows.Skip(skipIndex).Take(takeCount).ToList(); if (initialChunk.Count == 0) { VisibleRoomRows = new List<RoomRow>(); return; } var resultRows = new List<RoomRow>(); var firstRoomId = initialChunk.First().Room.Id; var lastRoomId = initialChunk.Last().Room.Id; var firstRoomRows = FilteredRoomRows.Where(x => x.Room.Id == firstRoomId).ToList(); resultRows.AddRange(firstRoomRows); var middleRows = initialChunk.Where(x => x.Room.Id != firstRoomId && x.Room.Id != lastRoomId).ToList(); resultRows.AddRange(middleRows); if (lastRoomId != firstRoomId) { var lastRoomRows = FilteredRoomRows.Where(x => x.Room.Id == lastRoomId).ToList(); resultRows.AddRange(lastRoomRows); } VisibleRoomRows = resultRows; } ``` **Environment** Radzen.Blazor Version: 6.2.4 .NET Version: 9 Browser: Edge 135.0.3179.98 (Official build) (64-bit) OS: Windows 11 **Additional Info** Screen Recording: https://github.com/user-attachments/assets/95289620-e34e-4cfb-8e4b-94ed0d9251df Sample project to test: [https://github.com/Abbossbek/RadzenDataGridVirtualization](https://github.com/Abbossbek/RadzenDataGridVirtualization) Console Errors: None observed **Priority** High - Breaks critical grid functionality in reservation management systems
Author
Owner

@enchev commented on GitHub (May 7, 2025):

I'm afraid that such case cannot work with virtualization enabled since the standard Blazor Virtualize component used under the hood will try to reuse rows during scrolling. We cannot do anything to avoid this.

@enchev commented on GitHub (May 7, 2025): I'm afraid that such case cannot work with virtualization enabled since the standard Blazor Virtualize component used under the hood will try to reuse rows during scrolling. We cannot do anything to avoid this.
Author
Owner

@Abbossbek commented on GitHub (May 7, 2025):

Thank you for the explanation. I’ve implemented a temporary workaround by adding a hidden "dummy" row to preserve the layout structure during virtualization. While this partially resolves the flickering, it introduces minor alignment issues.

The issue specifically affects the first cell of the first row when combined with rowspan. Virtualization recycles this critical row, breaking the spanning logic entirely. Key observations:

  1. The first row’s rowspan defines the cell height for subsequent rows.
  2. When this row is recycled during scrolling, the rowspan context is lost.
  3. Subsequent rows render without the spanning anchor, causing layout collapse.

Could the Radzen team investigate why the initial row’s recycling disrupts the entire spanning structure? Even partial fixes for this edge case would stabilize the layout.

I'm afraid that such case cannot work with virtualization enabled since the standard Blazor Virtualize component used under the hood will try to reuse rows during scrolling. We cannot do anything to avoid this.

@Abbossbek commented on GitHub (May 7, 2025): Thank you for the explanation. I’ve implemented a temporary workaround by adding a hidden "dummy" row to preserve the layout structure during virtualization. While this partially resolves the flickering, it introduces minor alignment issues. The issue specifically affects **the first cell of the first row** when combined with `rowspan`. Virtualization recycles this critical row, breaking the spanning logic entirely. Key observations: 1. The first row’s `rowspan` defines the cell height for subsequent rows. 2. When this row is recycled during scrolling, the `rowspan` context is lost. 3. Subsequent rows render without the spanning anchor, causing layout collapse. Could the Radzen team investigate why the **initial row’s recycling** disrupts the entire spanning structure? Even partial fixes for this edge case would stabilize the layout. > I'm afraid that such case cannot work with virtualization enabled since the standard Blazor Virtualize component used under the hood will try to reuse rows during scrolling. We cannot do anything to avoid this.
Author
Owner

@enchev commented on GitHub (May 7, 2025):

I'm afraid that we are not aware of any way that will improve/fix this. We accept pull ideas/pull requests.

@enchev commented on GitHub (May 7, 2025): I'm afraid that we are not aware of any way that will improve/fix this. We accept pull ideas/pull requests.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/radzen-blazor#1747