From maui-skills
Guides .NET MAUI XAML data bindings including compiled bindings, value converters, binding modes, multi-binding, relative bindings, BindingContext, and MVVM with INotifyPropertyChanged.
npx claudepluginhub davidortinau/maui-skills --plugin maui-skillsThis skill uses the workspace's default tool permissions.
Set `Mode` explicitly **only** when overriding the default. Most properties already have the right default:
Guides .NET MAUI XAML/C# data bindings: compiled x:DataType, INotifyPropertyChanged/ObservableObject, converters, modes, relative bindings, fallbacks, and MVVM practices.
Guides .NET MAUI performance optimization including profiling with dotnet-trace, compiled bindings, layout efficiency, image optimization, trimming, NativeAOT, and startup improvements. For slow apps, large sizes, or poor responsiveness.
Converts long inline XAML bindings to Property Element Syntax for better readability. Use when expressions exceed 100 characters, include nested RelativeSource, MultiBinding, or validation rules.
Share bugs, ideas, or general feedback.
Set Mode explicitly only when overriding the default. Most properties already have the right default:
<!-- ✅ Defaults — omit Mode -->
<Label Text="{Binding Score}" /> <!-- OneWay is the default -->
<Entry Text="{Binding UserName}" /> <!-- TwoWay is the default -->
<Switch IsToggled="{Binding DarkMode}" /> <!-- TwoWay is the default -->
<!-- ✅ Override when needed -->
<Label Text="{Binding Title, Mode=OneTime}" />
<Entry Text="{Binding SearchQuery, Mode=OneWayToSource}" />
<!-- ❌ Redundant — just noise -->
<Label Text="{Binding Score, Mode=OneWay}" />
<Entry Text="{Binding UserName, Mode=TwoWay}" />
Compiled bindings are 8–20× faster than reflection-based bindings. Enable with x:DataType.
Place x:DataType only where BindingContext is set:
BindingContext.Do not scatter x:DataType on child elements. Adding x:DataType="x:Object" on children to "escape" compiled bindings is an anti-pattern — it disables compile-time checking and reintroduces reflection.
<!-- ✅ Correct: x:DataType only where BindingContext is set -->
<ContentPage x:DataType="vm:MainViewModel">
<StackLayout>
<Label Text="{Binding Title}" />
<Slider Value="{Binding Progress}" />
</StackLayout>
</ContentPage>
<!-- ❌ Wrong: x:DataType scattered on children -->
<ContentPage x:DataType="vm:MainViewModel">
<StackLayout>
<Label Text="{Binding Title}" />
<Slider x:DataType="x:Object" Value="{Binding Progress}" />
</StackLayout>
</ContentPage>
DataTemplate always needs its own x:DataType:
<CollectionView ItemsSource="{Binding People}">
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="model:Person">
<Label Text="{Binding FullName}" />
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
| Warning | Meaning |
|---|---|
| XC0022 | Binding path not found on the declared x:DataType |
| XC0023 | Property is not bindable |
| XC0024 | x:DataType type not found |
| XC0025 | Binding used without x:DataType (non-compiled fallback) |
<WarningsAsErrors>XC0022;XC0025</WarningsAsErrors>
Fully AOT-safe, no reflection:
label.SetBinding(Label.TextProperty,
static (PersonViewModel vm) => vm.FullName);
entry.SetBinding(Entry.TextProperty,
static (PersonViewModel vm) => vm.Age,
mode: BindingMode.TwoWay,
converter: new IntToStringConverter());
MAUI automatically marshals PropertyChanged to the UI thread — you can raise it from any thread. However, direct ObservableCollection mutations (Add/Remove) from background threads may still crash:
// ✅ Safe for PropertyChanged
await Task.Run(() => Items = LoadData());
// ⚠️ ObservableCollection.Add — dispatch to UI thread
MainThread.BeginInvokeOnMainThread(() => Items.Add(newItem));
x:DataType or code SetBinding with lambdas) are trimmer- and AOT-safe.OneTime mode for truly static data to skip change-tracking registration.