C++/WinRT UWPのOnLaunchedでボタンが変更できない原因と正しいUI初期化(Loaded/InitializeComponent/MVVM実装例)

「C++/WinRT の UWP アプリで OnLaunched からページ内ボタンの表示を変えたいのに、myButton が見つからない」という相談は非常に多いテーマです。この記事では、なぜエラーになるのかをライフサイクルの観点から丁寧に解きほぐし、実運用で堅牢に動く初期化パターン(Loaded/Loading、InitializeComponent オーバーライド、パラメータ渡し、MVVM)を具体的な C++/WinRT のコードとともにまとめます。貼り付けてすぐ使えるサンプルと落とし穴も網羅します。

目次

問題の核心:OnLaunched から myButton に触れない理由

現象:MainPage.cpp のクリック ハンドラーからは動いていた下記コード

myButton().Content(box_value(L"Clicked"));

を App.cpp の OnLaunched 末尾へ移すと、error C3861: 'myButton': identifier not found。

理由は単純です。myButton は MainPage の XAML に x:Name="myButton" を付けたことでページ クラスのメンバーとして生成されます。つまり「MainPage のインスタンス」が存在し、そのインスタンス上で myButton() アクセサを呼ぶ必要があります。

一方の OnLaunched はアプリ全体の起動イベントであり、呼ばれた時点では「ページの生成と XAML 要素ツリーのロード」がまだ完了していない場合がほとんどです。App クラスのスコープには myButton という識別子(メンバー)は定義されていないため、コンパイル エラーになります。

整理:C++/WinRT(UWP)のライフサイクルと UI 可用性

よく使うイベントと「XAML 要素に触れてよいか」を表にしました。これを把握しておくと迷いが一気になくなります。

タイミング / イベント主な責務ページの XAML 要素UI 操作の安全性
App::OnLaunchedウィンドウ生成、Frame 配置、初回 Navigate未生成 or 生成直後(未ロード)❌ 直接触らない(ページに委譲)
MainPage::InitializeComponentXAML の具現化(要素インスタンス化)生成済み(論理ツリーは構築中)⭕ 生成直後の初期化に最適
MainPage::Loadingレイアウト前の最終初期化存在する(レイアウト未計測)⭕ 内容設定は可/サイズ依存は不可
MainPage::Loadedレイアウト完了直後完全に利用可能⭕ 内容・サイズ依存処理どちらも可
MainPage::OnNavigatedToナビゲーション パラメータ受け取り利用可能(通常は InitializeComponent 済)⭕ パラメータ反映に最適

OnLaunched では「ページに仕事を委譲」するのが定石

OnLaunched は「ウィンドウや Frame の準備」「初回ナビゲーション」「グローバル サービスの初期化」に専念させ、UI の細かい初期化は ページ側(MainPage)で実施するのが UWP 流儀です。以下に、実務で使い分けやすい推奨 4 パターンを示します。

パターンA:Loaded/Loading イベントで UI を初期化

最も分かりやすいアプローチです。XAML が具現化された直後(Loading)またはレイアウト完了後(Loaded)に UI を触ります。

XAML 側(例:Loaded)

<Page
    x:Class="MyApp.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Loaded="OnLoaded">

```
&lt;Grid&gt;
    &lt;Button x:Name="myButton" Content="Default"/&gt;
&lt;/Grid&gt;
```

 

C++/WinRT 側

// MainPage.h
struct MainPage : MainPageT<MainPage>
{
    MainPage();
    void OnLoaded(IInspectable const&, Windows::UI::Xaml::RoutedEventArgs const&);
};

// MainPage.cpp
using namespace winrt;
using namespace Windows::UI::Xaml;

MainPage::MainPage()
{
InitializeComponent();
// XAML で Loaded をバインドしていないなら、コードで登録してもOK
// Loaded({ this, &MainPage::OnLoaded });
}

void MainPage::OnLoaded(IInspectable const&, RoutedEventArgs const&)
{
myButton().Content(box_value(L"Clicked"));
} 

Loading を使う場合のハンドラー シグネチャは void OnLoading(IInspectable const&, IInspectable const&) です。レイアウト値(ActualWidth 等)を参照しない単純な内容変更は Loading でも十分です。

パターンB:InitializeComponent をオーバーライドして即時初期化

