前のページ次のページ上に戻るホーム BREW C++ ライブラリ & GUI フレームワーク & XML ミドルウェア : SophiaFramework UNIVERSE 5.0

3.2. ウィンドウ

3.2.1. ウィンドウの定義と実装

3.2.1.1. MyWindow の定義

HelloWorld アプレットのコードをカスタマイズしてウィンドウ(SFZWindow)を定義・実装します。

1 キー が押されると、ウィンドウを作成しウィンドウに文字列 "Hello World" を表示します。

クリアキーが押されると、ウィンドウを閉じます。

図 3.1. ウィンドウ内に文字列 "HelloWorld" を表示する HelloWorld アプレット

ウィンドウ内に文字列 "HelloWorld" を表示する HelloWorld アプレット

SFZWindow を継承したカスタム ウィンドウ(MyWindow)を定義します。

例 3.9. MyWindow の定義

// MyWindow の定義
// 便利な型を生成するマクロ
SFMTYPEDEFRESPONDER(MyWindow)
class MyWindow : public SFZWindow // SFZWindow を継承する
{
    // インスタンスのコピーを禁止するマクロ
    SFMSEALRESPONDER(MyWindow) 
    // SFYResponder から MyWindow に至る継承順序を指定するマクロ
    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);
    // 描画ハンドラ(描画イベント発生時に最初に呼び出される HandleRenderRequest 仮想関数をオーバーライドする)
    virtual Void HandleRenderRequest(SFXGraphicsPtr graphics) const; 
private:
    // キーハンドラ
    XANDLER_DECLARE_BOOLEVENT(OnKey)
};
[Tip] SFMTYPEDEFRESPONDER マクロ

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

