{
  "schema": "https://ai-atoms.com/schemas/skill-v1.json",
  "type": "skill",
  "id": "skill/maui-data-binding",
  "version": "1.0.1",
  "name": "maui-data-binding",
  "description": "Guidance for .NET MAUI XAML and C# data bindings — compiled bindings, INotifyPropertyChanged / ObservableObject, value converters, binding modes, multi-binding, relative bindings, fallbacks, and MVVM best practices. USE FOR: setting up compiled bindings with x:DataType, implementing INotifyPropertyChanged or CommunityToolkit ObservableObject, creating IValueConverter / IMultiValueConverter, choosing binding modes, configuring BindingContext, relative bindings, binding fallbacks, StringFormat, code-behind SetBinding with lambdas, and enforcing XC0022/XC0025 warnings. DO NOT USE FOR: CollectionView item templates and layouts (use maui-collectionview), Shell navigation data passing (use maui-shell-navigation), dependency injection (use maui-dependency-injection), or animations triggered by property changes (use .NET MAUI animation APIs).",
  "system_prompt_fragment": "# .NET MAUI Data Binding\n\nWire UI controls to ViewModel properties with compile-time safety, correct\nchange notification, and minimal overhead. Prefer compiled bindings everywhere\nand treat binding warnings as build errors.\n\n## When to Use\n\n- Adding `x:DataType` compiled bindings to a new or existing page\n- Implementing `INotifyPropertyChanged` or CommunityToolkit `ObservableObject`\n- Creating or consuming `IValueConverter` / `IMultiValueConverter`\n- Choosing the correct `BindingMode` for a control property\n- Setting `BindingContext` in XAML or code-behind\n- Using relative bindings (`Self`, `AncestorType`, `TemplatedParent`)\n- Applying `StringFormat`, `FallbackValue`, or `TargetNullValue`\n- Writing AOT-safe code bindings with `SetBinding` and lambdas (.NET 9+)\n\n## When Not to Use\n\n- **CollectionView layouts / templates** — use the `maui-collectionview` skill\n- **Shell navigation parameters** — use the `maui-shell-navigation` skill\n- **Service registration / DI** — use the `maui-dependency-injection` skill\n- **Property-change-triggered animations** — use built-in [.NET MAUI animation APIs](https://learn.microsoft.com/dotnet/maui/user-interface/animation/basic)\n\n## Inputs\n\n- A .NET MAUI project targeting .NET 8 or later\n- XAML pages or C# code-behind where bindings are declared\n- A ViewModel class (or plan to create one)\n\n---\n\n## Compiled Bindings — x:DataType Placement\n\nCompiled bindings are **8–20× faster** than reflection-based bindings and are\nrequired for NativeAOT / trimming. Enable them with `x:DataType`.\n\n### Placement rules\n\nSet `x:DataType` **only where `BindingContext` is set**:\n\n1. **Page / View root** — where you assign `BindingContext`.\n2. **DataTemplate** — which creates a new binding scope.\n\nDo **not** scatter `x:DataType` on arbitrary child elements. Adding\n`x:DataType=\"x:Object\"` on children to escape compiled bindings is an\nanti-pattern — it disables compile-time checking and reintroduces reflection.\n\n```xml\n<!-- ✅ Correct: x:DataType at the page root -->\n<ContentPage xmlns:vm=\"clr-namespace:MyApp.ViewModels\"\n             x:DataType=\"vm:MainViewModel\">\n    <StackLayout>\n        <Label Text=\"{Binding Title}\" />\n        <Slider Value=\"{Binding Progress}\" />\n    </StackLayout>\n</ContentPage>\n\n<!-- ❌ Wrong: x:DataType scattered on children -->\n<ContentPage x:DataType=\"vm:MainViewModel\">\n    <StackLayout>\n        <Label Text=\"{Binding Title}\" />\n        <Slider x:DataType=\"x:Object\" Value=\"{Binding Progress}\" />\n    </StackLayout>\n</ContentPage>\n```\n\n### DataTemplate always needs its own x:DataType\n\n```xml\n<CollectionView ItemsSource=\"{Binding People}\">\n    <CollectionView.ItemTemplate>\n        <DataTemplate x:DataType=\"model:Person\">\n            <Label Text=\"{Binding FullName}\" />\n        </DataTemplate>\n    </CollectionView.ItemTemplate>\n</CollectionView>\n```\n\n### Enforce binding warnings as errors\n\n| Warning | Meaning |\n|---------|---------|\n| **XC0022** | Binding path not found on the declared `x:DataType` |\n| **XC0023** | Property is not bindable |\n| **XC0024** | `x:DataType` type not found |\n| **XC0025** | Binding used without `x:DataType` (non-compiled fallback) |\n\nAdd to the `.csproj`:\n\n```xml\n<WarningsAsErrors>XC0022;XC0025</WarningsAsErrors>\n```\n\n---\n\n## Binding Modes\n\nSet `Mode` explicitly **only** when overriding the default. Most properties\nalready have the correct default:\n\n| Mode | Direction | Use case |\n|------|-----------|----------|\n| `OneWay` | Source → Target | Display-only (default for most properties) |\n| `TwoWay` | Source ↔ Target | Editable controls (`Entry.Text`, `Switch.IsToggled`) |\n| `OneWayToSource` | Target → Source | Read user input without pushing back to UI |\n| `OneTime` | Source → Target (once) | Static values; no change-tracking overhead |\n\n```xml\n<!-- ✅ Defaults — omit Mode -->\n<Label Text=\"{Binding Score}\" />\n<Entry Text=\"{Binding UserName}\" />\n<Switch IsToggled=\"{Binding DarkMode}\" />\n\n<!-- ✅ Override only when needed -->\n<Label Text=\"{Binding Title, Mode=OneTime}\" />\n<Entry Text=\"{Binding SearchQuery, Mode=OneWayToSource}\" />\n\n<!-- ❌ Redundant — adds noise -->\n<Label Text=\"{Binding Score, Mode=OneWay}\" />\n<Entry Text=\"{Binding UserName, Mode=TwoWay}\" />\n```\n\n---\n\n## BindingContext and Property Paths\n\nEvery `BindableObject` inherits `BindingContext` from its parent unless\nexplicitly set. Property paths support dot notation and indexers:\n\n```xml\n<Label Text=\"{Binding Address.City}\" />\n<Label Text=\"{Binding Items[0].Name}\" />\n```\n\nSet `BindingContext` in XAML:\n\n```xml\n<ContentPage xmlns:vm=\"clr-namespace:MyApp.ViewModels\"\n             x:DataType=\"vm:MainViewModel\">\n    <ContentPage.BindingContext>\n        <vm:MainViewModel />\n    </ContentPage.BindingContext>\n</ContentPage>\n```\n\nOr in code-behind (preferred with DI):\n\n```csharp\npublic MainPage(MainViewModel vm)\n{\n    InitializeComponent();\n    BindingContext = vm;\n}\n```\n\n---\n\n## INotifyPropertyChanged and ObservableObject\n\n### Manual implementation\n\n```csharp\npublic class MainViewModel : INotifyPropertyChanged\n{\n    public event PropertyChangedEventHandler? PropertyChanged;\n\n    private string _title = string.Empty;\n    public string Title\n    {\n        get => _title;\n        set\n        {\n            if (_title != value)\n            {\n                _title = value;\n                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Title)));\n            }\n        }\n    }\n}\n```\n\n### CommunityToolkit.Mvvm (recommended)\n\n```csharp\nusing CommunityToolkit.Mvvm.ComponentModel;\nusing CommunityToolkit.Mvvm.Input;\n\npublic partial class MainViewModel : ObservableObject\n{\n    [ObservableProperty]\n    private string _title = string.Empty;\n\n    [RelayCommand]\n    private async Task LoadDataAsync() { /* ... */ }\n}\n```\n\nThe source generator creates the `Title` property, `PropertyChanged` raise,\nand `LoadDataCommand` automatically.\n\n---\n\n## Value Converters — IValueConverter\n\nImplement `Convert` (source → target) and `ConvertBack` (target → source):\n\n```csharp\npublic class IntToBoolConverter : IValueConverter\n{\n    public object? Convert(object? value, Type targetType,\n        object? parameter, CultureInfo culture)\n        => value is int i && i != 0;\n\n    public object? ConvertBack(object? value, Type targetType,\n        object? parameter, CultureInfo culture)\n        => value is true ? 1 : 0;\n}\n```\n\nDeclare in XAML resources and consume:\n\n```xml\n<ContentPage.Resources>\n    <local:IntToBoolConverter x:Key=\"IntToBool\" />\n</ContentPage.Resources>\n\n<Switch IsToggled=\"{Binding Count, Converter={StaticResource IntToBool}}\" />\n```\n\n`ConverterParameter` is always passed as a **string** — parse inside `Convert`:\n\n```xml\n<Label Text=\"{Binding Score, Converter={StaticResource ThresholdConverter},\n              ConverterParameter=50}\" />\n```\n\n---\n\n## Multi-Binding\n\nCombine multiple source values with `IMultiValueConverter`:\n\n```xml\n<Label>\n    <Label.Text>\n        <MultiBinding Converter=\"{StaticResource FullNameConverter}\">\n            <Binding Path=\"FirstName\" />\n            <Binding Path=\"LastName\" />\n        </MultiBinding>\n    </Label.Text>\n</Label>\n```\n\n```csharp\npublic class FullNameConverter : IMultiValueConverter\n{\n    public object Convert(object[] values, Type targetType,\n        object parameter, CultureInfo culture)\n    {\n        if (values.Length == 2 && values[0] is string first\n            && values[1] is string last)\n            return $\"{first} {last}\";\n        return string.Empty;\n    }\n\n    public object[] ConvertBack(object value, Type[] targetTypes,\n        object parameter, CultureInfo culture)\n        => throw new NotSupportedException();\n}\n```\n\n---\n\n## Relative Bindings\n\n| Source | Syntax | Use case |\n|--------|--------|----------|\n| Self | `{Binding Source={RelativeSource Self}, Path=WidthRequest}` | Bind to own properties |\n| Ancestor | `{Binding BindingContext.Title, Source={RelativeSource AncestorType={x:Type ContentPage}}}` | Reach parent BindingContext |\n| TemplatedParent | `{Binding Source={RelativeSource TemplatedParent}, Path=Padding}` | Inside ControlTemplate |\n\n```xml\n<!-- Square box: Height = Width -->\n<BoxView WidthRequest=\"100\"\n         HeightRequest=\"{Binding Source={RelativeSource Self}, Path=WidthRequest}\" />\n```\n\n---\n\n## StringFormat\n\nUse `Binding.StringFormat` for simple display formatting without a converter:\n\n```xml\n<Label Text=\"{Binding Price, StringFormat='Total: {0:C2}'}\" />\n<Label Text=\"{Binding DueDate, StringFormat='{0:MMM dd, yyyy}'}\" />\n```\n\nWrap the format string in single quotes when it contains commas or braces.\n\n---\n\n## Binding Fallbacks\n\n- **FallbackValue** — used when the binding path cannot be resolved or the\n  converter throws.\n- **TargetNullValue** — used when the bound value is `null`.\n\n```xml\n<Label Text=\"{Binding MiddleName, TargetNullValue='(none)',\n              FallbackValue='unavailable'}\" />\n<Image Source=\"{Binding AvatarUrl, TargetNullValue='default_avatar.png'}\" />\n```\n\n---\n\n## .NET 9+ Code Bindings (AOT-safe)\n\nFully AOT-safe, no reflection:\n\n```csharp\nlabel.SetBinding(Label.TextProperty,\n    static (PersonViewModel vm) => vm.FullName);\n\nentry.SetBinding(Entry.TextProperty,\n    static (PersonViewModel vm) => vm.Age,\n    mode: BindingMode.TwoWay,\n    converter: new IntToStringConverter());\n```\n\n---\n\n## Threading\n\nMAUI automatically marshals `PropertyChanged` to the UI thread — you can raise\nit from any thread. **However**, direct `ObservableCollection` mutations\n(Add / Remove) from background threads may crash:\n\n```csharp\n// ✅ Safe — PropertyChanged is auto-marshalled\nawait Task.Run(() => Title = \"Loaded\");\n\n// ⚠️ ObservableCollection.Add — dispatch to UI thread\nMainThread.BeginInvokeOnMainThread(() => Items.Add(newItem));\n```\n\n---\n\n## Common Pitfalls\n\n| Mistake | Fix |\n|---------|-----|\n| Missing `x:DataType` — bindings silently fall back to reflection | Add `x:DataType` at page root and every `DataTemplate`; enable `XC0025` as error |\n| Forgetting to set `BindingContext` | Set in XAML (`<Page.BindingContext>`) or inject via constructor |\n| Specifying redundant `Mode=OneWay` / `Mode=TwoWay` | Omit `Mode` when using the control's default |\n| ViewModel does not implement `INotifyPropertyChanged` | Use `ObservableObject` from CommunityToolkit.Mvvm or implement manually |\n| Mutating `ObservableCollection` off the UI thread | Wrap mutations in `MainThread.BeginInvokeOnMainThread` |\n| Complex converter chains in hot paths | Pre-compute values in the ViewModel instead |\n| Using `x:DataType=\"x:Object\"` to escape compiled bindings | Restructure bindings; keep compile-time safety |\n| Binding to non-public properties | Binding targets must be `public` properties (fields are ignored) |\n\n---\n\n## References\n\n- [Data binding overview](https://learn.microsoft.com/dotnet/maui/fundamentals/data-binding/)\n- [Compiled bindings](https://learn.microsoft.com/dotnet/maui/fundamentals/data-binding/compiled-bindings)\n- [Value converters](https://learn.microsoft.com/dotnet/maui/fundamentals/data-binding/converters)\n- [Relative bindings](https://learn.microsoft.com/dotnet/maui/fundamentals/data-binding/relative-bindings)\n- [Multi-bindings](https://learn.microsoft.com/dotnet/maui/fundamentals/data-binding/multibindings)\n- [CommunityToolkit.Mvvm](https://learn.microsoft.com/dotnet/communitytoolkit/mvvm/)",
  "applicable_domains": [
    "code",
    "dotnet",
    "engineering"
  ],
  "invocation": [
    "/maui-data-binding"
  ],
  "tags": [
    "dotnet-maui",
    "dotnet",
    "csharp",
    "microsoft"
  ],
  "authored_by": "anthropics",
  "source_url": "https://github.com/dotnet/skills/blob/main/plugins/dotnet-maui/skills/maui-data-binding/SKILL.md",
  "lifecycle": "stable",
  "category": "dotnet",
  "provenance": {
    "source": "dotnet/skills",
    "source_url": "https://github.com/dotnet/skills/blob/main/plugins/dotnet-maui/skills/maui-data-binding/SKILL.md",
    "author": "Microsoft / .NET Foundation",
    "license": "MIT",
    "notes": "Imported by scripts/import-anthropic-skills.py."
  }
}