New to Telerik UI for .NET MAUI? Start a free 30-day trial

Implement Selection Column for DataGrid

Environment

Version Product Author
7.1.0 Telerik UI for .NET MAUI DataGrid Dobrinka Yordanova

Description

In a .NET MAUI application, you may want to select a DataGrid row through a column or all grid rows from the header of this column.

Solution

To implement such a column and to avoid the usage of the templates, use the Cell Renderer approach. This approach uses SkiaSharp to draw the elements inside the DataGrid cell. In such scenarios, use a DataGridBooleanColumn with a Skia CellRenderer. The cell renderer will draw a checkbox element in the cell.

Here is the implementation:

1. Draw a CheckBox using the CellRenderer approach.

**1.1** Create a custom `CheckboxColumnRenderer` class that inherits from `DataGridCellRenderer`. 

**1.2** Override the `RenderContainer` method and draw the custom control.
public class CheckboxColumnRenderer : DataGridCellRenderer
{
    protected override void RenderContainer(DataGridCellRendererRenderContext renderContext)
    {
        EmployeeDto club = (EmployeeDto)renderContext.Item;

        if (renderContext is DataGridSkiaSharpCellRendererRenderContext skRenderContext)
        {
            this.DrawCheckBox(club.OnLeave, skRenderContext, skRenderContext.Bounds);
        }
    }

    private void DrawCheckBox(bool isChecked, DataGridSkiaSharpCellRendererRenderContext skRenderContext, Rect bounds)
    {
        double horizontalPadding = 8;
        double x = bounds.X + horizontalPadding;
        double h = 20;
        double y = bounds.Y + (bounds.Height - h) / 2;
        double displayScale = skRenderContext.DisplayScale;

        // Reusing Height for the width so the checkbox is square
        Rect rect = new Rect(x, y, h, h);

        var skiaRect = SkiaUtils.ToSKRect(rect, displayScale);

        // checkbox border
        using var paint = new SKPaint();
        paint.Style = SKPaintStyle.Stroke;
        paint.Color = SKColors.Black;
        paint.StrokeWidth = 2;
        skRenderContext.Canvas.DrawRect(skiaRect, paint);

        if (isChecked)
        {
            // checkmark
            using var checkMarkPaint = new SKPaint();
            checkMarkPaint.Style = SKPaintStyle.Stroke;
            checkMarkPaint.Color = SKColors.Black;
            checkMarkPaint.StrokeWidth = 2;

            var path = new SKPath();
            path.MoveTo(skiaRect.Left + 4, skiaRect.MidY);
            path.LineTo(skiaRect.MidX, skiaRect.Bottom - 4);
            path.LineTo(skiaRect.Right - 4, skiaRect.Top + 4);
            skRenderContext.Canvas.DrawPath(path, checkMarkPaint);
        }
    }

    internal static class SkiaUtils
    {
        public static SKRect ToSKRect(Rect rect, double scale = 1)
        {
            return new SKRect((float)(scale * rect.Left), (float)(scale * rect.Top), (float)(scale * rect.Right), (float)(scale * rect.Bottom));
        }

        public static Rect ToRect(SKRect skRect, double scale = 1)
        {
            return new Rect(scale * skRect.Left, scale * skRect.Top, scale * skRect.Right, scale * skRect.Bottom);
        }
    }
}

2. In the XAML, set the custom CheckboxColumnCellRenderer class to the DataGridBooleanColumn.CellRenderer property:

<ContentPage.Resources>
    <local:CheckboxColumnRenderer x:Key="CheckboxColumnCellRenderer" />
</ContentPage.Resources>

<Grid>
    <telerik:RadDataGrid x:Name="EmployeesGrid"
                         RenderMode="SkiaSharp"
                         ItemsSource="{Binding Employees}"
                         AutoGenerateColumns="False">
        <telerik:RadDataGrid.Columns>
            <telerik:DataGridBooleanColumn PropertyName="OnLeave"
                                           CellRenderer="{StaticResource CheckboxColumnCellRenderer}">
                <telerik:DataGridBooleanColumn.HeaderStyle>
                    <telerik:DataGridColumnHeaderStyle TextMargin="0"
                                                        FilterIndicatorMargin="0"
                                                        BorderThickness="0" />
                </telerik:DataGridBooleanColumn.HeaderStyle>
                <telerik:DataGridBooleanColumn.HeaderContentTemplate>
                    <DataTemplate>
                        <Grid>
                            <telerik:RadCheckBox IsChecked="{Binding BindingContext.AreAllItemsChecked, Mode=TwoWay, Source={x:Reference EmployeesGrid}}"
                                                    CornerRadius="0"
                                                    StrokeWidth="2"
                                                    CheckedColor="Black"
                                                    Margin="5,0,0,0"
                                                    HeightRequest="30"
                                                    WidthRequest="30" />
                        </Grid>
                    </DataTemplate>
                </telerik:DataGridBooleanColumn.HeaderContentTemplate>
            </telerik:DataGridBooleanColumn>
            <telerik:DataGridTextColumn PropertyName="Name"
                                        HeaderText="Name" />
            <telerik:DataGridNumericalColumn PropertyName="Position"
                                                HeaderText="Position" />
        </telerik:RadDataGrid.Columns>
    </telerik:RadDataGrid>
</Grid>

3. Add a sample data model:

public class EmployeeDto : Employee
{
    private bool onLeave;

    public EmployeeDto(Employee source)
    {
        this.Name = source.Name;
        this.Position = source.Position;
        this.Salary = source.Salary;
        this.StartDate = source.StartDate;
        this.VacationTotal = source.VacationTotal;
        this.VacationUsed = source.VacationUsed;
    }

    public bool OnLeave
    {
        get => onLeave;
        set => SetProperty(ref onLeave, value);
    }
}

4. Add the ViewModel:

public class MainViewModel : ViewModelBase
{
    private ObservableRangeCollection<EmployeeDto> employees;

    public MainViewModel()
    {
        Employees = new ObservableRangeCollection<EmployeeDto>(SampleDataService.Current.GenerateEmployeeData()
            .Select(e => new EmployeeDto(e))
            .Take(40));
    }

    public ObservableRangeCollection<EmployeeDto> Employees
    {
        get => employees;
        set => SetProperty(ref employees, value);
    }

    public bool AreAllItemsChecked
    {
        get => Employees.All(e => e.OnLeave);
        set
        {
            foreach (var employeeDto in Employees) 
                employeeDto.OnLeave = value;
        }
    }
}

This is the result:

.NET MAUI DataGrid Selection Column

In this article