.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 が画像を出力フォルダーにコピーする際にフォルダー構造をフラット化する、という挙動です。これにより、ビルド後の配置と、コードで指定しているパスが一致しなくなってしまいます。
| プラットフォーム | ビルド後の配置 | フォルダー構造 | 実行時の参照例 |
|---|---|---|---|
| Android | Assets / Resources 配下 | サブフォルダーを保持 | MainMenuBts/play.png でも参照可能 |
| Windows | bin\Debug\net8.0-windows\... などの出力フォルダー | フラット化される(1階層に並ぶ) | play.png のみ有効、MainMenuBts/play.png は不一致 |
つまり、Windows 実行時には 出力フォルダーに単純に play.png が置かれているだけであり、MainMenuBts/play.png というパスのファイルは存在しません。そのため、Source = "MainMenuBts/play.png" と書いても一致せず、画像が読み込まれないのです。
最短の解決策:画像をフラットに置き、ファイル名だけで参照する
もっともシンプルで再現性の高い解決策は、サブフォルダー構造をやめて画像をフラットに配置することです。手順は非常に単純です。
手順 1:画像を Resources\Images 直下に移動する
- プロジェクト内の画像ファイルを、次のように移動します。
- 変更前:
Resources\Images\MainMenuBts\play.png - 変更後:
Resources\Images\play.png
- 変更前:
- 既定の
MauiImage設定(<MauiImage Include="Resources\Images\**" />)があれば、 特別な設定は不要です。そのままビルド時に自動的に画像が登録されます。
手順 2:コード側の参照を「ファイル名のみ」に変更する
C# コードは次のように修正します。
var playButton = new ImageButton
{
Source = "play.png", // ← フォルダー名を削除
BackgroundColor = Colors.Transparent,
WidthRequest = scaleImages.Scaling(),
HeightRequest = scaleImages.Scaling(),
};
XAML で指定している場合も同様です。
<!-- 修正前 -->
<ImageButton
Source="MainMenuBts/play.png"
BackgroundColor="Transparent" />
<!-- 修正後 -->
<ImageButton
Source="play.png"
BackgroundColor="Transparent" />
手順 3:クリーンビルドして動作を確認する
- 一度「クリーン」または
dotnet cleanを実行する - Windows をスタートアッププロジェクトにして再ビルド
- 画像が表示されるかを確認する
この方法は、画像の数が増えても設定変更が最小限で済みます。クロスプラットフォームで同じファイル名だけで参照できるため、後々の保守も非常に楽になります。
フォルダー階層を維持したい場合の代替策:LogicalName を使う
「どうしてもフォルダー構造ごと管理したい」「MainMenuBts/play.png というパスで書いたコードを変えたくない」というケースでは、.csproj 側で個別に LogicalName を設定する方法があります。
.csproj の設定例
<ItemGroup>
<MauiImage Include="Resources\Images\MainMenuBts\play.png">
<LogicalName>MainMenuBts/play.png</LogicalName>
</MauiImage>
</ItemGroup>
このように LogicalName を指定すると、実行時には Source = "MainMenuBts/play.png" というパスで参照できるようになります。
var playButton = new ImageButton
{
Source = "MainMenuBts/play.png",
BackgroundColor = Colors.Transparent,
};
ただし、この方法には明確なトレードオフがあります。
| 項目 | フラット構造(推奨) | LogicalName で階層維持 |
|---|---|---|
| 設定の手間 | ほぼ不要(既定設定のままでよい) | 画像ごとに .csproj へ追記が必要 |
| 画像が増えた場合 | 移動と命名ルールの整理だけで済む | .csproj が肥大化し、管理が大変になる |
| 可読性 | ファイル名だけなので比較的シンプル | フォルダー構造がそのままパスに反映され、階層管理しやすい |
| おすすめ度 | 高い(実践向き) | 限定的(画像点数が少ない場合などに限定) |
画像数が少ないサンプルアプリやプロトタイプであれば LogicalName を利用しても良いですが、実務レベルのアプリでは フラット構造+ファイル名参照のほうが圧倒的に楽です。
Windows 出力フォルダーを確認して挙動を理解する
「なぜ画像が見つからないのか」を実感するには、実際に Windows の出力フォルダーを覗いてみるのがおすすめです。
出力フォルダーの場所の一例
デバッグビルドの場合、例えば次のようなパスに展開されます(構成によって多少異なります)。
<プロジェクトルート>\bin\Debug\net8.0-windows\win10-x64\
このフォルダーを開くと、Resources\Images 以下に置いていたファイルがどのようにコピーされているかが分かります。サブフォルダーなしで、play.png、pause.png、stop.png といったファイルが 1 階層に並んでいれば、「フラット化」されていることが確認できます。
| 元の配置 | 出力フォルダーでの配置(例) |
|---|---|
Resources\Images\MainMenuBts\play.png | bin\...\play.png |
Resources\Images\SubMenu\settings.png | bin\...\settings.png |
Resources\Images\Icons\exit.png | bin\...\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 では見えない」問題は、プラットフォームごとのリソース配置の違いを意識していないと発生しがちです。まとめると次のようになります。
| 観点 | Android | Windows |
|---|---|---|
| ビルド後のフォルダー構造 | サブフォルダーを保持しやすい | 基本的にフラット化される |
| パス指定 | 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.pngicon_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 出力フォルダーを覗いて実際の配置を確認する習慣をつけておくと、今後の開発・デバッグがぐっとスムーズになります。

コメント