mcpField
M
maui-data-binding

maui-data-binding

github.com/dotnet開発5.8k3.4k 回インストール1 日前に更新
リポジトリ

maui-data-binding について

.NET MAUI プロジェクトのデータバインディング実装とトラブルシューティングを支援するスキルです。コンパイル時バインディング、変更通知、XAML と C# での動作を扱います。

主な機能

  1. 01x:DataType によるコンパイル時バインディング
  2. 02BindingContext とバインディングモードの案内
  3. 03プロパティ変更通知への対応
  4. 04コンバーターと相対バインディングの解説
  5. 05.NET 9+ の AOT セーフな C# バインディング

活用シーン

  1. 01MAUI ページへの型付きバインディング追加
  2. 02更新されないバインディングの診断
  3. 03マルチバインディングによる値の結合
  4. 04トリミングや AOT ビルドでのリフレクション回避

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/dotnet/skills --skill maui-data-binding

インストール後、Claude Code で/maui-data-bindingを入力して読み込まれたか確認します。

同梱ファイルreferences/data-binding-api.md

詳細

.NET MAUI データバインディング

UI コントロールを ViewModel のプロパティに接続し、コンパイル時の安全性、正しい変更通知、最小限のオーバーヘッドを実現します。あらゆる箇所でコンパイル済みバインディングを優先し、バインディング警告はビルドエラーとして扱います。

使用する場面

  • 新しいページまたは既存のページに x:DataType のコンパイル済みバインディングを追加する
  • INotifyPropertyChanged または CommunityToolkit の ObservableObject を実装する
  • IValueConverter / IMultiValueConverter を作成または使用する
  • コントロールのプロパティに適切な BindingMode を選択する
  • XAML またはコードビハインドで BindingContext を設定する
  • 相対バインディング(Self、AncestorType、TemplatedParent)を使用する
  • StringFormat、FallbackValue、TargetNullValue を適用する
  • SetBinding とラムダを使って AOT セーフなコードバインディングを記述する(.NET 9+)

使用しない場面

  • CollectionView のレイアウト/テンプレート — maui-collectionview スキルを使用してください
  • Shell のナビゲーションパラメーター — maui-shell-navigation スキルを使用してください
  • サービスの登録/DI — maui-dependency-injection スキルを使用してください
  • プロパティ変更をトリガーとするアニメーション — 組み込みの .NET MAUI アニメーション API を使用してください

入力

  • .NET 8 以降を対象とする .NET MAUI プロジェクト
  • バインディングを宣言する XAML ページまたは C# コードビハインド
  • ViewModel クラス(または作成する予定)

回答内容に影響するルール

すべてのバインディングに関する回答で、次のルールを適用してください。「コンパイルできる」ことと「実際に UI が更新される」ことの違いはここにあります。

状況することしないこと
x:DataType の配置場所を決めるバインディングスコープが始まる場所、つまりページ/ビューのルートと、各 DataTemplate に設定する親の BindingContext を共有する任意の子要素にばらばらに設定する
バインディングがリフレクションにフォールバックする(XC0022 / XC0023)そのバインディングスコープに適切な x:DataType を追加する。XC0023 の場合は明示的な x:DataType="{x:Null}" を削除するx:DataType="x:Object" で警告を抑える — これではコンパイル時チェックが無効になります
DataTemplate が外側のスコープから x:DataType を継承する(XC0024)DataTemplate 自身に 独自の x:DataType を設定する誤った型に対して解決される状態のままにする
ViewModel の変更通知ObservableObject + [ObservableProperty] を使用するか、INotifyPropertyChanged を実装する単純な POCO 基底クラスを使う — バインディングは更新されません
バインディングの表示が空になるBindingContext が実際に設定されているか確認するバインディングパスが間違っていると決めつける
コンパイル済みバインディングを必須にするMauiEnableXamlCBindingWithSourceCompilation を true に設定し、その後で <WarningsAsErrors>XC0022;XC0025</WarningsAsErrors> を設定するプロジェクトで Source= / RelativeSource バインディングを使っている場合、スイッチを設定せずに XC0025 をエラーに昇格する

依頼されておらず、実際の不具合も解決しない ViewModel の再構成やコンバーターの追加はしないでください。x:DataType の追加は別です。ページのバインディングを編集している場合、コンパイル済みバインディングの使用を推奨することは対応範囲内です。


コンパイル済みバインディング — x:DataType の配置

コンパイル済みバインディングはリフレクションベースのバインディングより 8~20 倍高速で、NativeAOT / トリミングには必須です。x:DataType で有効にしてください。

配置ルール

x:DataType は BindingContext を設定する場所にのみ設定してください。

  1. ページ/ビューのルート — BindingContext を割り当てる場所。
  2. DataTemplate — 新しいバインディングスコープを作成する場所。

