Xamarin.Forms/.NET MAUI PickerのItemDisplayBindingでXC0022警告が消えない原因とx:DataTypeでCompiled Bindingを有効化する方法

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が参照している対象の型が違います。

対象代表的なプロパティ実行時のBindingContextCompiled Bindingで必要な型注釈
Picker本体のBinding(ItemsSource/SelectedItemなど)ItemsSource, SelectedItemページ(または親要素)のViewModelページ/レイアウト側の x:DataType="ViewModel型"
ItemDisplayBindingName など“表示用”プロパティ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される
  • ItemDisplayBindingItem に対して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型ItemsSelectedCategory など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(モデル)に表示専用プロパティを作る方法です。例えば NameCode を組み合わせて表示したいなら、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>Itemx: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の警告と上手く付き合えるようになります。

この記事を書いた人

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

コメント

コメントする

目次