2007 年 8 月 28 日 23 時 40 分

ホストの作成 #4: 描画システム


このアーカイブは同期化されません。 mixi の日記が更新されても、このアーカイブには反映されません。


昨日の続き。MainProc の実装を行う。

MainProc は、スクリーンセーバー本体の
ウィンドウプロシージャーである。

scrnsave.lib に用意されている
DefScreenSaverProc 関数を使えば、
スクリーンセーバー固有の処理を任せることができるため、
MainProc 内部はそれほど複雑にならない。

========== Application.cpp ==========

#include "config.hpp"
#include "Application.hpp"

#pragma unmanaged

namespace Loafer {
namespace ScreenSaver {
namespace Host {

(…前略…)

LRESULT Application::MainProc(
        HWND hwnd, UINT message,
        WPARAM wParam, LPARAM lParam) {

    try {

        switch (message) {

        case WM_CREATE:
            dispatcher.Create(hwnd);
            break; // 既定の処理にも回す

        case WM_DESTROY:
            dispatcher.Destroy(hwnd);
            break; // 既定の処理にも回す

        case WM_ERASEBKGND:
            // ちらつき防止のため、背景を初期化しない
            return 0;

        case WM_PAINT:
            dispatcher.Paint(hwnd);
            return 0;

        }

    } catch (...) {
        // あらゆる例外は握りつぶす
    }

    // 残りは既定の処理に任せる
    return DefScreenSaverProc(hwnd, message, wParam, lParam);

}

}}} // Loafer::ScreenSaver::Host

========== end of Application.cpp ==========

WM_CREATE と WM_DESTROY は、
ウィンドウの構築と破棄に関するメッセージである。
これらメッセージを受け取って処理し、
Dispatcher に初期化と後始末を通知する。

WM_ERASEBKGND と WM_PAINT は描画処理の要である。
Windows の描画システムは 2 パスであり、
最初に背景を初期化するために WM_ERASEBKGND が呼ばれ、
続いて、本体の描画のために WM_PAINT が呼ばれる。

描画が 2 パスである理由は、描画処理の効率化のためだ。
一般的には WM_ERASEBKGND で背景全体を単色で塗りつぶし、
WM_PAINT で必要な部分だけ描画を行う。

WM_ERASEBKGND は、アプリケーションが処理しなくても
既定の処理である DefScreenSaverProc (DefWindowProc) が、
ウィンドウに関連付けられた背景ブラシを使って、
自動的に領域全体を塗りつぶしてくれる。

そのため、アプリケーションは、WM_PAINT を処理し、
最小限の描画コードを書くだけで済むのである。

ただ、この描画手順は、スクリーンセーバーのように
表現豊かで描画の頻度が高いアプリケーションには向かない。

理由は、WM_ERASEBKGND での描画(背景初期化)が、
画面に反映されるからである。
言い換えると、WM_ERASEBKGND が呼ばれたが、
WM_PAINT が呼ばれていない状態が表示されてしまうので、
描画頻度が高いとちらつきの原因になるのである。

例えば、Windows のメモ帳を起動し、
適当に文字を入力した後、
ウィンドウのサイズを色々変えてみる。

メモ帳の内部のテキストボックスは、
サイズが変わるたびに再描画されるのだが、
その際にちらつきが生じているのが分かると思う。

このちらつきを防ぐ簡単な方法は、
WM_ERASEBKGND では全く描画を行わず、
WM_PAINT で背景を含む全体を一度に描画することである。

そのためには、WM_ERASEBKGND の既定の処理を行わず、
アプリケーションが独自に WM_ERASEBKGND を受け取り、
DefScreenSaverProc (DefWindowProc) に渡すことなく、
直接戻り値として 0 を返せば良い。

戻り値 0 は、ここでは背景を初期化せず、
後々 WM_PAINT 等で背景の描画も行うことを意味している。

WM_PAINT では、Dispatcher に描画を指示しているが、
ここでは必ず全体を描画しなければならないことになる。

上記で処理したもの以外のメッセージは、
DefScreenSaverProc に渡されるため、
キーボードの押下やマウスの移動等は自動的に処理される。

では、このソースもコンパイラにかける。

cl /c /W4 /clr /FoApplication.obj Application.cpp

このソースも純粋な C++ だけだが、
リンクのことを考えて、/clr スイッチを使い、
#pragma unmanaged を指定することにした。

これで、Application.obj もできた。



Copyright (c) 1994-2007 Project Loafer. All rights reserved.