Windows API Primer   描画の基礎

ホーム   目次

GDI

  1. GDI (Graphics Deveice Interface、グラフィックス・デバイス・インターフェイス) はデバイス (ディスプレイやプリンター) に図形・画像・テキストを描画するための関数群です。
  2. GDI を使うには、デバイスコンテキスト (Device context) と呼ばれるデータ構造へのハンドルが必要になります。
  3. デバイスコンテキストは、ウィンドウプロシージャの中で取得しますが、WM_PAINTメッセージの中で取得する場合と、その他のメッセージの中で取得する場合とでは、取得する方法が違います。
  4. WM_PAINT メッセージとは、ウィンドウが新たに作成された時と、ウィンドウに無効領域が発生して再描画が必要になった時に発生するメッセージです。
  5. 無効領域とは、ウィンドウのサイズが変更された時(最小化も含む)や、ウィンドウの一部または全部が、他のウィンドウによって隠された場合などに発生します。

BeginPaint

  1. WM_PAINT メッセージの中でデバイスコンテキストを取得する場合には、BeginPaint 関数を使います。
  2. BeginPaint 関数によるデバイスコンテキストの取得には、PAINTSTRUCT という構造体が必要になります。
  3. BeginPaint 関数で取得したデバイスコンテキストは、描画が終了した後に、EndPaint 関数で必ず解放しなければなりません。

起動直後に何かを描画したい場合には、WM_CREATE メッセージの中で描画すれば良いような気がしますが、WM_CREATE メッセージの直後には、必ず WM_PAINT メッセージが発生します。
そして、デバイスコンテキストによる描画は、メッセージ間で共有されません。つまり WM_CREATE で描画したとしても、WM_PAINT メッセージでは、その描画は消えることになります。
ですから、デバイスコンテキストによる描画は、通常、WM_PAINT の中で行うことになります。

beginpaint.c


#include <windows.h>

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

int     WINAPI   WinMain(HINSTANCE hInstance,
			 HINSTANCE hPrevInstance,
			 LPSTR     lpCmdLine,
			 int       nCmdShow)
{
  WNDCLASS wc;
  HWND     hwnd;
  MSG      msg;

  wc.style         = CS_HREDRAW | CS_VREDRAW;
  wc.lpfnWndProc   = WndProc;
  wc.cbClsExtra    = 0;
  wc.cbWndExtra    = 0;
  wc.hInstance     = hInstance;
  wc.hIcon         = NULL;
  wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
  wc.hbrBackground = (HBRUSH)COLOR_BACKGROUND + 1;
  wc.lpszMenuName  = NULL;
  wc.lpszClassName = TEXT("BEGINPAINT");

  RegisterClass(&wc);

  hwnd             = CreateWindow(TEXT("BEGINPAINT"),
				  TEXT("Windos API Primer"),
				  WS_OVERLAPPEDWINDOW | WS_VISIBLE,
				  CW_USEDEFAULT, CW_USEDEFAULT, 300, 200,
				  NULL, NULL, hInstance, NULL);

  while (GetMessage(&msg, NULL, 0, 0))
    {
      DispatchMessage(&msg);
    }

  return msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
  HDC         hdc;
  RECT        rect;
  PAINTSTRUCT ps;
  
  switch (msg)
    {
    case WM_PAINT:
      GetClientRect(hwnd, &rect);
      hdc = BeginPaint(hwnd, &ps);
      Rectangle(hdc,
		rect.right / 2 - 10, rect.bottom / 2 - 10,
		rect.right / 2 + 10, rect.bottom / 2 + 10);
      EndPaint(hwnd, &ps);
      break;
    case WM_DESTROY:
      PostQuitMessage(0);
      break;
    }
  return DefWindowProc(hwnd, msg, wParam, lParam);
}
    

実行結果


ウィンドウのサイズを変更しても、四角形は常にウィンドウの中央に表示されます。

