45일차. Window API 활용
윈도우 API를 사용하여 창을 띄우고 그곳에 이미지를 출력할 수 있다.
#include <Windows.h>
#include <cassert>
#include <string>
LRESULT CALLBACK WndProc(HWND handle, UINT message, WPARAM wparam, LPARAM lParam);
int APIENTRY WinMain( // 운영체제에서 필요할 때 호출되는 함수
HINSTANCE hInstance, // 윈도우의 창의 실체를 말한다. 창을 띄우는 자체에 대한 식별자 를 말한다.
HINSTANCE prevInstace, // 이전에 대한 정보값.
LPSTR lpszCmdParam, // 문자열. 형식. 헝가리안 표기법 을 사용한다.
int nCmdShow // cmd : 명령프롬프트.[ command window ] 화면에 어떤식으로 나타낼 것인가. 문자열형태로 나타낼것인가 를 표시한것.
// 위 항목들은 작성하지 않아도 자동으로 들어간다. nullptr 같은 느낌
)
{
WNDCLASSA wnd_class; // A : ASCII 윈도우에 지정할것 이라는 의미.
wnd_class.cbClsExtra = 0;
wnd_class.cbWndExtra = 0; // 확장된 공간을 사용할 것 인가.
wnd_class.hbrBackground = static_cast<HBRUSH>(GetStockObject(WHITE_BRUSH)); // 백그라운드 색상을 어떻게 사용할 것 인가.
wnd_class.hCursor = LoadCursor(nullptr, IDC_CROSS); // 커서 모양을 십자가 모양으로 사용하겠다. 라는 의미.
wnd_class.hIcon = LoadIcon(nullptr, IDI_ERROR); // 프로그램 실행했을 때 작업표시줄에 나타나는 아이콘 모양을 ERROR로 표시할것 이다.
wnd_class.hInstance = hInstance;
wnd_class.lpfnWndProc = WndProc; // 윈도우의 포인터와 함수펑션을 연결해주는 역할
wnd_class.lpszClassName = "First Window"; // 파일화 시켰을 때, 클래스 이름을 정의해주는것.
wnd_class.lpszMenuName = nullptr; // 메뉴바 를 이야기한다.
wnd_class.style = CS_HREDRAW | CS_VREDRAW; // 수평 또는 수직이 바뀌었을 때 다시그려준다는 의미.
RegisterClassA(&wnd_class); // 위 클래스 항목들을 레지스터에 등록하여 이름으로 찾아쓸 수 있다.
HWND hwnd = CreateWindowA
(
"First Window",
"Hello, Window!",
WS_OVERLAPPEDWINDOW, // 스타일 중 하나. 여러개중 하나 선택하여 사용가능.
0,
0,
1080,
720,
nullptr, // 윈도우의 부가창을 만드는 것.
nullptr, // 메뉴
hInstance,
nullptr
); // 화면을 구성하였다.
assert(hwnd != nullptr);
ShowWindow(hwnd, nCmdShow);
ShowCursor(TRUE);
MSG message;
ZeroMemory(&message, sizeof(MSG)); // 윈도우 메모리 초기화 방식.
while (GetMessage(&message, nullptr, 0, 0))
{
TranslateMessage(&message);
DispatchMessage(&message);
}
DestroyWindow(hwnd); // 윈도우 창 날리기
UnregisterClassA("First Window", hInstance); // 윈도우 창을 해제해준다.
return 0;
}
LRESULT CALLBACK WndProc(HWND handle, UINT message, WPARAM wParm, LPARAM lParam)
{
static POINT position;
static POINT start;
static POINT end;
static BOOL is_clicked = FALSE; // 마우스 클릭 여부 확인.
static RECT rect1 = { 100, 100, 200, 200 };
static RECT rect2 = { 300, 300, 400, 400 };
static BOOL is_intersect = FALSE;
switch (message) // 들어온 메세지 처리하는 부분.
{
case WM_CLOSE:
case WM_DESTROY:
PostQuitMessage(0); // 메세지에 0 이 들어오면서 윈도우를 종료해준다. 문제가 없으면 0 있으면 1이 리턴된다.
break;
default:
return DefWindowProcA(handle, message, wParm, lParam); // 처리 안한게 있는 상태라면 이곳으로 리턴시킨다.
}
return 0;
}
위 코드가 기본적으로 1080 / 720 사이즈의 윈도우 창을 띄워주는 코드이다.
여기에서 WndProc 의 함수를 수정하여. 다양한 구성을 시도할 수 있다.
다음 밑 부터 작성된 코드는
switch (message)
{
의 밑
case WM_CLOSE: 의 위 에 위 코드를 삽입하여 출력해보면
다음과 같은 결과가 나타난다.
기본 텍스트 출력하기.
case WM_LBUTTONDOWN :
{
std::string str = "Mouse Clicked!!";
HDC hdc = GetDC(handle); // GetDc : 화면과 관련된 디바이스 컨텍스트 핸들을 얻어오려는 것. [ HDC : handle to Device Context 윈도우 운영체제 그래픽작업 수행해주는 것. ]
// 핸들을 사용해서 관련 상수를 얻어와야함.
// 원 삼각형 사각형 등을 그릴 수 있게 하기위해 받아온다.
TextOutA(hdc, 500, 300, str.c_str(), str.length());
ReleaseDC(handle, hdc); // 핸들을 반환해준다.
}
break;
case WM_PAINT: // 윈도우가 다시 그려질 때 발생하는 메세지. 처음 생성시. 움직여서 가려질 때 다시출력해주어야 하는데, 그때 호출이 된다.
{
std::string str = "WM_PAINT message occurred!!";
PAINTSTRUCT ps;
HDC hdc = BeginPaint(handle, &ps); // 그리기 시작할 때 불러옴
TextOutA(hdc, 500, 350, str.c_str(), str.length());
EndPaint(handle, &ps);
}
break;
위 코드를 설명해보자. case WM_LBUTTONDOWN 의 경우. UINT의 값을 받아온 message임으로.
윗부분 코드 줄 중 wnd_class.lpfnWndProc 에 WndProc이 들어감으로.
정의 피킹으로 위치참조를 해보면, 다음과 같이. CALLBACK* WNDPROC 형태로 함수포인터 로 사용됨을 확인할 수 있다.
즉, 입력값을 참조하여 인터럽트가 일어났을 경우, switch의 case를 전부 참조하여. 해당되는 case에 대한 코드를 실행하게 만드는 것이라고 이해하면 된다.
다음으로 넘어가면. WM_LBUTTONDOWN. 그러니까 마우스의 왼쪽버튼을 다운 시켰을 때. 동작한다.
string 형태의 str 변수를 생성하고 " Mouse Clicked!! " 이라는 문장을 삽입한다.
그 후 HDC 형태의 hdc 변수를 생성하고. 매개변수로 받아온 HWND를 GetDC 함수를 사용하여
HDC에서 불필요한 HWND값을 절삭하고 HDC 형태의 hdc 변수에 복사대입한다.
그 후, TextOutA 함수를 사용해서 x위치 500 y위치 300 의 위치에 string 형태의 str을 c타입의 char 배열로 변환한 뒤, 배열사이즈를 입력하여 문자열을 출력한다는 것을 hdc의 주소에 기입해준다.
ReleaseDC를 통해 handle를 참조하여 hdc의 핸들을 os에 반환해준다.
WM_PAINT 을 통해 Switch로 윈도우가 다시 그려질 때 갱신하여 해당됨으로 출력해준다.
string 형태의 str을 선언하여 WM_PAINT message occurred!! 를 str에 문자열을 기입해준다.
PAINTSTRUCT 형태의 ps를 선언한다.
HDC 형의 hdc를 선언하여, BeginPaint를 사용하여 handle을 참조하여 ps를 hdc에 복사대입한다.
TextOutA 를 통하여, hdc의 주소값의 x500 y350에 해당하는 위치에 str의 문자열을 대입한다.
그리고 ps를 할당해제 해준다.
위와같은 코드열을 읽어보면 출력문이 나타난다.
키보드 인풋으로 문자 이동하기
case WM_KEYDOWN:
if (wParm == VK_UP) position.y -= 10;
else if (wParm == VK_DOWN) position.y += 10;
if (wParm == VK_RIGHT) position.x += 10;
else if (wParm == VK_LEFT) position.x -= 10;
InvalidateRect(handle, nullptr, TRUE); // 잔상없이 움직일 수 있도록 만들었다. 화면을 다 날렸다가 다시 표시하라.
break;
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(handle, &ps);
TextOut(hdc, position.x, position.y, "♥", 2);
EndPaint(handle, &ps);
break;
}
위와 같이 키보드의 십자방향키 를 통해 좌표를 변경해줄 수 있다.
마우스의 좌표를 표시한다.
case WM_MOUSEMOVE: // 마우스 움직임
position.x = LOWORD(lParam); // 마우스 이벤트 lParam값을 16비트로 추출한다.
position.y = HIWORD(lParam); // 마우스 이벤트로 y좌표를 얻어온다. 16비트로
InvalidateRect(handle, nullptr, TRUE);
break;
case WM_PAINT:
{
std::string str = "";
str += std::to_string(position.x); // 정수를 문자열로 변경시켜준다.
str += ", ";
str += std::to_string(position.y); // 정수를 문자열로 변경시켜준다.
PAINTSTRUCT ps;
HDC hdc = BeginPaint(handle, &ps);
TextOutA(hdc, position.x, position.y, str.c_str(), str.length());
//Line
MoveToEx(hdc, 100, 100, nullptr);
LineTo(hdc, 500, 500);
//도형
Rectangle(hdc, 500, 500, 600, 600);
Ellipse(hdc, 300, 300, 400, 400);
EndPaint(handle, &ps);
}
break;
위치 좌표에 대한 값을 출력해준다.
그림판 처럼 그림을 그릴 수 있다.
case WM_LBUTTONDOWN: // 좌클릭 했을 때 그려짐
position.x = LOWORD(lParam);
position.y = HIWORD(lParam);
is_clicked = TRUE;
break;
case WM_MOUSEMOVE:
if (is_clicked)
{
HDC hdc = GetDC(handle);
MoveToEx(hdc, position.x, position.y, nullptr);
position.x = LOWORD(lParam);
position.y = HIWORD(lParam);
LineTo(hdc, position.x, position.y); // 선 그려주기
ReleaseDC(handle, hdc);
}
break;
case WM_LBUTTONUP:
is_clicked = FALSE;
break;
드래그 하여 사각형을 그린다.
case WM_LBUTTONDOWN:
start.x = LOWORD(lParam);
start.y = HIWORD(lParam);
is_clicked = TRUE;
break;
case WM_MOUSEMOVE:
if (is_clicked)
{
end.x = LOWORD(lParam);
end.y = HIWORD(lParam);
InvalidateRect(handle, nullptr, TRUE);
}
break;
case WM_LBUTTONUP:
is_clicked = FALSE;
break;
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(handle, &ps);
HPEN cur_pen = CreatePen(PS_DASH, 1, RGB(0, 255, 0));
HPEN old_pen = static_cast<HPEN>(SelectObject(hdc, cur_pen)); // 생성 정보를 다시 담아준다.
HBRUSH cur_brush = CreateSolidBrush((RGB(100, 4, 25)));
HBRUSH old_brush = static_cast<HBRUSH>(SelectObject(hdc, cur_brush));
Rectangle(hdc, start.x, start.y, end.x, end.y);
SelectObject(hdc, old_pen);
DeleteObject(cur_pen);
SelectObject(hdc, old_brush);
DeleteObject(cur_brush);
EndPaint(handle, &ps);
}
break;