WIN32 API 핸들, DC, 윈도우 이벤트

Date:     Updated:

카테고리:

태그:

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로 해주면 기존에 있던 걸 싸그리 초기화 한다.

WINAPI 카테고리 내 다른 글 보러가기

댓글 남기기