コード説明

  1. Rectangle 関数は、指定のデバイスコンテキストに矩形(長方形)を描画する関数です。引数は次のとおりです。
    1. 第1引数に、デバイスコンテキストを指定します。
    2. 第2引数に、矩形の左上のX座標を指定します。
    3. 第3引数に、矩形の左上のY座標を指定します。
    4. 第4引数に、矩形の右下のX座標を指定します。
    5. 第5引数に、矩形の右下のY座標を指定します。

GetDC

デバイスコンテキストによる描画は WM_PAINT の中で行うのが常套ですが、他のメッセージの中でデバイスコンテキストによる描画が行えると、より柔軟なプログラムが作れます。

WM_PAINT メッセージ以外のメッセージでデバイスコンテキストを取得するには、GetDC 関数を使います。GetDC 関数で取得したデバイスコンテキストは、描画が終了した後に、ReleaseDC 関数で必ず解放しなければなりません。

getdc.c


#include <windows.h>

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

int     WINAPI   WinMain(HINSTANCE hInstance,
			 HINSTANCE hPrevInstance,
			 LPSTR     lpCmdLine,
			 int       nCmdShow)
{
  WNDCLASS wc;
  HWND     hwnd;
  MSG      msg;

  wc.style         = CS_HREDRAW | CS_VREDRAW;
  wc.lpfnWndProc   = WndProc;
  wc.cbClsExtra    = 0;
  wc.cbWndExtra    = 0;
  wc.hInstance     = hInstance;
  wc.hIcon         = NULL;
  wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
  wc.hbrBackground = (HBRUSH)COLOR_BACKGROUND + 1;
  wc.lpszMenuName  = NULL;
  wc.lpszClassName = TEXT("GETDC");

  RegisterClass(&wc);

  hwnd             = CreateWindow(TEXT("GETDC"),
				  TEXT("Windos API Primer"),
				  WS_OVERLAPPEDWINDOW | WS_VISIBLE,
				  CW_USEDEFAULT, CW_USEDEFAULT, 300, 200,
				  NULL, NULL, hInstance, NULL);

  while (GetMessage(&msg, NULL, 0, 0))
    {
      DispatchMessage(&msg);
    }

  return msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
  HDC hdc;
  int x, y;
  switch (msg)
    {
    case WM_LBUTTONDOWN:
      hdc = GetDC(hwnd);
      x   = LOWORD(lParam);
      y   = HIWORD(lParam);
      Rectangle(hdc, x - 10, y - 10, x + 10, y + 10);
      ReleaseDC(hwnd, hdc);
      break;
    case WM_DESTROY:
      PostQuitMessage(0);
      break;
    }
  return DefWindowProc(hwnd, msg, wParam, lParam);
}
    

実行結果


ウィンドウをクリックすると、その場所に次々と四角形が描画されていきます。ただしウィンドウのサイズを変更すると、すべての四角形が消えてしまいます。

応用1

クリックで四角形を描画して、ウィンドウのサイズを変更してもその描画が残るようになれば、もっと良くなります。

increaserect.c


#include <windows.h>

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

int     WINAPI   WinMain(HINSTANCE hInstance,
			 HINSTANCE hPrevInstance,
			 LPSTR     lpCmdLine,
			 int       nCmdShow)
{
  WNDCLASS wc;
  HWND     hwnd;
  MSG      msg;

  wc.style         = CS_HREDRAW | CS_VREDRAW;
  wc.lpfnWndProc   = WndProc;
  wc.cbClsExtra    = 0;
  wc.cbWndExtra    = 0;
  wc.hInstance     = hInstance;
  wc.hIcon         = NULL;
  wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
  wc.hbrBackground = (HBRUSH)COLOR_BACKGROUND + 1;
  wc.lpszMenuName  = NULL;
  wc.lpszClassName = TEXT("BEGINPAINT");

  RegisterClass(&wc);

  hwnd             = CreateWindow(TEXT("BEGINPAINT"),
				  TEXT("Windos API Primer"),
				  WS_OVERLAPPEDWINDOW | WS_VISIBLE,
				  CW_USEDEFAULT, CW_USEDEFAULT, 300, 200,
				  NULL, NULL, hInstance, NULL);

  while (GetMessage(&msg, NULL, 0, 0))
    {
      DispatchMessage(&msg);
    }

  return msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
  static RECT        rect[10000];
  static int         nCount = 0;
         PAINTSTRUCT ps;
	 int         i, x, y;
	 HDC         hdc;
  
  switch (msg)
    {
    case WM_LBUTTONDOWN:
      x = LOWORD(lParam);
      y = HIWORD(lParam);
      SetRect(&rect[nCount], x - 10, y - 10, x + 10, y + 10);
      hdc = GetDC(hwnd);
      Rectangle(hdc,
		rect[nCount].left,  rect[nCount].top,
		rect[nCount].right, rect[nCount].bottom);
      ReleaseDC(hwnd, hdc);
      nCount++;
      break;
    case WM_PAINT:
      hdc = BeginPaint(hwnd, &ps);
      for (i = 0; i< nCount; i++)
	{
	  Rectangle(hdc,
		    rect[i].left, rect[i].top, rect[i].right, rect[i].bottom);
	}
      EndPaint(hwnd, &ps);
      break;
    case WM_DESTROY:
      PostQuitMessage(0);
      break;
    }
  return DefWindowProc(hwnd, msg, wParam, lParam);
}
    

