akgulebubekir/Maui.DataGrid

DataGrid component for Maui

338

stars

373

commits

C#

primary language

Sep 7, 2026

updated

android
datagrid
datagrid-component
datagridview
dotnet
dotnet6
ios
library
maui
maui-apps
winui

README

Maui.DataGrid

DataGrid library for .NET MAUI applications.

NuGet version (akgul.Maui.Datagrid) CodeQL

Supported Platforms

The library itself targets net10.0 and contains no platform-specific code, so it runs anywhere .NET MAUI runs. The minimum OS versions below are the ones declared by the sample app in this repository, and are the versions the library is exercised against.

PlatformMinimum versionStatus
AndroidAPI 24 (Android 7.0)Built and tested by the sample app
iOS16.0Built and tested by the sample app
MacCatalyst15.0Built and tested by the sample app
Windows10.0.19041.0 (targeting 10.0.26100.0)Built and tested by the sample app
Tizen6.5Should work; the sample's Tizen target is commented out and not built in CI
Other MAUI platformsExpected to work, not verified

Windows is only added to the sample's target frameworks when the build host is Windows, so the sample can be restored and built on macOS and Linux without the Windows SDK.

To build the sample for Tizen, uncomment the Tizen target framework in Maui.DataGrid.Sample.csproj and install the Tizen tooling from Tizen.NET.

Requirements

To consume the NuGet package:

  • A .NET MAUI app on .NET 10 (net10.0-android, net10.0-ios, net10.0-maccatalyst, net10.0-windows..., etc.)
  • .NET MAUI 10.0.80 or newer (Microsoft.Maui.Controls)

The package references Microsoft.Maui.Controls with PrivateAssets="all", so it does not force a MAUI version on your app — your app's own MauiVersion is used. No MauiProgram registration or Use...() call is needed; only the XAML namespace (see Getting Started).

To build this repository:

  • .NET SDK 10.0.301 or newer — pinned in global.json with "rollForward": "latestFeature"
  • The .NET MAUI workload: dotnet workload restore
  • Platform SDKs for whichever targets you build (Android SDK, Xcode for iOS/MacCatalyst, Windows SDK for Windows)
  • Optional: Visual Studio 2022 (latest, with the ".NET Multi-platform App UI development" workload) or VS Code with the .NET MAUI extension

RestorePackagesWithLockFile is enabled and RestoreLockedMode is turned on for CI builds, so packages.lock.json must be committed whenever a package reference changes. The library also builds with IsTrimmable and IsAotCompatible, and static analysis is strict (AnalysisLevel=latest-all, EnforceCodeStyleInBuild, StyleCop) — warnings will fail your local build if you introduce them.

Installation

dotnet add package akgul.Maui.DataGrid

Or via the Package Manager console:

Install-Package akgul.Maui.DataGrid

Getting Started

Add the XAML namespace and declare a DataGrid with its columns:

 xmlns:dg="clr-namespace:Maui.DataGrid;assembly=Maui.DataGrid"

<dg:DataGrid ItemsSource="{Binding Teams}" SelectionMode="Single" SelectedItem="{Binding SelectedTeam}"
                RowHeight="70" HeaderHeight="50" BorderColor="{StaticResource GridBorderColor}"
                HeaderBackground="{StaticResource GridHeaderBgColor}" HeaderBordersVisible="{Binding HeaderBordersVisible}"
                PullToRefreshCommand="{Binding RefreshCommand}" IsRefreshing="{Binding IsRefreshing}" PaginationEnabled="{Binding PaginationEnabled}" PageSize="5"
                ActiveRowColor="{StaticResource ActiveRowColor}">
    <dg:DataGrid.Columns>
        <dg:DataGridColumn Title="Logo" PropertyName="Logo" SortingEnabled="False">
            <dg:DataGridColumn.CellTemplate>
                <DataTemplate x:DataType="x:String">
                    <Image Source="{Binding}" HorizontalOptions="Center" VerticalOptions="Center"
                           Aspect="AspectFit" HeightRequest="60" />
                </DataTemplate>
            </dg:DataGridColumn.CellTemplate>
        </dg:DataGridColumn>
        <dg:DataGridColumn Title="Team" PropertyName="Name" IsVisible="{Binding TeamColumnVisible}" Width="{Binding TeamColumnWidth}" />
        <dg:DataGridColumn Title="Won" PropertyName="Won" Width="0.5*" IsVisible="{Binding WonColumnVisible}" />
        <dg:DataGridColumn Title="Lost" PropertyName="Lost" Width="0.5*" />
        <dg:DataGridColumn PropertyName="Home">
            <dg:DataGridColumn.FormattedTitle>
                <FormattedString>
                    <Span Text="Home" TextColor="Black" FontSize="13" FontAttributes="Bold" />
                    <Span Text=" (won-lost)" TextColor="#333333" FontSize="11" />
                </FormattedString>
            </dg:DataGridColumn.FormattedTitle>
        </dg:DataGridColumn>
        <dg:DataGridColumn Title="Win %" PropertyName="Percentage" Width="0.75*" StringFormat="{}{0:0.00}" />
        <dg:DataGridColumn Title="Streak" PropertyName="Streak" Width="0.75*">
            <dg:DataGridColumn.CellTemplate>
                <DataTemplate x:DataType="m:Streak">
                    <ContentView HorizontalOptions="Fill" VerticalOptions="Fill"
                                 BackgroundColor="{Binding Converter={StaticResource StreakToColorConverter}}">
                        <Label Text="{Binding}" HorizontalOptions="Center" VerticalOptions="Center"
                               TextColor="Black" />
                    </ContentView>
                </DataTemplate>
            </dg:DataGridColumn.CellTemplate>
        </dg:DataGridColumn>
    </dg:DataGrid.Columns>
    <dg:DataGrid.RowsBackgroundColorPalette>
        <dg:PaletteCollection>
            <Color>#F2F2F2</Color>
            <Color>#FFFFFF</Color>
        </dg:PaletteCollection>
    </dg:DataGrid.RowsBackgroundColorPalette>
