前のページ次のページ上に戻るホーム SophiaFramework UNIVERSE 5.3

3.4. ウィンドウ

3.4.1. ウィンドウの定義

ウィンドウは、コントロールやコンテナを配置するためのレスポンダです。 1 つのアプリケーションクラスに同時に複数のウィンドウを持たせて重ねて表示できます。

3.4.1.1. MyWindow の定義

下図のカスタムウィンドウ MyWindow を定義します。

図 3.2. MyWindow: 中央に文字列 "Hello Window" を描画する

MyWindow: 中央に文字列 "Hello Window" を描画する

カスタムウィンドウ MyWindow の仕様は以下の通りです。

  1. 1 キー(AVK_1)が押されると、 カスタムウィンドウ MyWindow を作成し、中央に文字列 "Hello Window" を描画します。
  2. クリアキー(AVK_CLR)が押されると、カスタムウィンドウ MyWindow を閉じます。

SFZWindow クラスを継承させてカスタムウィンドウ MyWindow を定義します。

例 3.10. MyWindow の定義

// MyWindow の定義
SFMTYPEDEFRESPONDER(MyWindow)  // 便利な型を生成するマクロ
class MyWindow : public SFZWindow // SFZWindow クラスを継承する
{
    SFMSEALRESPONDER(MyWindow)  // インスタンスのコピーを禁止するマクロ

    // SFYResponder からこのクラスに至るまでの継承順序を指定するマクロ
    SFMRESPONDERINSTANTIATEFOUR(MyWindow, SFZWindow, SFYContainer, SFYWidget, SFYResponder)

public:

    // 小文字アルファベットまたは記号 4 文字からなるタイプは予約されているので
    // MyWindow のタイプを大文字アルファベット('M', 'W', 'N', 'D')で定義する
    enum CodeEnum {
        CODE_TYPE = four_char_code('M', 'W', 'N', 'D')
    };

public:

    // スマートポインタで管理される MyWindow インスタンスを生成するための関数
    static MyWindowSmp NewInstance(SFCErrorPtr exception = null);

protected:
    explicit MyWindow(Void) static_throws;
    virtual ~MyWindow(Void);

    // 描画ハンドラ: 最初に呼び出される SFYWidget クラス登録されたデフォルトの描画ハンドラから呼び出される
    // MyWindow 自身の描画処理は HandleRenderRequest 仮想関数をオーバーライドして実装する
    virtual Void HandleRenderRequest(SFXGraphicsPtr graphics) const; 

private:

    // キーハンドラ
    XANDLER_DECLARE_BOOLEVENT(OnKey)

};
[Tip] SFMTYPEDEFRESPONDER マクロ

SFMTYPEDEFRESPONDER は、 引数に指定したレスポンダクラスに関する便利なユーザー定義型を自動生成するマクロです。 例えば、引数に指定されたレスポンダのスマートポインタの型 MyWindowSmp はこのマクロで定義されます。

[Tip] SFMRESPONDERINSTANTIATE マクロ

SFMRESPONDERINSTANTIATE は、 RealView Compilation Tools for BREW 1.2 コンパイラの不具合を回避するためのマクロです。 RealView Compilation Tools for BREW 1.2 以外のコンパイラでは無視されます。

新たにレスポンダクラスを定義する場合、 SFMRESPONDERINSTANTIATE マクロを利用して SFYResponder から新たに定義するクラスまでの継承順序を記述する必要があります。

MyWindow は SFZWindowSFZWindowSFYContainerSFYContainerSFYWidgetSFYWidgetSFYResponder というように SFYResponder から MyWindow まで 4 階層に渡って継承するので SFMRESPONDERINSTANTIATEFOUR マクロを使います。

SFYResponder からの継承が 3 階層の場合は SFMRESPONDERINSTANTIATETHREE マクロ、 5 階層の場合は SFMRESPONDERINSTANTIATEFIVE マクロを使います。 7 階層継承する場合の SFMRESPONDERINSTANTIATESEVEN マクロまで用意されています。

