タイトル:ウィンドウクラスを解説し、ボタンクリック処理を実装する

VS 2022 を 17.9.1 以降のバージョンにアップグレードすると、新規作成した WinUI3 C++ プロジェクトの標準サンプルにボタンが1つ配置されます。ボタンをクリックすると裏側で各種処理を実行できます。

新しいプロジェクトを作成しましょう

前回の講義ではプロジェクト内の各ファイルと役割について解説しました。今回は空のウィンドウを新規作成し、ウィンドウ上にボタンを追加してクリック時の処理を記述します。

先ほど説明した通り、エントリークラスはこちらの App クラスです

    void App::OnLaunched([[maybe_unused]] LaunchActivatedEventArgs const& e)
    {
        window = make<MainWindow>();
        window.Activate();
    }Code language: C++ (cpp)

この箇所からメインウィンドウが生成され、アプリ起動時に最初に表示される画面となります。以下がメインウィンドウの構成で、1つのウィンドウを構成する4つのファイルを順に解説します。

ウィンドウ

MainWindow.xaml(画面定義ファイル)

純粋なXMLマークアップ言語でUIレイアウトを記述するためのファイルで、画面の表示だけを担当し、業務ロジックは記述しません。

このファイルにはレイアウトコントロールやButton、TextBoxなど各種ウィンドウコントロールを記述できます。この画面定義形式はWPFから継承されており、本質はXMLです。裏側のイベント処理を記述するため、同名の.hファイルと.cppファイルをセットで用意する必要があります。

公式の標準サンプルではXAMLが空の状態で、何のコントロールも配置されていません

<?xml version="1.0" encoding="utf-8"?>
<Window
    x:Class="WinuiCppDemo.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:WinuiCppDemo"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    Title="WinuiCppDemo">

    <Grid>

    </Grid>
</Window>Code language: YAML (yaml)

MainWindow.idl

インターフェース定義ファイルで、C++/WinRT独自の中核ファイルです

IDL = Interface Definition Language(インターフェース定義言語)の略称で、ウィンドウが外部に公開するAPI、プロパティ、イベントを定義し、C++/WinRTにおける言語間相互運用の架け橋となります。

  1. ウィンドウクラスの公開メンバー(独自プロパティ、独自イベント、呼び出し可能なメソッド)を定義する
  2. コンパイラがIDLをもとにWinRT規格のABIインターフェースコードを自動生成し、C++、C#、JSから同一ウィンドウを呼び出せるようにする
  3. 画面XAMLのデータバインディングや独自コントロールは、IDLにプロパティを宣言しないと正常に動作しない

WinUI3 C++/WinRT に .idl が必須な理由

WinRTが言語に依存しないバイナリABI規格だからです

Windows Runtime(WinRT)は単なるC++ライブラリではなく、複数言語共通のバイナリインターフェース規格です。C++、C#、Python、JSは統一されたインターフェース記述によって同一のWinRTコンポーネントを呼び出せ、C++ヘッダーファイルだけでは言語間の連携ができません。

C++ヘッダーはC++コンパイラのみ認識でき、C#やJSには読み取れません。一方IDLは中立的なインターフェース言語で、コンパイラが全言語で解釈可能なABI連携コードに変換します。

以下はサンプルのIDLファイルの中身です

namespace WinuiCppDemo
{
    [default_interface]
    runtimeclass MainWindow : Microsoft.UI.Xaml.Window
    {
        MainWindow();
        Int32 MyProperty;
    }
}

IDLは記述用言語と先ほど説明しました。上記のIDLファイルは先頭に名前空間 namespace WinuiCppDemo を記述し、続いて [default_interface] 属性を記載しています。

この属性は runtimeclass既定のメインインターフェースを付与するもので、C++/WinRTでウィンドウや独自コントロールを作成する際に必須の記述です。

MIDLコンパイラは IMainWindow という純粋仮想インターフェースを自動生成し、クラスのコンストラクタ、プロパティ、メソッドといった全メンバーを格納します。

[default_interface]IMainWindow が当該クラスが外部に公開するメインABIインターフェースであることを指定します。

波括弧内の MainWindow(); は引数なしコンストラクタの宣言で、明示的に記述する必要があります。XAMLエンジンがウィンドウをインスタンス化する際にこの既定コンストラクタを呼び出します。

こちらは .xaml.h に記述するコンストラクタの実装と対応しています。

Int32 MyProperty;

WinRT依存プロパティ(バインド可能プロパティ)を宣言しており、これがIDLの価値を示す核心部分です。

Int32:WinRT標準の基本型(C++の int32_t に対応);

MyProperty:完全なプロパティシステムが自動生成されます:

  • 取得用メソッド(Getter):int32_t MyProperty();
  • 設定用メソッド(Setter):void MyProperty(int32_t value);
  • プロパティ変更イベント:MyPropertyChanged(プロパティの値が変更されると自動発火し、XAMLの x:Bind がUIを自動更新する仕組み)

