.NET MAUI PickerのItemDisplayBindingでXamlC警告XC0022/XC0045を解消する混在バインディング実装

.NET MAUI の Picker で「ItemsSource/SelectedItem は ViewModel、表示テキストは ItemsSource の要素」といった混在バインディングをすると、ページに x:DataType を付けているのに XamlC 警告(XC0022)が残ることがあります。原因は “BindingContext の型” と “各アイテムの型” が同じ要素内で切り替わる点にあります。この記事では警告の意味を整理し、XC0045 を出さずに compiled binding 化する実装パターンを具体例付きで解説します。

目次

症状:Picker の ItemDisplayBinding だけ XamlC 警告 XC0022 が出る

.NET MAUI(CommunityToolkit.Mvvm)で、たとえば性別選択や都道府県選択などを Picker で実装する際、よくある形が次の「混在バインディング」です。

  • ItemsSource:ViewModel のコレクション(例:ObservableCollection<PickerRoot> Genders
  • SelectedItem:ViewModel の選択中要素(例:PickerRoot SelectedItemGender
  • ItemDisplayBinding:コレクション要素の表示用プロパティ(例:PickerRoot.PickerText

ページ側(ContentPage など)で x:DataType="viewModels:LoginViewModel" を指定して compiled binding を有効化しているにもかかわらず、次のような XamlC 警告が出ることがあります。

  • XC0022ItemDisplayBinding="{Binding PickerText}" が compiled binding になっていない、または型が推論できない

そこで「じゃあ Picker の型をアイテム型にすればいいのでは?」と考えて、Picker 要素に x:DataType="models:PickerRoot" を付けると、今度は別の警告に変わります。

  • XC0045Property "Genders" not found on "PickerRoot"(ViewModel のプロパティが見つからない)

この 2 つの警告はセットで出やすく、原因を理解していないと「どっちを直せばいいのか分からない」状態になりがちです。

結論:Picker 要素に x:DataType=”models:PickerRoot” は付けない

結論はシンプルです。

Picker 要素の BindingContext は ViewModel のままにする必要があるため、Picker 要素に x:DataType="models:PickerRoot" を付けてはいけません。

ただし、PickerTextItemsSource の各要素(= PickerRoot) のプロパティです。したがって、compiled binding 化したいなら「Picker ではなく、ItemDisplayBinding 側だけ」アイテム型を指定します。

実装例(このパターンが最も安全で、警告も消しやすいです):

&lt;Picker
    ItemsSource=&quot;{Binding Genders}&quot;
    SelectedItem=&quot;{Binding SelectedItemGender, Mode=TwoWay}&quot;
    ItemDisplayBinding=&quot;{Binding PickerText, x:DataType=models:PickerRoot}&quot;
    FontFamily=&quot;OpenSansSemibold&quot;
    VerticalOptions=&quot;CenterAndExpand&quot;
    HorizontalOptions=&quot;FillAndExpand&quot;
    FontSize=&quot;12&quot; /&gt;

ポイントはここだけです。

  • ItemsSourceSelectedItem は ViewModel のプロパティなので、Picker の x:DataType は ViewModel であるべき
  • ItemDisplayBinding は「各アイテムに対するバインディング」なので、Binding の中で x:DataType=models:PickerRoot を指定する

なぜ起きる?x:DataType が決めているものを整理する

まず押さえたいのは、x:DataType の役割です。x:DataType は「その XAML スコープ(要素や DataTemplate)での Binding を、どの型としてコンパイルするか」を決めます。

たとえばページに以下があるとします。

&lt;ContentPage
    ...
    x:DataType=&quot;viewModels:LoginViewModel&quot;&gt;

このとき、ページ配下の多くの要素は BindingContext を継承するため、通常は LoginViewModel を前提に compiled binding できます。つまり、次のような Binding は「LoginViewModel に該当プロパティがあるか」をビルド時にチェックできます。

&lt;Label Text=&quot;{Binding Title}&quot; /&gt;
&lt;Button Command=&quot;{Binding LoginCommand}&quot; /&gt;

一方で、Picker の ItemDisplayBinding は少し特殊です。表示テキストは「ItemsSource の各要素」に対して評価されます。つまり、実行時にはこういう意図です。

  • ItemsSource の要素が PickerRoot なら、表示文字列は PickerRoot.PickerText を参照する

しかし、XamlC(XAML コンパイラ)は「今この Binding がどの型に対するものか」を静的に判断する必要があります。ページは ViewModel 型、ItemDisplayBinding はアイテム型……というように、同じ要素(Picker)内で参照すべき型が混在すると、警告(XC0022)が出やすくなります。

重要:「UI の BindingContext(ViewModel)」と「ItemsSource の要素(Item)」は別物です。ItemDisplayBinding は “Item に対する Binding” なのに、ページの x:DataType だけでは “Item の型” を確定できません。

やってはいけない例:Picker にアイテム型の x:DataType を付ける

警告を消そうとして次のように書くと、XC0045 が出ます。

&lt;Picker
    x:DataType=&quot;models:PickerRoot&quot;
    ItemsSource=&quot;{Binding Genders}&quot;
    SelectedItem=&quot;{Binding SelectedItemGender, Mode=TwoWay}&quot;
    ItemDisplayBinding=&quot;{Binding PickerText}&quot; /&gt;

これは当然で、Picker の BindingContext が「PickerRoot 前提」になってしまうためです。すると {Binding Genders} は “PickerRoot に Genders プロパティがあるはず” と解釈され、見つからずに警告になります。

実際に Picker が参照したいのは次の 2 種類です。

  • Picker 要素のプロパティ(ItemsSource / SelectedItem):ViewModel のプロパティ
  • ItemDisplayBinding:ItemsSource の要素(= Item)のプロパティ

つまり、Picker 自体の型を Item にしてしまうと、前者が壊れます。

正しい直し方:ItemDisplayBinding の Binding だけ x:DataType を指定する

正解は「Picker の BindingContext(型)は ViewModel のまま維持」しつつ、ItemDisplayBinding の Binding だけ アイテム型(PickerRoot)を指定することです。

&lt;Picker
    ItemsSource=&quot;{Binding Genders}&quot;
    SelectedItem=&quot;{Binding SelectedItemGender, Mode=TwoWay}&quot;
    ItemDisplayBinding=&quot;{Binding PickerText, x:DataType=models:PickerRoot}&quot; /&gt;

こうすると、

  • ItemsSourceSelectedItem は ViewModel(ページで指定した x:DataType)としてコンパイル
  • ItemDisplayBindingPickerRoot としてコンパイル

という形で、必要な場所だけ型が切り替わり、XC0022/ XC0045 の両方を回避しやすくなります。

どこがどの型?混乱しやすいポイントを表で整理

「この Binding は ViewModel?それとも Item?」が腹落ちすると、同種の警告で迷いにくくなります。

バインディングを書く場所主な参照元想定する型x:DataType を置く場所
ContentPage / Layout 配下の通常の BindingBindingContextViewModel(例:LoginViewModel)ページやコンテナ要素(例:ContentPage の x:DataType)
Picker.ItemsSourceBindingContextViewModel(例:LoginViewModel)ページ側の x:DataType に従う
Picker.SelectedItemBindingContextViewModel(例:LoginViewModel)ページ側の x:DataType に従う
Picker.ItemDisplayBindingItemsSource の各要素Item(例:PickerRoot)Binding の中で x:DataType を上書き

この表の考え方は、Picker に限らず CollectionView / ListView の ItemTemplate や、DataTemplate を使う UI 全般で共通です。「コンテナは ViewModel、テンプレート内は Item」という切り替えがあるところで、x:DataType の置き場所を誤ると同じ種類の警告が出ます。

サンプル実装:CommunityToolkit.Mvvm でのモデルと ViewModel

ここからは、実際にコピペして動かしやすい形で、モデルと ViewModel 例を載せます。性別を Picker で選ぶケースを想定します。

モデル(PickerRoot)

表示用の文字列(PickerText)と、実際に使う値(Value)を持たせておくと、UI 表示とビジネスロジックを分離しやすくなります。

namespace YourApp.Models;

public class PickerRoot
{
    public string PickerText { get; }
    public string Value { get; }

    public PickerRoot(string pickerText, string value)
    {
        PickerText = pickerText;
        Value = value;
    }
}

ViewModel(LoginViewModel)

CommunityToolkit.Mvvm を使うなら、[ObservableProperty] でプロパティを簡潔に書けます。SelectedItem は UI から変更されるので、基本的に TwoWay で OK です。

using CommunityToolkit.Mvvm.ComponentModel;
using System.Collections.ObjectModel;
using YourApp.Models;

namespace YourApp.ViewModels;

public partial class LoginViewModel : ObservableObject
{
    public ObservableCollection&lt;PickerRoot&gt; Genders { get; } = new()
    {
        new PickerRoot(&quot;未選択&quot;, &quot;&quot;),
        new PickerRoot(&quot;男性&quot;, &quot;M&quot;),
        new PickerRoot(&quot;女性&quot;, &quot;F&quot;),
        new PickerRoot(&quot;その他&quot;, &quot;X&quot;)
    };

    [ObservableProperty]
    private PickerRoot? selectedItemGender;

    // 必要なら選択変更時に Value を取り出して別プロパティへ反映するなど
    partial void OnSelectedItemGenderChanged(PickerRoot? value)
    {
        // value?.Value を使ってAPI送信用のコードへ変換、など
    }
}

XAML(ページ)

ページの x:DataType を ViewModel にしておき、Picker は ViewModel のまま、ItemDisplayBinding だけアイテム型を指定します。

&lt;ContentPage
    xmlns=&quot;http://schemas.microsoft.com/dotnet/2021/maui&quot;
    xmlns:x=&quot;http://schemas.microsoft.com/winfx/2009/xaml&quot;
    xmlns:viewModels=&quot;clr-namespace:YourApp.ViewModels&quot;
    xmlns:models=&quot;clr-namespace:YourApp.Models&quot;
    x:Class=&quot;YourApp.Views.LoginPage&quot;
    x:DataType=&quot;viewModels:LoginViewModel&quot;&gt;

    &lt;VerticalStackLayout Padding=&quot;16&quot; Spacing=&quot;12&quot;&gt;

        &lt;Label Text=&quot;性別&quot; FontAttributes=&quot;Bold&quot; /&gt;

        &lt;Picker
            Title=&quot;選択してください&quot;
            ItemsSource=&quot;{Binding Genders}&quot;
            SelectedItem=&quot;{Binding SelectedItemGender, Mode=TwoWay}&quot;
            ItemDisplayBinding=&quot;{Binding PickerText, x:DataType=models:PickerRoot}&quot; /&gt;

    &lt;/VerticalStackLayout&gt;
&lt;/ContentPage&gt;

この形にしておくと、

  • ViewModel のプロパティ名変更(Genders / SelectedItemGender)をしてもコンパイル時に検知しやすい
  • Item 側(PickerRoot)の表示プロパティ名変更(PickerText)もコンパイル時に検知しやすい
  • XamlC 警告を「握りつぶす」のではなく、意味を持って解消できる

というメリットが得られます。

Binding Mode の整理:ItemsSource を TwoWay にしなくていい理由

混在バインディングのサンプルで、ItemsSourceMode=TwoWay を付けてしまう例を見かけることがあります。結論として、通常は不要です。

プロパティよくある設定推奨理由
ItemsSourceMode=TwoWay指定しない(OneWay のままで十分)UI が ItemsSource 自体を書き換える場面はほぼない(コレクションの中身更新は ObservableCollection が担う)
SelectedItemMode=OneWayMode=TwoWayユーザー操作で選択が変わるため、ViewModel に反映させたい
SelectedIndexMode=TwoWay状況次第Index ベースで扱う設計の場合はあり。ただし Item ベースの方が可読性・保守性は高い

ItemsSource の TwoWay は「悪い」というより「意図が伝わりにくい」設定になりがちです。保守の観点では、必要なところだけ TwoWay にする方が安心です。

よくあるつまずき:x:DataType を付けたのに警告が残るときのチェック

同じ書き方をしたつもりでも、プロジェクト構成や名前空間でハマることがあります。警告が消えない場合は、次を順に確認すると切り分けが早いです。

名前空間(xmlns)の参照先が合っているか

  • xmlns:models="clr-namespace:YourApp.Models" が実際の namespace と一致しているか
  • 別アセンブリにモデルを分けている場合、;assembly=XXXX を付ける必要があるか

別アセンブリ例:

xmlns:models=&quot;clr-namespace:YourApp.Models;assembly=YourApp.Core&quot;

クラスやプロパティが public になっているか

XamlC の型チェックは public な型・メンバーを前提にすることが多いです。internal class PickerRootinternal string PickerText のようになっていると、意図通りに解釈されず警告が残るケースがあります(プロジェクト設定や参照状況にもよります)。

ItemDisplayBinding の x:DataType は “Binding の中” に書けているか

今回のポイントはここです。Picker 要素に付けるのではなく、ItemDisplayBinding の Binding 式の中に書きます。

  • OK:ItemDisplayBinding="{Binding PickerText, x:DataType=models:PickerRoot}"
  • NG:<Picker x:DataType="models:PickerRoot" ... />

Page 側の x:DataType と混ざっていないか

ページに x:DataType を付けると、配下の多くは ViewModel 前提でコンパイルされます。だからこそ、アイテム型が必要な Binding だけ「上書き」する、という発想が重要になります。

別解:PickerRoot の ToString() を表示テキストにする(ただし注意点あり)

ItemDisplayBinding を使わず、PickerRootToString() をオーバーライドして表示させる方法もあります。Picker はデフォルトで ToString() を表示に使うため、次のようにできます。

public class PickerRoot
{
    public string PickerText { get; }
    public string Value { get; }

    public PickerRoot(string pickerText, string value)
    {
        PickerText = pickerText;
        Value = value;
    }

    public override string ToString() =&gt; PickerText;
}

この場合、XAML はシンプルになります。

&lt;Picker
    ItemsSource=&quot;{Binding Genders}&quot;
    SelectedItem=&quot;{Binding SelectedItemGender, Mode=TwoWay}&quot; /&gt;

ただし、この方法は「楽だが設計上のトレードオフ」があります。

観点ToString() 方式ItemDisplayBinding 方式
実装のシンプルさ◎(XAML が短い)○(1行増える程度)
表示要件の変更(多言語・条件分岐など)△(ToString にロジックが寄りやすい)◎(表示プロパティを差し替えやすい)
デバッグ時の識別△(Value を見たいときに不便なことがある)○(モデルのまま保持できる)
MVVM の責務分離△(表示都合がモデルに入りやすい)◎(表示用プロパティを設計しやすい)

小規模で「表示は常に 1 文字列で十分」なら ToString() 方式でも良いですが、ログイン画面のように将来仕様が増えやすい場所では、明示的な ItemDisplayBinding の方が安全です。

別解:ItemsSource を string にしてしまう(本当に表示だけでよい場合)

もし Picker の要素が「表示文字列そのもの」でよいなら、ObservableCollection<string> にするのが最も単純です。

public ObservableCollection&lt;string&gt; Genders { get; } = new()
{
    &quot;未選択&quot;,
    &quot;男性&quot;,
    &quot;女性&quot;,
    &quot;その他&quot;
};

[ObservableProperty]
private string? selectedGender;
&lt;Picker
    ItemsSource=&quot;{Binding Genders}&quot;
    SelectedItem=&quot;{Binding SelectedGender, Mode=TwoWay}&quot; /&gt;

ただしこの方式は、

  • API 送信用コード(M/F/X など)
  • 内部的なID
  • 表示文言のローカライズ

といった「表示以外の値」が必要になった瞬間に、結局モデル(PickerRoot のような型)へ戻すことになりがちです。要件が固まっていない段階なら、最初からモデル型で持っておく方が後戻りが少なくなります。

compiled binding をちゃんと効かせるメリット

「警告が出ても動くなら放置してもよいのでは?」と思うかもしれませんが、compiled binding を適切に効かせると、実務では次の恩恵が出ます。

  • リファクタリング耐性:プロパティ名を変えたときに XAML 側がコンパイルエラー/警告で気づける
  • 実行時エラーの予防:タイポで “画面が静かに空になる” 事故を減らせる
  • 性能面の安定:大量のアイテムを描画する画面で Binding のコストが積み上がるのを抑えられる

特に Picker は「画面を構成する小さな部品」ですが、ログイン・登録など頻出ページに置かれることが多いので、ここで正しいパターンを押さえておくと、別の画面(CollectionView や DataTemplate を使う画面)でも同じ考え方が使えます。

まとめ:混在バインディングは “型を分けて指定” がコツ

.NET MAUI の Picker で起きる XamlC 警告(XC0022 / XC0045)は、ほとんどの場合「ViewModel と Item の型を同じ場所で扱っているのに、x:DataType の付け方が 1 箇所に寄っている」ことが原因です。

  • Picker の ItemsSource / SelectedItem は ViewModel のプロパティ → Picker は ViewModel 型のまま
  • ItemDisplayBinding は ItemsSource の要素に対する Binding → Binding の中でアイテム型を指定

この方針で統一しておけば、「Picker だけ警告が残る」「無理に Picker に x:DataType を付けて別の警告が出る」といった状況から抜け出しやすくなります。混在バインディングは珍しい実装ではなく、むしろ MAUI の UI では頻出なので、今回のパターンをテンプレとして手元に置いておくのがおすすめです。

この記事を書いた人

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

コメント

コメントする

目次