컴퓨터/Win32-API
지난 포스트에 이어서 이제 그림판에 기능을 부여하고 동작시켜보도록 합시다.
이때 더블 버퍼링을 적용하여 시각적으로 불편한 부분이 없는 그림판 동작을 해보도록 하겠습니다.
이제 메모리 공간에서 그림을 그리고 고속 복사를 통해 연속적으로 넘겨줍니다. 따라서 제어할 전역 변수를 선언해줍시다.
그리고 시작 점을 기억하기 위한 POINT값 하나를 선언하겠습니다.
HDC memDC;
HBITMAP memBitmap;
POINT stPos;
이제 선언된 메모리 DC와 비트맵에 초기 핸들 값을 할당하는 함수를 선언합니다.
이때 DC값은 2차 포인터로 생성하여 주어야합니다.
//메모리 DC 및 비트맵에 메모리 할당 하기
void CreateBackPage(HWND hWnd, HINSTANCE hInst, HDC* memDC , HBITMAP* memBitmap)
{
HDC hdc = GetDC(hWnd);
*memDC = CreateCompatibleDC(hdc);
RECT rect;
GetClientRect(hWnd, &rect);
*memBitmap = CreateCompatibleBitmap(hdc, rect.right, rect.bottom);
(HBITMAP)SelectObject(*memDC, *memBitmap);
FillRect(*memDC, &rect, (HBRUSH)GetStockObject(WHITE_BRUSH));
ReleaseDC(hWnd, hdc);
}
이후 WM_CREATER에서 호출해주도록 합시다.
//윈도우 생성 메세지
case WM_CREATE:
{
//버튼 생성
CreateButton(L"Pen", 20, 20, 50, 50, (HMENU)1, hWnd, hInst);
CreateButton(L"Erase", 20, 75, 50, 50, (HMENU)2, hWnd, hInst);
//버튼 생성 및 이미지 씌우기
//색상 테이블 생성
CreateRGBTable(L"RGBtable", 80, 30, 150, 120, (HMENU)10, hWnd, hInst);
//더블 버퍼링을 위한 메모리 셋팅
CreateBackPage(hWnd, hInst, &memDC, &memBitmap);
break;
}
이제부터는 선언된 비트맵에 메모리 DC를 이용해서 그린 후 고속 복사를 통해 복사를 합니다.
따라서 아래의 몇 가지 부분을 수정해 주도록 합시다.
case WM_HSCROLL:
{
SetScrollFunction(wParam, lParam);
InvalidateRect(hWnd, NULL, FALSE);
UpdateWindow(hWnd);
break;
}
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
SetColor(hWnd,memDC);
RECT rect;
GetClientRect(hWnd, &rect);
BitBlt(hdc, 0, 0, rect.right, rect.bottom, memDC, 0, 0, SRCCOPY);
EndPaint(hWnd, &ps);
break;
}
case WM_DESTROY:
DeleteObject(memBitmap);
DeleteDC(memDC);
PostQuitMessage(0);
break;
이제 마우스 좌클릭을 통한 그리기 기능 구현을 하도록 합시다.
우선 호출되는 구조를 보면 아래와 같습니다.
/*각종 마우스 동작에 따른 컨트롤*/
case WM_MOUSEMOVE:
{
if (wParam == MK_LBUTTON) {
//그리기 모드
if (GetFunction(wParam, lParam, hWnd) == 1)
{
stPos = Draw(hWnd, memDC, wParam, lParam, stPos);
HDC hdc = GetDC(hWnd);
RECT rect;
GetClientRect(hWnd, &rect);
BitBlt(hdc, 0, 0, rect.right, rect.bottom, memDC, 0, 0, SRCCOPY);
ReleaseDC(hWnd, hdc);
}
//지우기 모드
else if (GetFunction(wParam, lParam, hWnd) == 2)
{
stPos = Eraser(hWnd, memDC, wParam, lParam, stPos);
HDC hdc = GetDC(hWnd);
RECT rect;
GetClientRect(hWnd, &rect);
BitBlt(hdc, 0, 0, rect.right, rect.bottom, memDC, 0, 0, SRCCOPY);
ReleaseDC(hWnd, hdc);
}
}
break;
}
case WM_LBUTTONDOWN:
{
//좌 클릭시 초기 좌표 값 저장
stPos.x = GET_X_LPARAM(lParam);
stPos.y = GET_Y_LPARAM(lParam);
break;
}
case WM_LBUTTONUP:
{
break;
}
case WM_RBUTTONDOWN:
{
break;
}
case WM_RBUTTONUP:
{
break;
}
그리기와 지우기 함수는 아래와 같습니다.
//그리기
POINT Draw(HWND hWnd, HDC hdc, WPARAM wParam, LPARAM lParam,POINT stPos)
{
//스크롤 Bar의 값 받아오기
int R = GetScrollPos(FindWindowExW(hWnd, NULL, L"scrollbar", L"R"), SB_CTL);
int G = GetScrollPos(FindWindowExW(hWnd, NULL, L"scrollbar", L"G"), SB_CTL);
int B = GetScrollPos(FindWindowExW(hWnd, NULL, L"scrollbar", L"B"), SB_CTL);
HPEN newPen = CreatePen(PS_SOLID, 10, RGB(R, G, B));
HPEN oldPen = (HPEN)SelectObject(hdc, newPen);
POINT pos;
pos.x = GET_X_LPARAM(lParam);
pos.y = GET_Y_LPARAM(lParam);
MoveToEx(hdc, stPos.x, stPos.y, NULL);
LineTo(hdc, pos.x, pos.y);
SelectObject(hdc, oldPen);
DeleteObject(newPen);
stPos.x = pos.x;
stPos.y = pos.y;
return stPos;
}
//지우기
POINT Eraser(HWND hWnd, HDC hdc, WPARAM wParam, LPARAM lParam, POINT stPos)
{
HPEN newPen = CreatePen(PS_SOLID, 10, RGB(255, 255, 255));
HPEN oldPen = (HPEN)SelectObject(hdc, newPen);
POINT pos;
pos.x = GET_X_LPARAM(lParam);
pos.y = GET_Y_LPARAM(lParam);
MoveToEx(hdc, stPos.x, stPos.y, NULL);
LineTo(hdc, pos.x, pos.y);
SelectObject(hdc, oldPen);
DeleteObject(newPen);
stPos.x = pos.x;
stPos.y = pos.y;
return stPos;
}
// Paint.cpp : Defines the entry point for the application.
//
#include "framework.h"
#include "Paint.h"
//함수 원형 선언 부분
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
// 전역변수
HINSTANCE hInst;
HDC memDC;
HBITMAP memBitmap;
POINT stPos;
// WinMain함수 부분
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR lpCmdLine,
_In_ int nCmdShow)
{
hInst = hInstance; // 인스턴스 핸들을 전역 변수에 저장합니다.
/*윈도우 설정 및 메인 윈도우를 생성합니다.*/
HWND hWnd = InitMainWindowSet(hInstance, &WndProc, L"Paint");
//정상적으로 생성되었다면 윈도우를 갱신합니다.
if (!hWnd)
{
return FALSE;
}
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
MSG msg;
// 기본 메시지 루프입니다:
while (GetMessage(&msg, nullptr, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int)msg.wParam;
}
// WndProc 함수부분
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
//윈도우 생성 메세지
case WM_CREATE:
{
//버튼 생성
CreateButton(L"Pen", 20, 20, 50, 50, (HMENU)1, hWnd, hInst);
CreateButton(L"Erase", 20, 75, 50, 50, (HMENU)2, hWnd, hInst);
//버튼 생성 및 이미지 씌우기
//색상 테이블 생성
CreateRGBTable(L"RGBtable", 80, 30, 150, 120, (HMENU)10, hWnd, hInst);
//더블 버퍼링을 위한 메모리 셋팅
CreateBackPage(hWnd, hInst, &memDC, &memBitmap);
break;
}
//메인 기능 그리기 Or 지우기
case WM_COMMAND:
{
//토글 버튼 처리하기
SetFunction(wParam, lParam, hWnd);
//각종 버튼에 따른 동작
break;
}
//스크롤 기능
case WM_HSCROLL:
{
SetScrollFunction(wParam, lParam);
InvalidateRect(hWnd, NULL, FALSE);
UpdateWindow(hWnd);
break;
}
/*각종 마우스 동작에 따른 컨트롤*/
case WM_MOUSEMOVE:
{
if (wParam == MK_LBUTTON) {
//그리기 모드
if (GetFunction(wParam, lParam, hWnd) == 1)
{
stPos = Draw(hWnd, memDC, wParam, lParam, stPos);
HDC hdc = GetDC(hWnd);
RECT rect;
GetClientRect(hWnd, &rect);
BitBlt(hdc, 0, 0, rect.right, rect.bottom, memDC, 0, 0, SRCCOPY);
ReleaseDC(hWnd, hdc);
}
//지우기 모드
else if (GetFunction(wParam, lParam, hWnd) == 2)
{
stPos = Eraser(hWnd, memDC, wParam, lParam, stPos);
HDC hdc = GetDC(hWnd);
RECT rect;
GetClientRect(hWnd, &rect);
BitBlt(hdc, 0, 0, rect.right, rect.bottom, memDC, 0, 0, SRCCOPY);
ReleaseDC(hWnd, hdc);
}
}
break;
}
case WM_LBUTTONDOWN:
{
//좌 클릭시 초기 좌표 값 저장
stPos.x = GET_X_LPARAM(lParam);
stPos.y = GET_Y_LPARAM(lParam);
break;
}
case WM_LBUTTONUP:
{
break;
}
case WM_RBUTTONDOWN:
{
break;
}
case WM_RBUTTONUP:
{
break;
}
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
SetColor(hWnd,memDC,memBitmap);
RECT rect;
GetClientRect(hWnd, &rect);
BitBlt(hdc, 0, 0, rect.right, rect.bottom, memDC, 0, 0, SRCCOPY);
EndPaint(hWnd, &ps);
break;
}
case WM_DESTROY:
DeleteObject(memBitmap);
DeleteDC(memDC);
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
#include "MySource.h"
//윈도우 창 설정
HWND InitMainWindowSet(HINSTANCE hInstance, WNDPROC WndProc,const WCHAR* name)
{
//윈도우 창 구조체 설정 및 적용하기
WNDCLASSEXW wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = NULL;
wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wcex.lpszMenuName = NULL;
wcex.lpszClassName = name;
wcex.hIconSm = NULL;
RegisterClassExW(&wcex);
//해당 윈도우 창을 가지고와서 윈도우 창 생성하기
return CreateWindowW(name, name, WS_MAXIMIZE| WS_SYSMENU | WS_CLIPSIBLINGS | WS_CLIPCHILDREN,
100, 100, 750, 750, nullptr, nullptr, hInstance, nullptr);
}
//버튼생성하기
void CreateButton(const WCHAR* name,LONG x,LONG y,LONG width ,LONG height, HMENU id, HWND hWnd, HINSTANCE hInst)
{
CreateWindowW(L"button", name, WS_CHILD | WS_VISIBLE | BS_CHECKBOX, x,y,width,height, hWnd, id, hInst,NULL);
}
//색상 선택 도구 만들기
void CreateRGBTable(const WCHAR* name, LONG left, LONG top, LONG right, LONG bottom, HMENU id, HWND hWnd, HINSTANCE hInst)
{
//RGB의 스크롤 바 생성 및 범위 설정
CreateWindowW(L"scrollbar", L"R", WS_CHILD | WS_VISIBLE | SBS_HORZ, right+20, top+15, right + 30, 15, hWnd, id, hInst, NULL);
SetScrollRange(FindWindowExW(hWnd, NULL, L"scrollbar", L"R"), SB_CTL, 0, 255, TRUE);
CreateWindowW(L"scrollbar", L"G", WS_CHILD | WS_VISIBLE | SBS_HORZ, right+20, top+40, right + 30, 15, hWnd, id+1, hInst, NULL);
SetScrollRange(FindWindowExW(hWnd, NULL, L"scrollbar", L"G"), SB_CTL, 0, 255, TRUE);
CreateWindowW(L"scrollbar", L"B", WS_CHILD | WS_VISIBLE | SBS_HORZ, right+20, top+65, right + 30, 15, hWnd, id+2, hInst, NULL);
SetScrollRange(FindWindowExW(hWnd, NULL, L"scrollbar", L"B"), SB_CTL, 0, 255, TRUE);
//SetColor(hWnd);
}
//메모리 DC 및 비트맵에 메모리 할당 하기
void CreateBackPage(HWND hWnd, HINSTANCE hInst, HDC* memDC , HBITMAP* memBitmap)
{
HDC hdc = GetDC(hWnd);
*memDC = CreateCompatibleDC(hdc);
RECT rect;
GetClientRect(hWnd, &rect);
*memBitmap = CreateCompatibleBitmap(hdc, rect.right, rect.bottom);
(HBITMAP)SelectObject(*memDC, *memBitmap);
FillRect(*memDC, &rect, (HBRUSH)GetStockObject(WHITE_BRUSH));
ReleaseDC(hWnd, hdc);
}
//색상 선택
void SetColor(HWND hWnd,HDC memdc,HBITMAP memBitmap)
{
//스크롤 Bar의 값 받아오기
int R = GetScrollPos(FindWindowExW(hWnd, NULL, L"scrollbar", L"R"), SB_CTL);
int G = GetScrollPos(FindWindowExW(hWnd, NULL, L"scrollbar", L"G"), SB_CTL);
int B = GetScrollPos(FindWindowExW(hWnd, NULL, L"scrollbar", L"B"), SB_CTL);
HBRUSH newBrush = CreateSolidBrush(RGB(R, G, B));
HBRUSH OldBrush = (HBRUSH)SelectObject(memdc, newBrush);
//선택된 색상 보여주기
Rectangle(memdc, 80, 30, 150, 120);
//스크롤 Bar의 값 표시해주기
WCHAR text[20];
wsprintf(text, L"R : %d ", R );
TextOutW(memdc, 360, 40, text, lstrlenW(text));
wsprintf(text, L"G : %d ", G );
TextOutW(memdc, 360, 70, text, lstrlenW(text));
wsprintf(text, L"B : %d ", B );
TextOutW(memdc, 360, 100, text, lstrlenW(text));
SelectObject(memdc, OldBrush);
DeleteObject(newBrush);
}
//그리기
POINT Draw(HWND hWnd, HDC hdc, WPARAM wParam, LPARAM lParam,POINT stPos)
{
//스크롤 Bar의 값 받아오기
int R = GetScrollPos(FindWindowExW(hWnd, NULL, L"scrollbar", L"R"), SB_CTL);
int G = GetScrollPos(FindWindowExW(hWnd, NULL, L"scrollbar", L"G"), SB_CTL);
int B = GetScrollPos(FindWindowExW(hWnd, NULL, L"scrollbar", L"B"), SB_CTL);
HPEN newPen = CreatePen(PS_SOLID, 10, RGB(R, G, B));
HPEN oldPen = (HPEN)SelectObject(hdc, newPen);
POINT pos;
pos.x = GET_X_LPARAM(lParam);
pos.y = GET_Y_LPARAM(lParam);
MoveToEx(hdc, stPos.x, stPos.y, NULL);
LineTo(hdc, pos.x, pos.y);
SelectObject(hdc, oldPen);
DeleteObject(newPen);
stPos.x = pos.x;
stPos.y = pos.y;
return stPos;
}
//지우기
POINT Eraser(HWND hWnd, HDC hdc, WPARAM wParam, LPARAM lParam, POINT stPos)
{
HPEN newPen = CreatePen(PS_SOLID, 10, RGB(255, 255, 255));
HPEN oldPen = (HPEN)SelectObject(hdc, newPen);
POINT pos;
pos.x = GET_X_LPARAM(lParam);
pos.y = GET_Y_LPARAM(lParam);
MoveToEx(hdc, stPos.x, stPos.y, NULL);
LineTo(hdc, pos.x, pos.y);
SelectObject(hdc, oldPen);
DeleteObject(newPen);
stPos.x = pos.x;
stPos.y = pos.y;
return stPos;
}
//이미지 씌우기
//더블버퍼링
//색상선택
//버튼 제어
//토글 값 셋팅
void SetFunction(WPARAM wParam, LPARAM lParam,HWND hWnd)
{
if (HIWORD(wParam) == BN_CLICKED)
{
switch (LOWORD(wParam))
{
case 1:
{
if (SendMessageW((HWND)lParam, BM_GETCHECK, 0, 0) == BST_UNCHECKED) {
SendMessageW((HWND)lParam, BM_SETCHECK, BST_CHECKED, 0);
SendMessageW(FindWindowExW(hWnd, NULL, L"button", L"Erase"), BM_SETCHECK, BST_UNCHECKED, 0);
return ;
}
else {
SendMessageW((HWND)lParam, BM_SETCHECK, BST_UNCHECKED, 0);
}
}
case 2:
{
if (SendMessageW((HWND)lParam, BM_GETCHECK, 0, 0) == BST_UNCHECKED) {
SendMessageW((HWND)lParam, BM_SETCHECK, BST_CHECKED, 0);
SendMessageW(FindWindowExW(hWnd, NULL, L"button", L"Pen"), BM_SETCHECK, BST_UNCHECKED, 0);
return ;
}
else {
SendMessageW((HWND)lParam, BM_SETCHECK, BST_UNCHECKED, 0);
}
}
} // 스위치문 종료
}
return ;
}
//토글 값 받아오기
int GetFunction(WPARAM wParam, LPARAM lParam, HWND hWnd)
{
// 1 == 그리기 2 == 지우기 없을 시 0
if (SendMessageW(FindWindowExW(hWnd, NULL, L"button", L"Pen"), BM_GETCHECK, BST_CHECKED, 0) == BST_CHECKED) return 1;
if (SendMessageW(FindWindowExW(hWnd, NULL, L"button", L"Erase"), BM_GETCHECK, BST_CHECKED, 0) == BST_CHECKED) return 2;
else return 0;
}
//스크롤 동작
void SetScrollFunction(WPARAM wParam, LPARAM lParam)
{
switch (LOWORD(wParam))
{
case SB_LINELEFT:
SetScrollPos((HWND)lParam, SB_CTL, max(0,GetScrollPos((HWND)lParam,SB_CTL)-1), TRUE);
break;
case SB_PAGELEFT:
SetScrollPos((HWND)lParam, SB_CTL, max(0, GetScrollPos((HWND)lParam, SB_CTL) - 5), TRUE);
break;
case SB_LINERIGHT:
SetScrollPos((HWND)lParam, SB_CTL, min(255, GetScrollPos((HWND)lParam, SB_CTL) + 1), TRUE);
break;
case SB_PAGERIGHT:
SetScrollPos((HWND)lParam, SB_CTL, min(255, GetScrollPos((HWND)lParam, SB_CTL) + 5), TRUE);
break;
case SB_THUMBTRACK:
SetScrollPos((HWND)lParam, SB_CTL, HIWORD(wParam), TRUE);
break;
}
}
//등등 기능을 쪼개서 하나씩 추가할 예정
컨트롤인 자식 윈도의 경우에 계속해서 그려지면서 깜빡이는 현상이 발생했습니다.
이를 개선하기 위해 메인 윈도우 생성 시 스타일 옵션을 추가했습니다.
//윈도우 창 설정
HWND InitMainWindowSet(HINSTANCE hInstance, WNDPROC WndProc,const WCHAR* name)
{
//윈도우 창 구조체 설정 및 적용하기
WNDCLASSEXW wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = NULL;
wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wcex.lpszMenuName = NULL;
wcex.lpszClassName = name;
wcex.hIconSm = NULL;
RegisterClassExW(&wcex);
//해당 윈도우 창을 가지고와서 윈도우 창 생성하기
return CreateWindowW(name, name, WS_MAXIMIZE| WS_SYSMENU | WS_CLIPSIBLINGS | WS_CLIPCHILDREN,
100, 100, 750, 750, nullptr, nullptr, hInstance, nullptr);
}
어느 정도 원하는 기능은 다 구현했습니다. 다만, 선택하는 공간에 그려지는 부분은 상당히 불편합니다.
다음 포스트에서 이런 애러 사항을 개선하도록 합시다.
WindowsAPI - 실습 그림판 구현하기 종합 (마지막 / 정리) (0) | 2022.04.23 |
---|---|
WindowsAPI - 실습 - 그림판 구현 6 (버튼 이미지 삽입 / 그리기 영역 ) (0) | 2022.04.20 |
WindowsAPI - 실습 - 그림판 구현하기 4 (버튼 제어 추가 & 오류 수정) (0) | 2022.04.19 |
WindowsAPI - 실습 - 그림판 구현하기 3 ( UI부분 구현 ) (0) | 2022.04.15 |
WindowsAPI - 실습 - 그림판 구현하기 2 ( 프로젝트 구조 설계 및 시작) (0) | 2022.04.14 |
91년생 공학엔지니어의 개발일지
TODAY :
YESTER DAY :
TOTAL :
Commnet