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

9.5. 描画処理

9.5.1. 概要

レスポンダの描画は、以下のようにして行われます。

  1. レスポンダを再描画領域に登録します。
  2. 描画エンジンを起動します。
  3. 再描画領域に登録したレスポンダの中で実際に再描画が必要なレスポンダだけが 描画イベントを受信します。
  4. 描画イベントを受信したレスポンダでは、 描画ハンドラを起動し、 自分自身を再描画します。
  5. 描画イベントを受信しなかったレスポンダは、 必要に応じて『デバイス画面保存用ビットマップ』を利用して、 可視領域を復元します。
  6. 再描画した領域をデバイス画面保存用ビットマップに上書きします。
[Note] 描画エンジンの起動について

描画エンジンの起動のタイミングは、以下の 3 つの何れかになります。

イベントループ終了時と、アプリ開始/再開時または優先的イベントハンドラ終了時は、 自動的に SFYResponder::Render 関数が呼び出され、描画エンジンが起動されます (アプリ側で描画エンジンを起動する必要はありません)。

コールバック終了時は、 開発者が明示的に SFYResponder::Render 関数を呼び出して、 描画エンジンを起動する必要があります (アプリ側で描画エンジンを起動する必要があります)。

[Note] デバイス画面保存用ビットマップ

SFYResponder::Render 関数は、 実際に再描画した領域を『デバイス画面保存用ビットマップ』 と呼ぶデバイス画面と同一サイズの内部ビットマップに上書き保存します。

"SFYResponder::Render(false)" の実行で描画イベントを受信しなかったレスポンダは、 親レスポンダまたは妹レスポンダの再描画により一部または全部が書き換えられてしまった場合、 デバイス画面保存用ビットマップからコピーして自分の可視領域を復元します。

デバイス画面保存用ビットマップを利用した再描画の最適化は、 内部で自動的に行われます。アプリ中断時にこのビットマップを解放するかどうかの判断を除き、 開発者はこれについて意識する必要はありません。

[Note] 再描画の最適化

"SFYResponder::Render(false)" による描画エンジン起動の場合、 可視領域再描画領域と交差領域を持つレスポンダだけが描画イベントを受信し、 描画ハンドラを起動します。

このとき、レスポンダの再描画は交差領域でクリッピングして高速に行われます。

9.5.2. 再描画領域の登録と再描画

レスポンダの再描画は、 再描画領域の登録と、 描画ハンドラの起動による実際の再描画の 2 段階に分けて行われます。

以下の USRResponder::SetText 関数の場合、 この関数はラベルテキストを設定し、 USRResponder レスポンダのローカル領域を再描画領域に登録します。

[Note] 注意

この段階では、ラベルテキストはまだ描画されません。

SFYResponder::Render(false) により描画エンジンを起動すると、 再描画領域に登録された USRResponder は、 可視領域を持つ場合に限り、 描画イベントを受信し、 OnRenderRequest 関数(描画ハンドラ)を起動します。

[Note] Render 関数の呼び出し

イベントループ終了時アプリ開始/再開時と優先的イベントハンドラ終了時の再描画では、 SFYResponder::Render 関数は自動的に呼び出されます (Render 関数を呼び出す必要はありません)。

コールバック終了時の再描画では、 開発者は明示的に Render 関数を呼び出す必要があります。

OnRenderRequest 関数は、 ラベルテキストを USRResponder レスポンダのローカル領域に描画します。

[Note] 注意

ラベルテキストは、このとき初めて実際に描画されます。

例 9.28. 再描画領域の登録と再描画

// 描画ハンドラの登録
USRResponder::USRResponder(Void) static_throws
{
    if (static_try()) {

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

        // 描画ハンドラを登録する
        static_throw(RegisterHandler(
            SFXEventRange(SFEVT_RESPONDER_RENDER, SFEVT_RESPONDER_RENDER, SFP16_RENDER_REQUEST, SFP16_RENDER_REQUEST),
            XANDLER_INTERNAL(OnRenderRequest)
        ));

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

    }
}

// 再描画領域の登録
Void USRResponder::SetText(SFXAnsiStringConstRef param)
{
    SFCError error(SFERR_NO_ERROR);

    if (!param.equals(_text)) {

        if ((error = _text.Set(param)) == SFERR_NO_ERROR) {

            Invalidate(); // 再描画領域を登録する
        }
    }

    return error;
}

