.NET MAUI TreeDataGrid Data Binding: Binding to Dynamic Data
The .NET MAUI TreeDataGrid supports binding to any type that implements the standard IDynamicMetaObjectProvider
DLR interface, such as DynamicObject
and ExpandoObject
.
Using Dynamic Data enables you to filter and sort the data inside the DataGrid both through the UI and programmatically.
Executing Dynamic Data on iOS and MacCatalyst
When you develop applications for Apple devices and plan to bind the DataGrid to dynamic data, you must consider the security restrictions set by Apple. These restrictions disallow the execution of dynamically generated code on a device. As a result, apps that use DynamicObject
or ExpandoObject
will crash in a release build for iOS and Mac Catalyst with a System.ExecutionEngineException
.
To prevent the exception, use the MtouchInterpreter
as recommended by Microsoft.
<PropertyGroup Condition="'$(Configuration)|$(RuntimeIdentifier)'=='Release|maccatalyst-arm64'">
<MtouchInterpreter>-all,+Telerik.Maui.Controls.dll</MtouchInterpreter>
</PropertyGroup>
For more information about these limitations and the suggested solution, see Microsoft's Mono interpreter on iOS and Mac Catalyst article.
Binding to DynamicObject
The following example shows how to implement a DynamicObject
class with Dynamic Language Runtime (DLR) and Common Language Runtime (CLR) fields and bind it to the TreeDataGrid. The standard CLR properties registered in the dynamic type must be exposed through the DLR API.
The model shows a class that derives from DynamicObject
and contains a CLR property called Id
. When the DataGrid auto-generates its columns, the TryGetMember
method of the DynamicObject
class will be used to fetch the values for each column. You must also implement a specific logic in the method to allow RadTreeDataGrid
to work with both CLR and DLR data.
1. Add the model for the TreeDataGrid:
public class MyDynamicObject : DynamicObject, INotifyPropertyChanged
{
readonly IDictionary<string, object> data;
public MyDynamicObject()
{
this.data = new Dictionary<string, object>();
}
public MyDynamicObject(IDictionary<string, object> source)
{
this.data = source;
}
public event PropertyChangedEventHandler PropertyChanged;
public override IEnumerable<string> GetDynamicMemberNames()
=> this.data.Keys;
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
result = this[binder.Name];
return true;
}
public override bool TrySetMember(SetMemberBinder binder, object value)
{
this[binder.Name] = value;
return true;
}
public object this[string columnName]
{
get
{
if (this.data.ContainsKey(columnName))
{
return this.data[columnName];
}
return null;
}
set
{
if (!this.data.ContainsKey(columnName))
{
this.data.Add(columnName, value);
this.OnPropertyChanged(columnName);
}
else
{
if (this.data[columnName] != value)
{
this.data[columnName] = value;
this.OnPropertyChanged(columnName);
}
}
}
}
private void OnPropertyChanged(string propertyName)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
2. Define the ViewModel
:
public class ViewModel
{
private int id;
public ViewModel()
{
this.Data = new ObservableCollection<MyDynamicObject>();
for (int i = 0; i < 100; i++)
{
MyDynamicObject dynamicObject = this.CreateDynamicObject(i, 0, 2);
this.Data.Add(dynamicObject);
}
}
public ObservableCollection<MyDynamicObject> Data { get; set; }
private MyDynamicObject CreateDynamicObject(int index, int level, int maxLevels)
{
MyDynamicObject dynamicObject = new MyDynamicObject();
int localId = this.id++;
dynamicObject["ID"] = localId;
for (int j = 0; j < 10; j++)
{
dynamicObject[string.Format("Column{0}", j)] = string.Format("Cell (id:{0}) {1} {2}", localId, index, j);
}
if (level < maxLevels)
{
ObservableCollection<MyDynamicObject> children = new ObservableCollection<MyDynamicObject>();
dynamicObject["Children"] = children;
for (int k = 0; k < 5; k++)
{
MyDynamicObject child = this.CreateDynamicObject(k, level + 1, 2);
children.Add(child);
}
}
return dynamicObject;
}
}
3. Define the RadTreeDataGrid
:
<telerik:RadTreeDataGrid x:Name="grid"
ItemsSource="{Binding Data}"
UserEditMode="Cell">
<telerik:RadTreeDataGrid.ItemDescriptor>
<telerik:TreeDataGridItemDescriptor ItemsSourceBinding="{Binding Children}" />
</telerik:RadTreeDataGrid.ItemDescriptor>
<telerik:RadTreeDataGrid.BindingContext>
<local:ViewModel />
</telerik:RadTreeDataGrid.BindingContext>
</telerik:RadTreeDataGrid>
4. Add the telerik
namespace:
xmlns:telerik="http://schemas.telerik.com/2022/xaml/maui"
This is the result:
Binding to ExpandoObject
1. Define the RadTreeDataGrid
control in XAML:
<telerik:RadTreeDataGrid x:Name="grid"
ItemsSource="{Binding Data}"
UserEditMode="Cell">
<telerik:RadTreeDataGrid.ItemDescriptor>
<telerik:TreeDataGridItemDescriptor ItemsSourceBinding="{Binding Children}" />
</telerik:RadTreeDataGrid.ItemDescriptor>
<telerik:RadTreeDataGrid.BindingContext>
<local:ViewModel />
</telerik:RadTreeDataGrid.BindingContext>
</telerik:RadTreeDataGrid>
2. Add the telerik
namespace:
xmlns:telerik="http://schemas.telerik.com/2022/xaml/maui"
3. Define the ViewModel
:
public class ViewModel
{
private int id;
public ViewModel()
{
this.Data = new ObservableCollection<ExpandoObject>();
for (int i = 0; i < 10; i++)
{
dynamic item = new ExpandoObject();
item.Country = string.Format("Country (id:{0}) {1} {2}", ++this.id, 0, i);
item.Capital = string.Format("Capital (id:{0}) {1} {2}", this.id, 0, i);
item.Population = i * 1000;
item.Children = new ObservableCollection<ExpandoObject>();
for (int k = 0; k < 3; k++)
{
dynamic child1 = new ExpandoObject();
child1.Country = string.Format("Country (id:{0}) {1} {2}", ++this.id, 1, k);
child1.Capital = string.Format("Capital (id:{0}) {1} {2}", this.id, 1, k);
child1.Population = k * 100;
child1.Children = new ObservableCollection<ExpandoObject>();
for (int m = 0; m < 3; m++)
{
dynamic child2 = new ExpandoObject();
child2.Country = string.Format("Country (id:{0}) {1} {2}", ++this.id, 2, m);
child2.Capital = string.Format("Capital (id:{0}) {1} {2}", this.id, 2, m);
child2.Population = i * 10 + k * 10 + m * 10;
child1.Children.Add(child2);
}
item.Children.Add(child1);
}
this.Data.Add(item);
}
}
public ObservableCollection<ExpandoObject> Data { get; set; }
}
This is the result:
Additional Resources
- .NET MAUI TreeDataGrid Product Page
- .NET MAUI TreeDataGrid Forum Page
- Telerik .NET MAUI Blogs
- Telerik .NET MAUI Roadmap