</dg:DataGrid>

A complete, runnable example lives in Maui.DataGrid.Sample — see MainPage.xaml.

Features

Columns and cells

PropertyName supports nested property paths, resolved by reflection against the runtime type of each intermediate value:

<dg:DataGridColumn Title="City" PropertyName="Address.City" />

Width accepts the same units as Grid: absolute (120), star (0.5*), or Auto. An Auto column is sized to the widest of its header cell and the cells of the rows currently on screen, and the header and every row are given that one width. Only the realized rows are measured, so scrolling to a longer value widens the column at that point rather than in advance; use an absolute width where that shift is unwelcome.

Use StringFormat for simple formatting, or CellTemplate for arbitrary content. Without a CellTemplate, a cell renders as a Label bound to PropertyName.

CellTemplate and EditCellTemplate also accept a DataTemplateSelector, which is resolved per row — SelectTemplate receives the row's item — so a cell's content can vary with its data.

Cells are created once per on-screen row and reused as rows are recycled while scrolling, so cell content should get everything it displays from its bindings rather than from work done when the template is instantiated. A DataTemplateSelector is re-consulted whenever a row is recycled, and its cell is rebuilt only if the selector picks a different template for the new item.

The default header style truncates a title too long for its column, so each header carries a tooltip of its own title. Override it with HeaderToolTip, or set HeaderToolTip="" for a header with no tooltip:

<dg:DataGridColumn Title="Won" HeaderToolTip="Games won at home" PropertyName="Won" />

Note that ToolTipProperties.Text cannot be used on a column instead: a DataGridColumn is not a view and never enters the visual tree, so an attached property set on one has nothing to attach to.

Sorting

Sorting is enabled by default (DataGrid.SortingEnabled), and each column can opt out with DataGridColumn.SortingEnabled="False". A column's underlying type must implement IComparable to be sortable; DataGridColumn.IsSortable() reports whether it does.

SortedColumnIndex is a SortData (index + SortingOrder) and is two-way bindable. An int implicitly converts to SortData, where a negative index means a descending sort:

<!-- Sort ascending on column 1 -->
<dg:DataGrid SortedColumnIndex="1" />

<!-- Sort descending on column 1 -->
<dg:DataGrid SortedColumnIndex="-1" />

Filtering

Set DataGrid.FilteringEnabled="True" to show a filter Entry in each header cell. Individual columns can opt out with DataGridColumn.FilteringEnabled="False", and DataGridColumn.FilterText is bindable so filters can be driven or read from a view model. Changing a filter resets PageNumber to 1.

Pagination

Set PaginationEnabled="True" to show the pagination footer. PageSize defaults to 100, must be greater than zero, and the page-size picker offers 5, 10, 50, 100, 200, 1000 unless you supply your own PageSizeList. PageCount is read-only (OneWayToSource). PageText and PerPageText exist so the footer labels can be localized, and PageSizeVisible="False" hides the page-size picker.

Selection

SelectionMode (None, Single, Multiple) replaces the obsolete SelectionEnabled. Use SelectedItem for Single and SelectedItems for Multiple — switching modes clears the one that no longer applies. Both are coerced against the grid's current items, so a selection that is not present in ItemsSource is dropped.

ItemSelected (event) fires on selection change. RowTappedCommand does too by default, receiving the SelectionChangedEventArgs — which means it does not fire when the already-selected row is tapped again, nor at all while SelectionMode="None". Set RowTappedCommandMode="Tap" to have every row tap execute the command with the tapped item as its parameter instead, regardless of SelectionMode:

<dg:DataGrid RowTappedCommand="{Binding RowTapped}" RowTappedCommandMode="Tap" />

In Tap mode the command is not executed from the selection-change path, so a tap executes it exactly once. RowTappedCommandMode defaults to SelectionChanged for backwards compatibility; the default is expected to change in the next major version.

Editing

Bind RowToEdit to the item that should render in edit mode. Cells in that row use DataGridColumn.EditCellTemplate (default: an Entry bound to PropertyName) instead of CellTemplate.

Pull to refresh

Bind PullToRefreshCommand (optionally with PullToRefreshCommandParameter) and IsRefreshing. RefreshingEnabled toggles the gesture, RefreshColor sets the spinner color, and the Refreshing event is raised when a refresh starts.

Row colors

RowsBackgroundColorPalette and RowsTextColorPalette take any IColorProvider. PaletteCollection is the built-in implementation and cycles its colors across rows. Implement IColorProvider yourself for data-driven colors:

internal sealed class OverdueColorProvider : IColorProvider
{
    public Color GetColor(int rowIndex, object item) =>
        item is Invoice { IsOverdue: true } ? Colors.MistyRose : Colors.White;
}

ActiveRowColor is the color of the selected row.

GetColor is re-evaluated for every visible row whenever the displayed items change — adding, removing, sorting, filtering, or changing page — so a row's color always matches its current index.

Empty state

NoDataView is shown when the grid has no rows (it maps to the underlying CollectionView's EmptyView).

Scrolling

dataGrid.ScrollTo(item, ScrollToPosition.MakeVisible, animated: true);

Threading

An ItemsSource collection may be added to, removed from, or cleared on any thread — the grid marshals the resulting sort, filter, and pagination work to the UI thread itself, so a background worker filling a collection needs no MainThread.BeginInvokeOnMainThread of its own. Note that this covers mutations of the collection only: the grid's properties, ItemsSource included, must be set on the UI thread like those of any other MAUI control.

Styling

HeaderLabelStyle, HeaderFilterStyle, SortIconStyle, and PaginationStepperStyle override the grid's defaults; the first three can also be set per column. Setting one of them back to null restores the built-in default. BorderColor, BorderThickness, HeaderBordersVisible, HeaderBackground, FooterBackground, FooterTextColor, FontFamily, and FontSize cover the rest of the chrome.

Borders are not drawn as outlines: each cell is inset by half of BorderThickness over a surface painted in BorderColor, and the surface showing through those insets is what looks like a grid line. So BorderColor is the grid line colour, and BorderThickness="0" removes the surface along with the lines — set it to zero (or HeaderBordersVisible="False" for the header alone) to see the grid's own background through the rows.

API Reference

DataGrid

All of the following are bindable properties.

PropertyTypeDefaultDescription
ItemsSourceIEnumerablenullRows to display. INotifyCollectionChanged sources are observed for changes.
ColumnsObservableCollection<DataGridColumn>emptyColumn definitions.
SelectionModeSelectionModeSingleNone, Single, or Multiple. Two-way.
SelectedItemobject?nullSelected row in Single mode. Two-way.
SelectedItemsIList<object>emptySelected rows in Multiple mode. Two-way.
RowTappedCommandICommandnullExecuted on row tap. Parameter and trigger depend on RowTappedCommandMode.
RowTappedCommandModeRowTappedCommandModeSelectionChangedSelectionChanged passes SelectionChangedEventArgs on selection change; Tap passes the tapped item on every tap.
RowToEditobjectnullRow rendered using EditCellTemplate.
SortingEnabledbooltrueEnables sorting for the grid.
SortedColumnIndexSortData?nullCurrent sort. Two-way. Negative int means descending.
SortIconPolygonnullCustom sort indicator shape.
SortIconStyleStylebuilt-inStyle for the sort indicator.
FilteringEnabledboolfalseShows per-column filter inputs.
PaginationEnabledboolfalseShows the pagination footer.
PageNumberint1Current page. Two-way.
PageCountint1Total pages. OneWayToSource.
PageSizeint100Rows per page; must be > 0. Two-way.
PageSizeListIList<int>5, 10, 50, 100, 200, 1000Choices in the page-size picker.
PageSizeVisiblebooltrueShows the page-size picker.
PageTextstring"Page:"Localizable page label.
PerPageTextstring"# per page:"Localizable per-page label.
PaginationStepperStyleStyle?built-inStyle for the pagination stepper.
RefreshingEnabledbooltrueEnables pull-to-refresh.
PullToRefreshCommandICommandnullExecuted on pull-to-refresh.
PullToRefreshCommandParameterobjectnullParameter for the refresh command.
IsRefreshingboolfalseRefresh indicator state. Two-way.
RefreshColorColorPurpleRefresh spinner color.
RowHeightint40Row height.
HeaderHeightint40Header height.
FooterHeightint50 on Android, 40 elsewhereFooter height.
HeaderBackgroundColorWhiteHeader background.
HeaderBordersVisiblebooltrueDraws borders in the header.
HeaderLabelStyleStylebuilt-inStyle for header labels (TargetType must be Label).
HeaderFilterStyleStylebuilt-inStyle for header filter inputs.
FooterBackgroundColorWhiteFooter background.
FooterTextColorColorBlackFooter text color.
BorderColorColorBlackGrid line color, and the color of the surface the cells sit on.
BorderThicknessThickness1Grid border thickness. Zero on every edge leaves no lines and a transparent surface. Two-way.
ActiveRowColorColorRGB(128, 144, 160)Selected row color.
RowsBackgroundColorPaletteIColorProviderPaletteCollection { White }Per-row background colors.
RowsTextColorPaletteIColorProviderPaletteCollection { Black }Per-row text colors.
FontFamilystringFont.Default.FamilyCell font family.
FontSizedouble13.0Cell font size.
ItemSizingStrategyItemSizingStrategyMeasureFirstItemSizing strategy of the underlying CollectionView.
NoDataViewViewnullShown when there are no rows.

Events

EventSignatureDescription
ItemSelectedEventHandler<SelectionChangedEventArgs>Raised when the selection changes.
RefreshingEventHandlerRaised when a pull-to-refresh starts.

Methods

MethodDescription
ScrollTo(object item, ScrollToPosition position, bool animated = true)Scrolls the given row into view.

DataGridColumn

PropertyTypeDefaultDescription
PropertyNamestringnullProperty path to bind, e.g. Name or Address.City.
Titlestring""Header text.
FormattedTitleFormattedStringnullRich header text; overrides Title.
HeaderToolTipstring?nullHeader tooltip. Unset, the title is used; set to "" for none.
WidthGridLengthStarColumn width (absolute, star, or auto).
IsVisiblebooltrueShows or hides the column.
StringFormatstring?nullFormat string for the default cell.
CellTemplateDataTemplate?nullDisplay template; defaults to a Label.
EditCellTemplateDataTemplate?nullEdit template; defaults to an Entry.
SortingEnabledbooltrueAllows sorting on this column.
FilteringEnabledbooltrueAllows filtering on this column.
FilterTextstringnullCurrent filter value. Two-way bindable.
LineBreakModeLineBreakModeWordWrapText wrapping for the default cell.
HorizontalContentAlignmentLayoutOptionsCenterHorizontal cell alignment.
VerticalContentAlignmentLayoutOptionsCenterVertical cell alignment.
PaddingThickness0Cell padding.
HeaderLabelStyleStyleinheritedHeader label style for this column.
HeaderFilterStyleStyleinheritedHeader filter style for this column.

Events

EventSignatureDescription
SizeChangedEventHandlerRaised when Width changes.
VisibilityChangedEventHandlerRaised when IsVisible changes.

Methods

MethodDescription
IsSortable()Returns whether the column's resolved data type implements IComparable.

Supporting types

TypeDescription
SortDataIndex + Order pair describing the current sort. Converts implicitly from int; SortData.FromInt32(int) treats a negative index as descending. Value-equality via Equals/GetHashCode.
SortingOrderNone, Ascendant, Descendant.
IColorProviderColor GetColor(int rowIndex, object item) — implement to color rows from data.
PaletteCollectionList<Color> implementing IColorProvider; cycles colors across rows. Falls back to White when empty.

Obsolete Members

ObsoleteUse instead
DataGrid.IsSortable / IsSortablePropertyDataGrid.SortingEnabled / SortingEnabledProperty
DataGrid.SelectionEnabled / SelectionEnabledPropertyDataGrid.SelectionMode / SelectionModeProperty

Dependencies

Current package version: 4.0.6.

DependencyVersionWhere
.NET SDK10.0.301 (rollForward: latestFeature)global.json
Microsoft.Maui.Controls10.0.80 ($(MauiVersion))Directory.Build.props
Library target frameworknet10.0Maui.DataGrid.csproj
DotNet.ReproducibleBuilds2.0.5 (build-only)Directory.Build.props
StyleCop.Analyzers1.2.0-beta.556 (build-only)Directory.Build.props
CommunityToolkit.Maui14.2.0sample only
xunit2.9.3tests only
xunit.runner.visualstudio3.0.0tests only
Microsoft.NET.Test.Sdk17.12.0tests only
coverlet.collector6.0.2tests only

Only Microsoft.Maui.Controls matters to consumers, and it is referenced with PrivateAssets="all" and ExcludeAssets="runtime", so the shipped package adds no runtime dependencies of its own beyond MAUI itself.

Building From Source

git clone https://github.com/akgulebubekir/Maui.DataGrid.git
cd Maui.DataGrid
dotnet workload restore
dotnet build Maui.DataGrid/Maui.DataGrid.csproj

Run the tests:

dotnet test Maui.DataGrid.Tests/Maui.DataGrid.Tests.csproj

Run the sample app (Windows builds unpackaged, so dotnet run works directly):

dotnet run --project Maui.DataGrid.Sample -f net10.0-windows10.0.26100.0

For other platforms pick the matching target framework, for example:

dotnet build Maui.DataGrid.Sample -t:Run -f net10.0-android
dotnet build Maui.DataGrid.Sample -t:Run -f net10.0-maccatalyst

The solution file is Maui.DataGrid.slnx.

Tip

If you are experiencing any issues on iOS, you can try adding the following to MauiProgram.cs

#if IOS || MACCATALYST
builder.ConfigureMauiHandlers(handlers =>
{
    handlers.AddHandler<Microsoft.Maui.Controls.CollectionView, Microsoft.Maui.Controls.Handlers.Items2.CollectionViewHandler2>();
});
#endif

Screenshots

Screenshot 2025-01-10 144417

Contributing

Issues and pull requests are welcome. Before opening a PR:

  • Build with the pinned SDK; the repo uses strict analysis (AnalysisLevel=latest-all, EnforceCodeStyleInBuild, StyleCop, WarningLevel=9999) and treats .editorconfig / stylecop.json as the style source of truth.
  • Run dotnet test Maui.DataGrid.Tests/Maui.DataGrid.Tests.csproj.
  • Commit updated packages.lock.json files if you change any package reference — CI restores in locked mode and will fail otherwise.
  • Public API changes are checked against the PackageValidationBaselineVersion in Maui.DataGrid.csproj; breaking changes need a baseline bump or a suppression entry.

License

Licensed under the MIT License.

Repository Activity

Alt

Star History

Star History Chart

Contributors

symbiogenesis

219 commits

akgulebubekir

93 commits

lendres

7 commits

akgulebubekir/Maui.DataGrid

DataGrid component for Maui

338

stars

373

commits

C#

primary language

Sep 7, 2026

updated

android
datagrid
datagrid-component
datagridview
dotnet
dotnet6
ios
library
maui
maui-apps
winui

README

Maui.DataGrid

DataGrid library for .NET MAUI applications.

NuGet version (akgul.Maui.Datagrid) CodeQL

Supported Platforms

The library itself targets net10.0 and contains no platform-specific code, so it runs anywhere .NET MAUI runs. The minimum OS versions below are the ones declared by the sample app in this repository, and are the versions the library is exercised against.

PlatformMinimum versionStatus
AndroidAPI 24 (Android 7.0)Built and tested by the sample app
iOS16.0Built and tested by the sample app
MacCatalyst15.0Built and tested by the sample app
Windows10.0.19041.0 (targeting 10.0.26100.0)Built and tested by the sample app
Tizen6.5Should work; the sample's Tizen target is commented out and not built in CI
Other MAUI platformsExpected to work, not verified

Windows is only added to the sample's target frameworks when the build host is Windows, so the sample can be restored and built on macOS and Linux without the Windows SDK.

To build the sample for Tizen, uncomment the Tizen target framework in Maui.DataGrid.Sample.csproj and install the Tizen tooling from Tizen.NET.

Requirements

To consume the NuGet package:

  • A .NET MAUI app on .NET 10 (net10.0-android, net10.0-ios, net10.0-maccatalyst, net10.0-windows..., etc.)
  • .NET MAUI 10.0.80 or newer (Microsoft.Maui.Controls)

The package references Microsoft.Maui.Controls with PrivateAssets="all", so it does not force a MAUI version on your app — your app's own MauiVersion is used. No MauiProgram registration or Use...() call is needed; only the XAML namespace (see Getting Started).

To build this repository:

  • .NET SDK 10.0.301 or newer — pinned in global.json with "rollForward": "latestFeature"
  • The .NET MAUI workload: dotnet workload restore
  • Platform SDKs for whichever targets you build (Android SDK, Xcode for iOS/MacCatalyst, Windows SDK for Windows)
  • Optional: Visual Studio 2022 (latest, with the ".NET Multi-platform App UI development" workload) or VS Code with the .NET MAUI extension

RestorePackagesWithLockFile is enabled and RestoreLockedMode is turned on for CI builds, so packages.lock.json must be committed whenever a package reference changes. The library also builds with IsTrimmable and IsAotCompatible, and static analysis is strict (AnalysisLevel=latest-all, EnforceCodeStyleInBuild, StyleCop) — warnings will fail your local build if you introduce them.

Installation

dotnet add package akgul.Maui.DataGrid

Or via the Package Manager console:

Install-Package akgul.Maui.DataGrid

Getting Started

Add the XAML namespace and declare a DataGrid with its columns:

 xmlns:dg="clr-namespace:Maui.DataGrid;assembly=Maui.DataGrid"

<dg:DataGrid ItemsSource="{Binding Teams}" SelectionMode="Single" SelectedItem="{Binding SelectedTeam}"
                RowHeight="70" HeaderHeight="50" BorderColor="{StaticResource GridBorderColor}"
                HeaderBackground="{StaticResource GridHeaderBgColor}" HeaderBordersVisible="{Binding HeaderBordersVisible}"
                PullToRefreshCommand="{Binding RefreshCommand}" IsRefreshing="{Binding IsRefreshing}" PaginationEnabled="{Binding PaginationEnabled}" PageSize="5"
                ActiveRowColor="{StaticResource ActiveRowColor}">
    <dg:DataGrid.Columns>
        <dg:DataGridColumn Title="Logo" PropertyName="Logo" SortingEnabled="False">
            <dg:DataGridColumn.CellTemplate>
                <DataTemplate x:DataType="x:String">
                    <Image Source="{Binding}" HorizontalOptions="Center" VerticalOptions="Center"
                           Aspect="AspectFit" HeightRequest="60" />
                </DataTemplate>
            </dg:DataGridColumn.CellTemplate>
        </dg:DataGridColumn>
        <dg:DataGridColumn Title="Team" PropertyName="Name" IsVisible="{Binding TeamColumnVisible}" Width="{Binding TeamColumnWidth}" />
        <dg:DataGridColumn Title="Won" PropertyName="Won" Width="0.5*" IsVisible="{Binding WonColumnVisible}" />
        <dg:DataGridColumn Title="Lost" PropertyName="Lost" Width="0.5*" />
        <dg:DataGridColumn PropertyName="Home">
            <dg:DataGridColumn.FormattedTitle>
                <FormattedString>
                    <Span Text="Home" TextColor="Black" FontSize="13" FontAttributes="Bold" />
                    <Span Text=" (won-lost)" TextColor="#333333" FontSize="11" />
                </FormattedString>
            </dg:DataGridColumn.FormattedTitle>
        </dg:DataGridColumn>
        <dg:DataGridColumn Title="Win %" PropertyName="Percentage" Width="0.75*" StringFormat="{}{0:0.00}" />
        <dg:DataGridColumn Title="Streak" PropertyName="Streak" Width="0.75*">
            <dg:DataGridColumn.CellTemplate>
                <DataTemplate x:DataType="m:Streak">
                    <ContentView HorizontalOptions="Fill" VerticalOptions="Fill"
                                 BackgroundColor="{Binding Converter={StaticResource StreakToColorConverter}}">
                        <Label Text="{Binding}" HorizontalOptions="Center" VerticalOptions="Center"
                               TextColor="Black" />
                    </ContentView>
                </DataTemplate>
            </dg:DataGridColumn.CellTemplate>
        </dg:DataGridColumn>
    </dg:DataGrid.Columns>
    <dg:DataGrid.RowsBackgroundColorPalette>
        <dg:PaletteCollection>
            <Color>#F2F2F2</Color>
            <Color>#FFFFFF</Color>
        </dg:PaletteCollection>
    </dg:DataGrid.RowsBackgroundColorPalette>
</dg:DataGrid>

A complete, runnable example lives in Maui.DataGrid.Sample — see MainPage.xaml.

Features

Columns and cells

PropertyName supports nested property paths, resolved by reflection against the runtime type of each intermediate value:

<dg:DataGridColumn Title="City" PropertyName="Address.City" />

Width accepts the same units as Grid: absolute (120), star (0.5*), or Auto. An Auto column is sized to the widest of its header cell and the cells of the rows currently on screen, and the header and every row are given that one width. Only the realized rows are measured, so scrolling to a longer value widens the column at that point rather than in advance; use an absolute width where that shift is unwelcome.

Use StringFormat for simple formatting, or CellTemplate for arbitrary content. Without a CellTemplate, a cell renders as a Label bound to PropertyName.

CellTemplate and EditCellTemplate also accept a DataTemplateSelector, which is resolved per row — SelectTemplate receives the row's item — so a cell's content can vary with its data.

Cells are created once per on-screen row and reused as rows are recycled while scrolling, so cell content should get everything it displays from its bindings rather than from work done when the template is instantiated. A DataTemplateSelector is re-consulted whenever a row is recycled, and its cell is rebuilt only if the selector picks a different template for the new item.

The default header style truncates a title too long for its column, so each header carries a tooltip of its own title. Override it with HeaderToolTip, or set HeaderToolTip="" for a header with no tooltip:

<dg:DataGridColumn Title="Won" HeaderToolTip="Games won at home" PropertyName="Won" />

Note that ToolTipProperties.Text cannot be used on a column instead: a DataGridColumn is not a view and never enters the visual tree, so an attached property set on one has nothing to attach to.

Sorting

Sorting is enabled by default (DataGrid.SortingEnabled), and each column can opt out with DataGridColumn.SortingEnabled="False". A column's underlying type must implement IComparable to be sortable; DataGridColumn.IsSortable() reports whether it does.

SortedColumnIndex is a SortData (index + SortingOrder) and is two-way bindable. An int implicitly converts to SortData, where a negative index means a descending sort:

<!-- Sort ascending on column 1 -->
<dg:DataGrid SortedColumnIndex="1" />

<!-- Sort descending on column 1 -->
<dg:DataGrid SortedColumnIndex="-1" />

Filtering

Set DataGrid.FilteringEnabled="True" to show a filter Entry in each header cell. Individual columns can opt out with DataGridColumn.FilteringEnabled="False", and DataGridColumn.FilterText is bindable so filters can be driven or read from a view model. Changing a filter resets PageNumber to 1.

Pagination

Set PaginationEnabled="True" to show the pagination footer. PageSize defaults to 100, must be greater than zero, and the page-size picker offers 5, 10, 50, 100, 200, 1000 unless you supply your own PageSizeList. PageCount is read-only (OneWayToSource). PageText and PerPageText exist so the footer labels can be localized, and PageSizeVisible="False" hides the page-size picker.

Selection

SelectionMode (None, Single, Multiple) replaces the obsolete SelectionEnabled. Use SelectedItem for Single and SelectedItems for Multiple — switching modes clears the one that no longer applies. Both are coerced against the grid's current items, so a selection that is not present in ItemsSource is dropped.

ItemSelected (event) fires on selection change. RowTappedCommand does too by default, receiving the SelectionChangedEventArgs — which means it does not fire when the already-selected row is tapped again, nor at all while SelectionMode="None". Set RowTappedCommandMode="Tap" to have every row tap execute the command with the tapped item as its parameter instead, regardless of SelectionMode:

<dg:DataGrid RowTappedCommand="{Binding RowTapped}" RowTappedCommandMode="Tap" />

In Tap mode the command is not executed from the selection-change path, so a tap executes it exactly once. RowTappedCommandMode defaults to SelectionChanged for backwards compatibility; the default is expected to change in the next major version.

Editing

Bind RowToEdit to the item that should render in edit mode. Cells in that row use DataGridColumn.EditCellTemplate (default: an Entry bound to PropertyName) instead of CellTemplate.

Pull to refresh

Bind PullToRefreshCommand (optionally with PullToRefreshCommandParameter) and IsRefreshing. RefreshingEnabled toggles the gesture, RefreshColor sets the spinner color, and the Refreshing event is raised when a refresh starts.

Row colors

RowsBackgroundColorPalette and RowsTextColorPalette take any IColorProvider. PaletteCollection is the built-in implementation and cycles its colors across rows. Implement IColorProvider yourself for data-driven colors:

internal sealed class OverdueColorProvider : IColorProvider
{
    public Color GetColor(int rowIndex, object item) =>
        item is Invoice { IsOverdue: true } ? Colors.MistyRose : Colors.White;
}

ActiveRowColor is the color of the selected row.

GetColor is re-evaluated for every visible row whenever the displayed items change — adding, removing, sorting, filtering, or changing page — so a row's color always matches its current index.

Empty state

NoDataView is shown when the grid has no rows (it maps to the underlying CollectionView's EmptyView).

Scrolling

dataGrid.ScrollTo(item, ScrollToPosition.MakeVisible, animated: true);

Threading

An ItemsSource collection may be added to, removed from, or cleared on any thread — the grid marshals the resulting sort, filter, and pagination work to the UI thread itself, so a background worker filling a collection needs no MainThread.BeginInvokeOnMainThread of its own. Note that this covers mutations of the collection only: the grid's properties, ItemsSource included, must be set on the UI thread like those of any other MAUI control.

Styling

HeaderLabelStyle, HeaderFilterStyle, SortIconStyle, and PaginationStepperStyle override the grid's defaults; the first three can also be set per column. Setting one of them back to null restores the built-in default. BorderColor, BorderThickness, HeaderBordersVisible, HeaderBackground, FooterBackground, FooterTextColor, FontFamily, and FontSize cover the rest of the chrome.

Borders are not drawn as outlines: each cell is inset by half of BorderThickness over a surface painted in BorderColor, and the surface showing through those insets is what looks like a grid line. So BorderColor is the grid line colour, and BorderThickness="0" removes the surface along with the lines — set it to zero (or HeaderBordersVisible="False" for the header alone) to see the grid's own background through the rows.

API Reference

DataGrid

All of the following are bindable properties.

PropertyTypeDefaultDescription
ItemsSourceIEnumerablenullRows to display. INotifyCollectionChanged sources are observed for changes.
ColumnsObservableCollection<DataGridColumn>emptyColumn definitions.
SelectionModeSelectionModeSingleNone, Single, or Multiple. Two-way.
SelectedItemobject?nullSelected row in Single mode. Two-way.
SelectedItemsIList<object>emptySelected rows in Multiple mode. Two-way.
RowTappedCommandICommandnullExecuted on row tap. Parameter and trigger depend on RowTappedCommandMode.
RowTappedCommandModeRowTappedCommandModeSelectionChangedSelectionChanged passes SelectionChangedEventArgs on selection change; Tap passes the tapped item on every tap.
RowToEditobjectnullRow rendered using EditCellTemplate.
SortingEnabledbooltrueEnables sorting for the grid.
SortedColumnIndexSortData?nullCurrent sort. Two-way. Negative int means descending.
SortIconPolygonnullCustom sort indicator shape.
SortIconStyleStylebuilt-inStyle for the sort indicator.
FilteringEnabledboolfalseShows per-column filter inputs.
PaginationEnabledboolfalseShows the pagination footer.
PageNumberint1Current page. Two-way.
PageCountint1Total pages. OneWayToSource.
PageSizeint100Rows per page; must be > 0. Two-way.
PageSizeListIList<int>5, 10, 50, 100, 200, 1000Choices in the page-size picker.
PageSizeVisiblebooltrueShows the page-size picker.
PageTextstring"Page:"Localizable page label.
PerPageTextstring"# per page:"Localizable per-page label.
PaginationStepperStyleStyle?built-inStyle for the pagination stepper.
RefreshingEnabledbooltrueEnables pull-to-refresh.
PullToRefreshCommandICommandnullExecuted on pull-to-refresh.
PullToRefreshCommandParameterobjectnullParameter for the refresh command.
IsRefreshingboolfalseRefresh indicator state. Two-way.
RefreshColorColorPurpleRefresh spinner color.
RowHeightint40Row height.
HeaderHeightint40Header height.
FooterHeightint50 on Android, 40 elsewhereFooter height.
HeaderBackgroundColorWhiteHeader background.
HeaderBordersVisiblebooltrueDraws borders in the header.
HeaderLabelStyleStylebuilt-inStyle for header labels (TargetType must be Label).
HeaderFilterStyleStylebuilt-inStyle for header filter inputs.
FooterBackgroundColorWhiteFooter background.
FooterTextColorColorBlackFooter text color.
BorderColorColorBlackGrid line color, and the color of the surface the cells sit on.
BorderThicknessThickness1Grid border thickness. Zero on every edge leaves no lines and a transparent surface. Two-way.
ActiveRowColorColorRGB(128, 144, 160)Selected row color.
RowsBackgroundColorPaletteIColorProviderPaletteCollection { White }Per-row background colors.
RowsTextColorPaletteIColorProviderPaletteCollection { Black }Per-row text colors.
FontFamilystringFont.Default.FamilyCell font family.
FontSizedouble13.0Cell font size.
ItemSizingStrategyItemSizingStrategyMeasureFirstItemSizing strategy of the underlying CollectionView.
NoDataViewViewnullShown when there are no rows.

Events

EventSignatureDescription
ItemSelectedEventHandler<SelectionChangedEventArgs>Raised when the selection changes.
RefreshingEventHandlerRaised when a pull-to-refresh starts.

Methods

MethodDescription
ScrollTo(object item, ScrollToPosition position, bool animated = true)Scrolls the given row into view.

DataGridColumn

PropertyTypeDefaultDescription
PropertyNamestringnullProperty path to bind, e.g. Name or Address.City.
Titlestring""Header text.
FormattedTitleFormattedStringnullRich header text; overrides Title.
HeaderToolTipstring?nullHeader tooltip. Unset, the title is used; set to "" for none.
WidthGridLengthStarColumn width (absolute, star, or auto).
IsVisiblebooltrueShows or hides the column.
StringFormatstring?nullFormat string for the default cell.
CellTemplateDataTemplate?nullDisplay template; defaults to a Label.
EditCellTemplateDataTemplate?nullEdit template; defaults to an Entry.
SortingEnabledbooltrueAllows sorting on this column.
FilteringEnabledbooltrueAllows filtering on this column.
FilterTextstringnullCurrent filter value. Two-way bindable.
LineBreakModeLineBreakModeWordWrapText wrapping for the default cell.
HorizontalContentAlignmentLayoutOptionsCenterHorizontal cell alignment.
VerticalContentAlignmentLayoutOptionsCenterVertical cell alignment.
PaddingThickness0Cell padding.
HeaderLabelStyleStyleinheritedHeader label style for this column.
HeaderFilterStyleStyleinheritedHeader filter style for this column.

Events

EventSignatureDescription
SizeChangedEventHandlerRaised when Width changes.
VisibilityChangedEventHandlerRaised when IsVisible changes.

Methods

MethodDescription
IsSortable()Returns whether the column's resolved data type implements IComparable.

Supporting types

TypeDescription
SortDataIndex + Order pair describing the current sort. Converts implicitly from int; SortData.FromInt32(int) treats a negative index as descending. Value-equality via Equals/GetHashCode.
SortingOrderNone, Ascendant, Descendant.
IColorProviderColor GetColor(int rowIndex, object item) — implement to color rows from data.
PaletteCollectionList<Color> implementing IColorProvider; cycles colors across rows. Falls back to White when empty.

Obsolete Members

ObsoleteUse instead
DataGrid.IsSortable / IsSortablePropertyDataGrid.SortingEnabled / SortingEnabledProperty
DataGrid.SelectionEnabled / SelectionEnabledPropertyDataGrid.SelectionMode / SelectionModeProperty

Dependencies

Current package version: 4.0.6.

DependencyVersionWhere
.NET SDK10.0.301 (rollForward: latestFeature)global.json
Microsoft.Maui.Controls10.0.80 ($(MauiVersion))Directory.Build.props
Library target frameworknet10.0Maui.DataGrid.csproj
DotNet.ReproducibleBuilds2.0.5 (build-only)Directory.Build.props
StyleCop.Analyzers1.2.0-beta.556 (build-only)Directory.Build.props
CommunityToolkit.Maui14.2.0sample only
xunit2.9.3tests only
xunit.runner.visualstudio3.0.0tests only
Microsoft.NET.Test.Sdk17.12.0tests only
coverlet.collector6.0.2tests only

Only Microsoft.Maui.Controls matters to consumers, and it is referenced with PrivateAssets="all" and ExcludeAssets="runtime", so the shipped package adds no runtime dependencies of its own beyond MAUI itself.

Building From Source

git clone https://github.com/akgulebubekir/Maui.DataGrid.git
cd Maui.DataGrid
dotnet workload restore
dotnet build Maui.DataGrid/Maui.DataGrid.csproj

Run the tests:

dotnet test Maui.DataGrid.Tests/Maui.DataGrid.Tests.csproj

Run the sample app (Windows builds unpackaged, so dotnet run works directly):

dotnet run --project Maui.DataGrid.Sample -f net10.0-windows10.0.26100.0

For other platforms pick the matching target framework, for example:

dotnet build Maui.DataGrid.Sample -t:Run -f net10.0-android
dotnet build Maui.DataGrid.Sample -t:Run -f net10.0-maccatalyst

The solution file is Maui.DataGrid.slnx.

Tip

If you are experiencing any issues on iOS, you can try adding the following to MauiProgram.cs

#if IOS || MACCATALYST
builder.ConfigureMauiHandlers(handlers =>
{
    handlers.AddHandler<Microsoft.Maui.Controls.CollectionView, Microsoft.Maui.Controls.Handlers.Items2.CollectionViewHandler2>();
});
#endif

Screenshots

Screenshot 2025-01-10 144417

Contributing

Issues and pull requests are welcome. Before opening a PR:

  • Build with the pinned SDK; the repo uses strict analysis (AnalysisLevel=latest-all, EnforceCodeStyleInBuild, StyleCop, WarningLevel=9999) and treats .editorconfig / stylecop.json as the style source of truth.
  • Run dotnet test Maui.DataGrid.Tests/Maui.DataGrid.Tests.csproj.
  • Commit updated packages.lock.json files if you change any package reference — CI restores in locked mode and will fail otherwise.
  • Public API changes are checked against the PackageValidationBaselineVersion in Maui.DataGrid.csproj; breaking changes need a baseline bump or a suppression entry.

License

Licensed under the MIT License.

Repository Activity

Alt

Star History

Star History Chart

Contributors

symbiogenesis

219 commits

akgulebubekir

93 commits

lendres

7 commits

Languages

C#

100.0%