実行結果


ウィンドウのサイズを変更しても、描画した四角形は消えません。

コード説明

  1. static RECT rect[10000];
    矩形(四角形)領域を保持する RECT 型の変数を static 指定しています。ウィンドウプロシージャは一つのメッセージを処理するといったん終了します。そしてそれからまたウィンドウプロシージャの別のメッセージが呼び出されます。rect 変数を static 指定しておかなければ、ウィンドウプロシージャが呼び出されるたびに初期値に戻ってしまいます。
    なお、rect 配列の要素数を 10000 に指定しています。四角形の数が 10000 個を超えると、このプログラムはエラーを起こしてしまいます。
  2. static int nCount = 0;
    カウント変数も static 指定しています。

応用2

左クリックでは四角形を描画して、右クリックですべての四角形が消えるようにしても面白いかもしれません。

eraserect.c


#include <windows.h>

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

int     WINAPI   WinMain(HINSTANCE hInstance,
			 HINSTANCE hPrevInstance,
			 LPSTR     lpCmdLine,
			 int       nCmdShow)
{
  WNDCLASS wc;
  HWND     hwnd;
  MSG      msg;

  wc.style         = CS_HREDRAW | CS_VREDRAW;
  wc.lpfnWndProc   = WndProc;
  wc.cbClsExtra    = 0;
  wc.cbWndExtra    = 0;
  wc.hInstance     = hInstance;
  wc.hIcon         = NULL;
  wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
  wc.hbrBackground = (HBRUSH)COLOR_BACKGROUND + 1;
  wc.lpszMenuName  = NULL;
  wc.lpszClassName = TEXT("BEGINPAINT");

  RegisterClass(&wc);

  hwnd             = CreateWindow(TEXT("BEGINPAINT"),
				  TEXT("Windos API Primer"),
				  WS_OVERLAPPEDWINDOW | WS_VISIBLE,
				  CW_USEDEFAULT, CW_USEDEFAULT, 300, 200,
				  NULL, NULL, hInstance, NULL);

  while (GetMessage(&msg, NULL, 0, 0))
    {
      DispatchMessage(&msg);
    }

  return msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
  static RECT        rect[10000];
  static int         nCount = 0;
         PAINTSTRUCT ps;
	 int         i, x, y;
	 HDC         hdc;
  
  switch (msg)
    {
    case WM_LBUTTONDOWN:
      x = LOWORD(lParam);
      y = HIWORD(lParam);
      SetRect(&rect[nCount], x - 10, y - 10, x + 10, y + 10);
      hdc = GetDC(hwnd);
      Rectangle(hdc,
		rect[nCount].left,  rect[nCount].top,
		rect[nCount].right, rect[nCount].bottom);
      ReleaseDC(hwnd, hdc);
      nCount++;
      break;
    case WM_RBUTTONDOWN:
      nCount = 0;
      InvalidateRect(hwnd, NULL, TRUE);
      break;
    case WM_PAINT:
      hdc = BeginPaint(hwnd, &ps);
      for (i = 0; i< nCount; i++)
	{
	  Rectangle(hdc,
		    rect[i].left, rect[i].top, rect[i].right, rect[i].bottom);
	}
      EndPaint(hwnd, &ps);
      break;
    case WM_DESTROY:
      PostQuitMessage(0);
      break;
    }
  return DefWindowProc(hwnd, msg, wParam, lParam);
}
    

