WIN32 API 핸들, DC, 윈도우 이벤트
카테고리: WINAPI
2일차Permalink
// Client.cpp : 애플리케이션에 대한 진입점을 정의합니다.
//
#include "framework.h"
#include "Client.h"
#define MAX_LOADSTRING 100
//WCHAR은 2바이트, wchar_t형임. (16비트)
//그거 이외엔 다른 게 없음(유니코드형을 위한 것)
// 전역 변수:
HINSTANCE hInst; // 현재 인스턴스입니다.
//메인함수가 처음 시작할 때 받아오는 값.
WCHAR szTitle[MAX_LOADSTRING]; // 제목 표시줄 텍스트입니다.
WCHAR szWindowClass[MAX_LOADSTRING]; // 기본 창 클래스 이름입니다.
//100길이로 그냥 선언해놓은 것.
// 이 코드 모듈에 포함된 함수의 선언을 전달합니다:
ATOM MyRegisterClass(HINSTANCE hInstance);
BOOL InitInstance(HINSTANCE, int);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM);
//함수들은 전방 선언해놓은 것.(함수 프로토타입)
POINT g_ptObjectPos = { 500, 300 };
POINT g_ptObjScale = { 100, 100 };
//_In_이나 _In_opt는?
//어떤 지역변수 앞에 그 변수의 용도를 적는 것.
//SAL 이라고 함. 주석 언어.
//주석으로 적는 것보다, 어느정도 함축적으로 약속된 키워드를 하는 것
//_In_ 데이터가 입력된다
//_In_opt_ 부가적으로 들어오는 데이터다.
//hInstance는 프로세스 시작의 메모리 주소값임.
//hPrevInstance는 프로세스 시작 이전의 메모리 주소 값.
//프로그램은 하나. 그런데 그 프로그램으로 여러 개의 프로세스 실행시킬때...
//나보다 먼저 실행된 이전 프로세스의 시작 주소.
//지금은 몇 개의 프로세스를 올려도 hInstance가 다 같은 값이 나옴.
//왜? 여러 번 생겨나면 서로 다른 별개의 주소에 올라가야 하는 게 아닌가??
//windows는 가상 메모리를 써서 그럼. 프로세스를 여러개 만들어냈지만...
//각 프로그램마다 자신만의 영역이 있음.(그러니까 물리 메모리는 0x2323던, 0x545던
//가상 메모리 시작점은 0x0000이라는 것임 <- 이해가 쓕쓕 되잖아
//pCmdLine은... 프로그램 실행할 때 문자열 넣어주면, lpCmdLine에 들어옴
//ex) output.exe -e 하면 -e라는 특정 옵션이 되는 것. 그런 느낌.
//LPWSTR은 wchar_t형의 포인터 타입. 얘내들은 이런 식으로 포인터까지 통으로 자료형으로 만들어버림.
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR lpCmdLine,
_In_ int nCmdShow)
{
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
//딱히 신경쓸 건 없음. 얘내 둘은 딱히 쓰이지 않는다... 라는 걸 선언한 느낌.
//매크로임.
// TODO: 여기에 코드를 입력합니다.
// 전역 문자열을 초기화합니다.
//위의 문자열 배열들에다가 문자열을 전달하고 있는데.
//visual studio의 리소스 뷰를 보면 String Table이라는 것이 있는데.
//IDS_APP_TITLE 값 : 103 , 캡션 : Client같은게 있음
//프로젝트의 기본 설정 테이블로 등록이 되어 있음.
//값은 Resource.h에 있는 전처리기로 #define되어 있음.
LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
LoadStringW(hInstance, IDC_CLIENT, szWindowClass, MAX_LOADSTRING);
// LoadStringW(hInstance, 103, szTitle, MAX_LOADSTRING);
// LoadStringW(hInstance, 109, szWindowClass, MAX_LOADSTRING);
//프로젝트 리소스 테이블에서 이 번호에 맞는 값을 가져와라.
//여기서는 밑으로 가서 설명하기.
// 윈도우 정보 등록하는 것.
MyRegisterClass(hInstance);
// 애플리케이션 초기화를 수행합니다:
// 여기서 윈도우 창을 생성함. 유저와 프로그램 사이의 인터페이스, 접점이라는 의미도 됨.
if (!InitInstance(hInstance, nCmdShow))
{
return FALSE;
}
//단축키 정보.
//리소스 뷰 -> Accelerator -> table
//특정 키와 조합했을 때, 키값.
//테이블 정보 불러와서.
HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_CLIENT));
MSG msg;
// 기본 메시지 루프입니다:
//윈도우가 꺼지면 이 while문이 꺼짐.
//무한 반복문을 돌고 있음.
//msg.hwnd -> 메시지가 발생한 윈도우. 따라서 메시지가 발생한 창을 알 수 있음
//한 프로세스에 여러 윈도우를 띄울수도 있고, 0개의 윈도우를 가질수도 있음...
//GetMessage는 Queue임.
//현재 포커싱 중인 프로세스 나한테 온 메시지가 있나.
//자기 큐쪽으로 메시지를 가져옴.
//해당 프로그램쪽으로 발생한 메시지들을 꺼내서 씀.
//발생한 메시지의 정보를 채우기 위해 &msg로 넣어줌...
//결국 메시지 큐에서 메시지 확인할 때까지 대기.
//true, false는 어떤 메시지였느냐에 따라 다름... 애초에 돌려면 메시지가 있어야함.
//WMQUIT는 마감용 메시지임(다른 일 다 끝남)
//반드시 메시지가 있어야하고, 메시지 반응형 방식.
//툭툭 건드려줘야 계속 반응하는 느낌. 메시지 하나만 목이 빠져라 기다림
//게임 클라이언트로 사용하기엔 부적합함...
while (GetMessage(&msg, nullptr, 0, 0))
{
// msg.message == WM_QUIT이면 FALSE반환함.
//그 이외에는 TRUE 당연히 계속 유지되어야 하니까.
//hAccelTable로 단축키를 불러와서 도움말의 ID정보가 IDM_ABOUT임.
//테이블의 단축키를 쓰지 않으면 필요없는 부분.
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
//해당 윈도우에서 그 메시지를 처리해라
//WndProc 함수 포인터로 그걸 처리함.
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return (int)msg.wParam;
}
//
// 함수: MyRegisterClass()
//
// 용도: 창 클래스를 등록합니다.
// 창 클래스를 우리가 정의하고, 등록하는 절차는 윈도우 OS에서 제공하는 함수.
ATOM MyRegisterClass(HINSTANCE hInstance)
{
WNDCLASSEXW wcex;
//구조체 채워서 함수에 전달하기.
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
//WndProc이란? 전방 선언 되어 있는 위 함수.
//함수 포인터.
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_CLIENT));
wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
//처음 생성되는 메뉴바 null 값을 넣으면 메뉴바 사라짐.
wcex.lpszMenuName = MAKEINTRESOURCEW(IDC_CLIENT);
//이거의 Key값을 szWindowClass로 사용함. 이 MyRegisterClass의 Key값임.
wcex.lpszClassName = szWindowClass;
wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));
//이건 윈도우 OS에서 제공하는 함수임...
return RegisterClassExW(&wcex);
}
//
// 함수: InitInstance(HINSTANCE, int)
//
// 용도: 인스턴스 핸들을 저장하고 주 창을 만듭니다.
//
// 주석:
//
// 이 함수를 통해 인스턴스 핸들을 전역 변수에 저장하고
// 주 프로그램 창을 만든 다음 표시합니다.
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
hInst = hInstance; // 인스턴스 핸들을 전역 변수에 저장합니다.
//szTitle 대신 "한재훈~" 넣어도 됨. 2번째 변수는 이름 말하는 거임.
//szWindowClass 키 값대로 만들어달라.
HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);
if (!hWnd)
{
return FALSE;
}
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
return TRUE;
}
//
// 함수: WndProc(HWND, UINT, WPARAM, LPARAM)
//
// 용도: 주 창의 메시지를 처리합니다.
//
// WM_COMMAND - 애플리케이션 메뉴를 처리합니다.
// WM_PAINT - 주 창을 그립니다.
// WM_DESTROY - 종료 메시지를 게시하고 반환합니다.
//
// 메시지가 발생한 윈도우 id값, 메시지등등이 같이 들어옴.
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_COMMAND://왠만한 메시지(단축키)는 이렇게 여기에서 처리함.
{
int wmId = LOWORD(wParam);
// 메뉴 선택을 구문 분석합니다:
switch (wmId)
{
case IDM_ABOUT:
DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
break;
case IDM_EXIT:
DestroyWindow(hWnd);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
}
break;
case WM_PAINT:
{
//어떤 상황일때 WM_PAINT라는 메시지를 넣어줄까
//우리 윈도우에 무효화 영역(Invalidate)이 발생한 경우. <- 윈도우 창이 있는데, 다른 프로그램 창에 의해 가려졌다.
//그러면 다른 창때문에 가려져서 안보임. 그러다가 창을 누르면 본래 창이 튀어나옴 <- 가려졌던 부분 다시 그려진다.
//라고 보통은 설명하는데. 예전엔 그랬다.
//강의에선 창을 완전히 줄였다가 다시 킬 때. <- 무효화 영역.
//가려진 부분은 비트맵 형태로 내부적으로 가지고 있었음. 그래서 요즘은 그냥 그거 가져오는 느낌임.
//switch랑 if-else랑 유사한데. switch문에서 {}를 썼네. 여기선. 왜냐하면 지역변수를 선언하기 위해서.
PAINTSTRUCT ps;
//hdc는 hWnd(어느 윈도우)인지 알아야 한다는 거임.
//Device Context란?
//그림을 그릴때 필요한 집합체가 device context임
//해당 DC가 목적지로 하는 윈도우에, DC내부의 펜이나 브러시를 사용하여... <- DC는 그 기능의 일종의 오브젝트
HDC hdc = BeginPaint(hWnd, &ps); //Devicde Context.(그리기)
//이런 애들은 DECLARE_HANDLE이라는 전처리기로 처리가 되어 있음.
// 그런 구조체를 만들어서, int형 하나 넣어주기. 라고 되어 있음.
// 왜 이렇게 전부 int형으로 만들어주는거냐? HWND, HPEN, HBRUSH <- ID 중복을 피하기 위해서이다.
// Device Context, Pen, Brush모두 다른 오브젝트들임. 전부 Hid로 받을수도 있는 거 아니냐? 그러면, 이게 나중에 복잡해지면 이게 펜인지, 브러시인지 구별하기가 어려움.
// 이렇게 나눠주면 실수할 여지가 줄어든다.
//
// HWND
// TODO: 여기에 hdc를 사용하는 그리기 코드를 추가합니다...
//여기는 수업에서 넣은 함수.
//윈도우 핸들 , 윈도우 좌표, HDC ?
//좌상단 좌표는 (0,0). 1 단위는?? px임. 해상도랑 관련 있음...
//1. 윈도우 핸들.
//Kernel Object <- 유저가 함부로 접근할 수 없는 것.
//접근하기 위해 윈도우는 객체에게 ID값을 줌. 그 ID값(HWND)로 ShowWindow등으로 요청을 함. (30번 윈도우를 보이게 해주세요. 이런 느낌)
//즉, 코어는 Kernel에서 관리하고, 유저는 ID값을 통해서 요청하여 관리함...
//hdc(id)값에다가 사각형을 이렇게 그려주세요.
//2. 그린다에 대한 것.
//우리 화면은 전부 픽셀의 엄청난 집합임.
//직접 펜을 만들어서 DC에 건네 줄 수 있지 않을까?
//CreatePen 쓸 때, 다 찾아보면서 해라. 검색하면 다 나옴.
//내부적으론 BGR인데 <<로 밀어서 순서 맞춰서 처리해줌.
HPEN hRedPen = CreatePen(PS_SOLID, 1, RGB(255, 0, 0));
//자주 쓰는 펜, 자주 쓰는 오브젝트는 이미 다 만들어놨음.
HBRUSH hBlueBrush = CreateSolidBrush(RGB(0, 0, 255));
//hdc보고, hRedPen을 선택하라. 라고 주는거임.
//기존에 있던 defaultPen은 붕 떠버림.
//HHDIOBJ를 SelectObject가 반환함. 그래서 HPEN으로 줄 수 없으니, 캐스팅을 해줘야만 함.
//기본 펜 ID값(기존에 사용하고 있던 ID값을 받아온다)
HPEN hDefaultPen = static_cast<HPEN>(SelectObject(hdc, hRedPen));
HBRUSH hDefaultBrush = static_cast<HBRUSH>(SelectObject(hdc, hBlueBrush));
/*
HBRUSH brush;
HBRUSH hDefaultBrush = static_cast<HBRUSH>(SelectObject(hdc, brush));
*/
//변경된 펜으로 사각형 그림.
Rectangle(hdc, g_ptObjectPos.x - g_ptObjScale.x / 2,
g_ptObjectPos.y - g_ptObjScale.y / 2,
g_ptObjectPos.x + g_ptObjScale.x / 2,
g_ptObjectPos.y + g_ptObjScale.y / 2);
//빨간 펜 다 썼으니, 기본 펜으로 다시 바꿔줌.
SelectObject(hdc, hDefaultPen);
SelectObject(hdc, hDefaultBrush);
//그리고 빨간 펜을 지워준다.
DeleteObject(hRedPen);
DeleteObject(hBlueBrush);
//그리기 종료.
EndPaint(hWnd, &ps);
}
break;
case WM_LBUTTONDOWN:
{
//마우스 왼쪽 누르기.
//4바이트 param인데, 2바이트 | 2바이트 이렇게 나눠서 비트마스킹... 비슷하게 해서 마우스 좌표를 보냄.
//코드보면 비트연산(<<, >>)으로 밀어서 가져옴.
//마우스의 x좌표, y좌표.
g_ptObjectPos.x = LOWORD(lParam);
g_ptObjectPos.y = HIWORD(lParam);
}
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
case WM_KEYDOWN:
{
//키가 눌리는 걸 감지하는 것.
//무슨 키냐, 라는 걸 알아야 하지 않나?
switch (wParam) {
case VK_UP:
//위쪽 화살표.
g_ptObjectPos.y -= 10;
//누를때마다 화면 갱신을 해줘야지.
// 이렇게 하면, 기존에 그려진 부분은 그대로 남아있슴... 그래서 기존에 있던 걸 지워줘야함.
//InvalidateRect(hWnd, nullptr, false);
//true로 바꾸면, 기존에 그려져 있던 픽셀들 전부 깔끔하게 청소함.
InvalidateRect(hWnd, nullptr, true);
break;
case VK_DOWN:
g_ptObjectPos.y += 10;
InvalidateRect(hWnd, nullptr, true);
break;
case VK_LEFT:
g_ptObjectPos.x -= 10;
InvalidateRect(hWnd, nullptr, true);
break;
case VK_RIGHT:
g_ptObjectPos.x += 10;
InvalidateRect(hWnd, nullptr, true);
break;
//꾹 누르고 있으면 한템포 느림...
//KEYDOWN은 한 번 눌리고, 어느정도 딜레이 이후에 계속 들어오는 방식이라 그럼.
case 'W':
{
int a = 0;
}
//일반 키. 대문자 기준으로만 됨.
break;
}
}
break;
default: //따로 정의되지 않은 수많은 단축키들.
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
// 정보 대화 상자의 메시지 처리기입니다.
INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
UNREFERENCED_PARAMETER(lParam);
switch (message)
{
case WM_INITDIALOG:
return (INT_PTR)TRUE;
case WM_COMMAND:
if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
{
EndDialog(hDlg, LOWORD(wParam));
return (INT_PTR)TRUE;
}
break;
}
return (INT_PTR)FALSE;
}
오늘 배운 것, 중요한 것만 정리 요약할 것이다. 주석은 현재 내 코드 상태를 그냥 가감없이 보여줄 것이다.
POINT g_ptObjectPos = { 500, 300 };
POINT g_ptObjScale = { 100, 100 };
- 후술에 쓰이겠지만, 그려지는 오브젝트의 위치, 크기를 선언해 놓은 부분이다.
case WM_PAINT:
{
//어떤 상황일때 WM_PAINT라는 메시지를 넣어줄까
//우리 윈도우에 무효화 영역(Invalidate)이 발생한 경우. <- 윈도우 창이 있는데, 다른 프로그램 창에 의해 가려졌다.
//그러면 다른 창때문에 가려져서 안보임. 그러다가 창을 누르면 본래 창이 튀어나옴 <- 가려졌던 부분 다시 그려진다.
//라고 보통은 설명하는데. 예전엔 그랬다.
//강의에선 창을 완전히 줄였다가 다시 킬 때. <- 무효화 영역.
//가려진 부분은 비트맵 형태로 내부적으로 가지고 있었음. 그래서 요즘은 그냥 그거 가져오는 느낌임.
//switch랑 if-else랑 유사한데. switch문에서 {}를 썼네. 여기선. 왜냐하면 지역변수를 선언하기 위해서.
PAINTSTRUCT ps;
//hdc는 hWnd(어느 윈도우)인지 알아야 한다는 거임.
//Device Context란?
//그림을 그릴때 필요한 집합체가 device context임
//해당 DC가 목적지로 하는 윈도우에, DC내부의 펜이나 브러시를 사용하여... <- DC는 그 기능의 일종의 오브젝트
HDC hdc = BeginPaint(hWnd, &ps); //Devicde Context.(그리기)
//이런 애들은 DECLARE_HANDLE이라는 전처리기로 처리가 되어 있음.
// 그런 구조체를 만들어서, int형 하나 넣어주기. 라고 되어 있음.
// 왜 이렇게 전부 int형으로 만들어주는거냐? HWND, HPEN, HBRUSH <- ID 중복을 피하기 위해서이다.
// Device Context, Pen, Brush모두 다른 오브젝트들임. 전부 Hid로 받을수도 있는 거 아니냐? 그러면, 이게 나중에 복잡해지면 이게 펜인지, 브러시인지 구별하기가 어려움.
// 이렇게 나눠주면 실수할 여지가 줄어든다.
//
// HWND
// TODO: 여기에 hdc를 사용하는 그리기 코드를 추가합니다...
//여기는 수업에서 넣은 함수.
//윈도우 핸들 , 윈도우 좌표, HDC ?
//좌상단 좌표는 (0,0). 1 단위는?? px임. 해상도랑 관련 있음...
//1. 윈도우 핸들.
//Kernel Object <- 유저가 함부로 접근할 수 없는 것.
//접근하기 위해 윈도우는 객체에게 ID값을 줌. 그 ID값(HWND)로 ShowWindow등으로 요청을 함. (30번 윈도우를 보이게 해주세요. 이런 느낌)
//즉, 코어는 Kernel에서 관리하고, 유저는 ID값을 통해서 요청하여 관리함...
//hdc(id)값에다가 사각형을 이렇게 그려주세요.
//2. 그린다에 대한 것.
//우리 화면은 전부 픽셀의 엄청난 집합임.
//직접 펜을 만들어서 DC에 건네 줄 수 있지 않을까?
//CreatePen 쓸 때, 다 찾아보면서 해라. 검색하면 다 나옴.
//내부적으론 BGR인데 <<로 밀어서 순서 맞춰서 처리해줌.
HPEN hRedPen = CreatePen(PS_SOLID, 1, RGB(255, 0, 0));
//자주 쓰는 펜, 자주 쓰는 오브젝트는 이미 다 만들어놨음.
HBRUSH hBlueBrush = CreateSolidBrush(RGB(0, 0, 255));
//hdc보고, hRedPen을 선택하라. 라고 주는거임.
//기존에 있던 defaultPen은 붕 떠버림.
//HHDIOBJ를 SelectObject가 반환함. 그래서 HPEN으로 줄 수 없으니, 캐스팅을 해줘야만 함.
//기본 펜 ID값(기존에 사용하고 있던 ID값을 받아온다)
HPEN hDefaultPen = static_cast<HPEN>(SelectObject(hdc, hRedPen));
HBRUSH hDefaultBrush = static_cast<HBRUSH>(SelectObject(hdc, hBlueBrush));
/*
HBRUSH brush;
HBRUSH hDefaultBrush = static_cast<HBRUSH>(SelectObject(hdc, brush));
*/
//변경된 펜으로 사각형 그림.
Rectangle(hdc, g_ptObjectPos.x - g_ptObjScale.x / 2,
g_ptObjectPos.y - g_ptObjScale.y / 2,
g_ptObjectPos.x + g_ptObjScale.x / 2,
g_ptObjectPos.y + g_ptObjScale.y / 2);
//빨간 펜 다 썼으니, 기본 펜으로 다시 바꿔줌.
SelectObject(hdc, hDefaultPen);
SelectObject(hdc, hDefaultBrush);
//그리고 빨간 펜을 지워준다.
DeleteObject(hRedPen);
DeleteObject(hBlueBrush);
//그리기 종료.
EndPaint(hWnd, &ps);
}
break;
case WM_LBUTTONDOWN:
{
//마우스 왼쪽 누르기.
//4바이트 param인데, 2바이트 | 2바이트 이렇게 나눠서 비트마스킹... 비슷하게 해서 마우스 좌표를 보냄.
//코드보면 비트연산(<<, >>)으로 밀어서 가져옴.
//마우스의 x좌표, y좌표.
g_ptObjectPos.x = LOWORD(lParam);
g_ptObjectPos.y = HIWORD(lParam);
}
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
case WM_KEYDOWN:
{
//키가 눌리는 걸 감지하는 것.
//무슨 키냐, 라는 걸 알아야 하지 않나?
switch (wParam) {
case VK_UP:
//위쪽 화살표.
g_ptObjectPos.y -= 10;
//누를때마다 화면 갱신을 해줘야지.
// 이렇게 하면, 기존에 그려진 부분은 그대로 남아있슴... 그래서 기존에 있던 걸 지워줘야함.
//InvalidateRect(hWnd, nullptr, false);
//true로 바꾸면, 기존에 그려져 있던 픽셀들 전부 깔끔하게 청소함.
InvalidateRect(hWnd, nullptr, true);
break;
case VK_DOWN:
g_ptObjectPos.y += 10;
InvalidateRect(hWnd, nullptr, true);
break;
case VK_LEFT:
g_ptObjectPos.x -= 10;
InvalidateRect(hWnd, nullptr, true);
break;
case VK_RIGHT:
g_ptObjectPos.x += 10;
InvalidateRect(hWnd, nullptr, true);
break;
//꾹 누르고 있으면 한템포 느림...
//KEYDOWN은 한 번 눌리고, 어느정도 딜레이 이후에 계속 들어오는 방식이라 그럼.
case 'W':
{
int a = 0;
}
//일반 키. 대문자 기준으로만 됨.
- WM_PAINT로 윈도우에서는 창 내부에 무언가를 그려준다.
- 윈도우에 무효화 영역(Invalidate)영역이 발생한 경우 WM_PAINT 메시지를 발생시킨다. 이는 예를 들어, 윈도우 창이 있는데 다른 윈도우 창에 가려졌다가 다시 보여질 경우, 혹은 최소화 버튼을 눌러서 안보이게 만들었다가 다시 끌어와서 보이는 경우다.
-
다만 요즘은 다른 창에 일부 가려진 정도는 비트맵으로 이미 데이터를 가져온 걸로 출력해주는 느낌이라 요즘은 좀 달라졌다.
- hdc는 전체적인 총괄 클래스, 오브젝트라고 생각하면 되는 것 같다.
-
그림을 그릴때 필요한 집합체(펜, 브러시, 창)들의 집합체가 hdc(device context)
-
HDC, HPEN, HBRUSH 전부 내부적으로는 int* 형이다. 근데 굳이 나눠주는 이유는 그게 무슨 용도로 쓰이는 ID인지 알기 위해서. 이게 펜인지, 브러시인지 프로그래머가 구별하기 쉽도록 나누어서 실수할 경우를 줄이는 것이다.
- 윈도우 핸들 . 윈도우 내부엔 Kernel Object가 있다. 유저는 그것에 함부로 접근할 수가 없는데, 접근하기 위해서 일단 윈도우는 객체에 ID값을 준다. 그 id값(HWND)로 ShowWindow로 요청을 한는 것처럼, 유저는 윈도우에 이 ID 프로세스에 사각형을 그려주세요… 이렇게 요청한다
//CreatePen 쓸 때, 다 찾아보면서 해라. 검색하면 다 나옴.
//내부적으론 BGR인데 <<로 밀어서 순서 맞춰서 처리해줌.
HPEN hRedPen = CreatePen(PS_SOLID, 1, RGB(255, 0, 0));
//자주 쓰는 펜, 자주 쓰는 오브젝트는 이미 다 만들어놨음.
HBRUSH hBlueBrush = CreateSolidBrush(RGB(0, 0, 255));
//hdc보고, hRedPen을 선택하라. 라고 주는거임.
//기존에 있던 defaultPen은 붕 떠버림.
//HHDIOBJ를 SelectObject가 반환함. 그래서 HPEN으로 줄 수 없으니, 캐스팅을 해줘야만 함.
//기본 펜 ID값(기존에 사용하고 있던 ID값을 받아온다)
HPEN hDefaultPen = static_cast<HPEN>(SelectObject(hdc, hRedPen));
HBRUSH hDefaultBrush = static_cast<HBRUSH>(SelectObject(hdc, hBlueBrush));
- 내부적으론 RGB가 아니라, BGR이다. 그래서 «8, «16을 통해 비트를 밀어준다. (내부 구현은)
- SelectObject하면, 기존에 쓰던 것의 포인터를 반환한다. 그걸 저렇게 변수로 받아오는 것.
case WM_KEYDOWN:
{
//키가 눌리는 걸 감지하는 것.
//무슨 키냐, 라는 걸 알아야 하지 않나?
switch (wParam) {
case VK_UP:
//위쪽 화살표.
g_ptObjectPos.y -= 10;
//누를때마다 화면 갱신을 해줘야지.
// 이렇게 하면, 기존에 그려진 부분은 그대로 남아있슴... 그래서 기존에 있던 걸 지워줘야함.
//InvalidateRect(hWnd, nullptr, false);
//true로 바꾸면, 기존에 그려져 있던 픽셀들 전부 깔끔하게 청소함.
InvalidateRect(hWnd, nullptr, true);
break;
case VK_DOWN:
g_ptObjectPos.y += 10;
InvalidateRect(hWnd, nullptr, true);
break;
case VK_LEFT:
g_ptObjectPos.x -= 10;
InvalidateRect(hWnd, nullptr, true);
break;
case VK_RIGHT:
g_ptObjectPos.x += 10;
InvalidateRect(hWnd, nullptr, true);
break;
//꾹 누르고 있으면 한템포 느림...
//KEYDOWN은 한 번 눌리고, 어느정도 딜레이 이후에 계속 들어오는 방식이라 그럼.
case 'W':
{
int a = 0;
}
//일반 키. 대문자 기준으로만 됨.
- 키가 눌리는 걸 감지하는 Case이다.
- VK_UP, VK_DOWN,VK_LEFT, VK_RIGHT로 키가 DOWN되는 것을 받는다.
- 여기서 InvalidateRect를 안하면, 오브젝트의 위치만 바꿨지 화면 갱신은 안해주는 모양새가 된다.
- 또, InvalidateRect의 끝 변수를 false로 하면, 기존에 있던 픽셀들을 초기화 하지 않아, 기존에 있던 색상이 그대로 남는다. true로 해주면 기존에 있던 걸 싸그리 초기화 한다.
댓글 남기기