「XAML 具現化直後」に初期化コードを差し込みたいときの小技です。MainPage.g.h にあるベースの InitializeComponent を呼んだ後、自前の初期化を続けます。

// MainPage.h
struct MainPage : MainPageT<MainPage>
{
    MainPage();
    void InitializeComponent(); // ★ 追加
};

// MainPage.cpp
using namespace winrt;
using namespace Windows::UI::Xaml;

MainPage::MainPage()
{
InitializeComponent(); // 自前オーバーライドが呼ばれる
}

void MainPage::InitializeComponent()
{
// ベース(生成コード)を必ず先に
MainPageT::InitializeComponent();

```
// ここなら XAML 要素は生成済み
myButton().Content(box_value(L"Clicked"));
```

} 

注意:ベース呼び出しを忘れると UI が構築されず、あらゆる要素参照が失敗します。必ず MainPageT::InitializeComponent() を先に呼び出してください。

パターンC:OnLaunched からパラメータを渡し、ページ側で反映

アプリの起動引数や設定値を ナビゲーション パラメータとして渡し、ページの OnNavigatedTo で UI に反映する正攻法です。

App::OnLaunched(パラメータ渡し)

using namespace winrt;
using namespace Windows::UI::Xaml;
using namespace Windows::ApplicationModel::Activation;

void App::OnLaunched(LaunchActivatedEventArgs const& e)
{
Frame rootFrame = Window::Current().Content().try_as();
if (!rootFrame)
{
rootFrame = Frame();
Window::Current().Content(rootFrame);
}

```
if (!rootFrame.Content())
{
    // 文字列をそのまま渡す例(box_value で IInspectable 化)
    rootFrame.Navigate(xaml_typename&lt;MyApp::MainPage&gt;(), box_value(L"Clicked"));
}

Window::Current().Activate();
```

} 

MainPage::OnNavigatedTo(受け取りと反映)

using namespace winrt;
using namespace Windows::UI::Xaml::Navigation;

void MainPage::OnNavigatedTo(NavigationEventArgs const& e)
{
auto text = winrt::unbox_value_or(e.Parameter(), L"");
if (!text.empty())
{
myButton().Content(box_value(text));
}
} 

この構成なら、OnLaunched は「値を渡すだけ」。UI 変更はページ自身が責務を負い、テスト性・拡張性が上がります。

パターンD:アプリ設定や ViewModel での間接初期化(MVVM 向け)

「起動時に保存済みの設定で UI を初期化する」場合は、ApplicationData のローカル設定や ViewModel を介すと見通しが良くなります。

ローカル設定を使う例

// App::OnLaunched(設定の既定値を用意)
using namespace Windows::Storage;
auto local = ApplicationData::Current().LocalSettings();
local.Values().Insert(L"MyButtonText", box_value(L"Clicked"));

// MainPage 側(Loaded や OnNavigatedTo で反映)
using namespace Windows::Storage;
void MainPage::OnLoaded(IInspectable const&, Windows::UI::Xaml::RoutedEventArgs const&)
{
auto local = ApplicationData::Current().LocalSettings();
if (auto v = local.Values().TryLookup(L"MyButtonText"))
{
myButton().Content(v); // Content は IInspectable なのでそのまま渡せる
}
} 

MVVM を採用している場合は、Button.Content を x:Bind / Binding で ViewModel のプロパティに結び付け、ViewModel へ値をセットするだけで UI が更新されるようにすると、ページのコードビハインドをほとんど書かずに済みます。

「OnLaunched で myButton を認識させたい」への現実解

結論として「原則できない/すべきでない」ですが、どうしてもアプリ起動処理中にページへ命令したいなら、Frame.Content からページ インスタンスを取得し、公開メソッド越しに指示を出す形にします(UI メンバーに直接触らない)。

// App::OnLaunched(Navigate 後)
rootFrame.Navigate(xaml_typename<MyApp::MainPage>());
Window::Current().Activate();

// ページのインスタンスを取得
if (auto page = rootFrame.Content().try_as())
{
page.SetInitialState(L"Clicked"); // ← 公開メソッドで依頼する
}

// MainPage.h
struct MainPage : MainPageT
{
MainPage();
void SetInitialState(winrt::hstring const& text); // 公開API
};

