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

9.4. 描画処理

9.4.1. 概要

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

  1. 開発者が定義したレスポンダクラスでは、 再描画が必要になる場合は SFYResponder::Invalidate 関数を呼び出して レスポンダの領域を再描画領域に登録します。
  2. SophiaFramework UNIVERSE 標準提供レスポンダでは、 テキストやフォント、色、状態、 領域、親、テキスト、フォント、色などの設定を行うと、 再描画する必要のあるレスポンダの領域が自動的に再描画領域に登録されます (最適化を行う場合を除き、再描画領域への登録は不要です)。
  3. レスポンダの SFYResponder::Render 関数を呼び出して 描画エンジンを起動します。
    [Note] 注意

    描画エンジンの起動は、以下の 3 つの場合に分類できます。

    アプリ開始時とレジューム時の全画面描画とイベントループにおける描画では、 SFYResponder::Render 関数は自動的に呼び出されます。

    コールバックにおける描画では、 開発者が明示的に SFYResponder::Render 関数を呼び出す必要があります。

  4. レスポンダツリー内で 可視領域を持つ、 Render 関数を呼び出したレスポンダとその子孫レスポンダが再描画の対象となります。
  5. 引数に false(デフォルト)を指定して Render 関数を呼び出した場合は、 描画エンジンの最適化処理(※)により、 実際に再描画が必要なレスポンダだけが描画イベントを受信します。
  6. 描画イベントを受信したレスポンダは、 描画ハンドラを起動して自分自身を再描画します。
  7. 描画イベントを受信しなかった可視領域を持つレスポンダは、 直下に配置されたレスポンダが描画ハンドラにより再描画され、その領域が可視領域と共通部分を持つ場合に限り、 前回再描画時に保存しておいた画面のバックアップ画像からコピーして可視領域を復元します。 そうでない場合は、何も行いません。
  8. 今回再描画した画面の画像を保存します。
[Note] 再描画の最適化

再描画領域に登録され、かつ、 可視領域が再描画領域と共通部分を持つレスポンダだけが描画イベントを受信し、描画ハンドラを起動します。

また、レスポンダの再描画はこの共通部分でクリッピングして高速に行われます。

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

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

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

[Note] 注意

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

次に、引数に false(デフォルト)を指定して SFYResponder::Render 関数を呼び出して描画エンジンを起動すると、 再描画領域に登録された USRResponder は、 可視領域を持つ場合に限り、 描画イベントを受信し、 描画ハンドラである OnRenderRequest 関数を起動します。

[Note] Render 関数の呼び出し

イベントループにおける描画処理や、 アプリ開始時とレジューム時の全画面描画処理では、 SFYResponder::Render 関数は自動的に呼び出されますので、 開発者は Render 関数を呼び出す必要はありません。

コールバックにおける描画処理の場合は、 明示的に Render 関数を呼び出す必要があります。

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

[Note] 注意

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

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

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

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

HandleRenderRequest 関数を利用して描画処理を実装すれば、 描画ハンドラの登録などが省略可能です。 上の例では、OnRenderRequest 描画ハンドラを定義・実装・登録していますが、 一般的には、描画処理は SFYWidget::HandleRenderRequest 関数をオーバーライドして実装します。

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

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

SophiaFramework UNIVERSE 標準提供のレスポンダクラスでは、 領域、状態、属性、親、姉妹間の順序、テキスト、フォント、色などの視覚的要素を変更すると、 再描画が必要なレスポンダの領域が自動的に再描画領域に登録されます。 param 引数にレスポンダの部分領域を指定して再描画の最適化を行う場合を除き、 SFYResponder::Invalidate 関数を呼び出す必要はありません。

開発者が定義したレスポンダクラスでは、 テキストやイメージなどレスポンダ内の視覚的な要素を変更した場合、 必ず Invalidate 関数により再描画領域を登録するようにしてください。 再描画領域を登録しなければ、 引数に false(デフォルト)を指定して SFYResponder::Render 関数を呼び出して描画エンジンを起動した場合に再描画されません。

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

SFYResponder::Invalidate 関数の引数を利用することにより、 再描画領域をレスポンダ内の部分的な領域に限定できます。

指定した領域のすべてが前面に配置されたレスポンダに隠れる場合、 可視領域を持つレスポンダであっても描画ハンドラは呼び出されません。 そうでない場合でも、指定した領域と可視領域の共通領域でクリッピングして極めて高速な描画処理が行われます。

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

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

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

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

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