[Tip] NewInstance 関数

レスポンダのインスタンスは new 演算子ではなく、 NewInstance 関数を使用して生成します。

NewInstance 関数で生成されたレスポンダのインスタンスは、 スマートポインタ(SFXResponderPointer)で管理されます。

具象レスポンダクラスの NewInstance 関数は、 SFYResponder::Factory 関数を利用して実装します。 詳細は、MyWindow の実装を参照してください。

[Important] レスポンダの描画

描画イベントを受信すると、 レスポンダは最初に起動される SFYWidget クラスに登録されたデフォルトの描画ハンドラ内から SFYWidget::HandleRenderRequest 仮想関数を呼び出します。

このとき、レスポンダクラスで SFYWidget::HandleRenderRequest 仮想関数をオーバーライドしていれば、 この関数が呼び出されます。

別途描画ハンドラを定義して登録することも可能ですが、 通常、レスポンダの描画は SFYWidget::HandleRenderRequest 関数をオーバーライドして記述します。

このとき、以下の点に注意してください。

  1. SFYWidget::HandleRenderRequest 関数をオーバーライドした場合、 親クラスの SFYWidget::HandleRenderRequest 関数の内容はすべて上書きされます。
  2. 最初に SFYWidget::HandleRenderRequest 関数が呼び出されます。 その後、開発者がレスポンダに登録した描画ハンドラが登録順に呼び出されます。
  3. SFYResponder::SetPropertyTransparent 関数を使用して透過属性を設定していない場合、 レスポンダの領域は SFYWidget::HandleRenderRequest 関数を実行する前に SFYWidget::SetBackgroundColor 関数を使用して設定した背景色(デフォルト: 白色)で塗り潰されます。

[Caution] レスポンダのタイプ

レスポンダのタイプは SFYResponder クラスを継承する新しいレスポンダクラスを定義するときに、 four_char_code マクロ関数を使用して「 4 文字リテラル」として設定します。

小文字アルファベットまたは記号からなる 4 文字リテラルは SophiaFramework UNIVERSE で予約されています。 アプリ開発用には、大文字アルファベット 4 文字からなる「 4 文字リテラル」を利用してください。

3.4.1.2. helloworld アプリケーションクラスの定義

helloworld アプリケーションクラスのメンバとして MyWindow のスマートポインタと MyWindow を作成する関数を追加します。

例 3.11. helloworld アプリケーションクラスの定義

// helloworld アプリケーションクラスの定義
SFMTYPEDEFCLASS(helloworld)  //  便利な型を生成するマクロ
class helloworld : public SFYApplication
{
    SFMSEALCOPY(helloworld)  // インスタンスのコピーを禁止するマクロ
private:
    // *** 太字が追加部分

    MyWindowSmp _myWindow; // MyWindow のスマートポインタ
public:
    static SFCInvokerPtr Factory(Void);

private:
    explicit helloworld(Void) static_throws;
    virtual ~helloworld(Void);

    SFCError Make(Void); // MyWindow を作成する関数

    XANDLER_DECLARE_VOIDRENDER(OnRenderRequest) // 描画ハンドラ
    XANDLER_DECLARE_BOOLEVENT(OnKey)            // キーハンドラ
};
[Note] スマートポインタ

レスポンダは NewInstance 関数を使用して生成し、 クラス名の末尾が Smp であるスマートポインタのインスタンスとして処理します。

スマートポインタの型はクラス定義文の直前で宣言する SFMTYPEDEFRESPONDER マクロの内部で定義されます。 たとえば、MyWindowSmp 型は SFMTYPEDEFRESPONDER(MyWindow) の内部で定義されます。

3.4.1.3. MyWindow の実装

スマートポインタで管理される MyWindow インスタンスを生成する NewInstance 関数と、 MyWindow のコンストラクタ、デストラクタ、描画ハンドラ、キーハンドラを実装します。

