.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 警告が出ることがあります。
- XC0022:
ItemDisplayBinding="{Binding PickerText}"が compiled binding になっていない、または型が推論できない
そこで「じゃあ Picker の型をアイテム型にすればいいのでは?」と考えて、Picker 要素に x:DataType="models:PickerRoot" を付けると、今度は別の警告に変わります。
- XC0045:
Property "Genders" not found on "PickerRoot"(ViewModel のプロパティが見つからない)
この 2 つの警告はセットで出やすく、原因を理解していないと「どっちを直せばいいのか分からない」状態になりがちです。
結論:Picker 要素に x:DataType=”models:PickerRoot” は付けない
結論はシンプルです。
Picker 要素の BindingContext は ViewModel のままにする必要があるため、Picker 要素に x:DataType="models:PickerRoot" を付けてはいけません。
ただし、PickerText は ItemsSource の各要素(= PickerRoot) のプロパティです。したがって、compiled binding 化したいなら「Picker ではなく、ItemDisplayBinding 側だけ」アイテム型を指定します。
実装例(このパターンが最も安全で、警告も消しやすいです):
<Picker
ItemsSource="{Binding Genders}"
SelectedItem="{Binding SelectedItemGender, Mode=TwoWay}"
ItemDisplayBinding="{Binding PickerText, x:DataType=models:PickerRoot}"
FontFamily="OpenSansSemibold"
VerticalOptions="CenterAndExpand"
HorizontalOptions="FillAndExpand"
FontSize="12" />
ポイントはここだけです。
ItemsSourceとSelectedItemは ViewModel のプロパティなので、Picker のx:DataTypeは ViewModel であるべきItemDisplayBindingは「各アイテムに対するバインディング」なので、Binding の中でx:DataType=models:PickerRootを指定する
なぜ起きる?x:DataType が決めているものを整理する
まず押さえたいのは、x:DataType の役割です。x:DataType は「その XAML スコープ(要素や DataTemplate)での Binding を、どの型としてコンパイルするか」を決めます。
たとえばページに以下があるとします。
<ContentPage
...
x:DataType="viewModels:LoginViewModel">
このとき、ページ配下の多くの要素は BindingContext を継承するため、通常は LoginViewModel を前提に compiled binding できます。つまり、次のような Binding は「LoginViewModel に該当プロパティがあるか」をビルド時にチェックできます。
<Label Text="{Binding Title}" />
<Button Command="{Binding LoginCommand}" />
一方で、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 が出ます。
<Picker
x:DataType="models:PickerRoot"
ItemsSource="{Binding Genders}"
SelectedItem="{Binding SelectedItemGender, Mode=TwoWay}"
ItemDisplayBinding="{Binding PickerText}" />
これは当然で、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)を指定することです。
<Picker
ItemsSource="{Binding Genders}"
SelectedItem="{Binding SelectedItemGender, Mode=TwoWay}"
ItemDisplayBinding="{Binding PickerText, x:DataType=models:PickerRoot}" />
こうすると、
ItemsSourceとSelectedItemは ViewModel(ページで指定したx:DataType)としてコンパイルItemDisplayBindingはPickerRootとしてコンパイル
という形で、必要な場所だけ型が切り替わり、XC0022/ XC0045 の両方を回避しやすくなります。
どこがどの型?混乱しやすいポイントを表で整理
「この Binding は ViewModel?それとも Item?」が腹落ちすると、同種の警告で迷いにくくなります。
| バインディングを書く場所 | 主な参照元 | 想定する型 | x:DataType を置く場所 |
|---|---|---|---|
| ContentPage / Layout 配下の通常の Binding | BindingContext | ViewModel(例:LoginViewModel) | ページやコンテナ要素(例:ContentPage の x:DataType) |
| Picker.ItemsSource | BindingContext | ViewModel(例:LoginViewModel) | ページ側の x:DataType に従う |
| Picker.SelectedItem | BindingContext | ViewModel(例:LoginViewModel) | ページ側の x:DataType に従う |
| Picker.ItemDisplayBinding | ItemsSource の各要素 | 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<PickerRoot> Genders { get; } = new()
{
new PickerRoot("未選択", ""),
new PickerRoot("男性", "M"),
new PickerRoot("女性", "F"),
new PickerRoot("その他", "X")
};
[ObservableProperty]
private PickerRoot? selectedItemGender;
// 必要なら選択変更時に Value を取り出して別プロパティへ反映するなど
partial void OnSelectedItemGenderChanged(PickerRoot? value)
{
// value?.Value を使ってAPI送信用のコードへ変換、など
}
}
XAML(ページ)
ページの x:DataType を ViewModel にしておき、Picker は ViewModel のまま、ItemDisplayBinding だけアイテム型を指定します。
<ContentPage
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:viewModels="clr-namespace:YourApp.ViewModels"
xmlns:models="clr-namespace:YourApp.Models"
x:Class="YourApp.Views.LoginPage"
x:DataType="viewModels:LoginViewModel">
<VerticalStackLayout Padding="16" Spacing="12">
<Label Text="性別" FontAttributes="Bold" />
<Picker
Title="選択してください"
ItemsSource="{Binding Genders}"
SelectedItem="{Binding SelectedItemGender, Mode=TwoWay}"
ItemDisplayBinding="{Binding PickerText, x:DataType=models:PickerRoot}" />
</VerticalStackLayout>
</ContentPage>
この形にしておくと、
- ViewModel のプロパティ名変更(Genders / SelectedItemGender)をしてもコンパイル時に検知しやすい
- Item 側(PickerRoot)の表示プロパティ名変更(PickerText)もコンパイル時に検知しやすい
- XamlC 警告を「握りつぶす」のではなく、意味を持って解消できる
というメリットが得られます。
Binding Mode の整理:ItemsSource を TwoWay にしなくていい理由
混在バインディングのサンプルで、ItemsSource に Mode=TwoWay を付けてしまう例を見かけることがあります。結論として、通常は不要です。
| プロパティ | よくある設定 | 推奨 | 理由 |
|---|---|---|---|
| ItemsSource | Mode=TwoWay | 指定しない(OneWay のままで十分) | UI が ItemsSource 自体を書き換える場面はほぼない(コレクションの中身更新は ObservableCollection が担う) |
| SelectedItem | Mode=OneWay | Mode=TwoWay | ユーザー操作で選択が変わるため、ViewModel に反映させたい |
| SelectedIndex | Mode=TwoWay | 状況次第 | Index ベースで扱う設計の場合はあり。ただし Item ベースの方が可読性・保守性は高い |
ItemsSource の TwoWay は「悪い」というより「意図が伝わりにくい」設定になりがちです。保守の観点では、必要なところだけ TwoWay にする方が安心です。
よくあるつまずき:x:DataType を付けたのに警告が残るときのチェック
同じ書き方をしたつもりでも、プロジェクト構成や名前空間でハマることがあります。警告が消えない場合は、次を順に確認すると切り分けが早いです。
名前空間(xmlns)の参照先が合っているか
xmlns:models="clr-namespace:YourApp.Models"が実際の namespace と一致しているか- 別アセンブリにモデルを分けている場合、
;assembly=XXXXを付ける必要があるか
別アセンブリ例:
xmlns:models="clr-namespace:YourApp.Models;assembly=YourApp.Core"
クラスやプロパティが public になっているか
XamlC の型チェックは public な型・メンバーを前提にすることが多いです。internal class PickerRoot や internal 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 を使わず、PickerRoot の ToString() をオーバーライドして表示させる方法もあります。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() => PickerText;
}
この場合、XAML はシンプルになります。
<Picker
ItemsSource="{Binding Genders}"
SelectedItem="{Binding SelectedItemGender, Mode=TwoWay}" />
ただし、この方法は「楽だが設計上のトレードオフ」があります。
| 観点 | ToString() 方式 | ItemDisplayBinding 方式 |
|---|---|---|
| 実装のシンプルさ | ◎(XAML が短い) | ○(1行増える程度) |
| 表示要件の変更(多言語・条件分岐など) | △(ToString にロジックが寄りやすい) | ◎(表示プロパティを差し替えやすい) |
| デバッグ時の識別 | △(Value を見たいときに不便なことがある) | ○(モデルのまま保持できる) |
| MVVM の責務分離 | △(表示都合がモデルに入りやすい) | ◎(表示用プロパティを設計しやすい) |
小規模で「表示は常に 1 文字列で十分」なら ToString() 方式でも良いですが、ログイン画面のように将来仕様が増えやすい場所では、明示的な ItemDisplayBinding の方が安全です。
別解:ItemsSource を string にしてしまう(本当に表示だけでよい場合)
もし Picker の要素が「表示文字列そのもの」でよいなら、ObservableCollection<string> にするのが最も単純です。
public ObservableCollection<string> Genders { get; } = new()
{
"未選択",
"男性",
"女性",
"その他"
};
[ObservableProperty]
private string? selectedGender;
<Picker
ItemsSource="{Binding Genders}"
SelectedItem="{Binding SelectedGender, Mode=TwoWay}" />
ただしこの方式は、
- 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 では頻出なので、今回のパターンをテンプレとして手元に置いておくのがおすすめです。

コメント