「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::InitializeComponent | XAML の具現化(要素インスタンス化) | 生成済み(論理ツリーは構築中) | ⭕ 生成直後の初期化に最適 |
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">
```
<Grid>
<Button x:Name="myButton" Content="Default"/>
</Grid>
```
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<MyApp::MainPage>(), 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<MyApp::MainPage>(), 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">
```
<Grid>
<Button x:Name="myButton" Content="Default" />
</Grid>
```
MainPage.xaml.h / .cpp
// MainPage.h
struct MainPage : MainPageT<MainPage>
{
MainPage();
```
void OnLoaded(IInspectable const&, Windows::UI::Xaml::RoutedEventArgs const&);
void OnNavigatedTo(Windows::UI::Xaml::Navigation::NavigationEventArgs const& 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 等を安全に参照できる。 |
| 起動引数や状態を反映したい | ナビゲーション パラメータ + OnNavigatedTo | UI と責務分離。テスト容易。多画面でも拡張しやすい。 |
| 再表示や戻る遷移でも整えたい | 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 点だけ適用してください。
App::OnLaunchedでNavigate(..., box_value(L"Clicked"))を渡す。MainPage::OnNavigatedToで受け取り、myButton().Content(...)に反映。- サイズに依存するなら
Loadedを併用。
これで「OnLaunched からボタンが変えられない」悩みは確実に解消します。

コメント