パソコン活用研究シリコンバレー(C、C++、の活用研究)

文字列の描画

=============================================================================

=============================================================================

CUIへの文字列の表示がC言語なら
printf("Hello");
の1行で出来たのに比べるとWindowsのGUIでの文字列の描画はかなり面倒な手数がかかります。

Windowsの描画のためにGDI関数が用意されています。  
GDI(Graphics devaice interface)

文字列の描画にはTextOutというGDI関数を使います。
BOOL TextOut(HDC hdc, int x, int y, LPCSTR lpstring, int cbstring);

TextOut関数は第1引数に デバイスコンテキストのハンドル、
第2引数と第3引数に文字列を描画するx,y座標
第4引数に描画する文字列
第5引数に描画する文字列のバイト数
を設定します。

デバイスコンテキストについては後で説明しますが、描画を行うのに必要な情報を格納したデータベースであり、
描画を行うにあたりまずこのデバイスコンテキストへのハンドルを取得する必要があります。

デバイスコンテキストの取得に使う関数はいくつかありますが、まずBeginPaint, GetDCなどの関数があります。
描画が終了した場合は、デバイスコンテキストのハンドルを解放してやる必要があります。
解放にはEndPaint、ReleaseDCなどの関数を使います。


前回(文字列の描画1)は画面をクリックしたら文字を表示するプログラムを作ってみましたが、画面サイズの変更など
で画面がクリアされて描画した文字が消えてしまう問題がありました。
今回はWM_PAINTメセージの処理の中で描画してみます。




#include "windows.h"
#include <stdio.h>

// 関数のプロトタイプ宣言
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

// エントリポイント
int APIENTRY WinMain(HINSTANCE hInstance,
                        HINSTANCE hPrevInstance,
                        LPSTR lpCmdLine,
                        int nCmdShow )
{
        WNDCLASSEX wcex;        // ウインドウクラス構造体
        HWND hWnd;              // ウインドウハンドル
        MSG msg;                // メッセージ構造体

        // ウィンドウクラス構造体を設定します。
        wcex.cbSize = sizeof(WNDCLASSEX); 
        wcex.style = CS_HREDRAW | CS_VREDRAW;
        wcex.lpfnWndProc = (WNDPROC)WndProc;
        wcex.cbClsExtra = 0;
        wcex.cbWndExtra = 0;
        wcex.hInstance = hInstance;
        wcex.hIcon = LoadIcon(NULL, IDI_APPLICATION);
        wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
        wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
        wcex.lpszMenuName = NULL;
        wcex.lpszClassName = "ModelApp";
        wcex.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
        
        // ウインドウクラスを登録します。
        RegisterClassEx(&wcex);

        // ウインドウを作成します。
        hWnd = CreateWindow(wcex.lpszClassName, // ウインドウクラス名
                "TextOut0",             // キャプション文字列
                WS_OVERLAPPEDWINDOW,            // ウインドウのスタイル
                CW_USEDEFAULT,                  // 水平位置
                CW_USEDEFAULT,                  // 垂直位置
                CW_USEDEFAULT,                  // 幅
                CW_USEDEFAULT,                  // 高さ
                NULL,                                   // 親ウインドウ
                NULL,                                   // ウインドウメニュー
                hInstance,                              // インスタンスハンドル
                NULL);                                  // WM_CREATE情報

                // ウインドウを表示します。
                ShowWindow(hWnd, nCmdShow);
                UpdateWindow(hWnd);

        // メッセージループ
        while(GetMessage(&msg, NULL, 0, 0)) 
        {
                TranslateMessage(&msg);
                DispatchMessage(&msg);
        }

        // 戻り値を返します。
        return msg.wParam;
}

// ウインドウプロシージャ
LRESULT CALLBACK WndProc(HWND hWnd,
                               UINT message,
                               WPARAM wParam,
                               LPARAM lParam)
{
        PAINTSTRUCT ps;
        HDC hdc;
        
        // メッセージの種類に応じて処理を分岐します。
        switch (message) 
        {
                
        case WM_PAINT:
                
                hdc=BeginPaint(hWnd,&ps);
                 
                TextOut(hdc, 20,20,"Hello",5);
                EndPaint(hWnd,&ps);

                return 0;


                case WM_DESTROY:
                        // ウインドウが破棄されたときの処理
                        PostQuitMessage(0);
                        return 0;
                default:
                        // デフォルトの処理
                        return DefWindowProc(hWnd, message, wParam, lParam);
        }
}


