.NET MAUIでMainPageでは表示できるのにDetailsPageだけ「XFC0045 Property ‘Name’ not found」と出る場合、原因はBindingContextの違いにあることがほとんどです。本記事ではRandoモデルを正しく渡し、Rando.Nameとしてバインドするまでを実例コードで整理します。
現象:DetailsPageでだけRandoのNameが表示されない(XFC0045)
.NET MAUI(XAML)で画面を分けて作っていると、MainPageでは正常に表示できるのに、DetailsPageに遷移した瞬間にバインディングが崩れることがあります。今回の典型例が次のエラーです。
XFC0045 Binding: Property "Name" not found on "RandoPro.ViewModel.RandoDetailsViewModel"
ポイントは、モデル(Rando)にNameが存在しているのに、XAMLが探している先がRandoDetailsViewModelになっていることです。つまり「Nameが無い」のではなく「見に行く場所が違う」状態です。
| 画面 | 期待している表示 | 実際のBindingContext | 起きがちなエラー |
|---|---|---|---|
| MainPage.xaml | Rando.Name / Rando.Distance など | Rando(モデル)や、Randoの一覧を持つViewModel | 特になし(表示できる) |
| DetailsPage.xaml | Rando.Name / Rando.Image など | RandoDetailsViewModel | XFC0045(Nameが見つからない) |
原因:XAMLのBindingは「BindingContext」からプロパティを辿る
XAMLの {Binding ...} は、基本的に現在のBindingContext(バインディングコンテキスト)を起点にプロパティを解決します。DetailsPageのBindingContextが RandoDetailsViewModel の場合、XAMLが見に行く先は次のようになります。
| XAMLの指定 | XAMLが探す場所 | 結果 |
|---|---|---|
{Binding Name} | RandoDetailsViewModel.Name | 存在しない → エラー |
{Binding Rando.Name} | RandoDetailsViewModel.Rando.Name | 存在する → 表示OK |
つまり、DetailsPageでモデルの値を表示したいなら、ViewModelが保持しているモデルプロパティ(例:Rando)経由でバインドする必要があります。
解決策:DetailsPageのバインディングを「Rando.プロパティ名」に揃える
まず最短で直すなら、DetailsPage.xaml側のバインディング指定を修正します。NG例は「モデルのプロパティ名を直で書いてしまう」パターンです。
修正前(NG)
<Label Text="{Binding Name}" />
<Image Source="{Binding Image}" />
<Label Text="{Binding Distance}" />
<Label Text="{Binding denivele}" />
修正後(OK)
<StackLayout Margin="0"> <Label Text="{Binding Rando.Name}" HorizontalOptions="Center" />
上のように、RandoDetailsViewModelが持つRandoプロパティを必ず経由させます。これだけで、XFC0045の原因になっている「参照先のズレ」は解消します。
| よくある修正ポイント | 修正前 | 修正後 |
|---|---|---|
| 名称 | {Binding Name} | {Binding Rando.Name} |
| 画像 | {Binding Image} | {Binding Rando.Image} |
| 距離 | {Binding Distance} | {Binding Rando.Distance} |
| 標高差 | {Binding denivele} | {Binding Rando.denivele} |
DetailsPageにRandoを「認識させる」ために必要なこと:ViewModelがRandoを受け取る
XAML側を直しても、ViewModelのRandoがnullのままだと当然表示されません。つまり、次の2点がセットで必要になります。
- DetailsPageのBindingContext(= RandoDetailsViewModel)が
Randoプロパティを持っている - 画面遷移時に、MainPageから選択された
RandoをDetailsPage側へ渡している
Shellナビゲーションを使っているなら、オブジェクトを渡す方法としてIQueryAttributableが扱いやすいです(QueryStringではなくDictionaryで渡せるため)。
RandoDetailsViewModel:IQueryAttributableでナビゲーションパラメータを受け取る
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Microsoft.Maui.ApplicationModel;
using Microsoft.Maui.Controls;
using System.Diagnostics;
public partial class RandoDetailsViewModel : ObservableObject, IQueryAttributable
{
private readonly IMap _map;
public RandoDetailsViewModel(IMap map)
{
_map = map;
}
[ObservableProperty]
private Rando? rando;
[ObservableProperty]
private string? niveauColor;
public void ApplyQueryAttributes(IDictionary<string, object> query)
{
if (query.TryGetValue("Rando", out var value) && value is Rando r)
{
Rando = r;
// 例:表示用の色を決めたい場合はここで計算する(プロジェクトに合わせて調整)
NiveauColor = "#95A5A6";
}
}
[RelayCommand]
async Task OpenPhotos(string? photoUrl)
{
if (string.IsNullOrWhiteSpace(photoUrl))
return;
// 例:写真URLをWebViewで表示するページに遷移
await Shell.Current.GoToAsync("PhotoPopupPage", new Dictionary<string, object>
{
["PhotoUrl"] = photoUrl
});
}
[RelayCommand]
async Task OpenMap()
{
if (Rando is null)
return;
try
{
await _map.OpenAsync(Rando.lat, Rando.lon, new MapLaunchOptions
{
Name = Rando.Name,
NavigationMode = NavigationMode.None
});
}
catch (Exception ex)
{
Debug.WriteLine($"Unable to launch maps: {ex.Message}");
await Shell.Current.DisplayAlert("Error, no Maps app!", ex.Message, "OK");
}
}
}
重要ポイントは、ViewModelに Rando プロパティがあり、そこに遷移パラメータで値をセットしていることです。これでXAMLの {Binding Rando.Name} が初めて成立します。
MainPage側:選択されたRandoをDetailsPageへ渡して遷移する
MainPageの一覧(CollectionViewなど)で選択されたモデルを、そのままDictionaryで渡します。キー名はViewModel側と一致させます(ここでは "Rando")。
async Task GoToDetailsAsync(Rando selected)
{
await Shell.Current.GoToAsync("DetailsPage", true, new Dictionary<string, object>
{
["Rando"] = selected
});
}
| チェック項目 | 具体例 | よくあるミス |
|---|---|---|
| パラメータキー | "Rando" | "rando" など大小文字違いで受け取れない |
| 渡す型 | selected(Rando) | DTOではなく別型を渡してキャストできない |
| 受け取り方法 | IQueryAttributable | QueryString前提の仕組みと混同する |
DetailsPageをルーティングに登録する:Routing.RegisterRouteの設定
Shellナビゲーションで GoToAsync("DetailsPage"...) を呼ぶ場合、ルート登録が抜けていると遷移自体が失敗します。DetailsPageを使うなら、AppShellで必ず登録します。
public partial class AppShell : Shell
{
public AppShell()
{
InitializeComponent();
RegisterRoutes();
}
void RegisterRoutes()
{
Routing.RegisterRoute("DetailsPage", typeof(DetailsPage));
Routing.RegisterRoute("PhotoPopupPage", typeof(PhotoPopupPage));
}
}
プロジェクトによっては nameof(DetailsPage) を使うとタイポを減らせます。ルート名(文字列)がずれると、画面が開かないかパラメータが届かない原因になります。
実務で効く改善ポイント:バインディング事故を減らすコツ
コンパイル時に気付ける「x:DataType(Compiled Bindings)」を活用する
今回のXFC0045は、XAMLコンパイル(XamlC)やCompiled Bindingsの恩恵で「実行してから気付く」ではなく「ビルド時に気付ける」タイプのエラーです。DetailsPageのルート要素に x:DataType を付けておくと、誤ったBindingを早期発見できます。
<ContentPage
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:vm="clr-namespace:RandoPro.ViewModel"
x:Class="RandoPro.Views.DetailsPage"
x:DataType="vm:RandoDetailsViewModel">
<!-- 省略 -->
</ContentPage>
この状態で {Binding Name} と書くと「ViewModelにNameが無い」と即座に検知できます。正しくは {Binding Rando.Name} です。
ネストしたモデルがnullの瞬間に備える(TargetNullValue / FallbackValue)
画面遷移直後、ViewModel側でパラメータを受け取る前の瞬間だけ Rando がnullになり、ラベルが空になることがあります。UI上のチラつきを避けたい場合は、次のように保険をかけると見た目が安定します。
<Label Text="{Binding Rando.Name, TargetNullValue='(読み込み中...)'}" />
<Image Source="{Binding Rando.Image, TargetNullValue='placeholder.png'}" />
プロパティ名の表記揺れを減らす(PascalCase推奨)
例にある denivele のような小文字プロパティは、XAML側で打ち間違いが起きやすく、チーム開発だと特に事故が増えます。可能ならモデル側は Denivele のようにPascalCaseへ寄せ、XAMLも合わせるのが安全です。
| 項目 | 推奨 | 理由 |
|---|---|---|
| モデルのプロパティ名 | Name / Distance / Denivele | C#標準の命名規約で読みやすく、検索もしやすい |
| コマンド名 | OpenPhotosCommand / OpenMapCommand | 1つのTapCommandに詰め込まない方が保守しやすい |
| ルート名 | nameof(DetailsPage) | 文字列直書きのタイポを防げる |
トラブルシューティング:まだ表示されないときの確認手順
「Rando.Nameに直したのに真っ白」「遷移はできるけど値が入らない」という場合は、次のチェックが効きます。
| 症状 | 原因の候補 | 確認方法 | 対処 |
|---|---|---|---|
| 遷移できない | ルート未登録 | AppShellでRegisterRouteしているか | Routing.RegisterRoute(...) を追加 |
| 遷移できるが表示が空 | Randoがnull | ApplyQueryAttributesにブレークポイント | キー名一致、nullチェック、TargetNullValue |
| XFC0045が消えない | Bindingパスが旧式のまま | XAMLを検索して {Binding Name} が残っていないか | すべて Rando. 経由に修正 |
| 一部だけ更新されない | プロパティ変更通知が無い | RandoやViewModelがINotifyPropertyChanged相当か | CommunityToolkitの [ObservableProperty] を活用 |
| 画像だけ出ない | URL/パスが不正、HTTPS制限など | 実際のURLをログに出す | 有効なURI、プレースホルダー、キャッシュ設定を見直す |
まとめ:DetailsPageでモデルのNameがバインドできないときの最短ルート
- XFC0045の本質:DetailsPageのBindingContextがViewModelなのに、
{Binding Name}でモデル直下のプロパティを参照していた - 最優先の修正:XAMLを
{Binding Rando.Name}のように、ViewModel内のモデルプロパティ経由に揃える - 値が入る仕組み:ShellナビゲーションでMainPageから選択したRandoをDictionaryで渡し、ViewModelが
IQueryAttributableで受け取る - 安定運用:AppShellで
Routing.RegisterRouteを忘れずに登録し、x:DataTypeで誤Bindingを早期に潰す
ここまで揃えば、DetailsPageでも Rando モデルの Name / Image / Distance などが確実に表示され、XFC0045も再発しにくくなります。

コメント