例 3.12. MyWindow の実装

// NewInstance 関数: MyWindow インスタンスを生成する
MyWindowSmp MyWindow::NewInstance(SFCErrorPtr exception)
{

   // Factory 関数を使用して MyWindow インスタンスを生成する
   // Factory 関数は SFYResponderSmp 型を返すので
   // static_pointer_cast 演算子を利用して MyWindowSmp 型にダウンキャストする
   return static_pointer_cast<MyWindow>(Factory(:: new MyWindow, exception));

}

// 配色定義
#define COLOR_MY_WINDOW_BACK (SFXRGBColor(0xCC, 0xFF, 0xCC, 0x00)) // 薄緑色: MyWindow の背景色

// コンストラクタ
MyWindow::MyWindow(Void) static_throws
{
    if (static_try()) {

        // レスポンダのタイプを設定する
        // four_char_code('M', 'W', 'N', 'D')がセットされる
        SetType(CODE_TYPE);

        // キーハンドラを登録する
        static_throw(RegisterHandler(
            SFXEventRange(SFEVT_KEY, SFEVT_KEY, SFP16_BEGIN, SFP16_END),
            XANDLER_INTERNAL(OnKey)
        ));

        // MyWindow 自身の描画処理は MyWindow::HandleRenderRequest 関数をオーバーライドして実装する
        // MyWindow::HandleRenderRequest 関数は SFYWidget クラスに登録されているデフォルトの描画ハンドラから呼び出されるので
        // 描画ハンドラの登録は不要

        // MyWindow の背景色を薄緑色に設定する
        SetBackgroundColor(COLOR_MY_WINDOW_BACK);
    }
}

// デストラクタ
MyWindow::~MyWindow(Void)
{
}

// 配色定義
#define COLOR_BLACK (SFXRGBColor(0x00, 0x00, 0x00, 0x00)) // 黒色: 文字列の色

// MyWindow の描画ハンドラの実装
Void MyWindow::HandleRenderRequest(SFXGraphicsPtr graphics) const
{
    // 描画を行うための SFXGraphics インスタンスは第 1 引数(graphics)として渡される
    // MyWindow のローカル領域の左上が描画領域の原点となる

    // MyWindow のローカル領域はコンストラクタ内で SFYWidget::SetBackgroundColor() を使用して
    // 設定した薄緑色で塗り潰される

    // MyWindow のローカル領域中央に文字列 "Hello Window" を黒色で描画する
    // ローカル領域は GetLocalBound()で取得する
    graphics->DrawSingleText("Hello Window", GetLocalBound(), COLOR_BLACK);

    // SFYResponder クラスを継承するレスポンダの描画ハンドラでは、下記の実行文は不要
    // graphics->Update(); 

    return;
}

// MyWindow のキーハンドラの実装
XANDLER_IMPLEMENT_BOOLEVENT(MyWindow, OnKey, invoker, event)
{
// invoker: キーハンドラを呼び出したレスポンダ MyWindow (SFYResponderPtr 型)
// event: キーイベント (SFXEventConstRef 型)

    switch (event.GetP16()) {

        case AVK_CLR: // クリアキーの SFEVT_KEY イベントを受信したとき

            // MyWindow を閉じる
            invoker->Terminate();

            // キーイベントを処理したので true を返す
            return true;

        case AVK_1: // 1 キーの SFEVT_KEY イベントを受信したとき

            // "1-key" をシミュレータのデバッグウィンドウに表示する
            TRACE("1-key");

            // キーイベントを処理したので true を返す
            return true;
    }

    // キーイベントを処理していないので false を返す
    return false;
}
[Tip] static_pointer_cast 演算子

SFYResponder::Factory 関数は SFYResponderSmp 型を返すので、 static_pointer_cast 演算子を利用して MyWindowSmp 型にダウンキャストします。

[Tip] SFYWidget::SetBackgroundColor 関数