マウスの動きと連動させてみます。
左クリックした位置に"X"を表示するようにしてみます。



以下、ウィンドウプロシージャの中のコードのみ掲載します。
WM_LBUTTONDOWNのメッセージハンドラーでは、マウスのクリックした位置(座標)を変数x,yに保持し、
InvalidateRect(hWnd,NULL,TRUE); で無効領域を発生させWM_PAINTメッセージが送られるようにしています。

実際の描画はWM\PAINTメッセージハンドラーの中に記述します。

// ウインドウプロシージャ
LRESULT CALLBACK WndProc(HWND hWnd,
                               UINT message,
                               WPARAM wParam,
                               LPARAM lParam)
{
        PAINTSTRUCT ps;
        char*txt="X";
        static int x=0;
        static int y=0;
        
        // メッセージの種類に応じて処理を分岐します。
        switch (message) 
        {
      HCURSOR hcur;
        case WM_LBUTTONDOWN:
        //        ShowCursor(false);

                x=LOWORD(lParam);
                y=HIWORD(lParam);
                InvalidateRect(hWnd,NULL,TRUE);
       return 0;
        case WM_PAINT:
                BeginPaint(hWnd,&ps);
                TextOut(ps.hdc, x,y,txt,strlen(txt));
                EndPaint(hWnd,&ps);
                return 0;


                case WM_DESTROY:
                        // ウインドウが破棄されたときの処理
                        PostQuitMessage(0);
                        return 0;
                default:
                        // デフォルトの処理
                        return DefWindowProc(hWnd, message, wParam, lParam);
        }
}




WM_PAINTメッセージの中でGetDCを使ってはいけない



WM_PAINTメッセージの処理のなかで
GetDCでデバイスコンテキストを取得しても同様に表示しますが、
こちらには問題があります。

一見、TextOut0.exeは問題ないように見えますが・・



TextOut0.cppのウインドウプロシージャのみ掲載します。

// ウインドウプロシージャ
LRESULT CALLBACK WndProc(HWND hWnd,
                               UINT message,
                               WPARAM wParam,
                               LPARAM lParam)
{
        HDC hdc;
        
        // メッセージの種類に応じて処理を分岐します。
        switch (message) 
        {
      
        case WM_PAINT:
                hdc=GetDC(hWnd);
                TextOut(hdc, 100,50,"Hello",5);
                ReleaseDC(hWnd,hdc);
                return 0;


                case WM_DESTROY:
                        // ウインドウが破棄されたときの処理
                        PostQuitMessage(0);
                        return 0;
                default:
                        // デフォルトの処理
                        return DefWindowProc(hWnd, message, wParam, lParam);
        }
}



GetDCは単にデバイスコンテキストへのハンドルを取得するだけで、無効領域の有効化を行わないので
WM_PAINTが呼び出され続けます。

以下、TaskmanagerでCPU使用率を見ると
textout0.exerが0%なのに対し、textout.exeは10.3%にもなっています。







WM_PAINTでGetDCを使ってはいけないを
もう少し目に見える形で試してみます。

Textouta.exeは左クリックでMessageBoxを表示させるようにしていますが、
左クリックしても何の変化もありません。
WM_PAINTメッセージが呼び出され続けているためと思われます。

ウィンドウプロシージャの部分だけ掲載しておきます。
WM_LBUTTONDOWNメッセージの処理の中に、MessageBoxの表示をするようにコードを
書いていますが、このプログラムを実行すると、マウスの左クリックをしても何の反応もありません。

