Xamarin.Formsや.NET MAUIのPickerでx:DataTypeを設定すると、ItemsSourceはCompiled BindingになるのにItemDisplayBindingだけXC0022警告が消えない…という沼にハマりがちです。本記事では原因(BindingContextがアイテムに切り替わる)を整理し、警告を消しつつ型安全とパフォーマンスを両立する具体的なXAML例を解説します。
起きている現象:PickerのItemDisplayBindingだけXC0022が消えない
MVVMで画面を作っていると、ページ(ContentPage)やレイアウト(Grid/StackLayoutなど)に x:DataType を付けてCompiled Binding(コンパイル時バインディング)を有効にするのは定番です。ところが、Picker だけは少しクセがあり、次のような状況に遭遇します。
ItemsSource="{Binding Items}"はViewModel上のItemsを参照しているので、Compiled Bindingとして認識され、警告も出ない- 一方で
ItemDisplayBinding="{Binding Name}"にはXC0022の警告が出続ける(「x:DataType を指定すればコンパイルできて高速化できる」系の警告)
そして「じゃあPicker自体に x:DataType を付ければいいのでは?」と考えて型を切り替えると、今度は ItemsSource 側が型解決できずに崩れたり、別の警告が出たりして、結局どちらか片方が犠牲になります。ここがこの問題の分かりにくいポイントです。
前提:Compiled Binding(x:DataType)とXC0022を“放置しない”メリット
まず、なぜ XC0022 を消す価値があるのかを整理します。警告はエラーではないため動作はしますが、放置すると次のコストが積み上がります。
| 観点 | Compiled Bindingが効いていると | 効いていないと(警告のまま) |
|---|---|---|
| パフォーマンス | リフレクション依存が減り、表示・スクロール・再描画の負荷が下がりやすい | バインディング解決が実行時依存になり、端末・画面数が増えるほどじわじわ効く |
| 型安全 | 存在しないプロパティ参照がコンパイル段階で気づきやすい | リネームや削除に気づけず、実機で初めて表示が崩れるケースが起きる |
| 保守性 | “このBindingは何の型に対するものか”が明確になり、レビューもしやすい | 暗黙の前提が増え、チームが大きくなるほど事故が起きやすい |
Pickerは入力コントロールの中でも使用頻度が高く、かつ画面遷移のたびに初期化されがちです。小さな警告でも、積み重なると影響が出ます。だからこそ、原因を理解して、きれいに解消しておくのが得策です。
結論:ItemDisplayBindingのBindingContextは“ページのViewModel”ではなく“ItemsSourceの各アイテム”
この問題の核心はシンプルです。ItemDisplayBindingのバインディングコンテキスト(BindingContext)は、ページのViewModelではなく、ItemsSourceに入っている各アイテムに切り替わるという点にあります。
つまり、同じPickerの中でも、Bindingが参照している対象の型が違います。
| 対象 | 代表的なプロパティ | 実行時のBindingContext | Compiled Bindingで必要な型注釈 |
|---|---|---|---|
| Picker本体のBinding(ItemsSource/SelectedItemなど) | ItemsSource, SelectedItem | ページ(または親要素)のViewModel | ページ/レイアウト側の x:DataType="ViewModel型" |
| ItemDisplayBinding | Name など“表示用”プロパティ | ItemsSource の各要素(アイテム) | {Binding ... , x:DataType=アイテム型} |
ここを理解すると、なぜ「Pickerにx:DataTypeを付けると片方が壊れる」のかが腑に落ちます。Pickerに x:DataType を付ける行為は、Picker配下にある“すべてのBinding”の既定の型を一括で決めてしまうためです。ところがPickerには、ViewModel型で解決したいBinding(ItemsSourceなど)と、Item型で解決したいBinding(ItemDisplayBinding)が共存しています。だから一括指定では破綻します。
よくある失敗例:Pickerにx:DataTypeを付けてしまう
例えば次のように「ItemDisplayBindingに合わせてPickerをItem型にする」実装は、一見正しそうに見えます。
<ContentPage
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:MauiApp5"
x:DataType="local:MyViewModel">
<Picker
x:DataType="local:Item"
ItemsSource="{Binding Items}"
ItemDisplayBinding="{Binding Name}" />
しかしこの場合、ItemsSource の {Binding Items} は local:Item に対して評価される前提になってしまいます。つまり「Item型にItemsプロパティはない」ため、型解決に失敗します。逆にPickerをViewModel型のままにすると、今度は ItemDisplayBinding が「ViewModelにNameはない」と判断され、警告が消えません。
解決策:Binding式の中でx:DataTypeを“アイテム型”として明示する
対処のポイントは、Picker全体に一律で型を付けるのではなく、型が切り替わるBindingだけに“局所的に”型注釈を付けることです。具体的には、ItemDisplayBinding のBinding式に x:DataType を書き、表示対象のアイテム型を明示します。
<ContentPage
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:MauiApp5"
x:DataType="local:MyViewModel">
<Picker
ItemsSource="{Binding Items}"
ItemDisplayBinding="{Binding Name, x:DataType=local:Item}" />
</ContentPage>
これで次が同時に成立します。
- ページ全体(Picker本体のBinding)は
MyViewModelに対してCompiled Bindingされる ItemDisplayBindingはItemに対してCompiled Bindingされる- 結果として
XC0022が解消され、型安全とパフォーマンスを両立できる
この書き方は「Bindingごとに型注釈を上書きできる」ことを利用しています。ページ/要素に付けた x:DataType は“既定値”として働きますが、Binding式に x:DataType が書かれている場合は、そのBindingだけ別の型として解釈されます。Pickerのようにコンテキストが切り替わるケースでは、この局所上書きが最も相性の良い回避策です。
動く最小サンプル:ViewModelとItemの型を揃える
実装時に迷いやすいのが「結局、どの型をどこに書けばいいのか」です。ここでは、実務でもよくある ObservableCollection<Item> をItemsSourceにして、SelectedItem をViewModelにTwoWayで戻す構成の最小サンプルを示します。
XAML(PickerのCompiled Bindingを両立)
<ContentPage
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:viewmodels="clr-namespace:MauiApp5.ViewModels"
xmlns:models="clr-namespace:MauiApp5.Models"
x:Class="MauiApp5.Views.SamplePage"
x:DataType="viewmodels:SampleViewModel">
<VerticalStackLayout Padding="16" Spacing="12">
<Label Text="カテゴリを選択してください" />
<Picker
Title="選択..."
ItemsSource="{Binding Categories}"
SelectedItem="{Binding SelectedCategory}"
ItemDisplayBinding="{Binding Name, x:DataType=models:Category}" />
<Label
Text="{Binding SelectedCategory.Name}"
FontAttributes="Bold" />
</VerticalStackLayout>
C#(モデル)
namespace MauiApp5.Models
{
public class Category
{
public string Name { get; set; } = string.Empty;
// 例:内部IDなど、表示以外の情報も持てる
public int Id { get; set; }
}
}
C#(ViewModel)
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using MauiApp5.Models;
namespace MauiApp5.ViewModels
{
public class SampleViewModel : INotifyPropertyChanged
{
public ObservableCollection Categories { get; } = new();
private Category? _selectedCategory;
public Category? SelectedCategory
{
get => _selectedCategory;
set
{
if (_selectedCategory == value) return;
_selectedCategory = value;
OnPropertyChanged();
}
}
public SampleViewModel()
{
Categories.Add(new Category { Id = 1, Name = "食料品" });
Categories.Add(new Category { Id = 2, Name = "日用品" });
Categories.Add(new Category { Id = 3, Name = "書籍" });
SelectedCategory = Categories.FirstOrDefault();
}
public event PropertyChangedEventHandler? PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string? propertyName = null)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
このサンプルのポイントは、次の2種類の型注釈が共存していることです。
ContentPage x:DataType="SampleViewModel":ページ全体のBinding(Categories/SelectedCategoryなど)を型安全にするItemDisplayBinding="{Binding Name, x:DataType=models:Category}":表示対象のアイテムの型(Category)を型安全にする
なぜこの指定でXC0022が消えるのか:警告の発生条件を分解する
XC0022は、ざっくり言うと「このBindingは型が分からない(または未指定)のでコンパイルできない。x:DataTypeを指定すればコンパイルできる可能性がある」という種類の警告です。PickerのItemDisplayBindingでは、まさに“型が曖昧”になりやすい構造が原因になります。
警告が出るパターンをもう少し噛み砕くと、次のようなロジックになります。
| 状況 | XAMLコンパイラが推論する型 | 結果 |
|---|---|---|
ページに x:DataType="ViewModel" があるが、ItemDisplayBindingは未注釈 | ViewModel型(ページの既定型) | Name がViewModelに存在しない場合、コンパイルできず警告 |
Pickerに x:DataType="Item" を付けてしまう | Item型 | Items や SelectedCategory などViewModel側のプロパティが見えず破綻 |
ItemDisplayBindingのBindingだけに x:DataType="Item" を付ける | ViewModel(他のBinding)とItem(表示用Binding)が共存 | 両方が型解決でき、警告が解消される |
つまり「PickerのItemDisplayBindingは、ページの文脈とは別の型で評価される」ことを、コンパイラにも正しく伝える必要がある、という話です。
実務の落とし穴:ItemsSourceの要素型が“別名前空間/別アセンブリ”にある場合
サンプルでは models:Category のように同一プロジェクトの名前空間を指していますが、現場ではモデルが別プロジェクト(例:MyApp.Core)に分かれていることも珍しくありません。その場合、次の点を確認します。
- xmlnsの定義が正しいか:
clr-namespace:とassembly=の指定が必要な場合がある - 型がpublicか:
internalだとXAML側から参照できず、型解決が失敗する - ビルド順・参照関係:ViewsプロジェクトからModelsプロジェクトを参照できているか
別アセンブリの例(概念例)を示します。
<ContentPage
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:vm="clr-namespace:MyApp.Views.ViewModels"
xmlns:core="clr-namespace:MyApp.Core.Models;assembly=MyApp.Core"
x:DataType="vm:SampleViewModel">
<Picker
ItemsSource="{Binding Items}"
ItemDisplayBinding="{Binding Name, x:DataType=core:Item}" />
この「xmlns:core」の書き方がズレていると、x:DataType による型注釈自体が解決できず、結果として警告が残る(または別の警告が増える)ので注意が必要です。
応用:表示文字列を加工したい場合(ConverterとComputed Property)
PickerはItemTemplateを持たないため、表示の自由度は高くありません。その代わり、ItemDisplayBindingと表示用プロパティ設計で十分対応できます。ここでは「表示名を加工したい」ケースを2パターンで紹介します。
パターンA:表示専用のプロパティをItem側に用意する(おすすめ)
最も事故が少ないのは、Item(モデル)に表示専用プロパティを作る方法です。例えば Name と Code を組み合わせて表示したいなら、DisplayText を用意します。
public class Category
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public string Code { get; set; } = string.Empty;
public string DisplayText => $"{Name}({Code})";
}
そしてXAMLは次のようにします。
<Picker
ItemsSource="{Binding Categories}"
SelectedItem="{Binding SelectedCategory}"
ItemDisplayBinding="{Binding DisplayText, x:DataType=models:Category}" />
表示用プロパティを用意する利点は、UI側のConverter依存が減り、リファクタリングもしやすくなる点です。特に複数画面で同じ表示を使う場合、表示ロジックをモデル側へ寄せておくと、修正箇所が集約されます。
パターンB:IValueConverterで加工する(使うなら範囲を絞る)
モデルに手を入れられない(外部ライブラリのDTOなど)場合はConverterが候補になります。この場合も、ItemDisplayBindingのBindingに対して x:DataType を付けておくことで、“入力型が何か”を明示できます。
<ContentPage.Resources>
<ResourceDictionary>
<local:CategoryToTextConverter x:Key="CategoryToTextConverter" />
</ResourceDictionary>
</ContentPage.Resources>
Converterの実装例は次の通りです(ConvertBackは未対応にしておくと事故が減ります)。
using System;
using System.Globalization;
using Microsoft.Maui.Controls;
using MauiApp5.Models;
namespace MauiApp5
{
public class CategoryToTextConverter : IValueConverter
{
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is Category c)
return $"{c.Name}(ID:{c.Id})";
return string.Empty;
}
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
=> throw new NotSupportedException();
}
}
ポイントは {Binding .} で“アイテム自身”をConverterへ渡すことです。こうしておけば、Converter内部で複数プロパティを参照して文字列を組み立てられます。反面、ConverterはUI層の資産になりやすいため、乱用すると依存関係が増えます。表示ルールが複数画面で共有されるなら、パターンAのほうが管理しやすいことが多いです。
応用:SelectedItemとItemDisplayBindingを併用するときの型安全
SelectedItem をViewModelへTwoWayでバインドする場合、SelectedCategory の型は当然 Category になります。ここでありがちなのが「SelectedItemの型を object にしてしまう」ことです。動くのですが、型安全が落ち、後でキャスト地獄になります。
おすすめは、ViewModel側のSelectedItemプロパティも具体型(Category? など)で持つことです。そうすると、SelectedCategory.Name のような参照もコンパイル時にチェックされ、Null許容を含めて設計できます。
チェックリスト:XC0022が残るときに見るべきポイント
上の解決策を入れたのに警告が残る場合、原因は大抵「型注釈が正しく解決できていない」か「Bindingが想定と違う文脈で解釈されている」かのどちらかです。確認項目をまとめます。
| チェック項目 | よくある症状 | 対処 |
|---|---|---|
ItemDisplayBindingのBindingに x:DataType を書いているか | ページ側にx:DataTypeがあるのに警告が消えない | ItemDisplayBinding="{Binding 表示プロパティ, x:DataType=アイテム型}" の形にする |
| xmlns(名前空間)が正しいか | x:DataType=models:Item が解決されず別の警告が出る | clr-namespace: と必要に応じて assembly= を見直す |
| モデル型がpublicか | ビルド時に型が見つからない | public class Item にする(internalだと参照できないことがある) |
| ItemsSourceの要素型とx:DataTypeが一致しているか | 表示プロパティが見つからない/警告が残る | ObservableCollection<Item> の Item と x:DataType を揃える |
| 表示プロパティ名のスペル | 実行時は空表示、ビルドでは警告 | リネーム後に古い名前が残っていないか確認(Compiled Bindingだと気づきやすい) |
“警告抑止”ではなく“正しい型注釈”として捉えるのがコツ
XC0022を消すためだけに警告を無視したり、無理やり抑止したりするのはおすすめしません。PickerのItemDisplayBindingは、そもそもページのViewModelとは異なる型を参照します。ここを正しくモデル化すると、次の効果が得られます。
- 表示用プロパティのリネームで即ビルド時に検知できる
- 「このPickerの表示はCategory(Item)に依存している」とXAMLだけで読み取れる
- ViewModel側のBinding(ItemsSource/SelectedItem)と責務が分離され、後から拡張しやすい
実務では、Pickerの表示名は仕様変更で頻繁に変わります。表示名が Name から DisplayName へ変わる、複数項目を連結する、言語切り替えに対応する…などです。こうした変更に耐えるためにも、ItemDisplayBindingの型注釈は“今後の保守性への投資”として捉えると、作業の価値が上がります。
まとめ:Pickerは“ViewModel型”と“Item型”の二重スコープを意識する
Xamarin.Forms / .NET MAUIのPickerで ItemDisplayBinding だけCompiled Bindingが効かず XC0022 が消えないのは、ItemDisplayBindingのBindingContextがItemsSourceの各アイテムに切り替わるためです。ページ(または親要素)に x:DataType を付けてViewModelを型注釈しつつ、ItemDisplayBinding のBinding式内で x:DataType=アイテム型 を明示すれば、両方のBindingを崩さずに警告を解消できます。
Pickerに限らず、「同じコントロールの中に、異なる型のBindingContextが混在する」ケースは他にもあります。困ったときは、まず“このBindingは誰(どの型)に対するものか”を切り分け、必要ならBinding単位で型注釈を上書きする——この考え方を持っておくと、XAMLの警告と上手く付き合えるようになります。

コメント