[Note] 注意

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

9.4.3. 描画ハンドラの起動

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

デフォルトの描画ハンドラは、 (透過でない場合は背景を塗り潰してから) SFYWidget::HandleRenderRequest 関数を呼び出します。

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

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

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

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

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

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

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

// 描画ハンドラの登録
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.28. 描画ハンドラの実装

// 描画ハンドラの実装
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.29. HandleRenderRequest 関数をオーバーライドして実装する方法

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

9.4.4. イベントループにおける描画処理

アプリケーションクラスは、 BREW 環境からイベント(BREW イベント)を受信すると、 SFYApplication::HandleEvent 関数を起動します。

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

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

[Note] 注意

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

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

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

// BREW イベントを受信したときに呼び出される仮想関数
/*protected virtual */Bool SFYApplication::HandleEvent(SFXEventConstRef event)
{
    // ここに BREW 環境から受信した各種イベントの分岐処理を記述する

    SFCError  error;
    Bool      result(false);

    // 配信エンジンを起動し、トレーサを使用してイベントを配信する
    // イベントを最初に受信するのはルートに関連付けられた SFYDistributer インスタンス
    // その後、トレーサの配信規則に基づいてルートを頂点とするレスポンダツリー上のレスポンダにイベントは配信される
    // ※_root はルート(SFZRoot)
    if ((error = _root->Distribute(event, &result)) == SFERR_NO_ERROR) {

        if (result) { // ※result 引数には、イベントの配信結果が格納されている

            if (IsRenderable()) { // 画面を再描画しても良い場合(優先的イベントハンドラが登録されていない場合)

                // 描画エンジンを起動し、画面を再描画する
                error = _root->Render();
        }
    }
    if (error != SFERR_NO_ERROR) {

        // 配信エンジンや描画エンジンの起動時にメモリ不足などの致命的エラーが発生した場合、HandleError() を呼び出す
        if (HandleError(event, error)) {

            result = true;
        }
    }

    return result; // イベントを処理したときは true を返し, そうでないときは false を返す

}// SFYApplication::HandleEvent //

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

[Note] 優先的イベントハンドラ

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

図 9.23. イベントが処理された場合


イベントループでイベントが処理された場合

イベントループ内で 1 つ以上のハンドラがイベントを処理して true を返すと、 アプリケーションクラスは Render 関数を呼び出して描画エンジンを起動し、再描画を行います。

図 9.24. イベントが処理されなかった場合


イベントループでイベントが処理されなかった場合

イベントループ内のすべてのハンドラが false を返した場合、 Render 関数は呼び出されないので描画エンジンは起動されません(再描画は行われません)。

9.4.5. コールバックにおける描画処理

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

イベントループにおける描画処理や、 アプリ開始時とレジューム時の全画面描画処理と異なり、 コールバックでは Render 関数が自動的に呼び出されないからです。

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


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

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

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

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

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


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

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

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.4.6. アプリ開始時とレジューム時の全画面描画処理

アプリ開始時とレジューム時は、 全画面を再描画するために SFYApplication::HandleRender 関数が自動的に呼び出されます。

SFYApplication::HandleRender 関数では、 下記のコードにあるように引数に true を指定して SFYResponder::Render 関数を呼び出して描画エンジンを起動します。

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

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

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

// 全画面の再描画が必要なときに呼び出される仮想関数
/*protected virtual */Bool SFYApplication::HandleRender(SFXEventConstRef event)
{
    // ここにアプリ開始時とレジューム時の全画面の再描画処理を記述する

    SFCError  error;
    Bool      result(false);

    if (IsRenderable()) { // 画面が再描画可能なら(優先的イベントハンドラが登録されていないなら)

        // 全画面を再描画する [ Render() の引数に "true" が指定されているため ]
        // ※_root はルート(SFZRoot)
        if ((error = _root->Render(true)) == SFERR_NO_ERROR) {

            result = true;
        }

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

            result = true;
        }
    }

    return result; // 全画面を再描画したときは true を返し, そうでないときは false を返す

}// SFYApplication::HandleRender //

参照: SFYResponder::Render | SFCApplication::HandleRender | SFCApplication::IsRenderable | SFCApplication::HandleError | SFZRoot | ルート

[Note] 優先的イベントハンドラ

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