任意の子要素に x:DataType をばらばらに設定しないでください。コンパイル済みバインディングを回避するために子要素へ x:DataType="x:Object" を追加するのはアンチパターンです。コンパイル時チェックが無効になり、リフレクションが再導入されます。


<ContentPage xmlns:vm="clr-namespace:MyApp.ViewModels"
             x:DataType="vm:MainViewModel">
    <StackLayout>
        <Label Text="{Binding Title}" />
        <Slider Value="{Binding Progress}" />
    </StackLayout>
</ContentPage>


<ContentPage x:DataType="vm:MainViewModel">
    <StackLayout>
        <Label Text="{Binding Title}" />
        <Slider x:DataType="x:Object" Value="{Binding Progress}" />
    </StackLayout>
</ContentPage>
DataTemplate には必ず独自の x:DataType が必要
<CollectionView ItemsSource="{Binding People}">
    <CollectionView.ItemTemplate>
        <DataTemplate x:DataType="model:Person">
            <Label Text="{Binding FullName}" />
        </DataTemplate>
    </CollectionView.ItemTemplate>
</CollectionView>
バインディングの警告をエラーとして扱う
警告意味
XC0022スコープ内に x:DataType がない状態でバインディングが使用されています — コンパイルされず、リフレクションにフォールバックします
XC0023x:DataType が**明示的に null**であるため、バインディングがコンパイルされていません
XC0024x:DataType が外側のスコープから引き継がれています — DataTemplate に独自の x:DataType を指定してください
XC0025明示的な Source があるため、バインディングがコンパイルされていません — <MauiEnableXamlCBindingWithSourceCompilation> を有効にしてください

これら4つのコードは .NET 10 / .NET 11 MAUI で検証済みです (Build.Tasks/BuildException.cs、ErrorMessages.resx)。診断番号は SDK のバンドによって異なるため、新しい SDK で使用する前に BuildException.cs を再確認してください。

.csproj に追加します。


<MauiEnableXamlCBindingWithSourceCompilation>true</MauiEnableXamlCBindingWithSourceCompilation>
<WarningsAsErrors>XC0022;XC0025</WarningsAsErrors>

このスイッチを有効にせずに XC0025 をエラーに昇格する場合は、プロジェクトに Source= / RelativeSource バインディングがないことを確認してください。そうでないと、 それらが報告されます。


バインディングモード

既定値を上書きする場合に限り、Mode を明示的に設定してください。ほとんどのプロパティには すでに適切な既定値が設定されています。

モード方向用途
OneWayソース → ターゲット表示専用(ほとんどのプロパティの既定値)
TwoWayソース ↔ ターゲット編集可能なコントロール(Entry.Text、Switch.IsToggled)
OneWayToSourceターゲット → ソースUI に反映せず、ユーザー入力を読み取る
OneTimeソース → ターゲット(1回)静的な値。変更追跡のオーバーヘッドがありません

<Label Text="{Binding Score}" />
<Entry Text="{Binding UserName}" />
<Switch IsToggled="{Binding DarkMode}" />


<Label Text="{Binding Title, Mode=OneTime}" />
<Entry Text="{Binding SearchQuery, Mode=OneWayToSource}" />


<Label Text="{Binding Score, Mode=OneWay}" />
<Entry Text="{Binding UserName, Mode=TwoWay}" />

BindingContext とプロパティパス

すべての BindableObject は、明示的に設定されていない限り、親から BindingContext を 引き継ぎます。プロパティパスでは、ドット表記とインデクサーを使用できます。

<Label Text="{Binding Address.City}" />
<Label Text="{Binding Items[0].Name}" />

XAML で BindingContext を設定します。

<ContentPage xmlns:vm="clr-namespace:MyApp.ViewModels"
             x:DataType="vm:MainViewModel">
    <ContentPage.BindingContext>
        <vm:MainViewModel />
    </ContentPage.BindingContext>
</ContentPage>

または、コードビハインドで設定します(DI を使用する場合に推奨)。

public MainPage(MainViewModel vm)
{
    InitializeComponent();
    BindingContext = vm;
}

INotifyPropertyChanged と ObservableObject

手動での実装
public class MainViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler? PropertyChanged;

    private string _title = string.Empty;
    public string Title
    {
        get => _title;
        set
        {
            if (_title != value)
            {
                _title = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Title)));
            }
        }
    }
}
CommunityToolkit.Mvvm(推奨)
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;

public partial class MainViewModel : ObservableObject
{
    [ObservableProperty]
    private string _title = string.Empty;

    [RelayCommand]
    private async Task LoadDataAsync() { /* ... */ }
}

ソースジェネレーターが Title プロパティ、PropertyChanged の発行、 LoadDataCommand を自動的に生成します。


