.NET MAUI で Windows ビルド時に画像が表示されない原因と解決策

.NET MAUI でクロスプラットフォームアプリを作っていると、「Android では PNG 画像がちゃんと表示されるのに、Windows ではまったく出てこない」という現象に遭遇しがちです。特に Source = "MainMenuBts/play.png" のようにサブフォルダー付きのパスで指定している場合、原因はほぼ「Windows ビルド時の画像リソースの扱い」にあります。この記事では、なぜそうなるのか、そしてどう直せば良いのかを、実践的な手順とともに詳しく解説します。

目次

.NET MAUI で「Windows だけ画像が表示されない」症状の概要

問題の典型的なパターンは次のようなものです。

  • 開発環境:.NET MAUI(C#)、マルチプラットフォーム(Android / Windows)
  • 画像ファイル:Resources\Images\MainMenuBts\play.png などサブフォルダー配下に配置
  • コード側の指定:Source = "MainMenuBts/play.png" のようにフォルダー名を含めて参照
  • 現象:
    • Android … 画像が正常に表示される
    • Windows … 画像がまったく表示されない(透明のまま、もしくは空白)

実際の C# コード例は、例えば次のようなものです。


var playButton = new ImageButton
{
    Source = "MainMenuBts/play.png",
    BackgroundColor = Colors.Transparent,
    WidthRequest  = scaleImages.Scaling(),
    HeightRequest = scaleImages.Scaling(),
};

Android ではこの書き方で問題なく表示される一方、Windows では表示されません。ここに「プラットフォームごとのリソースの扱い方の違い」が隠れています。

原因:Windows ビルドでは画像リソースが「フラット化」される

.NET MAUI プロジェクトには、既定で次のような設定が含まれています。


<MauiImage Include="Resources\Images\**" />

これは「Resources\Images 以下の画像を MAUI 画像リソースとしてビルドに含める」という指定です。ところが ビルド後の配置方法はプラットフォームによって異なります。

特に重要なのは、Windows では MSBuild が画像を出力フォルダーにコピーする際にフォルダー構造をフラット化する、という挙動です。これにより、ビルド後の配置と、コードで指定しているパスが一致しなくなってしまいます。

プラットフォームビルド後の配置フォルダー構造実行時の参照例
AndroidAssets / Resources 配下サブフォルダーを保持MainMenuBts/play.png でも参照可能
Windowsbin\Debug\net8.0-windows\... などの出力フォルダーフラット化される(1階層に並ぶ)play.png のみ有効、MainMenuBts/play.png は不一致

つまり、Windows 実行時には 出力フォルダーに単純に play.png が置かれているだけであり、MainMenuBts/play.png というパスのファイルは存在しません。そのため、Source = "MainMenuBts/play.png" と書いても一致せず、画像が読み込まれないのです。

最短の解決策:画像をフラットに置き、ファイル名だけで参照する

もっともシンプルで再現性の高い解決策は、サブフォルダー構造をやめて画像をフラットに配置することです。手順は非常に単純です。

手順 1:画像を Resources\Images 直下に移動する

  1. プロジェクト内の画像ファイルを、次のように移動します。
    • 変更前:Resources\Images\MainMenuBts\play.png
    • 変更後:Resources\Images\play.png
  2. 既定の MauiImage 設定(<MauiImage Include="Resources\Images\**" />)があれば、 特別な設定は不要です。そのままビルド時に自動的に画像が登録されます。

手順 2:コード側の参照を「ファイル名のみ」に変更する

C# コードは次のように修正します。


var playButton = new ImageButton
{
    Source = "play.png", // ← フォルダー名を削除
    BackgroundColor = Colors.Transparent,
    WidthRequest  = scaleImages.Scaling(),
    HeightRequest = scaleImages.Scaling(),
};

XAML で指定している場合も同様です。


&lt;!-- 修正前 --&gt;
&lt;ImageButton
    Source="MainMenuBts/play.png"
    BackgroundColor="Transparent" /&gt;

&lt;!-- 修正後 --&gt;
&lt;ImageButton
    Source="play.png"
    BackgroundColor="Transparent" /&gt;

手順 3:クリーンビルドして動作を確認する

  1. 一度「クリーン」または dotnet clean を実行する
  2. Windows をスタートアッププロジェクトにして再ビルド
  3. 画像が表示されるかを確認する

この方法は、画像の数が増えても設定変更が最小限で済みます。クロスプラットフォームで同じファイル名だけで参照できるため、後々の保守も非常に楽になります。

フォルダー階層を維持したい場合の代替策:LogicalName を使う

「どうしてもフォルダー構造ごと管理したい」「MainMenuBts/play.png というパスで書いたコードを変えたくない」というケースでは、.csproj 側で個別に LogicalName を設定する方法があります。

.csproj の設定例


&lt;ItemGroup&gt;
  &lt;MauiImage Include="Resources\Images\MainMenuBts\play.png"&gt;
    &lt;LogicalName&gt;MainMenuBts/play.png&lt;/LogicalName&gt;
  &lt;/MauiImage&gt;
&lt;/ItemGroup&gt;

このように LogicalName を指定すると、実行時には Source = "MainMenuBts/play.png" というパスで参照できるようになります。


var playButton = new ImageButton
{
    Source = "MainMenuBts/play.png",
    BackgroundColor = Colors.Transparent,
};

ただし、この方法には明確なトレードオフがあります。

項目フラット構造(推奨)LogicalName で階層維持
設定の手間ほぼ不要(既定設定のままでよい)画像ごとに .csproj へ追記が必要
画像が増えた場合移動と命名ルールの整理だけで済む.csproj が肥大化し、管理が大変になる
可読性ファイル名だけなので比較的シンプルフォルダー構造がそのままパスに反映され、階層管理しやすい
おすすめ度高い(実践向き)限定的(画像点数が少ない場合などに限定)

画像数が少ないサンプルアプリやプロトタイプであれば LogicalName を利用しても良いですが、実務レベルのアプリでは フラット構造+ファイル名参照のほうが圧倒的に楽です。

Windows 出力フォルダーを確認して挙動を理解する

「なぜ画像が見つからないのか」を実感するには、実際に Windows の出力フォルダーを覗いてみるのがおすすめです。

出力フォルダーの場所の一例

デバッグビルドの場合、例えば次のようなパスに展開されます(構成によって多少異なります)。


&lt;プロジェクトルート&gt;\bin\Debug\net8.0-windows\win10-x64\

このフォルダーを開くと、Resources\Images 以下に置いていたファイルがどのようにコピーされているかが分かります。サブフォルダーなしで、play.png、pause.png、stop.png といったファイルが 1 階層に並んでいれば、「フラット化」されていることが確認できます。

元の配置出力フォルダーでの配置(例)
Resources\Images\MainMenuBts\play.pngbin\...\play.png
Resources\Images\SubMenu\settings.pngbin\...\settings.png
Resources\Images\Icons\exit.pngbin\...\exit.png

ここで「MainMenuBts フォルダーが存在しない」ことを視覚的に確認できれば、Windows 側でパスが一致していないことが理解しやすくなります。

よくあるハマりどころとチェックリスト

画像が表示されない原因は、パス以外にもいくつか考えられます。Windows と Android での挙動差も踏まえて、チェックリストとして整理しておきます。

チェック項目ポイント確認方法
ファイルの配置場所Resources\Images 配下にあるかソリューションエクスプローラーでパスを確認
ビルドアクションMauiImage になっているかファイルのプロパティで「ビルド アクション」を確認
Source の指定Windows ではファイル名のみで参照しているかコードや XAML で Source="play.png" になっているか確認
大文字・小文字ファイル名と完全一致しているかPlay.png と play.png の違いに注意
出力フォルダー実際にファイルがコピーされているかbin\Debug\net8.0-windows\... を開いて確認

ここまで確認しても表示されない場合、多くは 参照パスのミスかビルドアクションの設定漏れです。まずは上記の項目をひとつずつ潰していくと、原因を特定しやすくなります。

Android と Windows の違いを意識した画像管理の考え方

今回のような「Android では見えるのに Windows では見えない」問題は、プラットフォームごとのリソース配置の違いを意識していないと発生しがちです。まとめると次のようになります。

観点AndroidWindows
ビルド後のフォルダー構造サブフォルダーを保持しやすい基本的にフラット化される
パス指定MainMenuBts/play.png のようにフォルダー付きでも動く場合が多いplay.png のようにファイル名だけを前提にするのが安全
デバッグのしやすさ実機やエミュレーター側で確認が必要出力フォルダーをそのままエクスプローラーで確認可能

クロスプラットフォームで安定して動く画像管理ルールを作るために、「ファイル名だけで参照する」「Resources\Images 直下に集約する」という方針を決めておくと、今回のようなトラブルをかなり減らすことができます。

.NET MAUI での画像命名と整理のベストプラクティス

画像をフラットに配置すると「ファイル名が衝突しやすくなるのでは?」と心配になるかもしれません。そこで実務でおすすめしやすい、命名と整理のアイデアをいくつか挙げておきます。

  • プレフィックスでグルーピングする
    • メインメニュー関連:mm_play.png、mm_settings.png
    • サブメニュー関連:sub_help.png、sub_back.png
  • 役割や状態を名前から判別できるようにする
    • btn_play_normal.png、btn_play_pressed.png
    • icon_warning.png、icon_info.png
  • 解像度違いの管理
    • 解像度別に別ファイルを用意する場合は @2x、@3x などのサフィックスを付ける
    • MAUI のマルチスケール画像機能を活用して、デバイス密度ごとに最適な画像を配布する

このように命名ルールを最初に決めておけば、フォルダー構造に頼らなくても画像の整理がしやすくなり、Windows のフラット化の影響も受けません。

トラブル再発防止のための実践的なポイント

最後に、同じ問題を繰り返さないために押さえておきたいポイントを整理しておきます。

  • 新しい画像を追加するときは必ず Resources\Images 直下に置く
  • Source には基本的に「ファイル名のみ」を指定する
    • Source="play.png"
    • Source="mm_settings.png"
  • 画像が表示されないときはまず Windows 出力フォルダーを開いて確認する
  • どうしてもフォルダー階層で管理したい場合のみ LogicalName を検討する
  • チーム開発では「画像配置ポリシー」を README などに明文化しておく

特に、出力フォルダーを確認するクセをつけると、画像に限らずフォントやその他のリソース周りのトラブルシューティングが一気にやりやすくなります。

まとめ:Windows の「フラット化」を前提に設計すれば解決できる

.NET MAUI で「Android では PNG 画像が表示されるのに、Windows では一切表示されない」という現象は、ほとんどの場合 Windows ビルド時に画像リソースがフラット化されることが原因です。Source = "MainMenuBts/play.png" のようにフォルダー名付きで参照していると、ビルド後の配置とパスが一致せず、Windows では画像が読み込めなくなります。

もっとも実践的でトラブルの少ない解決策は、次の 2 点です。

  • 画像をサブフォルダーから取り除き、Resources\Images 直下にフラットに配置する
  • コード側はファイル名だけで参照する(例:Source = "play.png")

どうしてもフォルダー階層を維持したいケースでは LogicalName を使った個別設定も可能ですが、画像数が増えると保守が煩雑になります。業務アプリや長期運用が前提のアプリでは、フラット構造+命名ルールで管理する方針を採用するのが現実的です。

.NET MAUI では「リソースの配置場所・ビルド時の挙動」と「実行時の参照パス」がプラットフォームごとに異なります。今回の画像表示トラブルをきっかけに、まずは Windows 出力フォルダーを覗いて実際の配置を確認する習慣をつけておくと、今後の開発・デバッグがぐっとスムーズになります。

この記事を書いた人

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

コメント

コメントする

目次