Windows API GDI | 그래픽 그리기 완벽 가이드
이 글의 핵심
Windows GDI는 화면 그리기를 담당하는 핵심 API입니다. HDC(Device Context)를 통해 도형, 텍스트, 이미지를 그리며, Pen과 Brush로 선과 면을 제어합니다. 더블버퍼링 기법으로 깜빡임을 제거하면 부드러운 그래픽을 구현할 수 있습니다.
들어가며
Windows GDI(Graphics Device Interface)는 1985년 Windows 1.0부터 제공된 그래픽 API입니다. 2000년대 초반까지 모든 Windows 애플리케이션의 UI 렌더링은 GDI로 구현되었습니다. 비록 DirectX나 Direct2D 같은 최신 API에 비해 성능은 떨어지지만, 간단하고 직관적인 API 설계 덕분에 여전히 많은 곳에서 사용됩니다.
// Windows GDI의 기본 - WM_PAINT에서 그리기
case WM_PAINT: {
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
TextOut(hdc, 10, 10, L"Hello, GDI!", 11);
Rectangle(hdc, 50, 50, 200, 150);
EndPaint(hwnd, &ps);
return 0;
}
단 6줄로 텍스트와 사각형을 그릴 수 있습니다.
왜 GDI를 배워야 할까?
실무에서 GDI가 사용되는 곳:
- 레거시 애플리케이션 - 2010년 이전 Windows 앱은 대부분 GDI 기반
- 경량 UI - 복잡한 3D 없이 간단한 차트, 그래프, 도표 그리기
- 프린터 출력 - GDI는 화면과 프린터를 동일한 API로 처리
- 커스텀 컨트롤 - 버튼, 리스트박스 등 기본 컨트롤의 커스텀 드로잉
특히 산업용 모니터링 소프트웨어, 의료 영상 뷰어, 금융 차트 프로그램 등에서 GDI는 여전히 핵심 기술입니다.
1. GDI 기본 개념
1.1 Device Context (DC)
DC는 “가상 캔버스”입니다.
// DC의 세 가지 획득 방법
// 1. WM_PAINT에서 (가장 일반적)
case WM_PAINT: {
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
// ... 그리기
EndPaint(hwnd, &ps);
return 0;
}
// 2. 언제든지 (WM_PAINT 외부)
HDC hdc = GetDC(hwnd);
// ... 그리기
ReleaseDC(hwnd, hdc);
// 3. 메모리 DC (더블버퍼링)
HDC hdcMem = CreateCompatibleDC(hdc);
// ... 그리기
DeleteDC(hdcMem);
1.2 GDI 객체
GDI 객체는 그리기 도구입니다:
// 펜 (선 그리기 도구)
HPEN hPen = CreatePen(PS_SOLID, 2, RGB(255, 0, 0)); // 빨간색 2픽셀 실선
// 브러시 (면 채우기 도구)
HBRUSH hBrush = CreateSolidBrush(RGB(0, 0, 255)); // 파란색 브러시
// 폰트 (텍스트 도구)
HFONT hFont = CreateFont(24, 0, 0, 0, FW_BOLD, ...);
// 비트맵 (이미지)
HBITMAP hBitmap = LoadBitmap(hInstance, MAKEINTRESOURCE(IDB_MYIMAGE));
// 사용 후 반드시 삭제!
DeleteObject(hPen);
DeleteObject(hBrush);
DeleteObject(hFont);
DeleteObject(hBitmap);
중요: GDI 객체는 반드시 삭제해야 합니다!
// ❌ 잘못된 코드 (메모리 누수)
for (int i = 0; i < 1000; i++) {
HPEN hPen = CreatePen(PS_SOLID, 1, RGB(i, 0, 0));
SelectObject(hdc, hPen);
// DeleteObject 누락!
}
// ✅ 올바른 코드
for (int i = 0; i < 1000; i++) {
HPEN hPen = CreatePen(PS_SOLID, 1, RGB(i, 0, 0));
HPEN hOldPen = (HPEN)SelectObject(hdc, hPen);
// ... 그리기
SelectObject(hdc, hOldPen);
DeleteObject(hPen);
}
2. 기본 도형 그리기
2.1 선과 사각형
case WM_PAINT: {
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
// 선 그리기
MoveToEx(hdc, 10, 10, NULL); // 시작점 이동
LineTo(hdc, 200, 100); // 선 그리기
// 사각형 (테두리만)
Rectangle(hdc, 50, 50, 200, 150);
// 둥근 사각형
RoundRect(hdc, 250, 50, 400, 150, 20, 20);
// 다각형
POINT points[] = {{100, 200}, {150, 250}, {120, 300}, {80, 280}};
Polygon(hdc, points, 4);
EndPaint(hwnd, &ps);
return 0;
}
2.2 원과 타원
case WM_PAINT: {
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
// 원 (정사각형 영역에 그려짐)
Ellipse(hdc, 50, 50, 150, 150); // 100x100 정사각형 → 원
// 타원
Ellipse(hdc, 200, 50, 400, 150); // 200x100 → 타원
// 호 (Arc)
Arc(hdc, 50, 200, 150, 300, 150, 200, 50, 250);
// 파이 (Pie)
Pie(hdc, 200, 200, 350, 350, 350, 225, 275, 200);
EndPaint(hwnd, &ps);
return 0;
}
2.3 Pen으로 선 스타일 변경
case WM_PAINT: {
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
int y = 50;
// 1. 실선 (기본)
HPEN hPen1 = CreatePen(PS_SOLID, 2, RGB(255, 0, 0));
HPEN hOldPen = (HPEN)SelectObject(hdc, hPen1);
MoveToEx(hdc, 50, y, NULL);
LineTo(hdc, 400, y);
TextOut(hdc, 420, y - 5, L"PS_SOLID", 8);
y += 30;
// 2. 점선
SelectObject(hdc, hOldPen);
DeleteObject(hPen1);
HPEN hPen2 = CreatePen(PS_DASH, 1, RGB(0, 255, 0));
SelectObject(hdc, hPen2);
MoveToEx(hdc, 50, y, NULL);
LineTo(hdc, 400, y);
TextOut(hdc, 420, y - 5, L"PS_DASH", 7);
y += 30;
// 3. 점선 (점만)
SelectObject(hdc, hOldPen);
DeleteObject(hPen2);
HPEN hPen3 = CreatePen(PS_DOT, 1, RGB(0, 0, 255));
SelectObject(hdc, hPen3);
MoveToEx(hdc, 50, y, NULL);
LineTo(hdc, 400, y);
TextOut(hdc, 420, y - 5, L"PS_DOT", 6);
y += 30;
// 4. 선-점-선-점
SelectObject(hdc, hOldPen);
DeleteObject(hPen3);
HPEN hPen4 = CreatePen(PS_DASHDOT, 1, RGB(255, 128, 0));
SelectObject(hdc, hPen4);
MoveToEx(hdc, 50, y, NULL);
LineTo(hdc, 400, y);
TextOut(hdc, 420, y - 5, L"PS_DASHDOT", 10);
// 정리
SelectObject(hdc, hOldPen);
DeleteObject(hPen4);
EndPaint(hwnd, &ps);
return 0;
}
2.4 Brush로 면 채우기
case WM_PAINT: {
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
// 단색 브러시
HBRUSH hBrush1 = CreateSolidBrush(RGB(255, 200, 200));
HBRUSH hOldBrush = (HBRUSH)SelectObject(hdc, hBrush1);
Rectangle(hdc, 50, 50, 150, 150);
// 해치 브러시 (패턴)
SelectObject(hdc, hOldBrush);
DeleteObject(hBrush1);
HBRUSH hBrush2 = CreateHatchBrush(HS_CROSS, RGB(0, 0, 255));
SelectObject(hdc, hBrush2);
Rectangle(hdc, 200, 50, 300, 150);
// 투명 브러시 (테두리만)
SelectObject(hdc, hOldBrush);
DeleteObject(hBrush2);
SelectObject(hdc, GetStockObject(NULL_BRUSH));
Rectangle(hdc, 350, 50, 450, 150);
// 정리
SelectObject(hdc, hOldBrush);
EndPaint(hwnd, &ps);
return 0;
}
2.5 색상과 그리기 모드
case WM_PAINT: {
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
// 배경 모드
SetBkMode(hdc, TRANSPARENT); // 투명 배경
// SetBkMode(hdc, OPAQUE); // 불투명 배경 (기본값)
// 배경 색상
SetBkColor(hdc, RGB(255, 255, 0)); // 노란색
// 텍스트 색상
SetTextColor(hdc, RGB(255, 0, 0)); // 빨간색
// ROP2 (Raster Operation)
SetROP2(hdc, R2_NOT); // 반전 모드
// SetROP2(hdc, R2_XORPEN); // XOR 모드 (지우개 효과)
TextOut(hdc, 10, 10, L"투명 배경 텍스트", 10);
EndPaint(hwnd, &ps);
return 0;
}
3. 텍스트 그리기
3.1 기본 텍스트 출력
case WM_PAINT: {
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
// TextOut (기본)
TextOut(hdc, 10, 10, L"Hello, Windows!", 15);
// DrawText (영역 내 정렬)
RECT rect = {10, 50, 300, 150};
DrawText(hdc, L"여러 줄 텍스트\n가운데 정렬", -1, &rect,
DT_CENTER | DT_VCENTER | DT_WORDBREAK);
// ExtTextOut (배경 지우기)
RECT bgRect = {10, 200, 200, 230};
ExtTextOut(hdc, 10, 200, ETO_OPAQUE, &bgRect,
L"배경 포함 텍스트", 10, NULL);
EndPaint(hwnd, &ps);
return 0;
}
3.2 폰트 사용
case WM_PAINT: {
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
int y = 20;
// 기본 폰트
TextOut(hdc, 10, y, L"기본 폰트", 5);
y += 30;
// 큰 폰트
HFONT hFont1 = CreateFont(
32, // 높이
0, // 너비 (0 = 자동)
0, // 각도
0, // 베이스라인 각도
FW_NORMAL, // 굵기
FALSE, // 이탤릭
FALSE, // 밑줄
FALSE, // 취소선
DEFAULT_CHARSET,
OUT_DEFAULT_PRECIS,
CLIP_DEFAULT_PRECIS,
DEFAULT_QUALITY,
DEFAULT_PITCH | FF_SWISS,
L"Arial" // 폰트 이름
);
HFONT hOldFont = (HFONT)SelectObject(hdc, hFont1);
TextOut(hdc, 10, y, L"큰 폰트", 4);
y += 40;
// 굵은 폰트
SelectObject(hdc, hOldFont);
DeleteObject(hFont1);
HFONT hFont2 = CreateFont(24, 0, 0, 0, FW_BOLD, FALSE, FALSE, FALSE,
DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
DEFAULT_QUALITY, DEFAULT_PITCH | FF_SWISS, L"맑은 고딕");
SelectObject(hdc, hFont2);
TextOut(hdc, 10, y, L"굵은 폰트", 5);
y += 35;
// 이탤릭
SelectObject(hdc, hOldFont);
DeleteObject(hFont2);
HFONT hFont3 = CreateFont(24, 0, 0, 0, FW_NORMAL, TRUE, FALSE, FALSE,
DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
DEFAULT_QUALITY, DEFAULT_PITCH | FF_SWISS, L"Times New Roman");
SelectObject(hdc, hFont3);
TextOut(hdc, 10, y, L"Italic Font", 11);
// 정리
SelectObject(hdc, hOldFont);
DeleteObject(hFont3);
EndPaint(hwnd, &ps);
return 0;
}
3.3 텍스트 크기 측정
// 텍스트 너비/높이 계산
HDC hdc = GetDC(hwnd);
const wchar_t* text = L"측정할 텍스트";
SIZE size;
GetTextExtentPoint32(hdc, text, (int)wcslen(text), &size);
wchar_t buf[256];
swprintf_s(buf, L"너비: %d, 높이: %d", size.cx, size.cy);
MessageBox(hwnd, buf, L"텍스트 크기", MB_OK);
ReleaseDC(hwnd, hdc);
4. 비트맵 (Bitmap) 그리기
4.1 비트맵 로드 및 그리기
// 전역 변수
HBITMAP g_hBitmap = NULL;
case WM_CREATE: {
// 비트맵 로드 (리소스에서)
g_hBitmap = LoadBitmap(
((CREATESTRUCT*)lParam)->hInstance,
MAKEINTRESOURCE(IDB_MYBITMAP)
);
// 또는 파일에서 로드
g_hBitmap = (HBITMAP)LoadImage(
NULL,
L"C:\\image.bmp",
IMAGE_BITMAP,
0, 0,
LR_LOADFROMFILE
);
return 0;
}
case WM_PAINT: {
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
if (g_hBitmap) {
// 메모리 DC 생성
HDC hdcMem = CreateCompatibleDC(hdc);
HBITMAP hOldBitmap = (HBITMAP)SelectObject(hdcMem, g_hBitmap);
// 비트맵 크기 얻기
BITMAP bm;
GetObject(g_hBitmap, sizeof(BITMAP), &bm);
// 비트맵 그리기
BitBlt(hdc, 10, 10, bm.bmWidth, bm.bmHeight,
hdcMem, 0, 0, SRCCOPY);
// 정리
SelectObject(hdcMem, hOldBitmap);
DeleteDC(hdcMem);
}
EndPaint(hwnd, &ps);
return 0;
}
case WM_DESTROY:
if (g_hBitmap) DeleteObject(g_hBitmap);
PostQuitMessage(0);
return 0;
4.2 비트맵 확대/축소
case WM_PAINT: {
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
if (g_hBitmap) {
HDC hdcMem = CreateCompatibleDC(hdc);
HBITMAP hOldBitmap = (HBITMAP)SelectObject(hdcMem, g_hBitmap);
BITMAP bm;
GetObject(g_hBitmap, sizeof(BITMAP), &bm);
// 원본 크기
BitBlt(hdc, 10, 10, bm.bmWidth, bm.bmHeight,
hdcMem, 0, 0, SRCCOPY);
// 2배 확대
StretchBlt(hdc, 10, 150, bm.bmWidth * 2, bm.bmHeight * 2,
hdcMem, 0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY);
// 0.5배 축소
StretchBlt(hdc, 300, 10, bm.bmWidth / 2, bm.bmHeight / 2,
hdcMem, 0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY);
SelectObject(hdcMem, hOldBitmap);
DeleteDC(hdcMem);
}
EndPaint(hwnd, &ps);
return 0;
}
4.3 투명 비트맵 (TransparentBlt)
#pragma comment(lib, "Msimg32.lib") // TransparentBlt 사용 시 필요
case WM_PAINT: {
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
if (g_hBitmap) {
HDC hdcMem = CreateCompatibleDC(hdc);
HBITMAP hOldBitmap = (HBITMAP)SelectObject(hdcMem, g_hBitmap);
BITMAP bm;
GetObject(g_hBitmap, sizeof(BITMAP), &bm);
// 투명 색상 지정 (마젠타 RGB(255, 0, 255)를 투명으로)
TransparentBlt(hdc, 10, 10, bm.bmWidth, bm.bmHeight,
hdcMem, 0, 0, bm.bmWidth, bm.bmHeight,
RGB(255, 0, 255));
SelectObject(hdcMem, hOldBitmap);
DeleteDC(hdcMem);
}
EndPaint(hwnd, &ps);
return 0;
}
5. 더블 버퍼링 (Double Buffering)
5.1 깜빡임 문제
// ❌ 나쁜 예: 화면에 직접 그리기 → 깜빡임 발생
case WM_PAINT: {
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
// 복잡한 그리기 작업
for (int i = 0; i < 100; i++) {
Rectangle(hdc, i * 5, i * 3, i * 5 + 20, i * 3 + 20);
Sleep(10); // 그리는 과정이 보임!
}
EndPaint(hwnd, &ps);
return 0;
}
5.2 더블 버퍼링 구현
// ✅ 좋은 예: 메모리에 먼저 그린 후 한 번에 복사
case WM_PAINT: {
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
RECT rect;
GetClientRect(hwnd, &rect);
// 1. 메모리 DC 생성
HDC hdcMem = CreateCompatibleDC(hdc);
// 2. 메모리 비트맵 생성
HBITMAP hbmMem = CreateCompatibleBitmap(hdc, rect.right, rect.bottom);
HBITMAP hbmOld = (HBITMAP)SelectObject(hdcMem, hbmMem);
// 3. 메모리 DC에 그리기 (화면에는 안 보임)
FillRect(hdcMem, &rect, (HBRUSH)(COLOR_WINDOW + 1));
for (int i = 0; i < 100; i++) {
HBRUSH hBrush = CreateSolidBrush(RGB(i * 2, 0, 255 - i * 2));
HBRUSH hOldBrush = (HBRUSH)SelectObject(hdcMem, hBrush);
Rectangle(hdcMem, i * 5, i * 3, i * 5 + 20, i * 3 + 20);
SelectObject(hdcMem, hOldBrush);
DeleteObject(hBrush);
}
// 4. 메모리 DC를 화면에 한 번에 복사 (깜빡임 없음!)
BitBlt(hdc, 0, 0, rect.right, rect.bottom, hdcMem, 0, 0, SRCCOPY);
// 5. 정리
SelectObject(hdcMem, hbmOld);
DeleteObject(hbmMem);
DeleteDC(hdcMem);
EndPaint(hwnd, &ps);
return 0;
}
5.3 더블 버퍼링 헬퍼 클래스
class DoubleBuffer {
private:
HDC m_hdc;
HDC m_hdcMem;
HBITMAP m_hbmMem;
HBITMAP m_hbmOld;
int m_width;
int m_height;
public:
DoubleBuffer(HDC hdc, int width, int height)
: m_hdc(hdc), m_width(width), m_height(height)
{
m_hdcMem = CreateCompatibleDC(hdc);
m_hbmMem = CreateCompatibleBitmap(hdc, width, height);
m_hbmOld = (HBITMAP)SelectObject(m_hdcMem, m_hbmMem);
}
~DoubleBuffer() {
// 화면에 복사
BitBlt(m_hdc, 0, 0, m_width, m_height, m_hdcMem, 0, 0, SRCCOPY);
// 정리
SelectObject(m_hdcMem, m_hbmOld);
DeleteObject(m_hbmMem);
DeleteDC(m_hdcMem);
}
HDC GetDC() const { return m_hdcMem; }
};
// 사용 예
case WM_PAINT: {
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
RECT rect;
GetClientRect(hwnd, &rect);
{
DoubleBuffer buffer(hdc, rect.right, rect.bottom);
HDC hdcMem = buffer.GetDC();
// hdcMem에 그리기
FillRect(hdcMem, &rect, (HBRUSH)(COLOR_WINDOW + 1));
TextOut(hdcMem, 10, 10, L"깜빡임 없음!", 9);
// 소멸자에서 자동으로 BitBlt 수행
}
EndPaint(hwnd, &ps);
return 0;
}
6. 실전 예제: 간단한 그림판
#include <windows.h>
#include <vector>
struct Point {
int x, y;
};
std::vector<std::vector<Point>> g_strokes; // 모든 선
std::vector<Point> g_currentStroke; // 현재 그리는 선
BOOL g_isDrawing = FALSE;
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int nCmdShow)
{
WNDCLASSEX wc = {};
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = WndProc;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_CROSS);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wc.lpszClassName = L"SimplePaint";
RegisterClassEx(&wc);
HWND hwnd = CreateWindow(
L"SimplePaint", L"간단한 그림판",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, 800, 600,
NULL, NULL, hInstance, NULL
);
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
MSG msg = {};
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int)msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg) {
case WM_LBUTTONDOWN: {
// 그리기 시작
g_isDrawing = TRUE;
g_currentStroke.clear();
Point pt = {LOWORD(lParam), HIWORD(lParam)};
g_currentStroke.push_back(pt);
SetCapture(hwnd); // 마우스 캡처
return 0;
}
case WM_MOUSEMOVE: {
if (g_isDrawing) {
Point pt = {LOWORD(lParam), HIWORD(lParam)};
g_currentStroke.push_back(pt);
// 즉시 그리기 (GetDC 사용)
HDC hdc = GetDC(hwnd);
if (g_currentStroke.size() >= 2) {
const Point& prev = g_currentStroke[g_currentStroke.size() - 2];
MoveToEx(hdc, prev.x, prev.y, NULL);
LineTo(hdc, pt.x, pt.y);
}
ReleaseDC(hwnd, hdc);
}
return 0;
}
case WM_LBUTTONUP: {
if (g_isDrawing) {
g_isDrawing = FALSE;
ReleaseCapture();
// 완성된 선을 저장
if (!g_currentStroke.empty()) {
g_strokes.push_back(g_currentStroke);
}
g_currentStroke.clear();
InvalidateRect(hwnd, NULL, TRUE);
}
return 0;
}
case WM_PAINT: {
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
// 저장된 모든 선 그리기
for (const auto& stroke : g_strokes) {
if (stroke.size() >= 2) {
for (size_t i = 1; i < stroke.size(); i++) {
MoveToEx(hdc, stroke[i - 1].x, stroke[i - 1].y, NULL);
LineTo(hdc, stroke[i].x, stroke[i].y);
}
}
}
EndPaint(hwnd, &ps);
return 0;
}
case WM_RBUTTONDOWN: {
// 우클릭: 모두 지우기
if (MessageBox(hwnd, L"모두 지우시겠습니까?", L"확인",
MB_YESNO | MB_ICONQUESTION) == IDYES) {
g_strokes.clear();
InvalidateRect(hwnd, NULL, TRUE);
}
return 0;
}
case WM_KEYDOWN: {
if (wParam == VK_ESCAPE) {
PostMessage(hwnd, WM_CLOSE, 0, 0);
}
return 0;
}
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, msg, wParam, lParam);
}
7. 실전 예제: 실시간 그래프
#include <windows.h>
#include <vector>
#include <random>
std::vector<int> g_data;
const int MAX_DATA = 100;
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int nCmdShow)
{
WNDCLASSEX wc = {};
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = WndProc;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wc.lpszClassName = L"RealtimeGraph";
RegisterClassEx(&wc);
HWND hwnd = CreateWindow(
L"RealtimeGraph", L"실시간 그래프",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, 800, 400,
NULL, NULL, hInstance, NULL
);
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
MSG msg = {};
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int)msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
static std::mt19937 rng(std::random_device{}());
static std::uniform_int_distribution<int> dist(0, 100);
switch (msg) {
case WM_CREATE:
// 초기 데이터
for (int i = 0; i < MAX_DATA; i++) {
g_data.push_back(dist(rng));
}
// 타이머 시작 (100ms마다)
SetTimer(hwnd, 1, 100, NULL);
return 0;
case WM_TIMER:
if (wParam == 1) {
// 새 데이터 추가
g_data.push_back(dist(rng));
// 오래된 데이터 제거
if (g_data.size() > MAX_DATA) {
g_data.erase(g_data.begin());
}
InvalidateRect(hwnd, NULL, FALSE);
}
return 0;
case WM_PAINT: {
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
RECT rect;
GetClientRect(hwnd, &rect);
// 더블 버퍼링
HDC hdcMem = CreateCompatibleDC(hdc);
HBITMAP hbmMem = CreateCompatibleBitmap(hdc, rect.right, rect.bottom);
HBITMAP hbmOld = (HBITMAP)SelectObject(hdcMem, hbmMem);
// 배경 채우기
FillRect(hdcMem, &rect, (HBRUSH)(COLOR_WINDOW + 1));
// 격자 그리기
HPEN hPenGrid = CreatePen(PS_DOT, 1, RGB(200, 200, 200));
HPEN hOldPen = (HPEN)SelectObject(hdcMem, hPenGrid);
for (int i = 0; i <= 10; i++) {
int y = rect.bottom * i / 10;
MoveToEx(hdcMem, 0, y, NULL);
LineTo(hdcMem, rect.right, y);
}
SelectObject(hdcMem, hOldPen);
DeleteObject(hPenGrid);
// 그래프 그리기
if (g_data.size() >= 2) {
HPEN hPenGraph = CreatePen(PS_SOLID, 2, RGB(0, 0, 255));
SelectObject(hdcMem, hPenGraph);
int graphWidth = rect.right - 40;
int graphHeight = rect.bottom - 40;
for (size_t i = 1; i < g_data.size(); i++) {
int x1 = 20 + (int)((i - 1) * graphWidth / MAX_DATA);
int y1 = rect.bottom - 20 - (g_data[i - 1] * graphHeight / 100);
int x2 = 20 + (int)(i * graphWidth / MAX_DATA);
int y2 = rect.bottom - 20 - (g_data[i] * graphHeight / 100);
MoveToEx(hdcMem, x1, y1, NULL);
LineTo(hdcMem, x2, y2);
}
SelectObject(hdcMem, hOldPen);
DeleteObject(hPenGraph);
}
// 현재 값 표시
if (!g_data.empty()) {
wchar_t buf[64];
swprintf_s(buf, L"현재 값: %d", g_data.back());
SetBkMode(hdcMem, TRANSPARENT);
SetTextColor(hdcMem, RGB(255, 0, 0));
HFONT hFont = CreateFont(20, 0, 0, 0, FW_BOLD, FALSE, FALSE, FALSE,
DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
DEFAULT_QUALITY, DEFAULT_PITCH | FF_SWISS, L"Arial");
HFONT hOldFont = (HFONT)SelectObject(hdcMem, hFont);
TextOut(hdcMem, 20, 20, buf, (int)wcslen(buf));
SelectObject(hdcMem, hOldFont);
DeleteObject(hFont);
}
// 화면에 복사
BitBlt(hdc, 0, 0, rect.right, rect.bottom, hdcMem, 0, 0, SRCCOPY);
// 정리
SelectObject(hdcMem, hbmOld);
DeleteObject(hbmMem);
DeleteDC(hdcMem);
EndPaint(hwnd, &ps);
return 0;
}
case WM_DESTROY:
KillTimer(hwnd, 1);
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, msg, wParam, lParam);
}
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- Windows API 기초 | 메시지 루프와 윈도우 프로시저 완벽 가이드
- Windows API 컨트롤 | 버튼·에디트·리스트박스 완벽 가이드
- C++ 스마트 포인터 | unique_ptr/shared_ptr 메모리 안전 가이드
이 글이 도움이 되셨나요? Windows GDI 그래픽 프로그래밍을 마스터하는 데 도움이 되었기를 바랍니다!
다음 글에서는 Windows API 다이얼로그와 공용 컨트롤을 다루겠습니다. 🚀