// MainPage.cpp
void MainPage::SetInitialState(winrt::hstring const& text)
{
// ここなら myButton に正攻法でアクセスできる
myButton().Content(box_value(text));
} 

このやり方なら App から UI 内部の構造(ボタンの名前)に依存せず、「初期状態を設定する」という抽象的な契約で疎結合を保てます。ただし、ページの生成タイミングと競合しないよう、Navigate の後に呼び出す点は守ってください。

「#include "MainPage.g.cpp" を追加すれば解決?」に対する答え

いいえ、解決しません。MainPage.g.cpp は XAML を C++/WinRT 型へマッピングする生成コードを含みますが、インスタンスが無い場所ではメンバーに触れません。App は MainPage の外側のクラスです。myButton は MainPage のメンバーなので、#include しても「識別子がスコープに現れる」わけではありません。コンパイラ エラー C3861 は、まさにそのスコープの問題を指しています。

実装の全体像(まとまったサンプル)

「パターンC:パラメータ渡し」を軸に、起動から UI 初期化までの最小構成をまとめます。

App.xaml.cpp

#include "pch.h"
#include "App.xaml.h"
#include "MainPage.xaml.h"

using namespace winrt;
using namespace Windows::UI::Xaml;
using namespace Windows::ApplicationModel::Activation;

void App::OnLaunched(LaunchActivatedEventArgs const& e)
{
Frame frame = Window::Current().Content().try_as();
if (!frame)
{
frame = Frame();
Window::Current().Content(frame);
}

```
if (!frame.Content())
{
    // 起動時に表示したい文字列を渡す
    frame.Navigate(xaml_typename&lt;MyApp::MainPage&gt;(), box_value(L"Clicked"));
}

Window::Current().Activate();
```

} 

MainPage.xaml

<Page
    x:Class="MyApp.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Loaded="OnLoaded">

```
&lt;Grid&gt;
    &lt;Button x:Name="myButton" Content="Default" /&gt;
&lt;/Grid&gt;
```

 

MainPage.xaml.h / .cpp

// MainPage.h
struct MainPage : MainPageT<MainPage>
{
    MainPage();

```
void OnLoaded(IInspectable const&amp;, Windows::UI::Xaml::RoutedEventArgs const&amp;);
void OnNavigatedTo(Windows::UI::Xaml::Navigation::NavigationEventArgs const&amp; e);
```

};

// MainPage.cpp
using namespace winrt;
using namespace Windows::UI::Xaml;
using namespace Windows::UI::Xaml::Navigation;

MainPage::MainPage()
{
InitializeComponent();
}

void MainPage::OnNavigatedTo(NavigationEventArgs const& e)
{
auto initText = unbox_value_or(e.Parameter(), L"");
if (!initText.empty())
{
myButton().Content(box_value(initText));
}
}

void MainPage::OnLoaded(IInspectable const&, RoutedEventArgs const&)
{
// 起動時以外の再表示(戻る/進む等)でも最終状態を整えたいときに使える
} 

使い分けチャート(どれを選ぶ?)

要件おすすめ手段理由
単に既定値を上書きしたいInitializeComponent オーバーライド生成直後に確実に適用。イベント配線が不要。
レイアウト後のサイズを見て決めたいLoaded ハンドラーレイアウト確定後なので ActualWidth 等を安全に参照できる。
起動引数や状態を反映したいナビゲーション パラメータ + OnNavigatedToUI と責務分離。テスト容易。多画面でも拡張しやすい。
再表示や戻る遷移でも整えたいLoaded または OnNavigatedTo表示のたびに整合性を回復できる。
設定値に基づく初期化ApplicationData(ローカル設定) or MVVM状態の永続化と疎結合を両立。

落とし穴とベストプラクティス

  • myButton をグローバルにしない:UI 要素を App 側のグローバルに持ち出す設計は壊れやすく、テスタビリティも失われます。ページの外からは 公開メソッドで用件を伝える に止めましょう。
  • ベース呼び出しを忘れない:InitializeComponent オーバーライド時は必ず MainPageT::InitializeComponent() を先に呼ぶ。
  • Loaded と Loading の違い:サイズ依存ありなら Loaded、単純な内容更新なら Loading で OK。
  • プリローンチ対応:一部の起動では e.PrelaunchActivated() が true の場合あり。UI 更新は実際に表示するタイミング(Activate 前後)に寄せると安全です。
  • NavigationCache:ページをキャッシュしていると Loaded が再び呼ばれないケースあり。状態復元が必要なら OnNavigatedTo も併用を。
  • MVVM での一元化:ボタンの Content を x:Bind で ViewModel に結ぶと、App 側は ViewModel の初期値を整えるだけで UI が反映されます。

