maui-data-binding について
.NET MAUI プロジェクトのデータバインディング実装とトラブルシューティングを支援するスキルです。コンパイル時バインディング、変更通知、XAML と C# での動作を扱います。
主な機能
- 01x:DataType によるコンパイル時バインディング
- 02BindingContext とバインディングモードの案内
- 03プロパティ変更通知への対応
- 04コンバーターと相対バインディングの解説
- 05.NET 9+ の AOT セーフな C# バインディング
活用シーン
- 01MAUI ページへの型付きバインディング追加
- 02更新されないバインディングの診断
- 03マルチバインディングによる値の結合
- 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 を設定する場所にのみ設定してください。
- ページ/ビューのルート —
BindingContextを割り当てる場所。 - 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 がない状態でバインディングが使用されています — コンパイルされず、リフレクションにフォールバックします |
| XC0023 | x:DataType が**明示的に null**であるため、バインディングがコンパイルされていません |
| XC0024 | x: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 プロパティである必要があります(フィールドは無視されます) |