Column Chooser for the MAUI data grid?

1 Answer 201 Views
DataGrid
Glenn
Top achievements
Rank 1
Glenn asked on 08 Aug 2023, 02:31 PM

Is there a column chooser available for the MAUI data grid component?  I'm migrating from another component kit and I have the need for the user to pick the columns they want to have in the grid.

 

Thanks.

Lance | Senior Manager Technical Support
Telerik team
commented on 08 Aug 2023, 03:29 PM

Just wanted to add a quick comment before you get started on anything. I have some extra time today and will write a demo for you and add it to my LanceMcCarthy/CustomMauiExamples repository on GitHub. Stay tuned...
Lance | Senior Manager Technical Support
Telerik team
commented on 08 Aug 2023, 06:21 PM

Hi Glenn, I've completed the demo. Instead of adding extra content to this post, I just updated my Answer to walk you through the concept and the code, then share the source code. I hope this will work for your needs, it should be mostly automatic.

1 Answer, 1 is accepted

Sort by
0
Lance | Senior Manager Technical Support
Telerik team
answered on 08 Aug 2023, 02:48 PM | edited on 08 Aug 2023, 06:20 PM

Hello Glenn,

Currently, this is not available out-of-the-box. We do have a Feature Request already opened, please take a moment to upvote and follow it => DataGrid: Column Chooser (telerik.com).

In the meantime, if you need this functionality, it's pretty easy to setup by just having a checkbox set the column's IsVisible property. The trick is to know how to create a list of checkboxes that represent each column.

Once you have that list, you only need to set IsVisible for that associated column:

var column = MyDataGrid.Columns.FirstOrDefault(col => col.HeaderText == dataItem.ColumnHeaderText);

 if (column != null)
        column.IsVisible = dataItem.IsChecked;

Demo

I have put together a demo app that shows you a simple and fast way to do this using the column's IsVisible property and associate it with a checkbox inside a custom chooser.

Before you get started, please note that this isn't an official control, it's strictly a conceptual demo for a custom control. It doesn't come with technical support and has not been run through our normal tests, you may need to change it to meet your needs and to make sure it works in your desired scenario.

Here's what the example looks like at runtime:

Custom Control Development

Here's the contents of the custom user control:

<!--  PRIMARY CONTENT OF CUSTOM COLUMN CHOOSER CONTROL --> 

   <telerik:RadItemsControl x:Name="PART_Chooser">
        <telerik:RadItemsControl.ItemTemplate>
            <DataTemplate x:DataType="local:ChooserDataItem">
                <HorizontalStackLayout Margin="10"
                             Spacing="10"
                             Padding="10">
                    <telerik:RadCheckBox IsChecked="{Binding IsChecked}"
                                         IsCheckedChanged="RadCheckBox_OnIsCheckedChanged"/>
                    <Label Text="{Binding ColumnHeaderText}"
                           TextColor="#99000000"
                           FontSize="12"/>
                </HorizontalStackLayout>
            </DataTemplate>
        </telerik:RadItemsControl.ItemTemplate>
    </telerik:RadItemsControl>

for the IsCheckChanged event handler, we find the matching column and hide/show it based on the checkbox's IsChecked value

private void RadCheckBox_OnIsCheckedChanged(object sender, IsCheckedChangedEventArgs e)
{
    if (AssociatedDataGrid == null)
        return;

    if (sender is RadCheckBox { BindingContext: ChooserDataItem dataItem })
    {
        var column = AssociatedDataGrid.Columns.FirstOrDefault(col => col.HeaderText == dataItem.ColumnHeaderText);

        // If the column exists, update the IsVisible value to match the IsChecked value
        if (column != null)
        {
            column.IsVisible = dataItem.IsChecked;
        }
    }
}

Finally, you need to attach the DataGrid to the custom control in some way. The easiest one is through a BindableProperty.

  • Once the RadDataGrid is initially bound, we iterate over the columns and populate our ColumnChooser
  • Then we subscribe to Columns.CollectionChanged, in case there are runtime changes and we need to update the chooser's items
public static readonly BindableProperty AssociatedDataGridProperty = BindableProperty.Create(
    nameof(AssociatedDataGrid),
    typeof(RadDataGrid),
    typeof(ColumnChooser),
    null,
    propertyChanged: OnAssociatedDataGridChanged);

public RadDataGrid AssociatedDataGrid
{
    get => (RadDataGrid)GetValue(AssociatedDataGridProperty);
    set => SetValue(AssociatedDataGridProperty, value);
}