SFYWidget クラスを継承するレスポンダでは、 ローカル領域の背景は SFYWidget::HandleRenderRequest 関数が実行される前に SFYWidget::SetBackgroundColor 関数で設定された色(デフォルトは白色)で塗り潰されます。

MyWindow のコンストラクタ内でこの関数を使用して背景色を薄緑色に設定しています。

[Tip] HandleRenderRequest 関数

レスポンダは描画イベントを受信すると、 最初に実行される SFYWidget クラスに登録されたデフォルトの描画ハンドラ内から SFYWidget::HandleRenderRequest 関数を呼び出します。

このとき、レスポンダクラスで SFYWidget::HandleRenderRequest 仮想関数をオーバーライドしていれば、 この関数が呼び出されます。

別途描画ハンドラを定義して登録することも可能ですが、 通常、レスポンダの描画は SFYWidget::HandleRenderRequest 関数をオーバーライドして記述します。

[Note] 描画イベント

helloworld アプリの場合、 アプリ開始イベント(SFEVT_APP_START)を処理する SFYApplication::HandleEvent 関数の内部で SFYResponder::Render 関数を呼び出します。 このときに、レスポンダは描画イベントを受信し、 再描画を行います。

[Note] MyWindow の描画領域

MyWindow の描画は、graphics 引数(SFXGraphics 型)を使用して MyWindow のローカル領域に対して行います。

[Note] MyWindow で処理されなかったキーイベント

MyWindow で処理されなかったキーイベントは、 MyWindow の親レスポンダである helloworld アプリケーションクラスがデフォルトで保持するルートSFZRoot) に委譲されて処理されます。 最終的にルートでも処理されなかった場合、描画エンジンは起動されません。

3.4.2. ウィンドウの作成

3.4.2.1. ウィンドウ内レスポンダの配置

レスポンダを生成してから配置するまでの一連の処理は以下の通りです。

  1. [必須] NewInstance 関数を呼び出してレスポンダのインスタンスを生成します。
  2. [必須] SFYResponder::SetParent 関数を呼び出して親レスポンダを設定します。
  3. [必須] SFYResponder::SetRealBound 関数を呼び出して親レスポンダのローカル領域内での自レスポンダの実領域を設定します。
  4. [必須] SFYResponder::SetState 関数を呼び出して自レスポンダの状態を設定します。
  5. [任意] 必要に応じて背景色などのプロパティを設定します。
  6. [任意] SFYResponder::ToFront 関数を呼び出して自レスポンダを最前面に配置します。

例 3.13. MyWindow の作成

// MyWindow を作成する関数
SFCError helloworld::Make(Void)
{
    SFCError error(SFERR_NO_ERROR);

    // MyWindow インスタンスを生成する
    if ((_myWindow = MyWindow::NewInstance(&error)) != null) {

        // MyWindow の親レスポンダを helloworld アプリケーションクラスが保持するルートに設定する
        // ※SFYApplication::GetThis() により helloworld アプリケーションクラスが保持するルートを取得できる
        error = _myWindow->SetParent(GetThis());

        if (error == SFERR_NO_ERROR) {

            // MyWindow の実領域をルートのローカル領域から(10, 10)だけ Deflate した領域に設定する
            // ※ルートのローカル領域はデフォルトでデバイス画面領域に初期設定されている
            _myWindow->SetRealBound(GetLocalBound().Deflate(10, 10));

            // MyWindow の状態を「可視+活性+操作可能+フォーカス」にまとめて設定する
            _myWindow->SetState(true, true, true, true);

            // MyWindow を最前面に移動する
            _myWindow->ToFront();
        }
    }
    return error;
}
[Note] SFYApplication::GetThis 関数

SFYApplication::GetThis 関数は、 アプリケーションクラスがデフォルトで保持するルートSFZRoot)を返します。

[Note] SFYApplication.GetLocalBound 関数