// 描画ハンドラ: ラベルテキストの描画
XANDLER_IMPLEMENT_VOIDRENDER(USRResponder, OnRenderRequest, invoker, reason, graphics)
{
    // ラベルテキストを描画する
    graphics->DrawSingleText(_text, GetLocalBound(), SFXRGBColor(0x00, 0x00, 0x00, 0x00));

    return;
}

// ※ 以下のように HandleRenderRequest 関数をオーバーライドして描画処理を実装することも可能
//    この場合、USRResponder::OnRenderRequest 関数は不要になる
Void USRResponder::HandleRenderRequest(SFXGraphicsPtr graphics) const
{
    // ラベルテキストを描画する
    graphics->DrawSingleText(_text, GetLocalBound(), SFXRGBColor(0x00, 0x00, 0x00, 0x00));

    return;
}
[Note] HandleRenderRequest 関数について

SFYWidget::HandleRenderRequest 関数をオーバーライドして描画処理を記述すれば、 描画ハンドラの登録を省略できます。

詳細は、描画ハンドラの解説を参照してください。

[Important] Invalidate 関数の呼び出しについて

SophiaFramework UNIVERSE 標準提供のレスポンダは、 領域、状態、属性、親、姉妹間の順序、テキスト、フォント、色などの視覚的要素が変更されると、 自動的に再描画領域に登録されますので、 SFYResponder::Invalidate 関数の呼び出しは不要です。

開発者が定義したレスポンダクラスでは、 テキストやイメージなどレスポンダ内の視覚的な要素を変更した場合、 Invalidate 関数により再描画領域を登録する必要があります。 この処理を怠ると、 Render(false) により描画エンジンを起動したときに、 レスポンダは再描画されません。

[Note] Invalidate 関数の引数について

SFYResponder::Invalidate 関数の引数にレスポンダの部分領域を再描画領域として登録できます。

指定した領域が前面に配置されたレスポンダに隠れる場合は、 可視領域を持つレスポンダであっても描画ハンドラは呼び出されません。 また、指定した領域と可視領域の交差領域でクリッピングして高速に再描画します。

[Note] Render 関数の引数について

引数に false(デフォルト値)を指定して SFYResponder::Render 関数を呼び出した場合、 可視領域SFYResponder::Invalidate 関数により登録した再描画領域と交差領域を持つレスポンダだけが、 描画イベントを受信します (再描画領域に登録しなかったレスポンダは描画イベントを受信しません)。

true を指定した場合は、 再描画領域の登録に関係なく、 可視領域を持つレスポンダはすべて描画イベントを受信します。

パフォーマンスの観点から、 Invalidate 関数により再描画が必要なレスポンダの領域だけを再描画領域に登録し、 引数に false を指定して Render 関数を呼び出して再描画する方法が推奨されます。

なお、Render 関数で再描画の対象となるレスポンダは、 レスポンダツリー内でこの関数を呼び出したレスポンダとその子孫レスポンダです。

[Note] 注意

再描画領域に登録されていても、 不可視状態であったり、 他のレスポンダに隠れて可視領域を持たないレスポンダは描画イベントを受信しません。

9.5.3. 描画ハンドラの起動

レスポンダは、 描画イベントを受信すると、 SFYWidget クラスの SFYWidget::SFYWidget コンストラクタ内の処理によって登録されたデフォルトの描画ハンドラを最初に起動します。

デフォルトの描画ハンドラは、 (透過でない場合は背景を塗り潰してから) SFYWidget::HandleRenderRequest 仮想関数を呼び出します。 このとき、レスポンダクラスでこの仮想関数がオーバーライドされていれば、 オーバーライドされた HandleRenderRequest 関数が呼び出されます。

その後、開発者がレスポンダに登録した描画ハンドラを登録順に起動します。

[Note] デフォルトの描画ハンドラ

デフォルトの描画ハンドラは、 (SFYResponder::SetPropertyTransparent 関数で設定された透過属性が false ならば SFYWidget::SetBackgroundColor 関数で設定された色で背景を塗り潰してから) SFYWidget::HandleRenderRequest 関数を呼び出します (参照: SFYWidget::SFYWidget コンストラクタの内部実装内の SFYWidget::OnRenderRequest 関数)。

SFYWidget::HandleRenderRequest 関数をオーバーライドすることにより、 レスポンダの描画処理を実装できます。 この場合、開発者は独自の描画ハンドラを実装してレスポンダに登録する手間を省略できます。

レスポンダを描画処理する手段としては、 SFYWidget::HandleRenderRequest 関数をオーバーライドして実装する方法がよく採られます。