// ウインドウプロシージャ
LRESULT CALLBACK WndProc(HWND hWnd,
                               UINT message,
                               WPARAM wParam,
                               LPARAM lParam)
{
        HDC hdc;
        
        // メッセージの種類に応じて処理を分岐します。
        switch (message) 
        {
    
        case WM_LBUTTONDOWN:
                // WM_PAINが呼び出され続けていて、MessageBoxが表示されないT
                MessageBox(NULL,"","",MB_OK);
                return 0;
                
                
        case WM_PAINT:
                hdc=GetDC(hWnd);
                TextOut(hdc, 100,50,"Hello",5);
                ReleaseDC(hWnd,hdc);
                return 0;


                case WM_DESTROY:
                        // ウインドウが破棄されたときの処理
                        PostQuitMessage(0);
                        return 0;
                default:
                        // デフォルトの処理
                        return DefWindowProc(hWnd, message, wParam, lParam);
        }
}


WM_PAINTメッセージが呼び出された回数を表示するプログラムですが、
TextOut2.exeはWM_PAINTが呼び出され続けるので、起動直後からカウントを数え続けます。


ウィンドウプロシージャ部分のみ掲載します。

// ウインドウプロシージャ
LRESULT CALLBACK WndProc(HWND hWnd,
                               UINT message,
                               WPARAM wParam,
                               LPARAM lParam)
{
        PAINTSTRUCT ps;
        HDC hdc;
        char txt[10];
        static int i=0;
        
        // メッセージの種類に応じて処理を分岐します。
        switch (message) 
        {

                
        case WM_PAINT:
                i=i+1;
                if ( i>10000) {i=0;}
        sprintf(txt, "%d",i);
//              MessageBox(hWnd,txt,"i",MB_OK);
                hdc=GetDC(hWnd);
//              hdc=BeginPaint(hWnd,&ps)
                TextOut(hdc, 20,20,txt,strlen(txt));
//              EndPaint(hWnd,&ps);
                ReleaseDC(hWnd,hdc);
                return 0;


                case WM_DESTROY:
                        // ウインドウが破棄されたときの処理
                        PostQuitMessage(0);
                        return 0;
                default:
                        // デフォルトの処理
                        return DefWindowProc(hWnd, message, wParam, lParam);
        }
}



Textout4.exeもWM_PAINTが呼ばれるたびに、呼ばれた回数を数えて表示するプログラムですが、
Textout2.exeと違い、デバイスコンテキストはBeginPaint()で取得しています。
このため、無限にWM_PAINTを呼び出し続けるということはありません。
また左クリックしたときにも、 InvalidateRect() でWM_PAINTを呼び出し、カウントするようにしています。

Textout4.exeに表示されるカウント数をみると、WM_PAINTが呼ばれるタイミングを推測することができます。
起動直後はカウント数は1で、1回のみ呼ばれたことがわかります。何もしなければカウント数はそのままです=WM_PAINTが呼ばれることはない。
左クリックでカウントは1づつ増加します。
画面サイズの変更で1ピクセル変更されるたびにWM_PAINTが呼ばれて、カウント数が増加することがわかります。




ウインドウプロシージャ部分のみ掲載します。


// ウインドウプロシージャ
LRESULT CALLBACK WndProc(HWND hWnd,
                               UINT message,
                               WPARAM wParam,
                               LPARAM lParam)
{
        PAINTSTRUCT ps;
        HDC hdc;
        char txt[10];
        static int i=0;
        
        // メッセージの種類に応じて処理を分岐します。
        switch (message) 
        {
   case WM_LBUTTONDOWN:
        //        ShowCursor(false);
                InvalidateRect(hWnd,NULL,TRUE);
        sprintf(txt, "%d",i);
                MessageBox(hWnd,txt,"i",MB_OK);
                
       return 0;
                
        case WM_PAINT:
                i=i+1;
                if ( i>10000) {i=0;}
//      sprintf(txt, "%d",i);
//              MessageBox(hWnd,txt,"i",MB_OK);
//              hdc=GetDC(hWnd);
                hdc=BeginPaint(hWnd,&ps);
                 sprintf(txt, "%d",i);
                TextOut(hdc, 20,20,txt,strlen(txt));
                EndPaint(hWnd,&ps);
//              ReleaseDC(hWnd,hdc);
                return 0;


                case WM_DESTROY:
                        // ウインドウが破棄されたときの処理
                        PostQuitMessage(0);
                        return 0;
                default:
                        // デフォルトの処理
                        return DefWindowProc(hWnd, message, wParam, lParam);
        }
}




 


TopPage