IDLに宣言したメンバーのみ、XAML上で {x:Bind MyProperty} によるバインディングが利用できます.h に通常のメンバー変数だけ記述してもXAML側で認識されず、バインディングがエラーを出力します。

IDLファイルを右クリックすると単独でコンパイル可能で、内部でmidl.exeツールが処理を実行します。C#向けのメタデータファイルと、C++向けのインターフェースヘッダーファイルが生成されます。

簡単に言うとIDLはインターフェースを生成し、言語間の呼び出しを実現すると同時に、XAMLのデータバインディングに必要なメタデータを提供する役割を持ちます。WinUI3 C#プロジェクトにIDLが不要なのは、C#がマネージド言語でリフレクションとメタデータを標準搭載しているからです。一方C++版WinUIはC++自体にリフレクション・メタデータの仕組みが存在しません。

.h にメンバー変数を記述するだけでは、XAMLエンジンから完全に見えない状態になります;

IDLに Int32 MyProperty; と宣言した場合のみ、コンパイラが以下を自動生成します:

  1. プロパティ取得・設定インターフェース
  2. プロパティ変更通知イベント MyPropertyChanged
  3. XAMLの静的バインディング検証を通過させるためのメタデータ

プロジェクト内で完全にC++だけを使用し、C#やJSといった他言語と一切連携しない場合でも、XAMLを使用するならIDLの記述が必須です。

WinRTはC++、C#、JS、Pythonがコンポーネントを相互呼び出しできる統一ABIを設計理念として開発されました:

IDLは特定のプログラミング言語に依存しない中立的な記述言語です。各言語のコンパイラはIDLから生成された .winmd メタデータを読み込み、クラス、プロパティ、メソッドを認識できます;

IDLファイルを右クリックするとコンパイルを実行できます

IDLの核心的な役割はXAMLのUIバインディングに必要なメタデータを提供することで、言語間相互運用は副次的な効果です。

IDLをコンパイルすると xxxx.g.h と xxxx.g.cpp ファイルが生成され、プロジェクト配下の Generated Files フォルダに格納されます。

Generated Files は自動生成コード用のディレクトリです

  1. midl.exe、cppwinrt.exe、XAMLコンパイラがプロジェクトビルド時に自動生成する各種ファイルを格納
  2. このフォルダ内のファイルを手動で編集してはいけません。再ビルド時に全て上書きされます
  3. VSのプロジェクト設定によりこのディレクトリがヘッダー検索パスに自動登録され、#include “MainWindow.g.h” でファイルを読み込めるようになっている

MainWindow.xaml.h

C++標準ヘッダーファイルで、MainWindow クラスの宣言を記述します。

IDLから生成された基礎インターフェースが自動的にインクルードされます;

クラスのフィールド、プライベート変数、前方宣言は全てこちらに記述し、通常のC++の.hヘッダーと同じ役割です。

使用する変数やメソッドの定義だけを記述し実装は記載しない。他のクラスはインクルードによってこのファイルを参照可能。

// ヘッダーガード:同一ヘッダーの重複インクルードを防止し、重複定義によるコンパイルエラーを回避
#pragma once

// MainWindow.idlのコンパイルで自動生成された連携用ヘッダーを読み込む
// .g.h にはIDLから生成されたIMainWindowインターフェース、MainWindowT<> CRTPテンプレート基底クラス、WinRTメタデータ定義が含まれる
// IDLに宣言したMyProperty、コンストラクタのインターフェースは全てこちらに定義されている
#include "MainWindow.g.h"

// 【実装用名前空間】ウィンドウの業務ロジック実装クラスを格納。独自のプロパティ・メソッドの実装を記述
namespace winrt::WinuiCppDemo::implementation
{
    // MainWindowT<MainWindow> は.g.hが提供するCRTPテンプレート基底クラス
    // 1. IDLで定義されたIMainWindowインターフェースを自動継承し、IDL宣言の全メンバー実装を強制
    // 2. WinUIウィンドウの底层ロジック、XAMLコンポーネント初期化、ABIインターフェース転送ロジックをカプセル化
    struct MainWindow : MainWindowT<MainWindow>
    {
        // IDLに宣言した MainWindow(); に対応するウィンドウ既定コンストラクタ
        MainWindow()
        {
            // 公式ルール:コンストラクタ内でInitializeComponent()を呼び出してはならない
            // XAMLコントロールの初期化はフレームワークが自動的に遅延実行する。コンストラクタ内で呼ぶとライフサイクルクラッシュが発生
            // 参考:Microsoft cppwinrt公式ドキュメント
            // See https://github.com/microsoft/cppwinrt/tree/master/nuget#initializecomponent
        }

