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.
1 Answer, 1 is accepted
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:
- Project README: CustomMauiExamples/src/LantzControls
- Control
Regards,
Lance | Manager Technical Support
Progress Telerik
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>
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>
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.