SFYApplication::GetLocalBound 関数は、 アプリケーションクラスがデフォルトで保持するルートSFZRoot) のローカル領域(画面全体の矩形領域)を返します。

[Tip] Deflate 関数

SFXRectangle(x, y, w, h)で表される矩形を SFXSize(a, b)だけ Deflate すると、 左上端の開始点座標が SFXGrid(x + a, y + b)、領域サイズが SFXSize(w - 2 * a, h - 2 * b)の矩形領域になります。

たとえば、 SFYApplication::GetLocalBound()で返されるアプリケーションクラス(ルート)のローカル領域(画面全体)が SFXRectangle(0, 0, 240, 320)であったとすると、 SFXSize(10, 10)だけ Deflate した後の領域は SFXRectangle(10, 10, 220, 300)となります。

参照: SFXRectangle::Deflate

[Note] デフォルトの仮想領域とローカル領域

実領域しか設定していないレスポンダの仮想領域は、 実領域と物理的に同じになります。 仮想領域はローカル領域と物理的に同じなので、 このときローカル領域も実領域と等しくなります。

MyWindow のローカル領域は、 左上端を原点とする相対座標系を使用して表現すると SFXRectangle(0, 0, 220, 300)になります。

※レスポンダの描画はローカル領域に対して行います。

[Note] 実領域

実領域とは、 親レスポンダのローカル領域の左上端を原点とする相対座標系を使用して表された、 レスポンダのクリッピング領域です。

仮想領域が設定されていない場合、 レスポンダのローカル領域は実領域と同じになります。 仮想領域が設定されている場合は、 ローカル領域に描画されたレスポンダの内容は、実領域でクリッピングされて画面に表示されます。

[Note] レスポンダ間の順序関係

レスポンダはレスポンダツリーと呼ばれる木構造で管理され、 画面の描画は木構造の順序関係に従って行われます。

レスポンダツリーの順序関係では、 子レスポンダは親レスポンダよりも前面、 姉レスポンダは妹レスポンダよりも前面にあります(子レスポンダは親レスポンダに設定した順に前面から配置されます)。

また、姉レスポンダの子レスポンダは妹レスポンダよりも前面にあります。

姉妹関係にあるレスポンダの 1 つを最前面に持ってくると、 その子レスポンダは自動的に他の姉妹レスポンダよりも前面に配置されます。

レスポンダを姉妹レスポンダのなかで最前面にする関数として SFYResponder::ToFront 関数が用意されています。

ウィンドウやダイアログに対して SFYResponder::ToFront 関数を呼び出すと、 その中に含まれるボタンコントロールやラベルコントロール、タブコントロールなどの子レスポンダもまとめて最前面に配置されます。

[Tip] SFYResponder::ToFront / SFYResponder::ToBack 関数

SFYResponder::ToFrontSFYResponder::ToBack 関数はウィンドウやダイアログなどのレスポンダを姉妹レスポンダ間で最前面、最背面に配置するために使います。

[Caution] 注意

レスポンダツリーにレスポンダを追加したとき、 そのレスポンダは姉妹レスポンダの中で最背面に配置されます。

画面に表示するには SFYResponder::ToFront 関数を呼び出してレスポンダを最前面に移動させます。

3.4.2.2. helloworld アプリケーションクラスのキーハンドラの変更

初期画面で 1 キー(AVK_1)が押されると MyWindow を表示するように helloworld アプリケーションクラスのキーハンドラを変更します。

例 3.14. helloworld アプリケーションクラスのキーハンドラの変更

// helloworld アプリケーションクラスのキーハンドラの実装
XANDLER_IMPLEMENT_BOOLEVENT(helloworld, OnKey, invoker, event)
{
    unused(invoker);

    // キーハンドラの処理
    switch (event.GetP16()) {
        case AVK_SELECT: // セレクトキーの SFEVT_KEY イベントを受信したとき
            Terminate(); // helloworld アプリを終了する
            return true;

        // *** 太字が追加部分

        case AVK_1: // 1 キーの SFEVT_KEY イベントを受信したとき

            // MyWindow を作成する
            if (Make() != SFERR_NO_ERROR) {
                return false;
            }

        // キーイベントを処理したので true を返す
        return true;
    }

    // キーイベントを処理しなかったときは false を返す
    return false;
}

