前回のおさらいとして、WindowsのGUIの為にOSはベースとして(ウィンドウそのものがそうであるような)矩形Rectangle-位置、幅・高さ)単位で、前後の重なり方を「Zオーダー」というもので管理して描画します。(矩形、楕円、その他多角形の「合成」としての領域Regionもありますが、取り敢えずスキップします。)従ってWindowsの画面は操作に従って何度も再描画されるわけです。また、再描画については、「WM_PAINTメッセージの際にBeginPaint関数とEndPaint関数の間に書く必要があります。

【WM_PAINT時に呼び出す関数の例】

bool OnPaint(HWND hWnd) {

    PAINTSTRUCT paint;
    HDC hDC = BeginPaint(hWnd, &paint);
    //ユーザー処理
    EndPaint(hWnd, &paint);
    return TRUE;         //WM_PAINTメッセージ処理をした場合はTRUEを返す
}

 

しかし、同時に「私もこのWindowsの描画処理のお約束(WM_PAINTメッセージ時にBeginPaintEndPaintの中に描画コードをいれる)というのが面倒且つ(プログラムのどこでも描画命令が描けないという)柔軟性の欠如でうんざりしていました」とも書きました。

 

ではどうしたかと言うと、或る雑誌に「描画を消えないようにするには」という記事が載っていたのでそれを参考としてCANVASクラスというものを作りました。使い方は以下の通り。

(1)ビットマップで「仮想ウィンドウ」(CANVASクラスインスタンス)を作り、WM_CREATEで初期設定を行う。

<Project.h>

///////////////////////
//仮想ウィンドウの作成
///////////////////////
CANVAS cvs;


<ProjectProc.hのOnCreate関数>

    //仮想ウィンドウの初期化
    cvs.SetCanvas(m_hWnd);

 

(2)アプリケーションプログラムでは、その「仮想ウィンドウ」に随時、どこからでも書き込める。(CANVASクラスのメンバー関数に、Line、Circle、Paint等の「描画関数」命令を設定)

<描画関数例>

Basic命令に模した関数
cvs.Dot(int x, int y);
cvs.Line(int x1, int y1, int x2, int y2);
cvs.Box(int x1, int y1, int x2, int y2, int br);
cvs.Circle(int x, int y, int r, int br);
Win32由来の関数
cvs.Ellipsed(int x1, int y1, int x2, int y2, int br) ;
cvs.Arced(int x1, int y1, int x2, int y2, int sx, int sy, int ex, int ey);
cvs.Chorded(int x1, int y1, int x2, int y2, int sx, int sy, int ex, int ey, int br); 
その他

cvs.GetBMP、cvs.PutBMP、cvs.FitBMP等

 

(3)書き込んだらウィンドウのクライアント領域全体を無効化(Invalidate)し、メッセージキューに置かれたWM_PAINTが処理される段階で「仮想ウィンドウ」をクライアント領域に張り付ける。(メンバー関数のOnPaint関数を使うが、これはSkeltonWizardが予め書いてくれる。)

<ProjectProc.hのOnPaint関数>

bool CMyWnd::OnPaint(WPARAM wParam, LPARAM lParam) {
    PAINTSTRUCT paint;
    cvs.OnPaint(BeginPaint(m_hWnd, &paint));
    EndPaint(m_hWnd, &paint);
    return TRUE;
}

 

21年前に作った、このCANVASクラスは結構重宝しており、私ごときが行う描画では大体これで済んでしまいました。

 

で、

 

いよいよ本題ですが、この間C#で古いBasicのプログラムを移植しようとして、「こんなに簡単になったC#だからきっと描画も簡単になって、PictureBoxコントロールのメソッドに描画系があるんだろうな」と思いながらWEBでググりましたが、結局は何も変わっていませんでした。(注)

注:大体、詳しく調べてはいませんが、GDI系は殆どがアンマネージドリソースで、使い終わったらDispose()メソッドを呼ぶ必要があるようです。

 

Formクラスでいえば、矢張りWM_PAINTに相当するOnPaintメッセージをオーバーライドしてデフォルトの処理の後に描画関係のメソッドを置きますが、そのメソッドはOnOPaintメソッドの引数であるPaintEventArgsのものなので、OnPaint以外では取得できません。

 

        protected override void OnPaint(PaintEventArgs e)
        {
            base.OnPaint(e);
            // FormのClientRectangleプロパティにより、Form領域の四角形を取得し、同じサイズで描画
            e.Graphics.DrawImage(bitmap, this.ClientRectangle);
        }
 

「おいおい、勘弁してくれよ。C#でもまたWM_PAINT縛りかよ。」と気落ちしたのですが、このeのプロパティの「Graphicsって何だ?」という疑問がわき、もう少し詳しくこのクラスらしきGraphicsPictureBoxを調べてみることにしました。

 

お後は次回ということで。