컴퓨터/Win32-API
WindowsAPI(Win32, GDI)를 이용해서 간단하게 그림판의 기능을 모사해보는 실습을 했습니다.
지난 과정들을 정리하고 스스로 평가하는 시간을 가져보도록 하겠습니다.
2022.04.13 - [컴퓨터/Win32-API] - WindowsAPI - 실습 - 그림판 구현하기 1 (개요)
2022.04.14 - [컴퓨터/Win32-API] - WindowsAPI - 실습 - 그림판 구현하기 2 ( 프로젝트 구조 설계 및 시작)
2022.04.15 - [컴퓨터/Win32-API] - WindowsAPI - 실습 - 그림판 구현하기 3 ( UI 부분 구현 )
2022.04.19 - [컴퓨터/Win32-API] - WindowsAPI - 실습 - 그림판 구현하기 4 (버튼 제어 추가 & 오류 수정)
2022.04.19 - [컴퓨터/Win32-API] - WindowsAPI - 실습 - 그림판 구현 5 (기능 구현 / 더블 버퍼링)
2022.04.20 - [컴퓨터/Win32-API] - WindowsAPI - 실습 - 그림판 구현 6 (버튼 이미지 삽입 / 그리기 영역 )
해당 프로젝트는 총 4가지의 디렉터리 구조로 되어있습니다.
각각의 영역에 대해 살펴보면 다음과 같습니다.
주로 제어하기 위한 영역은 MySource 영역과 Source 영역입니다.
구분을 지은 이유는 다음과 같습니다.
해당 디렉터리에서는 각각의 함수를 기능별로 분할하여 구현하도록 만들었습니다.
해당 소스코드는 크게 3가지 부분으로 나누어집니다.
//윈도우 창 설정
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);
}
HWND를 반환하는 함수로서, 메인 윈도의 기본적인 설정 및 이름을 정의해 줍니다.
//버튼생성하기
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 | BS_PUSHLIKE | BS_ICON, x,y,width,height, hWnd, id, hInst,NULL);
}
void CreateButtonImg(const WCHAR* buttonName,int imgName, HWND hWnd, HINSTANCE hInst)
{
HICON hIcon = LoadIcon(hInst, MAKEINTRESOURCE(imgName));
SendMessageW(FindWindowExW(hWnd, NULL, L"button", buttonName), BM_SETIMAGE, IMAGE_ICON, (LPARAM)hIcon);
}
버튼 컨트롤을 생성하는 함수와 이미지를 덮을 수 있는 함수입니다.
더블 버퍼링 기법을 사용하기 위한 함수입니다.
//메모리 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 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);
}
해당 함수에서 색상표에 대한 스크롤 바를 생성해 줍니다.
//색상 선택
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;
}
그리기와 지우기 기능을 구현해서 POINT 측, 윈도 좌표 값을 반환해 줍니다.
기능 선택의 경우 그리기 or 지우기 하나만 선택을 해야 합니다. 따라서 메시지를 보고 판단하고 제어합니다.
//토글 값 셋팅
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;
}
}
이후 동작을 위해 해당 소스코드에서 함수들을 적재적소에 호출하여 구현해줍시다.
// 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);
//버튼 생성 및 이미지 씌우기
CreateButtonImg(L"Pen", IDI_ICON2, hWnd, hInst);
CreateButtonImg(L"Erase", IDI_ICON1, 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 && stPos.y >= 150) {
//그리기 모드
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;
}
간단하게 그림판을 구현하기 위한 실습을 해봤습니다.
나름대로 각각의 함수마다 결합도를 최소화시키기 위해 노력했습니다.
결합도 최소화 (함수의 변경에 따른 다른 함수에 영향을 최소화)
추가로 보완이 돼야 하는 가장 최우선 점은 함수를 호출하는 과정에서 정상적인 동작을 하지 않았을 때의 처리가 필요합니다.
이후 다른 실습이나 해당 소스코드를 이용하여 추가적인 구현을 할 경우 해당 내용을 해소하는 방향으로 진행하겠습니다.
WindowsAPI - DialogBox 만들고 사용해보기 (대화 상자 사용해 보기) (0) | 2022.04.28 |
---|---|
WindowsAPI - Dialog Boxes(대화상자) (0) | 2022.04.27 |
WindowsAPI - 실습 - 그림판 구현 6 (버튼 이미지 삽입 / 그리기 영역 ) (0) | 2022.04.20 |
WindowsAPI - 실습 - 그림판 구현 5 (기능 구현 / 더블 버퍼링) (0) | 2022.04.19 |
WindowsAPI - 실습 - 그림판 구현하기 4 (버튼 제어 추가 & 오류 수정) (0) | 2022.04.19 |
91년생 공학엔지니어의 개발일지
TODAY :
YESTER DAY :
TOTAL :
Commnet