[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 マクロまで用意されています。

[Important] レスポンダのインスタンス生成 : NewInstance() 関数

スマートポインタ(SFXResponderPointer)で管理されるレスポンダのインスタンスは new 演算子ではなく、 NewInstance() 関数を利用して生成します。

インスタンスを持つ具象レスポンダでは、SFYResponder::Factory 関数を利用して NewInstance() 関数を実装する必要があります。

[Note] レスポンダの描画

HelloWorld アプリケーションクラスのように外部からレスポンダを描画する場合は描画ハンドラを登録して行います。

レスポンダが自分自身を描画する場合は、 描画イベント発生時に最初に呼び出される HandleRenderRequest() 仮想関数をオーバーライドして描画処理を記述します。

描画ハンドラについての注意事項は以下のとおりです。

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

[Caution] レスポンダのタイプについて

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

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

3.2.1.2. HelloWorld の定義

HelloWorld のメンバに、先ほど定義した MyWindow のスマートポインタと MyWindow を作成する関数を宣言します。

例 3.10. 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.2.1.3. MyWindow の実装

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

例 3.11. MyWindow の実装

// MyWindow インスタンスを生成する NewInstance() 関数
MyWindowSmp MyWindow::NewInstance(SFCErrorPtr exception)
{
   // Factory() 関数を使って MyWindow インスタンスを生成する 
   // Factory() 関数は SFYResponderSmp 型を返すので 
   // static_pointer_cast 演算子を使って MyWindowSmp 型にダウンキャストする 
   return static_pointer_cast<MyWindow>(Factory(:: new MyWindow, exception));
}
// コンストラクタ
MyWindow::MyWindow(Void) static_throws
{
    if (static_try()) {
        // レスポンダのタイプを設定する
        SetType(CODE_TYPE);

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

        // MyWindow::HandleRenderRequest 関数は描画イベントを受信すると必ず最初に呼び出されるので
        // この関数に MyWindow の描画処理を記述すればよい
        // MyWindow 用描画ハンドラを新たに定義、実装、登録する必要はない
    }
}

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

// 配色定義

// 黒色
#define COLOR_BLACK                 (SFXRGBColor(0x00, 0x00, 0x00, 0x00))
// 薄緑色
#define COLOR_MY_WINDOW_BACK        (SFXRGBColor(0xCC, 0xFF, 0xCC, 0x00))

// MyWindow の描画ハンドラ
Void MyWindow::HandleRenderRequest(SFXGraphicsPtr graphics) const
{
    // MyWindow のローカル領域を薄緑色で描画する
    graphics->FillRectangle(GetLocalBound(), COLOR_MY_WINDOW_BACK);

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

// キーハンドラ (MyWindow が最前面にあるときにキーが押されると呼び出される) 
// invoker はこのハンドラを呼び出したレスポンダ。SFYResponderPtr 型 (この場合、MyWindow)
// event はキーイベント。SFXEventConstRef 型
XANDLER_IMPLEMENT_BOOLEVENT(MyWindow, OnKey, invoker, event)
{
    switch (event.GetP16()) {
        case AVK_CLR: // クリアキーが押されたとき
            // MyWindow を閉じる
            invoker->Terminate(); // キーイベントを処理したので true を返す
            return true;
        case AVK_1: // 1キーが押されたとき
            // デバッグ文字列 "1-key" をシミュレータのデバッグウィンドウに表示する
            TRACE("1-key"); // キーイベントを処理したので true を返す
            return true;
    }
    return false; // キーイベントを処理していないので false を返す
}
[Tip] static_pointer_cast 演算子

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

[Tip] HandleRenderRequest() 関数

レスポンダは描画イベントを受信すると、 最初に HandleRenderRequest() 関数を呼び出します。

HandleRenderRequest() 関数をオーバーライドして描画処理を実装した場合、 描画ハンドラをレスポンダに登録する必要はありません。

※注意事項 : HandleRenderRequest() 関数をオーバーライドすると、 親クラスで実装されていた HandleRenderRequest() 関数の内容は上書きされます。

[Note] レスポンダの背景描画

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

[Note] 描画イベント発生のタイミング

HelloWorld アプレットの場合、 イベントループの最後のタイミングで自動的に呼び出される Render() 関数によって描画エンジンが起動され、その中で描画イベントは発生します。

[Note] MyWindow の描画領域

MyWindow の描画は、グラフィックスオブジェクト graphics を使って MyWindow のローカル領域に対して行います。

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

MyWindow で処理されなかったキーイベントは、 MyWindow の親レスポンダである HelloWorld アプリケーションクラスがデフォルトで保持するルート(SFZRoot)に委譲されて処理されます。

3.2.2. ウィンドウの作成と配置

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

レスポンダを作成し配置するための一連の処理は以下のようになります。

■レスポンダを作成し配置する方法(レスポンダを画面に表示するための一連の処理)

  1. NewInstance 関数を呼び出してレスポンダのインスタンスを生成します。
  2. SFYResponder::SetParent 関数を呼び出して既に配置されているレスポンダを親レスポンダとして設定します。
  3. SFYResponder::SetRealBound 関数を呼び出して親レスポンダのローカル領域内での自レスポンダの実領域を設定します。
  4. SFYResponder::SetState 関数を呼び出して自レスポンダの状態を設定します。
  5. 必要に応じてラベル項目などレスポンダ毎に定義されているパラメータを設定します。
  6. SFYResponder::ToFront 関数を呼び出して子レスポンダと一緒に自レスポンダを最前面に配置します。

例 3.12. MyWindow の作成と配置

// MyWindow の作成と配置
SFCError HelloWorld::Make(Void)
{
    SFCError error(SFERR_NO_ERROR);

    // MyWindow のインスタンスを生成する
    if ((_myWindow = MyWindow::NewInstance(&error)) != null) {
        // MyWindow の親をアプリケーションクラスに設定する
        // → アプリケーションクラスが保持するルート(SFZRoot)が親になる
        error = _myWindow->SetParent(GetThis());
        if (error == SFERR_NO_ERROR) {
            // MyWindow の実領域を携帯電話画面から(15, 20)だけ Deflate した矩形に設定する
            _myWindow->SetRealBound(_myWindow->GetSuitableBound(GetLocalBound().Deflate(15, 20)));
            // MyWindow の状態を「可視+活性+操作可能+フォーカス」にまとめて設定する
            _myWindow->SetState(true, true, true, true);
            // MyWindow を最前面に配置する
            _myWindow->ToFront();
        }
    }
    return error;
}
[Note] アプリケーションクラスのレスポンダ

アプリケーションクラスを引数にして SFYResponder::SetParent 関数を呼び出すと、 アプリケーションクラスがデフォルトで保持するルート(SFZRoot)を親レスポンダとして設定したことになります。

[Note] アプリケーションクラスのローカル領域

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(15, 20) だけ Deflate した後の領域は SFXRectangle(15, 20, 210, 280) となります。

関連情報 : SFXRectangle::Deflate | SFXRectangle::Inflate | SFXRectangle | SFXSize | SFXGrid

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

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

MyWindow の領域は MyWindow のローカル領域の相対座標系で (0, 0, 210, 280) になります。

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

[Note] 実領域

実領域とは、レスポンダの領域の中で可視である部分の矩形領域(可視領域)のことです。 親レスポンダのローカル領域の相対座標系で表します。

仮想領域が設定されていない場合、 レスポンダの領域は実領域と等しく、すべて可視領域になります。 仮想領域が設定されている場合は、レスポンダの実領域部分だけが可視領域となります。

[Note] レスポンダの順序関係 : ToFront() 関数

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

レスポンダツリーの順序関係では、 子レスポンダは親レスポンダよりも前面、 姉レスポンダは妹レスポンダよりも前面にあります。

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

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

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

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

[Tip] ToFront() 関数と ToBack() 関数

SFYResponder::ToFrontSFYResponder::ToBack 関数はウィンドウやダイアログなどのレスポンダを子レスポンダもまとめて画面に表示・非表示するために使います。

[Caution] 注意

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

画面に表示するには SFYResponder::ToFront 関数を呼び出すなどして最前面に移動させる必要があります。

3.2.2.2. HelloWorld キーハンドラの変更

1 キーを押すとウィンドウが表示されるように HelloWorld のキーハンドラをカスタマイズします。

例 3.13. HelloWorld : キーハンドラのカスタマイズ

// キーハンドラ
XANDLER_IMPLEMENT_BOOLEVENT(HelloWorld, OnKey, invoker, event)
{
    unused(invoker);

    // キーハンドラの処理
    switch (event.GetP16()) {
        case AVK_SELECT: // セレクトキーが押されたとき
            Terminate(); // アプレットを終了する
            return true;

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

        case AVK_1: //1キーが押されたとき
            if (Make() != SFERR_NO_ERROR) {
                return false;
            }
        // キーイベントを処理したので true を返す
        return true;
    }
    return false; // キーイベントを処理しないときは false を返す
}

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

携帯電話の画面に表示されているウィンドウやダイアログなどのレスポンダをいますぐ終了して閉じるには SFYResponder::Terminate 関数を呼び出します。

このとき、 レスポンダツリー上のこのレスポンダ以下の枝部分のすべてのレスポンダが、 レスポンダツリーから切り離されてバラバラに分解され、 子レスポンダもまとめて終了処理されます。

例 3.14. MyWindow のキーハンドラ : クリアキーが押されたときのウィンドウの終了処理(ウィンドウを閉じる)

// MyWindow のキーハンドラ : クリアキーが押されたときのウィンドウの終了処理
XANDLER_IMPLEMENT_BOOLEVENT(MyWindow, OnKey, invoker, event)
{
    switch (event.GetP16()) {
        case AVK_CLR: // クリアキーが押されたとき
            // MyWindow を終了する(MyWindow を閉じる)

            invoker->Terminate();// invoker : MyWindow のポインタ (SFYResponderPtr 型)
 
            // この段階では MyWindow はまだヒープメモリ上に残っている

            // MyWindow は参照カウントがゼロになったときにヒープメモリから解放される

            // HelloWorld アプリケーションクラスが MyWindow を保持しているので
            // MyWindow は HelloWorld アプレットが終了するときにヒープメモリから解放される
            
            return true;

        case AVK_1: 

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

}
[Important] Terminate() 関数

レスポンダは使われなくなったタイミングで自動的に終了処理され、メモリからも解放されます。

SFYResponder::Terminate 関数は、 ダイアログやメニューなどユーザーの選択アクションに応じて直ぐに閉じて終了する必要のあるレスポンダに対して利用します。

アプレットの終了処理を行う SFCApplication::Terminate 関数と SFYResponder::Terminate 関数は処理内容の範囲が異なります。

[Note] レスポンダの終了処理

レスポンダに対して SFYResponder::Terminate 関数を実行して終了処理を行うと、 レスポンダツリー上のこのレスポンダ以下の枝部分がレスポンダツリーから切り離されます。

このとき枝部分に含まれるレスポンダはすべてバラバラに分解され、終了処理を行ったレスポンダの有効状態は「無効」になります。

枝部分のレスポンダの状態はすべて「視覚的 OFF」となり、 視覚的状態が変化した場合、状態イベントが発生します。

枝部分のレスポンダは、解体されることにより参照カウントがゼロになったとき、 SFYResponder::Terminate 関数が自動的に呼び出されます。

レスポンダの終了処理を行った直後、レスポンダはヒープ上にメモリを割り当てられたままです。

実際にヒープ上に割り当てられたレスポンダのメモリが自動的に解放されるのは、 レスポンダの参照カウントがゼロになったときです。

HelloWorld アプレットの例では、MyWindow のスマートポインタは HelloWorld アプリケーションクラスで保持されているので、 実際に MyWindow のメモリが解放されるのは HelloWorld アプレットが終了するときになります。

[Warning] delete 演算子

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

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

レスポンダを閉じるときは SFYResponder::Terminate 関数を利用します。

同時にヒープメモリ上からも直ちに解放したい場合は、 レスポンダを保持しているスマートポインタの SFXResponderPointer::Release 関数を呼び出すなどしてレスポンダの参照カウンタをゼロにする必要があります。

関連情報 : SFYResponder::Terminate | SFXResponderPointer::Release | レスポンダツリー | 状態 | 状態イベント[SFEVT_RESPONDER_STATE] | 状態イベント専用ハンドラ[XANDLER_DECLARE_VOIDSTATE] キーイベント[SFEVT_KEY から SFEVT_KEY_HOOK_RELEASE] | 引数のある汎用ハンドラ[XANDLER_DECLARE_VOIDEVENT または XANDLER_DECLARE_BOOLEVENT]

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

  1. 1キーを押すと、ウィンドウが表示されます。
  2. 更に1キーを押すと、デバッグウィンドウに "1-key" が表示されます。
  3. クリアキーを押すと、ウィンドウは閉じます。

図 3.2. 実行結果

実行結果

図 3.3. デバッグウィンドウ

デバッグウィンドウ
[Note] 注意
デバッグウィンドウは、メニューから [ 表示 ] - [ 出力ウィンドウ ] を選ぶと表示されます。

関連情報 : ウィンドウ(基礎編)