値コンバーター — IValueConverter

Convert(ソース → ターゲット)と ConvertBack(ターゲット → ソース)を実装します。

public class IntToBoolConverter : IValueConverter
{
    public object? Convert(object? value, Type targetType,
        object? parameter, CultureInfo culture)
        => value is int i && i != 0;

    public object? ConvertBack(object? value, Type targetType,
        object? parameter, CultureInfo culture)
        => value is true ? 1 : 0;
}

XAML リソースで宣言して使用します。

<ContentPage.Resources>
    <local:IntToBoolConverter x:Key="IntToBool" />
</ContentPage.Resources>

<Switch IsToggled="{Binding Count, Converter={StaticResource IntToBool}}" />

ConverterParameter は常に文字列として渡されます。Convert 内で解析してください。

<Label Text="{Binding Score, Converter={StaticResource ThresholdConverter},
              ConverterParameter=50}" />

マルチバインディング

IMultiValueConverter を使用して、複数のソース値を組み合わせます。

<Label>
    <Label.Text>
        <MultiBinding Converter="{StaticResource FullNameConverter}">
            <Binding Path="FirstName" />
            <Binding Path="LastName" />
        </MultiBinding>
    </Label.Text>
</Label>
public class FullNameConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType,
        object parameter, CultureInfo culture)
    {
        if (values.Length == 2 && values[0] is string first
            && values[1] is string last)
            return $"{first} {last}";
        return string.Empty;
    }

    public object[] ConvertBack(object value, Type[] targetTypes,
        object parameter, CultureInfo culture)
        => throw new NotSupportedException();
}

相対バインディング

ソース構文用途
Self{Binding Source={RelativeSource Self}, Path=WidthRequest}自身のプロパティへのバインド
Ancestor{Binding BindingContext.Title, Source={RelativeSource AncestorType={x:Type ContentPage}}}親の BindingContext へのアクセス
TemplatedParent{Binding Source={RelativeSource TemplatedParent}, Path=Padding}ControlTemplate 内

<BoxView WidthRequest="100"
         HeightRequest="{Binding Source={RelativeSource Self}, Path=WidthRequest}" />

StringFormat

コンバーターを使わずに簡単な表示形式を設定するには、Binding.StringFormat を使用します。

<Label Text="{Binding Price, StringFormat='Total: {0:C2}'}" />
<Label Text="{Binding DueDate, StringFormat='{0:MMM dd, yyyy}'}" />

カンマまたは波括弧を含む場合は、書式文字列を単一引用符で囲みます。


バインディングのフォールバック

  • FallbackValue — バインディングパスを解決できない場合、または コンバーターで例外が発生した場合に使用されます。
  • TargetNullValue — バインドされた値が null の場合に使用されます。
<Label Text="{Binding MiddleName, TargetNullValue='(none)',
              FallbackValue='unavailable'}" />
<Image Source="{Binding AvatarUrl, TargetNullValue='default_avatar.png'}" />

.NET 9 以降のコードバインディング(AOT 対応)

リフレクションを使用せず、完全に AOT 対応です。

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 は PropertyChanged を自動的に UI スレッドにマーシャリングするため、任意のスレッドから 発行できます。ただし、バックグラウンドスレッドから ObservableCollection を直接変更 (追加/削除)すると、クラッシュする場合があります。

// ✅ Safe — PropertyChanged is auto-marshalled
await Task.Run(() => Title = "Loaded");

// ⚠️ ObservableCollection.Add — dispatch to UI thread
MainThread.BeginInvokeOnMainThread(() => Items.Add(newItem));

よくある落とし穴

間違い修正方法
x:DataType の指定漏れ — バインディングが警告なくリフレクションにフォールバックしますページのルートと各 DataTemplate に x:DataType を追加し、XC0022 を昇格させます(バインディングの警告をエラーとして扱うを参照)
BindingContext の設定忘れXAML(<Page.BindingContext>)で設定するか、コンストラクター経由で注入します
冗長な Mode=OneWay / Mode=TwoWay の指定コントロールの既定値を使う場合は Mode を省略します
ViewModel が INotifyPropertyChanged を実装していないCommunityToolkit.Mvvm の ObservableObject を使うか、手動で実装します
UI スレッド以外から ObservableCollection を変更する変更を MainThread.BeginInvokeOnMainThread でラップします
ホットパスで複雑なコンバーターのチェーンを使う代わりに ViewModel で値を事前計算します
コンパイル済みバインディングを回避するために x:DataType="x:Object" を使うバインディングを再構成し、コンパイル時の安全性を維持します
非公開プロパティへのバインディングバインディング先は public プロパティである必要があります(フィールドは無視されます)

参考資料

コメント