コード説明

  1. InvalidateRect 関数はウィンドウの指定の範囲を無効領域にします。引数は次のとおりです。
    1. 第1引数に、ウィンドウを指定します。
    2. 第1引数に、無効領域にしたい RECT 範囲を指定します。NULL を指定するとウィンドウのクライント領域のすべてになります。
    3. 第3引数に、TRUE を指定すると、ウィンドウクラスの hbrBackground で指定した色でウィンドウが塗りつぶされます。
  2. ウィンドウに無効領域が発生すると、WM_PAINT メッセージが発生します。しかし nCount を 0 にしているので、四角形が再描画されることはありません。

応用3

描画される四角形は常に一つにして、クリックした場所に四角形が移動するのも面白いでしょう。

moverect.c


#include <windows.h>

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

int     WINAPI   WinMain(HINSTANCE hInstance,
			 HINSTANCE hPrevInstance,
			 LPSTR     lpCmdLine,
			 int       nCmdShow)
{
  WNDCLASS wc;
  HWND     hwnd;
  MSG      msg;

  wc.style         = CS_HREDRAW | CS_VREDRAW;
  wc.lpfnWndProc   = WndProc;
  wc.cbClsExtra    = 0;
  wc.cbWndExtra    = 0;
  wc.hInstance     = hInstance;
  wc.hIcon         = NULL;
  wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
  wc.hbrBackground = (HBRUSH)COLOR_BACKGROUND + 1;
  wc.lpszMenuName  = NULL;
  wc.lpszClassName = TEXT("BEGINPAINT");

  RegisterClass(&wc);

  hwnd             = CreateWindow(TEXT("BEGINPAINT"),
				  TEXT("Windos API Primer"),
				  WS_OVERLAPPEDWINDOW | WS_VISIBLE,
				  CW_USEDEFAULT, CW_USEDEFAULT, 300, 200,
				  NULL, NULL, hInstance, NULL);

  while (GetMessage(&msg, NULL, 0, 0))
    {
      DispatchMessage(&msg);
    }

  return msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
  static int  x, y = -10;
  PAINTSTRUCT ps;
  HDC         hdc;
  
  switch (msg)
    {
    case WM_LBUTTONDOWN:
      x = LOWORD(lParam);
      y = HIWORD(lParam);
      InvalidateRect(hwnd, NULL, TRUE);
      break;
    case WM_PAINT:
      hdc = BeginPaint(hwnd, &ps);
      Rectangle(hdc, x - 10, y - 10, x + 10, y + 10);
      EndPaint(hwnd, &ps);
      break;
    case WM_DESTROY:
      PostQuitMessage(0);
      break;
    }
  return DefWindowProc(hwnd, msg, wParam, lParam);
}
    

コード説明

  1. static int x, y = -10;
    マウスがクリックされた位置のX座標とY座標を格納する x 変数と y 変数は static 指定しておかなければなりません。
    ウィンドウプロシージャは、メッセージが発生するたびに呼び出されます。WM_LBUTTONDOWN メッセージを処理した後は、いったんウィンドウプロシージャは終了します。そしてそれからウィンドウプロシージャの WM_PAINT メッセージが呼び出されます。
    x 変数と y 変数を static 指定しておかなければ、そのたびに初期値に書き換えられてしまします。
    なお、初期値を -10 にしているのは、既定値の 0 では、ウィンドウが作成された時点で、ウィンドウの左上に幅10ピクセル、縦10ピクセルの四角形が描画されてしまうからです。


37988 visits
Posted: Jun. 07, 2020
Update: Jun. 07, 2020

ホーム   目次