private static void OnAssociatedDataGridChanged(BindableObject bindable, object oldValue, object newValue)
{
    if (bindable is ColumnChooser self)
    {
        if (newValue is RadDataGrid newDataGrid)
        {
            // STEP 1 - Setup initial chooser items based on the existing columns
            foreach (var column in newDataGrid.Columns)
            {
                self.chooserItems.Add(new ChooserDataItem(column.IsVisible, column.HeaderText));
            }


            // STEP 2 - Subscribe to collection changed to update the chooser items if the columns change
            newDataGrid.Columns.CollectionChanged += (s, e) =>
            {
                if (e.NewItems?.Count > 0)
                {
                    foreach (var item in e.NewItems)
                    {
                        if (item is DataGridColumn newColumn)
                        {
                            var matchingItem = self.chooserItems.FirstOrDefault(x => x.ColumnHeaderText == newColumn.HeaderText);

                            if (matchingItem == null)
                            {
                                self.chooserItems.Add(new ChooserDataItem(true, newColumn.HeaderText));
                            }
                        }
                    }
                }

                if (e.OldItems?.Count > 0)
                {
                    foreach (var item in e.OldItems)
                    {
                        if (item is DataGridColumn oldColumn)
                        {
                            var matchingItem = self.chooserItems.FirstOrDefault(x => x.ColumnHeaderText == oldColumn.HeaderText);

                            if (matchingItem != null)
                            {
                                self.chooserItems.Remove(matchingItem);
                            }
                        }
                    }
                }
            };
        }
    }
}

Usage

Now, you can use the custom control anywhere there is a DataGrid. In my demo, I have a DataGrid on MainPage:

<Grid ColumnDefinitions="*, Auto">
    <telerik:RadDataGrid x:Name="MyDataGrid"
                         AutoGenerateColumns="False">
        <telerik:RadDataGrid.Columns>
            <telerik:DataGridTextColumn PropertyName="Name" 
                                        HeaderText="Name"/>
            <telerik:DataGridNumericalColumn PropertyName="Age" 
                                             HeaderText="Age"/>
            <telerik:DataGridDateColumn PropertyName="DateOfBirth" 
                                        HeaderText="DOB"
                                        CellContentFormat="{}{0:MM/dd/yyyy}" />
        </telerik:RadDataGrid.Columns>
    </telerik:RadDataGrid>

    <local:ColumnChooser x:Name="Chooser" 
                         AssociatedDataGrid="{x:Reference MyDataGrid}"
                         Margin="20"
                         WidthRequest="200"
                         Grid.Column="1"/>
</Grid>

Code

You can get the full source code by going to my https://github.com/LanceMcCarthy/CustomMauiExamples repo on GitHub. Here are direct links to the relevant code for this example:

Regards,
Lance | Manager Technical Support
Progress Telerik

A brand new .NET MAUI course was just added to the Virtual Classroom. The training course is developed to help you get started with the Telerik UI for .NET MAUI components and features. It aims to put you in the shoes of an engineer who adds new features to an existing application. You can check it out at https://learn.telerik.com
Clint
Top achievements
Rank 1
Iron
Iron
commented on 14 Aug 2023, 12:40 PM

This looks great. I was working a similar approach but had to shift priorities. I will definitely use this when I get back to my MAUI project.
Clint
Top achievements
Rank 1
Iron
Iron
commented on 22 Oct 2023, 04:03 PM

I tried this and it works great, but I have a couple of questions. I'm using a grid layout, and I put the RadDataGrid and the column chooser into a side drawer. The entire page is using grid layout, I left off the top most part though to save space.

All of my grids are populated by a datatable. In order for the column chooser to be populated with all the columns, I'm using Chooser.AssociatedDataGrid = dataGridPlugins; in the button I press to show the sidedrawer.

What do you think would be the appropriate way to do this, given that most of my grids are populated with datatables that will get their data from users loading files on the form? I wasn't sure that putting it with the sidedrawer activation button was really the right way.

When I first go to this page, my select columns checkbox is only a couple pixels tall and not really visible. First usage of group headers also seem to have a height issue. Resizing a columns width fixes both these issues.

Is there something I'm doing in layout that is causing these height issues?

 