3.4.3. ウィンドウの終了処理

ウィンドウやメニュー、ダイアログを閉じる(終了する)には、 SFYResponder::Terminate 関数を呼び出します。

例 3.15. MyWindow のキーハンドラの実装

// MyWindow のキーハンドラの実装
XANDLER_IMPLEMENT_BOOLEVENT(MyWindow, OnKey, invoker, event)
{
    switch (event.GetP16()) {
        case AVK_CLR: // クリアキーの SFEVT_KEY イベントを受信したとき

            // MyWindow を閉じる(終了する)
            // invoker: MyWindow のポインタ (SFYResponderPtr 型)
            invoker->Terminate();

            // この段階では MyWindow はヒープメモリ上にまだ残っている
            // MyWindow は参照カウントが 0 になったときにヒープメモリから解放される

            // helloworld アプリケーションクラスの _myWindow によって参照されているので
            // MyWindow は helloworld アプリが終了するときにヒープメモリから解放される

            return true;

        case AVK_1:

    // ...(省略)...

}
[Important] Terminate 関数

レスポンダの終了処理を行うSFYResponder::Terminate 関数を呼び出すと、 レスポンダはレスポンダツリーから切り離され、 子レスポンダとの親子関係も解消されます。

通常、SFYResponder::Terminate 関数は、 スマートポインタで管理されるレスポンダの参照カウントが 0 になったタイミングで自動的に呼び出されるので、 ほとんどのレスポンダでこの関数を呼び出す必要はありません。

ダイアログやメニューなどユーザーの選択アクションに応じて直ちに閉じる(終了する)必要のあるレスポンダでは、 明示的にこの関数を呼び出してレスポンダの終了処理をその場で行います。 ただし、閉じたレスポンダがスマートポインタの変数によって参照されていると、 この関数を実行した後もヒープメモリ上にそのまま残ります。 強制的にヒープメモリから解放するには、 レスポンダを参照しているスマートポインタの変数に別のレスポンダを代入する、 あるいは、明示的に SFXResponderPointer::Release 関数を呼び出すなどして参照カウントを 0 にする必要があります。

[Tip] Tip

SFCApplication::Terminate 関数はアプリの終了処理を行う関数です。

[Warning] delete 演算子

レスポンダツリー上のレスポンダに対して delete 演算子を実行してはいけません。

delete 演算子を実行した後もレスポンダツリー上にレスポンダは残ったままなので、 delete 演算子を実行すれば不整合が生じます。

レスポンダを明示的に閉じる(終了する)には、 SFYResponder::Terminate 関数を呼び出します。

参照: SFYResponder::Terminate | SFCApplication::Terminate | SFXResponderPointer::Release | レスポンダツリー

3.4.4. シミュレータでの実行

  1. 初期画面で 1 キー(AVK_1)を押すと、MyWindow が表示されます。
  2. MyWindow で 1 キー(AVK_1)を押すと、デバッグウィンドウに "1-key" が表示されます。
  3. MyWindow でクリアキー(AVK_CLR)を押すと、MyWindow は閉じます。

図 3.3. 初期画面で 1 キー(AVK_1)を押した直後の画面

初期画面で 1 キー(AVK_1)を押した直後の画面

図 3.4. MyWindow で 1 キー(AVK_1)を押した直後のデバッグウィンドウ

MyWindow で 1 キー(AVK_1)を押した直後のデバッグウィンドウ
[Tip] デバッグウィンドウの表示方法

デバッグウィンドウを表示するには、 BREW シミュレータのメニューバーから

[表示]→[出力ウィンドウ]

を選択します。

参照: ウィンドウ(基礎編)