参照: 各レスポンダクラスの HandleRenderRequest 関数の内部実装

例 9.29. 描画ハンドラの登録

// 描画ハンドラの登録
USRResponder::USRResponder(Void) static_throws
{
    if (static_try()) {

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

        static_throw(RegisterHandler(
            SFXEventRange(SFEVT_RESPONDER_RENDER, SFEVT_RESPONDER_RENDER, SFP16_RENDER_REQUEST, SFP16_RENDER_REQUEST),
            XANDLER_INTERNAL(OnRenderRequest)
        ));

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

    }
}
[Tip] Tip

描画ハンドラは、SFYResponder::RegisterHandler 関数を使用して登録します。

例 9.30. 描画ハンドラの実装

// 描画ハンドラの実装
XANDLER_IMPLEMENT_VOIDRENDER(USRResponder, OnRenderRequest, invoker, reason, graphics)
{
    // "Hello World" を中央に黒色で描画する
    graphics->DrawSingleText("Hello World", 
                              GetLocalBound(), 
                              SFXRGBColor(0x00, 0x00, 0x00, 0x00));
    return;
}
[Tip] Tip

描画ハンドラは、 graphics 引数(SFXGraphics) を使用してレスポンダのローカル領域を描画します。

[Note] HandleRenderRequest 関数をオーバーライド

以下のように SFYWidget::HandleRenderRequest 関数をオーバーライドして USRResponder::HandleRenderRequest 関数にレスポンダの描画処理を実装することも可能です。

この場合、USRResponder::OnRenderRequest 関数は不要になります。 USRResponder::HandleRenderRequest 関数を描画ハンドラとして登録する必要もありません。

例 9.31. HandleRenderRequest 関数をオーバーライドして実装する方法

// HandleRenderRequest 関数を利用した描画処理の実装
Void USRResponder::HandleRenderRequest(SFXGraphicsPtr graphics) const
{
    // "Hello World" を中央に黒色で描画する
    graphics->DrawSingleText("Hello World", 
                              GetLocalBound(), 
                              SFXRGBColor(0x00, 0x00, 0x00, 0x00));
    return;
}

9.5.4. イベントループ終了時の再描画

アプリケーションクラスは、 SFEVT_APP_START / SFEVT_APP_RESUME / SFEVT_APP_STOP / SFEVT_APP_SUSPEND イベントを受信したとき、 あるいは優先的イベントハンドラがイベントを処理しなかったとき、 SFYApplication::HandleEvent 関数を起動します。

SFYApplication::HandleEvent 関数は、 イベントループを開始し、 イベントループの最終段階で BREW イベントが処理された場合(イベントループ内の 1 つ以上のハンドラの戻り値が true である場合)、 "SFYResponder::Render(false)" により描画エンジンを起動します。

描画エンジンによる処理 (レスポンダの状態や配置の計算、可視領域の判定、 再描画領域の有無)の結果、 再描画が必要なレスポンダだけが描画イベントを受信し、 描画ハンドラを起動します。

[Note] 注意

SFYResponder::Invalidate 関数を使用して再描画領域を登録していても、 不可視状態であったり、 他のレスポンダに隠れてしまって可視領域を持たないレスポンダや、 可視領域が存在しても再描画領域が他のレスポンダに隠れてしまうレスポンダは、 描画イベントを受信しません。

この場合、描画ハンドラは起動されません。

例 9.32. SFYApplication::HandleEvent 関数の実装

// アプリが BREW 環境からイベントを受信したときに呼び出される関数
/*protected virtual */Bool SFYApplication::HandleEvent(SFXEventConstRef event)
{
    SFCError  error;
    Bool      result(false);

    // 配信エンジンを起動してイベントを配信する
    // ※ イベントは最初にルートに関連付けられた SFYDistributer インスタンスに配信される
    //    その後、トレーサの配信規則に基づいてルート以下のレスポンダツリーに配信される
    if ((error = _root->Distribute(event, &result)) == SFERR_NO_ERROR) {
        // (_root はルート、result 引数にはイベントの処理結果が格納される)

        if (event.GetType() != SFEVT_APP_STOP & event.GetType() != SFEVT_APP_SUSPEND)) {  
            // 再描画が必要な場合

            if (IsRenderable()) {  // 優先的イベントハンドラが登録されていない場合

                // 描画エンジンを起動してルート以下のレスポンダツリーを再描画する
                error = _root->Render();
            }
        }
    }
    if (error != SFERR_NO_ERROR) {
        // 配信エンジンや描画エンジンの起動時にメモリ不足などの致命的エラーが発生した場合

        if (HandleError(event, error)) {

            result = true;
        }
    }
    if ((event.GetType() == SFEVT_APP_SUSPEND) & IsRendererIntermissive()) {
        // サスペンド時に描画エンジン内部のデバイス画面保存用ビットマップを解放する場合

        // 描画エンジンを終了する
        _renderer.Terminate();
    }
    return result; // イベントを処理したときは true を返し, そうでないときは false を返す

}// SFYApplication::HandleEvent //

