Shift + Click Multiselection

I'm working on a custom component based on RadzenDataGrid that allows row selection by range (similar to how Windows Explorer works with Shift + click). Internally, the logic works fine — the "Value" variable gets updated correctly with the selected items.

@typeparam TItem

<RadzenDataGrid@ref="_dataGrid"
                      Data="@Data"
                      TItem="TItem"
                      AllowColumnResize="true"
                      AllowSorting="true"
                      SelectionMode="DataGridSelectionMode.Multiple"
                      PageSize="10"
                      Value="@Value"
                      ValueChanged="@ValueChanged"
                      CellClick="OnRowClick">
    <Columns>
        @ChildContent
    </Columns>
</RadzenDataGrid>

@code {
    [Parameter]
    public RenderFragment ChildContent { get; set; } = default!;

    [Parameter]
    public IEnumerable<TItem> Data { get; set; } = Enumerable.Empty<TItem>();

    [Parameter]
    public IList<TItem> Value { get; set; } = new List<TItem>();

    [Parameter]
    public EventCallback<IList<TItem>> ValueChanged { get; set; }

    private RadzenDataGrid<TItem> _dataGrid;
    private TItem? _startRow;

    private async Task OnRowClick(DataGridCellMouseEventArgs<TItem> args)
    {
        if (args.ShiftKey && _startRow != null)
        {
            await SelectRange(args);
            return;
        }

        _startRow = args.Data;

        if (Value.Contains(args.Data))
        {
            Value.Remove(args.Data);
        }
        else
        {
            Value.Add(args.Data);
        }

        if (ValueChanged.HasDelegate)
        {
            await ValueChanged.InvokeAsync(Value);
        }

        await InvokeAsync(() => _dataGrid.Reload());
    }

    private async Task SelectRange(DataGridCellMouseEventArgs<TItem> args)
    {
        if (_startRow == null || args.Data == null)
            return;

        var dataList = Data.ToList();
        var startIndex = dataList.IndexOf(_startRow);
        var endIndex = dataList.IndexOf(args.Data);

        if (startIndex == -1 || endIndex == -1)
            return;

        if (startIndex > endIndex)
            (startIndex, endIndex) = (endIndex, startIndex);

        Value.Clear();

        for (var i = startIndex; i <= endIndex; i++)
        {
            var rowData = dataList[i];
            if (!Value.Contains(rowData))
                Value.Add(rowData);
        }

        if (ValueChanged.HasDelegate)
        {
            await ValueChanged.InvokeAsync(Value);
        }

        await InvokeAsync(() => _dataGrid.Reload());
    }
}
@page "/demo-grid"

<h3 class="mb-4 text-xl font-semibold">Demo de SelectableDataGrid</h3>

<Test TItem="Person"
      Data="@people"
      @bind-Value="@selectedEmployees">
    <RadzenDataGridColumn TItem="Person" Property="Name" Title="Nombre" />
    <RadzenDataGridColumn TItem="Person" Property="Age" Title="Edad" />
    <RadzenDataGridColumn TItem="Person" Property="City" Title="Ciudad" />
</Test>

@if (selectedEmployees?.Any() == true)
{
    <h4 class="mt-4 text-lg font-semibold">Empleados seleccionados:</h4>
    <ul class="list-disc list-inside">
        @foreach (var emp in selectedEmployees)
        {
            <li>
                <strong>@emp.Name</strong><br />
                Edad: @emp.Age<br />
                Ciudad: @emp.City
            </li>
        }
    </ul>
}

@code {
    IList<Person> selectedEmployees = new List<Person>();

    private List<Person> people = new()
    {
        new Person { Name = "Ana", Age = 30, City = "Madrid" },
        new Person { Name = "Luis", Age = 45, City = "Barcelona" },
        new Person { Name = "Carla", Age = 27, City = "Sevilla" },
        new Person { Name = "Juan", Age = 35, City = "Valencia" }
    };

    public class Person
    {
        public string Name { get; set; } = string.Empty;
        public int Age { get; set; }
        public string City { get; set; } = string.Empty;
    }
}

It seems like the @bind-Value is not updating the selectedEmployees list as expected. Even though the Value inside the component has the correct selected data, the changes are not reflected outside.

Any ideas on what might be going wrong or how to ensure two-way binding works properly in this case?

In case of collection of items Blazor bindings can work only when updating the entire collection and will not do anything if you add/remove items.

1 Like

Thank you, I managed to fix the issue. The problem was that the selected items were being updated both by my custom logic and also by the default behavior of the DataGrid. As a result, the bindings were being triggered twice once by my code and once by the built-in functionality causing unexpected behavior.