        // IDLの Int32 MyProperty; に対応、自動生成されたプロパティ取得インターフェース
        // プロパティのint32_t値を返却、XAMLのx:Bindがデータ読み取り時に呼び出す
        int32_t MyProperty();
        // IDLの Int32 MyProperty; に対応、自動生成されたプロパティ設定インターフェース
        // プロパティ値を変更すると、内部でMyPropertyChanged変更イベントが自動発火しx:BindがUIを更新
        void MyProperty(int32_t value);
    };
}

// 【ファクトリ名前空間】WinRTオブジェクトファクトリクラス。MainWindowウィンドウオブジェクトを外部に公開・インスタンス化する役割
namespace winrt::WinuiCppDemo::factory_implementation
{
    // MainWindowTの第2引数に上記のimplementation::MainWindow実装クラスを紐付け
    // 役割:XAMLエンジンや外部コード(C#/他コンポーネント)がウィンドウインスタンスを作成するための統一ABI入口を提供
    struct MainWindow : MainWindowT<MainWindow, implementation::MainWindow>
    {
    };
}Code language: C++ (cpp)

MainWindow.xaml.cpp

C++実装ファイルで、全ての業務ロジックコードをこちらに記述します。

  • MainWindow.xaml.h に宣言した全メソッドの実装を記述
  • コントロールのイベント処理(ボタンクリック、ウィンドウロード、入力応答など)
  • データ処理、ネットワーク、ファイル操作、業務計算
  • UIコントロール操作、バインドプロパティの変更、ダイアログ表示、ページ遷移などのインタラクティブロジック
// 事前コンパイルヘッダーを読み込み、プロジェクト共通ヘッダーとWinRT基礎投影ヘッダーを一括参照しビルド速度を高速化
#include "pch.h"
// 現在のウィンドウクラスのヘッダーを読み込み、MainWindow実装クラスの宣言を参照
#include "MainWindow.xaml.h"

// 条件付きコンパイル:IDLから自動生成された底层連携ファイル MainWindow.g.cpp の存在を判定
// .g.cpp はmidl+cppwinrtツールが.idlをもとに生成し、プロパティ変更イベント・底层ABI転送ロジックをカプセル化
// 存在する場合はインクルードし、MyPropertyChanged通知とXAML実行時バインディングを正常動作させる
#if __has_include("MainWindow.g.cpp")
#include "MainWindow.g.cpp"
#endif

// グローバル名前空間の省略記述、以降のコードでwinrt:: プレフィックスを省略可能
using namespace winrt;
// WinUI Xamlの中核コントロール、ウィンドウ、ルーティングイベントなどのAPI名前空間
using namespace Microsoft::UI::Xaml;

// 公式備考:WinUIプロジェクト構造・テンプレート資料URL
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.

// implementation名前空間:MainWindowウィンドウの業務ロジック実装を格納、ヘッダーのクラス宣言と対応
namespace winrt::WinuiCppDemo::implementation
{
    // IDLに宣言したMyPropertyプロパティ【取得メソッド】の実装
    // Int32型のプロパティ値を返却XAMLのx:Bindがデータ読み取り時にこの関数を呼び出す
    int32_t MainWindow::MyProperty()
    {
        // 標準テンプレートは未実装例外を出力するので、実際の保存変数を返すロジックに置き換える必要がある
        throw hresult_not_implemented();
    }

    // IDLに宣言したMyPropertyプロパティ【設定メソッド】の実装
    // value:変更後の新しいプロパティ値。値を変更すると底层が自動的にMyPropertyChangedイベントを発火し、バインド先UIを更新
    void MainWindow::MyProperty(int32_t /* value */)
    {
        // 標準テンプレートは未実装例外を出力するので、変数への代入ロジックを追加する必要がある
        throw hresult_not_implemented();
    }
}Code language: PHP (php)

次にこのメインウィンドウにボタンを追加します。ボタンをクリックするとMyPropertyプロパティの値が1加算され、現在のMyPropertyの値を表示するダイアログが開くように実装します。

まずXAMLにボタンコントロールを追加します

    <Grid>
        <Button Content="Click Me" HorizontalAlignment="Center" VerticalAlignment="Center" Click="OnButtonClick"/>
    </Grid>Code language: C++ (cpp)

ボタンには「Click Me」と表示され、クリックイベント OnButtonClick が紐付けられます

カーソルを OnButtonClick に合わせF12キーを押すと、VSが自動的にこのイベントの実行関数を作成します。

すると MainWindow.xaml.h と MainWindow.xaml.cpp にそれぞれ定義が追加されます

void OnButtonClick(winrt::Windows::Foundation::IInspectable const& sender, winrt::Microsoft::UI::Xaml::RoutedEventArgs const& e);Code language: C++ (cpp)

新しいメソッド定義が追加されました。

メソッドの実装内容は MainWindow.xaml.cpp に記述されます

次にcppファイルのコードを編集します

今回の講義はここまでとし、次回の講義でこのボタンクリック機能を実装し、プロパティバインディングについて詳しく解説します。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です