参照: SFCApplication::HandleEvent | SFCApplication::IsRenderable | SFCApplication::HandleError | SFYApplication::IsRendererIntermissive | SFYResponder::Distribute | SFYResponder::Render | SFYRenderer::Initialize | SFYRenderer::Terminate | SFZRoot | ルート | トレーサ | 配信エンジン | 描画エンジン | 描画イベント | 描画ハンドラ | イベントループ | 可視領域 | 再描画領域

[Note] 優先的イベントハンドラについて

SFXEventBypass クラスの解説を参照してください。

図 9.23. 再描画が必要である場合


再描画が必要である場合

イベントループ内で 1 つ以上のハンドラがイベントを処理して true を返す場合、 SFEVT_APP_START(アプリ開始イベント)/ SFEVT_APP_RESUME(アプリ再開イベント)を受信した場合、 または、優先的イベントハンドラの登録が解除された場合、 優先的イベントハンドラが登録されていなければ、 アプリケーションクラスは SFYResponder::Render 関数を呼び出して描画エンジンを起動し、 再描画を行います。

図 9.24. 再描画が不要である場合


再描画が不要である場合

イベントループ内のすべてのハンドラが false を返した場合や、 SFEVT_APP_STOP(アプリ終了イベント)/ SFEVT_APP_SUSPEND(アプリ中断イベント)を受信した場合、 または優先的イベントハンドラが登録されている場合、 SFYResponder::Render 関数は呼び出されないので描画エンジンは起動されません (再描画は行われません)。

9.5.5. コールバック終了時の再描画

タイマーやネットワークなどのコールバックで再描画を行うには、 再描画領域の登録の後に、 明示的に SFYResponder::Render 関数を呼び出して 描画エンジンを起動する必要があります。

[Caution] 注意

イベントループ終了時の再描画と異なり、 コールバックでは SFYResponder::Render 関数が自動的に呼び出されません。

図 9.25. コールバック時の描画処理 1: Render 関数を呼び出さない場合


コールバック時の描画処理 1: Render 関数を呼び出さない場合

コールバックはイベントループ外の処理なので、 描画エンジンは自動的に起動されません。

レスポンダの領域を再描画領域に登録するだけでは再描画されません。

再描画を行うには、明示的に SFYResponder::Render 関数を呼び出して描画エンジンを起動する必要があります。

図 9.26. コールバック時の描画処理 2: Render 関数を呼び出す場合


コールバック時の描画処理 2: Render 関数を呼び出す場合
  1. レスポンダの領域を再描画領域に登録します。
  2. 明示的に SFYResponder::Render 関数を呼び出して描画エンジンを起動します。
  3. 再描画が必要なレスポンダは、描画イベントを受信し、描画ハンドラを起動します。
  4. 描画ハンドラはレスポンダを再描画します。

例 9.33. コールバック内での再描画