よくある質問(FAQ)

Q1. OnLaunched から myButton を直接操作する方法は?

原則ありません。myButton は MainPage のメンバーであり、App のスコープ外だからです。どうしても必要な場合は Frame.Content() から MainPage を取得し、SetInitialState のような公開メソッド越しに依頼してください(上記サンプル参照)。

Q2. 起動時の UI 初期化の「正しい」場所は?

要件次第ですが、既定値上書きなら InitializeComponent、サイズ依存なら Loaded、引数反映なら OnNavigatedTo が第一候補です。すべてを OnLaunched に集約しないのがポイントです。

Q3. #include "MainPage.g.cpp" を追加すれば参照できますか?

できません。生成コードをインクルードしても、App は MainPage のメンバーにアクセスする資格(インスタンスとスコープ)を持ちません。

チェックリスト:これで「起動時 UI 初期化」は迷わない

  • UI 初期化はページ側に寄せる(InitializeComponent / Loaded / OnNavigatedTo)。
  • OnLaunched は構成管理に専念(Frame 準備、Navigate、ウィンドウ Activate)。
  • 値の受け渡しはパラメータ or 設定 or ViewModelで疎結合に。
  • ページ外から UI 要素へ直接アクセスしない(公開メソッドで抽象化)。
  • サイズ依存の UI は Loaded で。

まとめ:OnLaunched で UI をいじらず、「適切な場所」で初期化する

OnLaunched はアプリの玄関口であって、ページの居間ではありません。myButton のような UI コントロールはページの責務で初期化する――この原則を守るだけで、C++/WinRT の UWP アプリは驚くほど壊れにくく、読みやすく、テストしやすくなります。今日からは、「OnLaunched では UI に触らない。ページの InitializeComponent/Loaded/OnNavigatedTo で整える」を合言葉に、スッキリした起動コードへ移行しましょう。


付録:ミニ逆引き

  • 起動と同時にボタンの文言だけ変えたい:MainPage::InitializeComponent で myButton().Content(...)
  • ボタンの幅に合わせて初期文言を調整したい:MainPage::Loaded で計測後に変更
  • 起動引数で初期文言を切り替えたい:App::OnLaunched でパラメータを渡し、OnNavigatedTo で反映
  • 設定値で復元したい:ローカル設定に保存し、ページ表示時に読み出して反映
  • 複数ページで同じ初期化を共有:公開ユーティリティ メソッド or 共通 ViewModel を用意

背景理解:アプリとページの責務分離

App クラス(App.cpp)は OS からのイベント ハブ(起動/サスペンド/リジューム等)。Page クラス(MainPage.cpp)は UI とそのロジックの単位。この分離は「UI を勝手にいじらない」の前提で機能します。UI の既定値以外の初期化は、XAML ツリーの存在とライフサイクルに同期する必要があり、その最適解が InitializeComponent、Loading、Loaded、OnNavigatedTo なのです。


発展:テスト容易性を高める設計

UI 文字列や表示状態を ViewModel のプロパティに集約し、ページは Binding で結ぶだけにすると、単体テストは ViewModel で完結。OnLaunched は ViewModel の起動時状態を整えて渡す――この設計に移行すれば、将来 WinUI/WinAppSDK へ移植するときも乗り換えがスムーズになります。


最後に:コピペで確認できる最小コード(総括)

最速で動作を確かめたい方は、以下 3 点だけ適用してください。

  1. App::OnLaunched で Navigate(..., box_value(L"Clicked")) を渡す。
  2. MainPage::OnNavigatedTo で受け取り、myButton().Content(...) に反映。
  3. サイズに依存するなら Loaded を併用。

これで「OnLaunched からボタンが変えられない」悩みは確実に解消します。

この記事を書いた人

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

コメント

コメントする

目次