<telerik:RadSideDrawer x:Name="drawer"
                       DrawerLength="170"
                       Grid.Row="5"
                       DrawerLocation="Right">
    <telerik:RadSideDrawer.MainContent>
        <telerik:RadDataGrid
            x:Name="dataGridPlugins"
            Margin="0,10,0,0"
            ItemsSource="{Binding}"            
            ShowGroupHeaderAggregates="True"
            ShowGroupFooters="True"
            UserEditMode="Cell"
            CurrentCellChanged="DataGrid_OnCurrentCellChanged"
            SelectionChanged="DataGrid_SelectionChanged"               
            SelectionUnit="Row"
            SelectionMode="Multiple"
            ToolTipProperties.Text=""
            AutoGenerateColumns="True" >

            <telerik:RadDataGrid.Columns>
                <telerik:DataGridBooleanColumn PropertyName="Select"
                                               HeaderText="Select">
                    <telerik:DataGridColumn.CellContentTemplate>
                        <DataTemplate>
                            <CheckBox IsChecked="{Binding Select}"
                                      VerticalOptions="Center"
                                      PropertyChanged="OnCheckboxChanged"
                                      HorizontalOptions="Center"/>
                        </DataTemplate>
                    </telerik:DataGridColumn.CellContentTemplate>
                    <telerik:DataGridBooleanColumn.AggregateDescriptors>
                        <telerik:PropertyAggregateDescriptor PropertyName="PluginID"
                                                             Function="Count"
                                                             Caption="Total:"/>
                    </telerik:DataGridBooleanColumn.AggregateDescriptors>
                </telerik:DataGridBooleanColumn>
                <telerik:DataGridTemplateColumn>
                    <telerik:DataGridTemplateColumn.CellContentTemplate>
                        <DataTemplate>
                            <Button Text="Generate"
                                    CommandParameter="{Binding}"
                                    Clicked="GenerateButton_Clicked"/>
                        </DataTemplate>
                    </telerik:DataGridTemplateColumn.CellContentTemplate>
                </telerik:DataGridTemplateColumn>
            </telerik:RadDataGrid.Columns>

            <telerik:RadDataGrid.GroupHeaderStyle>
                <telerik:DataGridGroupHeaderStyle
                    BackgroundColor="LightBlue"
                    BorderThickness="1"
                    BorderColor="BlanchedAlmond"
                    TextColor="Black"
                    ButtonMargin="50,0,0,0" />
            </telerik:RadDataGrid.GroupHeaderStyle>

            <telerik:RadDataGrid.GroupHeaderTemplate>
                <DataTemplate x:DataType="gridContext:GroupHeaderContext">
                    <HorizontalStackLayout>
                        <CheckBox VerticalOptions="Center"
                                  HorizontalOptions="Center" />
                        <Label Text="{Binding Group.Key}" VerticalOptions="Center" />
                        <Label Text="{Binding AggregateValues}" VerticalOptions="Center" />
                    </HorizontalStackLayout>
                </DataTemplate>
            </telerik:RadDataGrid.GroupHeaderTemplate>

            <telerik:RadDataGrid.AlternateRowBackgroundStyle>
                <telerik:DataGridBorderStyle
                        BackgroundColor="LightBlue"
                        BorderThickness="1"
                        BorderColor="BlanchedAlmond" />
            </telerik:RadDataGrid.AlternateRowBackgroundStyle>
        </telerik:RadDataGrid>
    </telerik:RadSideDrawer.MainContent>

    <telerik:RadSideDrawer.DrawerContent>
        <StackLayout>
            <dataGrid:ColumnChooser x:Name="Chooser"
                                    Margin="20"/>
        </StackLayout>
    </telerik:RadSideDrawer.DrawerContent>
</telerik:RadSideDrawer>
Lance | Senior Manager Technical Support
Telerik team
commented on 23 Oct 2023, 12:40 PM

Hi Clint, the idea to put the chooser in a SideDrawer is a good one. The problem you're having is because you're using one of the worse possible controls available for dynamic UI... the StackLayout.

Never use VerticalStackLayout or HorizontalStackLayout unless:

  • You want the children to be squeezed to the bare minimum amount of space
  • The children will never change size
  • You understand the opposite dimension is infinite (i.e. the VerticalStackLayout has infinite width and minimum height)

If you want UI that can expand into the size it needs, use a Grid instead.

 <telerik:RadSideDrawer.DrawerContent>
        <Grid>
            <dataGrid:ColumnChooser x:Name="Chooser"
                                    Margin="20"/>
        </Grid>
    </telerik:RadSideDrawer.DrawerContent>

Clint
Top achievements
Rank 1
Iron
Iron
commented on 23 Oct 2023, 01:52 PM

Thanks Lance! This works with one caveat. 

I have to either remove HorizontalOptions="Center" OR add a WidthRequest when using HorizontalOptions.

When I first switched everything to grid, I was getting the same behavior until I made these changes.

Lance | Senior Manager Technical Support
Telerik team
commented on 23 Oct 2023, 02:36 PM

Hi Clint, that makes sense because of the ContentView's default settings.  Thanks for sharing!
Tags
DataGrid
Asked by
Glenn
Top achievements
Rank 1
Answers by
Lance | Senior Manager Technical Support
Telerik team
Share this question
or