SFCError USRResponder::OpenConnection(Void)
{
    SFCError error(SFERR_NO_ERROR);

    _label->SetText("); 

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

    error = _http.Connect(XALLBACK_INTERNAL(OnConnect));

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

    return error;
}

// http 接続完了コールバックの実装
XALLBACK_IMPLEMENT_SFXHTTPCONNECTION(USRResponder, OnConnect, error)
{
    if (error == SFERR_NO_ERROR) {

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

    }
    _label->SetText("OnConnect was called."); // Invalidate 関数は SetText 関数の内部で呼び出される
    Render(); // 明示的に描画エンジンを起動する

    return;
}
[Tip] 再描画の高速化

再描画を高速に行うには、 コールバックの最後で再描画する必要のあるすべてのレスポンダを子孫に持つレスポンダの SFYResponder::Render 関数を 1 度だけ呼び出すようにします。

9.5.6. アプリ開始/再開時と優先的イベントハンドラ終了時の全画面再描画

アプリ開始/再開時と優先的イベントハンドラ終了時は、 全画面(デバイス画面とルート以下のレスポンダツリー) を再描画する必要があります。

SFYApplication::HandleRender 関数は、この再描画を行うための関数です。

[Note] 優先的イベントハンドラについて

SFXEventBypass クラスの解説を参照してください。

[Note] SFYApplication::HandleRender 関数が呼び出されるタイミング

アプリ開始/再開時は、 アプリが SFEVT_APP_START / SFEVT_APP_RESUME イベントを受信した時に呼び出されます。

優先的イベントハンドラ終了時は、、 全画面を再描画するために呼び出す SFCApplication::RenderDeviceScreen 関数から呼び出されます。

例 9.34. SFCApplication::RenderDeviceScreen 関数の内部実装

/*public */Bool SFCApplication::RenderDeviceScreen(Void)
{
    return HandleRender(SFXEvent(0, 0, 0));
}// SFCApplication::RenderDeviceScreen //

SFYApplication::HandleRender 関数の内部実装では、 デバイス画面保存用ビットマップの有無に応じて 引数に false / true を指定して SFYResponder::Render 関数を呼び出し、全画面の再描画を行います。

[Note] Render 関数の引数について

Render 関数の引数に true を指定した場合は、 再描画領域の登録に関係なく、 可視領域を持つすべての再描画対象のレスポンダが、 描画イベントを受信し、 描画ハンドラを起動します。

false を指定した場合は、 再描画領域を登録した可視領域を持つレスポンダだけが再描画の対象となります。

[Note] デバイス画面保存用ビットマップについて

描画エンジン内部では、 前回 Render 関数実行後の画面内容をデバイス画面保存用ビットマップとして保存しています。

アプリ中断時や優先的イベントハンドラ終了時の全画面再描画では、 デバイス画面保存用ビットマップが利用可能であれば、 描画ハンドラを呼び出す代わりにそのビットマップから復元することにより再描画を最適化しています。

※ デバイス画面保存用ビットマップを利用して全画面を復元するには、 ルートSFYResponder::Recover 関数を呼び出します。

例 9.35. SFYApplication::HandleRender 関数の内部実装

// アプリ開始/再開時と優先的イベントハンドラ終了時に呼び出される仮想関数
/*protected virtual */Bool SFYApplication::HandleRender(SFXEventConstRef event)
{
    // ここに全画面(デバイス画面とルート以下のレスポンダツリー)の再描画を記述する
    SFCError  error;
    Bool      result(false);

    if (SFCApplication::HandleRender(event)) { // デバイス画面を再描画する

        // ルート以下のレスポンダツリーを再描画する
        // ※1. 可能であれば、デバイス画面保存用ビットマップを利用して高速に再描画行う
        // ※2. SFEVT_APP_START 受信時などデバイス画面保存用ビットマップが利用不可の場合は引数に true を指定して Render 関数を呼び出す(強制再描画を行う)
        error = _root->Render((event.GetType() == SFEVT_APP_START) || (_root->Recover() != SFERR_NO_ERROR));

        if (error == SFERR_NO_ERROR) {

            result = true;
        }
        else {
            // 描画エンジンの起動時にメモリ不足などの致命的エラーが発生した場合、HandleError() を呼び出す
            HandleError(event, error);
        }
    }
    return result;
}// SFYApplication::HandleRender //

参照: SFCApplication::HandleRender | SFCApplication::HandleError | SFCApplication::RenderDeviceScreen | SFYResponder::Render | SFYResponder::Recover | SFZRoot | ルート | 描画エンジン

[Caution] "_root->Render()" について

SFYApplication::HandleRender 関数では、

error = _root->Render((event.GetType() == SFEVT_APP_START) || (_root->Recover() != SFERR_NO_ERROR));

により、SFEVT_APP_START 受信時またはデバイス画面保存用ビットマップが利用不可である場合は、 "_root->Render(true)" が実行されます。 このとき、ルート以下の 可視領域を持つすべてのレスポンダが描画イベントを受信します。

逆に、デバイス画面保存用ビットマップが利用できる場合は、 "_root->Render(false)" が実行されます。

この場合、再描画領域を登録していないレスポンダや、 再描画領域が可視領域と交差領域を持たないレスポンダは、描画イベントを受信しません。

再描画領域を登録していないレスポンダでも、 再描画された親レスポンダや妹レスポンダと交差領域を持ちその影響を受けるレスポンダは、 デバイス画面保存用ビットマップからコピーして自分の可視領域を高速に復元します。