.NET MAUIで「JSONは読めているのに、ImageやLabelだけが出ない」現象は、データやMVVMではなく“見た目側(XAMLリソース)”に原因が潜んでいることがあります。この記事では、実際にハマりやすいStaticResourceのスタイル未定義(または読み込み漏れ)を軸に、切り分けと再発防止までまとめます。
起きている現象:JSONは読めるのに、画面だけ空っぽになる
今回の状況を整理すると、ポイントは次のとおりです。
- data.cs → data.json へデータソースを移行中
- ボタンのうち「get closest」では正しい結果が表示される(=JSON読み込み自体は成功)
- しかし「get randos」ではMainPage.xaml上でImageとLabelが表示されない
- デバッグ上は「画像パスやデータが取得できている」ように見えるが、UIに出ない
この手の問題は、つい「JSONのデシリアライズが失敗している」「BindingContextがズレた」「Imageのパスが違う」と疑いがちです。しかし、ボタンAでは表示できていて、ボタンBだけ表示できない場合、データ取得より“そのときだけ適用されるXAMLの設定”を疑うのが近道です。
結論:原因はMVVMやJSONではなく「存在しないStyle(StaticResource)の参照」
原因は、XAMLで存在しないStaticResource(Style)を参照していたことでした。たとえば、次のような指定がある一方で、CardView や LargeLabel がResourceDictionary内に定義されていない(あるいは読み込めていない)と、期待どおりに描画されない、もしくはページのロード時点で例外が発生してUIが崩れて見える、ということが起こります。
<Frame HeightRequest="125" Style="{StaticResource CardView}">
...
</Frame>
<Label Style="{StaticResource LargeLabel}" Text="{Binding Name}" />
「でも例外が出ていない(ように見える)」というケースもあります。実際には、IDEの出力ウィンドウやデバッガにだけ警告・例外が出ていて見落としていたり、例外が発生しても画面が真っ白ではなく“たまたま”表示が続く構造になっていて一部が出ないように見えることもあります。まずは「XAMLリソースの解決が成功しているか」を疑ってください。
なぜStaticResourceの未定義が「表示されない」に見えるのか
StaticResourceは、XAMLが読み込まれる時点で必ず見つかることが前提の参照です。つまり、キーが存在しない/辞書がマージされていない/名前が違う、といった状態は「その場で解決不能」です。
このとき起きやすい現象は、ざっくり次の3パターンです。
| 見え方 | 内部で起きがちなこと | 結果 |
|---|---|---|
| 特定のView(FrameやLabel)だけ出ない | スタイル適用時に例外/スタイル内Setterの影響(サイズ0、透明、余白過大など) | 「データはあるのにUIが出ない」ように見える |
| ページ全体が崩れる/一部だけ初期化されない | XAMLの読み込み途中で止まる、または以降の要素が生成されない | イベントやBindingのせいに見える |
| Debugでは気づきにくい | 例外がOutputに出ているが、画面操作はできてしまう | 原因がデータ側にあると誤認しやすい |
さらに厄介なのは、「get closest」では表示される点です。これは「get closestで使う領域には問題のStyleが登場しない」「get randosでだけCardViewやLargeLabelが使われる」など、ボタンごとに通るUIツリーが違うことで説明がつきます。データは正しくても、表示側が“その瞬間だけ”壊れていると、症状がボタン単位で分かれます。
最短で原因を切り分ける手順(まずはStyleを疑う)
時間を溶かしがちなポイントを潰すため、切り分けは次の順番がおすすめです。
手順1:Style指定を一旦削除して「表示できるか」を確認
まずは、問題の要素からStyle指定を外してみてください。最初は見た目がダサくてもOKです。目的は「UIは出せるのか(Bindingは生きているのか)」を確認することです。
<Frame HeightRequest="125">
...
</Frame>
<Label Text="{Binding Name}" />
これで表示できるなら、原因はほぼStyle参照(キー名、辞書読み込み、マージ漏れ、適用内容)に絞れます。逆に、これでも出ないなら、BindingContextやレイアウト制約(Grid/StackLayoutのサイズ)やIsVisibleなど、別の線を追えばよいです。
手順2:Styleのキー名を「参照側」と「定義側」で突き合わせる
StaticResourceはキー名が命です。参照側が CardView なら、定義側も x:Key="CardView" である必要があります。Cardview / Card_View / cardView のような微妙な差でもアウトです。
手順3:スタイルを別ファイルにしているなら「MergedDictionaries」を確認
定義したつもりのスタイルが、実はアプリ側に読み込まれていない、というパターンが非常に多いです。特に、ResourceDictionaryを分割しているプロジェクトでは、マージ設定が抜けると一発でハマります。
正しい対処:参照しているStyleをResourceDictionaryに定義する
対処はシンプルで、XAMLで参照しているキーのStyleを、アプリのリソースとして定義します。定義場所は大きく2通りです。
パターンA:App.xamlにまとめて定義する(全ページで使う)
全ページで使い回すスタイルは、App.xaml の <Application.Resources> に置くのが分かりやすいです。
<Application
x:Class="YourApp.App"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml">
<Application.Resources>
<ResourceDictionary>
<Style x:Key="CardView" TargetType="Frame">
<Setter Property="CornerRadius" Value="12" />
<Setter Property="Padding" Value="12" />
<Setter Property="Margin" Value="0,8" />
<Setter Property="HasShadow" Value="True" />
</Style>
<Style x:Key="LargeLabel" TargetType="Label">
<Setter Property="FontSize" Value="18" />
<Setter Property="FontAttributes" Value="Bold" />
<Setter Property="LineBreakMode" Value="TailTruncation" />
</Style>
</ResourceDictionary>
</Application.Resources>
</Application>
これで、ページ側の {StaticResource CardView} / {StaticResource LargeLabel} が解決できるようになります。
パターンB:ResourceDictionaryを別ファイルに分けてマージする(規模が大きい場合)
スタイルが増えてきたら、Styles.xaml のようなResourceDictionaryファイルに分けると管理しやすいです。その場合はマージ(MergedDictionaries)が必須です。
Styles.xaml
<ResourceDictionary
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml">
<Style x:Key="CardView" TargetType="Frame">
<Setter Property="CornerRadius" Value="12" />
<Setter Property="Padding" Value="12" />
<Setter Property="Margin" Value="0,8" />
<Setter Property="HasShadow" Value="True" />
</Style>
<Style x:Key="LargeLabel" TargetType="Label">
<Setter Property="FontSize" Value="18" />
<Setter Property="FontAttributes" Value="Bold" />
<Setter Property="LineBreakMode" Value="TailTruncation" />
</Style>
App.xaml(マージ)
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Resources/Styles/Styles.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
この「Sourceのパス違い」「フォルダ構成の変更」「ファイル名変更」があると、定義していても読み込まれず、結果としてStaticResourceが解決できません。分割管理している人ほど、ここは必ずチェックしてください。
StaticResourceでハマりやすい落とし穴チェックリスト
再発防止のために、ありがちなミスを先回りで潰しておきましょう。
| チェック項目 | ありがちなミス | 対策 |
|---|---|---|
| キー名 | 参照側と定義側で大文字小文字やスペルが違う | 参照文字列をコピーして定義側へ貼り付ける |
| 定義場所 | Page.Resourcesに定義したのに、別ページから参照している | 全体共通ならApp.xamlへ、ページ限定ならContentPage.Resourcesへ |
| 辞書のマージ | 別ファイルに定義したがMergedDictionariesに追加していない | App.xamlにマージし、Sourceパスも確認する |
| TargetType | Frame用のStyleをLabelに当てている/型不一致 | TargetTypeを正しく設定し、使い回しは用途ごとに分ける |
| Setter内容 | Padding/Margin/HeightRequest等が強すぎて表示領域が潰れる | 一度Setterを最小化し、少しずつ戻して原因を特定する |
| テーマ/状態分岐 | Dark/LightやVisualStateで色が背景と同化する | 一時的にTextColor/BackgroundColorを固定して見える化 |
「get randos」だけ出ないときの具体的なデバッグの観点
今回のようにボタンによって症状が変わる場合、次の観点が効果的です。
観点1:そのボタンで表示するテンプレート/要素だけにStyle参照が混ざっていないか
例えば、ランダム表示用のカードだけが CardView / LargeLabel を使っているなら、まさにそこが地雷です。データ取得のロジックは正しくても、UI生成の途中でスタイル解決に失敗して描画が止まる(または見えない)可能性があります。
観点2:Styleが「表示しない」状態を作っていないか
未定義の問題が解決しても、Styleの中身が原因で結果的に見えなくなるケースがあります。たとえば次のような設定があると、UIは存在していても“見えない”状態になります。
- Opacity = 0(透明)
- TextColor が背景色と同じ(同化)
- HeightRequest/WidthRequest が極端に小さい(潰れる)
- Margin が大きすぎて画面外に押し出す
- IsVisible = False をStyleで指定してしまう
対策としては、Styleをいきなり完成形に戻すのではなく、Setterを最小限にしてから段階的に戻すのが確実です。
観点3:Bindingが正しいかの確認は「見える化」してやる
「デバッグ上は値が取れている」は、だいたい本当です。ただしUI側のBindingが別インスタンスを見ていたり、表示テンプレート上のBindingContextが変わっていることもあります。見える化のために、まずは固定色&固定文字で“描画そのもの”を確認するのが早いです。
<Label Text="描画テスト" TextColor="Red" />
これすら出ないなら、StyleやBinding以前にレイアウト構造(Gridの行高さ、StackLayoutの親がHeight=0、ScrollViewの内側など)が怪しい、と判断できます。
StaticResourceとDynamicResourceの違い(今回の学びとして押さえる)
今回の根本原因は「参照先が存在しない」なので、まずは定義とマージが最重要です。そのうえで、次の違いも知っておくと設計が安定します。
| 種類 | 解決タイミング | 向いている用途 | 注意点 |
|---|---|---|---|
| StaticResource | XAML読み込み時に即解決 | 基本のスタイル、固定リソース | キーが無いと解決不能。辞書の読み込み漏れに弱い |
| DynamicResource | 実行時に遅延解決(変更にも追随) | テーマ切り替え、実行時に変わる色や値 | 乱用すると追跡が難しくなる。まずはStaticで堅く作る |
「とりあえずDynamicにすれば直る」という話ではありません。根本の未定義・未マージを放置すると別の不具合が出ます。まずは参照先を正しく用意し、必要なところだけDynamicにするのが安全です。
今後ハマらないための実務的なコツ
コツ1:スタイルキーは命名規則を固定する
キー名のブレは、長期的に見ると確実に事故ります。例えば、用途別に接頭辞を付けるだけでも混乱が減ります。
- Frame用:Frame.Card、Frame.ListItem
- Label用:Label.Title、Label.Body
- Color用:Color.Primary、Color.Surface
命名に一貫性があると、参照ミスやタイポが激減します。
コツ2:スタイルを分割するなら「入口はApp.xaml」一本にする
Stylesを複数ファイルに分ける場合でも、マージの入口をApp.xamlに固定しておくと、どのページからでも同じリソース解決ルールになり、トラブルの発生率が下がります。ページごとにResourcesを持つのは、ページ限定スタイルだけに絞るのが無難です。
コツ3:まずはStyleを外して“表示できる状態”を作る
見た目に凝っているほど、デバッグは難しくなります。詰まったら「Styleを外す」「固定色で表示する」「固定文字で描画する」という原始的な確認が最速です。データ、Binding、レイアウト、Styleのどこに問題があるかが、数分で分かります。
補足:Imageが絡むときに一緒に確認しておくと安心なこと
今回の本丸はStyleでしたが、Imageが絡む画面は別の落とし穴も多いので、ついでに“最低限の確認項目”だけ置いておきます。
- ローカル画像なら、プロジェクトでの扱い(MauiImageなど)と参照名が一致しているか
- URL画像なら、通信可否(https、証明書、ネットワーク)とキャッシュの影響を切り分けできているか
- Imageの親レイアウトが高さ0になっていないか(Gridの行定義、ScrollView内の制約など)
ただし、今回のように「デバッグ上はパスが取れている」「別の操作では表示できる」状況なら、Image自体よりもスタイルやレイアウト制約の影響を優先して疑うのが効率的です。
まとめ:データが正しいのにUIが出ないなら、まずXAMLリソースを疑う
.NET MAUIで「JSONは読めているのに、Image/Labelが表示されない」ケースは、MVVMやデシリアライズが原因とは限りません。特に、XAMLでStaticResourceを使っていると、スタイルキーの未定義・マージ漏れ・キー名の不一致が“表示されない”という形で現れます。
最短ルートは、Style指定を外して表示確認 → キー名突合 → MergedDictionaries確認の順に切り分けることです。データ側で長時間悩む前に、XAMLリソースの足元を固めるだけで、驚くほどスッと解決するはずです。

コメント