.NET MAUI DetailsPageでNameがバインドできない原因と解決策(XFC0045・BindingContext・Shellナビゲーション)

.NET MAUIでMainPageでは表示できるのにDetailsPageだけ「XFC0045 Property ‘Name’ not found」と出る場合、原因はBindingContextの違いにあることがほとんどです。本記事ではRandoモデルを正しく渡し、Rando.Nameとしてバインドするまでを実例コードで整理します。

日程Fit。無料・登録不要。「いつ空いてる?」を、ひとつのリンクで。リンクを送って、○△×でかんたん日程調整。無料で日程を作る。
目次

現象: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.xamlRando.Name / Rando.Distance などRando(モデル)や、Randoの一覧を持つViewModel特になし(表示できる)
DetailsPage.xamlRando.Name / Rando.Image などRandoDetailsViewModelXFC0045(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ではなく別型を渡してキャストできない
受け取り方法IQueryAttributableQueryString前提の仕組みと混同する

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 / DeniveleC#標準の命名規約で読みやすく、検索もしやすい
コマンド名OpenPhotosCommand / OpenMapCommand1つのTapCommandに詰め込まない方が保守しやすい
ルート名nameof(DetailsPage)文字列直書きのタイポを防げる

トラブルシューティング:まだ表示されないときの確認手順

「Rando.Nameに直したのに真っ白」「遷移はできるけど値が入らない」という場合は、次のチェックが効きます。

症状原因の候補確認方法対処
遷移できないルート未登録AppShellでRegisterRouteしているかRouting.RegisterRoute(...) を追加
遷移できるが表示が空RandoがnullApplyQueryAttributesにブレークポイントキー名一致、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も再発しにくくなります。

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次