<Cursor2e.cpp>
#include "windows.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, // ウインドウクラス名 "Cursor2e", // キャプション文字列 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) { HDC hdc; 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); hdc=GetDC(hWnd); // クリックすると描画、画面サイズ変更など無効領域が発生すると画面が消去され、新たにクリックした位置に表示 TextOut(hdc, x,y,txt,strlen(txt)); ReleaseDC(hWnd,hdc); return 0; case WM_DESTROY: // ウインドウが破棄されたときの処理 PostQuitMessage(0); return 0; default: // デフォルトの処理 return DefWindowProc(hWnd, message, wParam, lParam); } }
ここでは、無効領域と再描画について、いろいろなコードを試しながら、この厄介な課題と対応策を探ってみます。
これらのテストを通して、画面に文字を表示するだけでもWindowsプログラムの奥の深さ(厄介さ)やお作法が
見えてくると思います。
(1)無効領域と再描画
(2) WM_PAINTハンドラにreturn 0;だけを記述
再描画はウインドウプロシージャにWM_PAINTに対する処理を記述していれば、その記述に従い、
また特にWM_PAINTハンドラーを記述していなければデフォルトのDefWindowProc()によって処理されます。
最初ののプログラム<Cursor2e.cpp>にはWM_PAINTハンドラーを記述していないので、再描画のメッセージは
DefWindowProc()によって処理されていました。
そこで、WM_PAINTハンドラに
return 0;
だけを記述した何もしないWM_PAINTハンドラを作ってみたプログラムの挙動を試してみます。
ウィンドウの最小化、最大化、サイズ変更があると画面はクリアされるのは上記のCursor2e.cppの時と同じですが、
画面が他の画面に隠れた時の挙動に差がでます。(試した環境はWindows11)
画面の一部に別のプログラムの画面が重なった
(画像7)
重なった画面を動かした後にマウスを左クリックすると前の画面はクリアされて
今マウスをクリックした場所に"X"が表示される。
※Cursor2fの画面がフォーカスを失った後に画面(クライアント領域だけではなく、システム領域も)
をクリックすると同様の挙動になる。
Cursor2e.cppのプログラムでは、画面が重なった後に重なった画面を移動した場合、前の画面はそのままで、
前の画面の描画に加えて新たに左クリックした場所に"X"が追加で描画されました。
(画像8)
推測ですが、TaskmanagerでCPU使用率を見るとアプリ起動後から10%程度を占拠しっぱなしなので
このプログラムではWM_PAINTメッセージが無限に呼ばれていっる状態になっていると思われます。
WM_PAINTメッセージハンドラでは何もしないでreturnしているので、無効領域が存在し続けてWM_PAINTが
呼ばれ続けている状態と思われます。
cursor2e.cppや後述するcursor2g.cppのプログラムではこのような高いCPU使用率は見られないので、
DefWindowProc()に処理を任せた場合や、cursor2g.cppのようなWM_PAINTメッセージハンドラの記述の仕方
をすれば正常描画な処理が行われているということになると思います。
つまり、WM_PAINTメッセージハンドラに return 0; だけを記述するというのはあまりよろしくないコード
(処理の仕方)で、ちゃんと無効領域を認識して処理する(無効領域をoffにする)コードを書かないといけない
ということです。(そのコードは後述のCursor2g.cppを見て下さい)
ウィンドウプロシージャの部分のコードのみ記載します。
<Cursor2f.cpp>
// ウインドウプロシージャ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { HDC hdc; 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); hdc=GetDC(hWnd); // クリックすると描画、画面サイズ変更など無効領域が発生すると画面が消去され、新たにクリックした位置に表示 // 別の画面に隠れたあと、クリックするといったん前の表示は消える。 TextOut(hdc, x,y,txt,strlen(txt)); ReleaseDC(hWnd,hdc); return 0; case WM_PAINT: return 0; case WM_DESTROY: // ウインドウが破棄されたときの処理 PostQuitMessage(0); return 0; default: // デフォルトの処理 return DefWindowProc(hWnd, message, wParam, lParam); } }
(3) WM_PAINTメッセージハンドラの記述
更にWM_PAINTメッセージに対するハンドラの処理を以下のようにしてみると
基本的な挙動はCursor2e.cppと同じようになりました。
case WM_PAINT:
BeginPaint(hWnd,&ps);
EndPaint(hWnd,&ps);
return 0;
<Cursor2g.cpp>
// ウインドウプロシージャ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { PAINTSTRUCT ps; HDC hdc; 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); hdc=GetDC(hWnd); // クリックすると描画、画面サイズ変更など無効領域が発生すると画面が消去され、新たにクリックした位置に表示 // 別の画面に隠れたあと、クリックするといったん前の表示は消える。 TextOut(hdc, x,y,txt,strlen(txt)); ReleaseDC(hWnd,hdc); return 0; case WM_PAINT: BeginPaint(hWnd,&ps); EndPaint(hWnd,&ps); return 0; case WM_DESTROY: // ウインドウが破棄されたときの処理 PostQuitMessage(0); return 0; default: // デフォルトの処理 return DefWindowProc(hWnd, message, wParam, lParam); } }
上記のプログラムは全て、ウィンドウクラス構造体の設定でウィンドウのsタイルとして
ウィンドウのサイズが変更された時は再描画されるように設定していました。
wcex.style = CS_HREDRAW | CS_VREDRAW;
次はこの設定を
wcex.style = 0;
とし、ウィンドウサイズの変更で再描画しないようにしたプログラムにしてみます。
ウィンドウプロシージャの中はCuursor2e.cppと同じです。
ウインドウスタイルがこの設定の場合、画面サイズの変更で画面の表示がクリアされることは
ありませんが、画面を小さくしたときに消えてしまった部分は画面を再び大きくしても消えたままです。
最初の状態
(画像9)
画面を小さく変更
(画像10)
再び画面を大きくしてみる。消えた部分は消えたまま
(画像11)
コードはウィンドウクラス構造体の設定の個所だけ掲載しておきます。
他は基本的にcursor2e.cppと同じです。
<cursor2h.cpp>
// ウィンドウクラス構造体を設定します。 wcex.cbSize = sizeof(WNDCLASSEX); wcex.style = 0; 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);
(4)BeginPaint()でデバイスコンテキストへのハンドルの取得
Cursor2f.cppのプログラムではデバイスコンテキストへのハンドルの取得はGetDC()で行っていましたが、
これをBeginPaint()に変えたプログラムCursor2d.cppを試してみます。WM_PAINTメッセージハンドラは記述しません。
Cursor2f.cppでは、マウスを左クリックするたびに"X"が重ねて描画されましたが、Cursor2d.cppでは
"X"は最初の1つしか描画されず、それ以降はマウスを左クリックしても何も描画されません。
ここに、デバイスコンテキストへのハンドルを取得するのにGetDC()を使う場合とBeginPaint()を使う場合の
差がでます。
BeginPaint()はWM_Paintメセージの処理の中で使う関数で、無効領域がある場合、無効領域を有効化して
デバイスコンテキストへのハンドルを返すという動作をする関数です。無効領域がない場合は有効なデバイスコンテキストへの
ハンドルを返しません。
従って、1つめの"X"を描画した後に、無効領域が発生しない限りBeginPaint()では有効なデバイスコンテキストへのハンドルが
取得できず、2つ目以降の"X"は描画されない、という挙動になっていると推測されます。
画面サイズが変更されて無効領域が発生すると、再び1つだけ新たなクリック位置に"X"の描画がされます。
Cursor2d.cppのCPU使用率をTaskmanagerで見ると、アプリの起動後、Windowサイズの変更後他界はCPU使用率になっていますが
左クリックで描画後はCPU使用率はほぼ0%になります。
アプリの起動後、Windowサイズの変更後は無効領域が存在しWM_PAINTメッセージが呼ばれ続けているが、描画時にBeginPaint()が
呼ばれて無効領域が有効化されてWM_PAINTメセッージが発生しなくなったものと考えます。
Cursor2d.cppのウィンドウプロシージャの部分のみ掲載します。
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); BeginPaint(hWnd,&ps); TextOut(ps.hdc, x,y,txt,strlen(txt)); EndPaint(hWnd,&ps); return 0; case WM_PAINT: return 0; case WM_DESTROY: // ウインドウが破棄されたときの処理 PostQuitMessage(0); return 0; default: // デフォルトの処理 return DefWindowProc(hWnd, message, wParam, lParam); } }