AI Smart Assistant in .NET MAUI TreeDataGrid
The .NET MAUI TreeDataGrid control offers AI-powered features that allow users to interact with the TreeDataGrid using natural language prompts. This functionality enables users to perform various operations such as sorting, grouping, filtering, and manipulating columns without needing to write code.
On mobile, the AI Smart Assistant panel can be accessed via a floating action button on the bottom-right corner of the TreeDataGrid. On desktop platforms, the AI Smart Assistant panel displays above the header area of the TreeDataGrid.
Here is how the AI Smart Assistant panel is arranged on desktop, with the panel above the TreeDataGrid header area, and on mobile, where it is opened from a floating action button in the bottom-right corner of the TreeDataGrid:

Here is how the AI Smart Assistant panel behaves on desktop and mobile when no suggestions are shown:

When no suggestions are available, the AI Smart Assistant panel shows only the prompt input area and any configured helper text, without a suggestions list.
Supported Operations
The following operations can be performed using AI Smart Assistant:
- Sorting—Users can sort the control by specifying the column and the sort direction (ascending or descending).
- Filtering—Users can filter the control based on specific criteria.
- Column Manipulation—Users can freeze, hide columns and more.
Getting Started with the AI Smart Assistant
To get started with the AI Smart Assistant functionality in the .NET MAUI TreeDataGrid, follow the steps:
Prerequisites
.NET9or later.- An AI provider. For example Azure OpenAI or OpenAI, etc.
Telerik.AI.SmartComponents.Extensionspackage.
1. Getting Started with the Telerik.AI.SmartComponents.Extensions
The AI Smart Assistant functionality relies on the Telerik.AI.SmartComponents.Extensions package. The package provides the necessary extensions to integrate AI capabilities into the control.
- Install the
Telerik.AI.SmartComponents.Extensionspackage in your .NET MAUI project.
dotnet add package Telerik.AI.SmartComponents.ExtensionsThe Telerik.AI.SmartComponents.Extensions package has a dependency on the Microsoft.Extensions.AI package.
-
Configure the AI services in your application. This typically involves setting up an AI provider (such as Azure OpenAI, OpenAI, etc.) and providing the necessary API keys or credentials.
-
Register the AI service and AI chat client in your application.
For the example, we will use the Azure OpenAI. To register the AI service and chat client, the following code is needed in MauiProgram.cs:
C#builder.Services.AddSingleton(sp => { return new AzureOpenAIClient(new Uri("AZURE_OPENAI_ENDPOINT"), new AzureKeyCredential("AZURE_OPENAI_API_KEY")); }); builder.Services.AddChatClient(services => services.GetRequiredService<AzureOpenAIClient>().GetChatClient("gpt-4.1").AsIChatClient() );
2. Process TreeDataGrid AI Requests
-
How to access the AI model in the ViewModel is a developer decision.
-
The
Telerik.AI.SmartComponents.Extensionslibrary provides two key methods:AddGridChatTools()—Configures the AI model with DataGrid-specific capabilities.ExtractGridResponse()—Extracts structured commands and messages from the AI response that the DataGrid can understand.
For the demo we will use a command in the
ViewModel. The command is bound to the DataGridUserCommand. The command executes when a prompt is submitted from the AI Smart Assistant panel.Here is a sample
ViewModeldefinition:C#public class AIPromptViewModel : NotifyPropertyChangedBase { private CancellationTokenSource cancellationTokenSource; private ICommand processAICommand; private ICommand cancelAICommand; private readonly IChatClient chatClient; public AIPromptViewModel(IChatClient chatClient) { this.chatClient = chatClient; } public ICommand ProcessAICommand { get => this.processAICommand ?? (this.processAICommand = new Command<DataGridPromptRequestCommandContext>(this.ExecuteProcessAI)); } public ICommand CancelAICommand { get => this.cancelAICommand ?? (this.cancelAICommand = new Command(this.ExecuteCancelAI)); } private async void ExecuteProcessAI(DataGridPromptRequestCommandContext context) { if (this.cancellationTokenSource != null) { // An AI request is already being processed return; } this.cancellationTokenSource = new CancellationTokenSource(); try { var request = JsonSerializer.Deserialize<GridAIRequest>(context.RequestJson); var options = new ChatOptions(); options.AddGridChatTools(request.Columns); List<ChatMessage> messages = request.Contents.Select(m => new ChatMessage(ChatRole.User, m.Text)).ToList(); ChatResponse completion = await this.chatClient.GetResponseAsync(messages, options); var response = JsonSerializer.Serialize(completion.ExtractGridResponse(), new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }); context.ResponseJson = response; } catch (OperationCanceledException) { // Cancellation was already handled by setting ProcessingState to Canceled // No need to set it again here } catch (Exception ex) { await this.ShowErrorAsync($"Failed to process request: {ex.Message}"); context.HasError = true; } finally { this.cancellationTokenSource?.Dispose(); this.cancellationTokenSource = null; } } private void ExecuteCancelAI() { this.cancellationTokenSource?.Cancel(); } protected async Task ShowErrorAsync(string message) { #if NET10_0_OR_GREATER await Microsoft.Maui.Controls.Application.Current?.Windows[0].Page?.DisplayAlertAsync("Error", message, "OK"); #else await Microsoft.Maui.Controls.Application.Current?.Windows[0].Page?.DisplayAlert("Error", message, "OK"); #endif } }-
Deserializes the incoming
JSON(context.RequestJson)into aGridAIRequest. This contains grid columns and user prompt contents. -
Creates
ChatOptionsand registers DataGrid-specific tools viaoptions.AddGridChatTools(request.Columns)so the AI knows what columns/operations are available. -
Maps each request content to a
ChatMessagewithChatRole.User. -
Calls the AI backend through
IChatClient.GetResponseAsync(messages, options)to get aChatResponse. -
Extracts the grid-specific instruction from the AI result with
completion.ExtractGridResponse(). -
Serializes the response to a
JSONand assign tocontext.ResponseJsonfor the DataGrid to consume.
-
Get the service registered in the
MauiProgram.csand pass it to the instance of theViewModel. Then set the binding context of the page to be thisViewModel.C#var chatClient = Application.Current.Handler.MauiContext.Services.GetRequiredService<IChatClient>(); this.viewModel = new AIPromptViewModel(chatClient); this.BindingContext = this.viewModel; -
Set the TreeDataGrid's
IsAIEnabledproperty totrue. When setting totrue, the AI Smart Assistant panel gets enabled, allowing users to input prompts.
Example Prompts
Here are some example prompts that users can use to interact with the TreeDataGrid:
- "Sort the data by the Name column in ascending order."
- "Filter the data to show only items with a price greater than 100."
- To freeze a column use the
lockkeyword. For example: "Lock the Price column."
Example with AI Smart Assistant
1. Add TreeDataGrid definition in XAML:
<telerik:RadTreeDataGrid x:Name="treeDataGrid"
ItemsSource="{Binding Items}"
IsAIEnabled="True"
AutoGenerateColumns="False">
<telerik:RadTreeDataGrid.ItemDescriptor>
<telerik:TreeDataGridItemDescriptor ItemsSourceBinding="{Binding Children}" />
</telerik:RadTreeDataGrid.ItemDescriptor>
<telerik:RadDataGrid.Commands>
<commands:DataGridUserCommand Id="PromptRequest"
Command="{Binding ProcessAICommand}" />
<commands:DataGridUserCommand Id="CancelPromptRequest"
Command="{Binding CancelAICommand}" />
</telerik:RadDataGrid.Commands>
<telerik:RadTreeDataGrid.AISettings>
<telerik:DataGridAISettings SuggestedPrompts="{Binding SuggestedPrompts}" />
</telerik:RadTreeDataGrid.AISettings>
<telerik:RadTreeDataGrid.Columns>
<telerik:DataGridTextColumn PropertyName="Name" />
<telerik:DataGridNumericalColumn PropertyName="Size" />
<telerik:DataGridTextColumn PropertyName="Type" />
</telerik:RadTreeDataGrid.Columns>
</telerik:RadTreeDataGrid>
2. Add the telerik namespace:
xmlns:telerik="http://schemas.telerik.com/2022/xaml/maui"
3. Add sample data model:
public class Data
{
public Data(string name, int size, string type)
{
this.Name = name;
this.Size = size;
this.Type = type;
this.Children = new ObservableCollection<Data>();
}
public string Name { get; set; }
public int Size { get; set; }
public string Type { get; set; }
public ObservableCollection<Data> Children { get; set; }
}
4. Define the ViewModel:
public class ViewModel
{
private static readonly HttpClient HttpClient = new HttpClient();
private CancellationTokenSource cancellationTokenSource;
private ICommand processAICommand;
private ICommand cancelAICommand;
private readonly ObservableCollection<string> suggestedPrompts;
public ViewModel()
{
this.suggestedPrompts = this.CreateDefaultSuggestedPrompts();
this.Items = this.CreateSampleData();
}
public ObservableCollection<Data> Items { get; private set; }
public ObservableCollection<string> SuggestedPrompts => this.suggestedPrompts;
public ICommand ProcessAICommand
{
get => this.processAICommand ?? (this.processAICommand = new Command<DataGridPromptRequestCommandContext>(this.ExecuteProcessAI));
}
public ICommand CancelAICommand
{
get => this.cancelAICommand ?? (this.cancelAICommand = new Command(this.ExecuteCancelAI));
}
private ObservableCollection<Data> CreateSampleData()
{
var root = new ObservableCollection<Data>();
var folderDocs = new Data("Documents", 0, "Folder");
folderDocs.Children.Add(new Data("Resume.docx", 24, "File"));
folderDocs.Children.Add(new Data("Budget.xlsx", 120, "File"));
// More documents
folderDocs.Children.Add(new Data("ProjectProposal.pdf", 340, "File"));
folderDocs.Children.Add(new Data("MeetingNotes.txt", 18, "File"));
var reportsFolder = new Data("Reports", 0, "Folder");
reportsFolder.Children.Add(new Data("Q1.pdf", 220, "File"));
reportsFolder.Children.Add(new Data("Q2.pdf", 240, "File"));
reportsFolder.Children.Add(new Data("Q3.pdf", 260, "File"));
reportsFolder.Children.Add(new Data("Q4.pdf", 280, "File"));
folderDocs.Children.Add(reportsFolder);
var folderMedia = new Data("Media", 0, "Folder");
var folderPhotos = new Data("Photos", 0, "Folder");
folderPhotos.Children.Add(new Data("Vacation1.jpg", 2048, "Image"));
folderPhotos.Children.Add(new Data("Vacation2.jpg", 1950, "Image"));
folderPhotos.Children.Add(new Data("Family1.png", 980, "Image"));
folderPhotos.Children.Add(new Data("Family2.png", 1120, "Image"));
folderPhotos.Children.Add(new Data("Sunset.tif", 3250, "Image"));
var eventsPhotos = new Data("Events", 0, "Folder");
eventsPhotos.Children.Add(new Data("Birthday.jpg", 1540, "Image"));
eventsPhotos.Children.Add(new Data("Conference.jpg", 1760, "Image"));
folderPhotos.Children.Add(eventsPhotos);
folderMedia.Children.Add(folderPhotos);
folderMedia.Children.Add(new Data("Video.mp4", 150000, "Video"));
folderMedia.Children.Add(new Data("Trailer.mov", 98000, "Video"));
var musicFolder = new Data("Music", 0, "Folder");
musicFolder.Children.Add(new Data("Track1.mp3", 5300, "Audio"));
musicFolder.Children.Add(new Data("Track2.mp3", 6100, "Audio"));
var albumFolder = new Data("Album - 2024", 0, "Folder");
albumFolder.Children.Add(new Data("SongA.flac", 14500, "Audio"));
albumFolder.Children.Add(new Data("SongB.flac", 15200, "Audio"));
musicFolder.Children.Add(albumFolder);
folderMedia.Children.Add(musicFolder);
var folderSrc = new Data("Source", 0, "Folder");
var subFolderApp = new Data("App", 0, "Folder");
subFolderApp.Children.Add(new Data("MainPage.xaml", 12, "Xaml"));
subFolderApp.Children.Add(new Data("MainPage.xaml.cs", 8, "CSharp"));
subFolderApp.Children.Add(new Data("AppShell.xaml", 10, "Xaml"));
subFolderApp.Children.Add(new Data("AppShell.xaml.cs", 7, "CSharp"));
var subFolderViewModels = new Data("ViewModels", 0, "Folder");
subFolderViewModels.Children.Add(new Data("MainViewModel.cs", 16, "CSharp"));
subFolderViewModels.Children.Add(new Data("DetailsViewModel.cs", 22, "CSharp"));
var subFolderServices = new Data("Services", 0, "Folder");
subFolderServices.Children.Add(new Data("DataService.cs", 30, "CSharp"));
subFolderServices.Children.Add(new Data("AuthService.cs", 26, "CSharp"));
folderSrc.Children.Add(subFolderApp);
folderSrc.Children.Add(subFolderViewModels);
folderSrc.Children.Add(subFolderServices);
folderSrc.Children.Add(new Data("Utils.cs", 4, "CSharp"));
var folderTests = new Data("Tests", 0, "Folder");
var unitFolder = new Data("UnitTests", 0, "Folder");
unitFolder.Children.Add(new Data("ViewModelTests.cs", 18, "CSharp"));
unitFolder.Children.Add(new Data("ServiceTests.cs", 20, "CSharp"));
var uiFolder = new Data("UITests", 0, "Folder");
uiFolder.Children.Add(new Data("LoginFlowTests.cs", 25, "CSharp"));
uiFolder.Children.Add(new Data("CheckoutFlowTests.cs", 28, "CSharp"));
folderTests.Children.Add(unitFolder);
folderTests.Children.Add(uiFolder);
folderSrc.Children.Add(folderTests);
var folderAssets = new Data("Assets", 0, "Folder");
var images = new Data("Images", 0, "Folder");
images.Children.Add(new Data("logo.png", 256, "Image"));
images.Children.Add(new Data("banner.jpg", 1024, "Image"));
images.Children.Add(new Data("icon.svg", 34, "Vector"));
var fonts = new Data("Fonts", 0, "Folder");
fonts.Children.Add(new Data("OpenSans-Regular.ttf", 1920, "Font"));
fonts.Children.Add(new Data("OpenSans-Bold.ttf", 2048, "Font"));
folderAssets.Children.Add(images);
folderAssets.Children.Add(fonts);
var folderConfigs = new Data("Configs", 0, "Folder");
folderConfigs.Children.Add(new Data("appsettings.json", 6, "Config"));
folderConfigs.Children.Add(new Data("launchSettings.json", 4, "Config"));
var envFolder = new Data("Environments", 0, "Folder");
envFolder.Children.Add(new Data("dev.json", 3, "Config"));
envFolder.Children.Add(new Data("staging.json", 3, "Config"));
envFolder.Children.Add(new Data("prod.json", 3, "Config"));
folderConfigs.Children.Add(envFolder);
var folderDownloads = new Data("Downloads", 0, "Folder");
folderDownloads.Children.Add(new Data("Installer.exe", 54000, "Binary"));
folderDownloads.Children.Add(new Data("Archive.zip", 24000, "Compressed"));
folderDownloads.Children.Add(new Data("Readme.md", 2, "Text"));
var folderSandbox = new Data("Sandbox", 0, "Folder");
var experiments = new Data("Experiments", 0, "Folder");
experiments.Children.Add(new Data("PhysicsSim.py", 12, "Script"));
experiments.Children.Add(new Data("MLModel.ipynb", 64, "Notebook"));
experiments.Children.Add(new Data("shader.glsl", 6, "Shader"));
var prototypes = new Data("Prototypes", 0, "Folder");
prototypes.Children.Add(new Data("UIPrototype.fig", 8500, "Design"));
prototypes.Children.Add(new Data("FlowDiagram.drawio", 1200, "Diagram"));
folderSandbox.Children.Add(experiments);
folderSandbox.Children.Add(prototypes);
root.Add(folderDocs);
root.Add(folderMedia);
root.Add(folderSrc);
root.Add(folderAssets);
root.Add(folderConfigs);
root.Add(folderDownloads);
root.Add(folderSandbox);
return root;
}
private ObservableCollection<string> CreateDefaultSuggestedPrompts()
{
return new ObservableCollection<string>
{
"Filter files by type equals to Image",
"Show files which size is greater than 2000",
"Sort by name ascending",
"Clear all filters and sorting"
};
}
private async void ExecuteProcessAI(DataGridPromptRequestCommandContext context)
{
if (this.cancellationTokenSource != null)
{
// An AI request is already being processed
return;
}
this.cancellationTokenSource = new CancellationTokenSource();
try
{
var request = JsonSerializer.Deserialize<object>(context.RequestJson);
var requestResult = await HttpClient.PostAsJsonAsync(
"https://demos.telerik.com/service/v2/ai/grid/smart-state",
request,
this.cancellationTokenSource.Token);
var response = await requestResult.Content.ReadAsStringAsync(this.cancellationTokenSource.Token);
context.ResponseJson = response;
}
catch (OperationCanceledException)
{
// Cancellation was already handled by setting ProcessingState to Canceled
}
catch (Exception ex)
{
await this.ShowErrorAsync($"Failed to process request: {ex.Message}");
context.HasError = true;
}
finally
{
this.cancellationTokenSource?.Dispose();
this.cancellationTokenSource = null;
}
}
private void ExecuteCancelAI()
{
this.cancellationTokenSource?.Cancel();
}
private async Task ShowErrorAsync(string message)
{
#if NET10_0_OR_GREATER
await Microsoft.Maui.Controls.Application.Current?.Windows[0].Page?.DisplayAlertAsync("Error", message, "OK");
#else
await Microsoft.Maui.Controls.Application.Current?.Windows[0].Page?.DisplayAlert("Error", message, "OK");
#endif
}
}
5. Define the user command for the ProcessAICommand and CancelAICommand:
public sealed class DataGridUserCommand : DataGridCommand
{
public static readonly BindableProperty CommandProperty =
BindableProperty.Create(nameof(Command), typeof(ICommand), typeof(DataGridUserCommand), null);
public ICommand Command
{
get => (ICommand)this.GetValue(CommandProperty);
set => this.SetValue(CommandProperty, value);
}
public override bool CanExecute(object parameter)
{
var command = this.Command;
if (this.Owner == null || command == null)
{
return false;
}
return command.CanExecute(parameter);
}
public override void Execute(object parameter)
{
var command = this.Command;
if (command != null)
{
command.Execute(parameter);
}
}
}
The TreeDataGrid AI Smart Assistant examples in the SDKBrowser Demo Application use a Telerik-hosted AI service for demonstration purposes only.
To use the AI Smart Assistant in your application, you must configure your own AI service.
How to do that is described in the Getting Started with the AI Smart Assistant article.
For a runnable example demonstrating the AI Smart Assistant, see the SDKBrowser Demo Application and go to the TreeDataGrid > AI Smart Assistant category.