3강 - 도형그리기 및 마우스

PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps); 
  • BeginPain는 HDC를 반환하고 H가 맨 앞에 있는걸로 보아 handle을 의미한다. dc는 클라이언트 화면에 그리기(출력하기) 위한 도구이다.

  • dc를 사용해 문자를 출력해보자.

샘플 코드

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
    case WM_COMMAND:
        {
            int wmId = LOWORD(wParam);
            // Parse the menu selections:
            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:
        {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hWnd, &ps); 
            // TODO: Add any drawing code that uses hdc here...

            /* 마우스의 위치 출력*/
            TCHAR mouse[64] = {};

            // wsprintf : 유니코드 문자열을 만들어주는 함수
            wsprintf(mouse, TEXT("x : %d y : %d"), _area.start.x, _area.start.y);

            // lstrlen(변수) : 유니코드 문자열의 길이를 구하는 함수
            TextOut(hdc, 600, 30, mouse, lstrlen(mouse));

            /* 텍스트 출력 */
            // 핸들, 좌표x, 좌표y, 유니코드문자열, 글자 갯수
            // 유니코드 문자열을 작성할 때, 따옴표 앞에 L을 붙이거나 TEXT 매크로를 이용한다.
            // L을 쓰는 것 보다 TEXT를 사용하는 것이 좋다. 왜냐하면 TEXT가 알아서
            // 멀티바이트인 경우에도 처리해주기 때문
            TextOut(hdc, 50, 50, TEXT("win32"), 5);
            TextOut(hdc, 55, 55, TEXT("win32"), 5);

            /* 도형 출력 */

            /* 사각형 그리기 */
            // 핸들, 사각형 왼쪽 상단 좌표x,y , 사격형 오른쪽 하단 좌표x,y, 
            Rectangle(hdc, 45, 45, 200, 200);            

            // ! 사각형을 텍스트 위에 출력하면 텍스트를 위로 덮어서 텍스트가 보이지 않는다.
            TextOut(hdc, 60, 60, TEXT("win32"), 5);

            /* 선 그리기 */
            // 시작점과 끝점을 설정
            // 선의 시작점 설정
            MoveToEx(hdc, 70, 100, NULL);
            // 끝점
            LineTo(hdc, 400, 150);
            // 끝점을 한번더 호출하면 끝점이 시작점이 되어 연결가능
            LineTo(hdc, 500, 100);

            // 시작점을 다시 지정해서 새로 그리기 가능
            MoveToEx(hdc, 600, 300, NULL);
            LineTo(hdc, 700, 400);

            /* 원 */
            // 45,45,200,200 사각형에 딱 맞는 원이 생성된다.
            Ellipse(hdc, 45, 45, 200, 200);

            /* 마우스 드래그하면 사각형이 그려지는 기능을 만들어보자 */
            if (_area.isStarted)
            {
                Rectangle(hdc, _area.start.x, _area.start.y, _area.end.x, _area.end.y);    
            }

            EndPaint(hWnd, &ps);
        }
        break;

        // 키가 눌러졌을때 들어오는 메세지

        // 마우스 좌클릭
    case WM_MOUSEMOVE:
        // 마우스가 움직일 때 들어오는 메시지
        if (_area.isStarted)
        {
            _area.end.x = lParam & 0x0000ffff;
            _area.end.y = lParam >> 16;

            InvalidateRect(hWnd, NULL, TRUE);
        }
        break;
    case WM_LBUTTONUP:
        // 마우스 왼쪽버튼이 떼질 때 발생하는 메시지
        if (_area.isStarted)
        {
            _area.isStarted = false;
            _area.end.x = lParam & 0x0000ffff;
            _area.end.y = lParam >> 16;

            InvalidateRect(hWnd, NULL, TRUE);
        }

        break;
    case WM_LBUTTONDOWN:
        // 마우스 위치는 lParam에 들어오게 되는데 16비트로 쪼개서 x,y값이
        // 32비트에 들어오게 된다. LOWORD, HIWORD 매크로를 이용해서 하위, 상위 16비트 값을 얻어온다.
        if (!_area.isStarted)
        {
            _area.isStarted = true;

            // 만약 lParam이 0x12345678 >> 16일때 결과는? 1234
            _area.start.x = lParam & 0x0000ffff;
            _area.start.y = lParam >> 16;

            _area.end.x = _area.start.x;
            _area.end.y = _area.start.y;
            // InvalidateRect 함수는 강제로 WM_PAINT 메시지를
            // 호출해주는 함수... 1번 인자 : 윈도우 핸들, 2번 인자 : 초기화할 영역
            // NULL을 넣어줄 경우 전체화면을 대상으로 갱신 진행
            // 3번 인자 : TRUE일 경우 현재 화면을 지우고 갱신, FALSE일 경우 안지우고 갱신
            InvalidateRect(hWnd, NULL, TRUE);
        }

        break;

    case WM_KEYDOWN:
        // 이 메시지가 들어올 경우 wParam에 어떤 키를 눌렀는지가 들어온다.
        switch (wParam)
        {
        case VK_ESCAPE:
            DestroyWindow(hWnd);
            break;
        }
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}

마우스 드래그 사각형 정리

처음에 구조체를 생성
struct _tagArea
{ bool isStarted;
POINT start;
POINT end;
};

마우스 움직이면

  1. 현재 마우스의 위치가 끝점이다.
  2. 즉, 움직일때 계속 위치가 갱신되어서 드래그 사각형이 계속 생성된다.

누르면

  1. bool 변수에 true를 넣는다.
  2. 시작점 저장
  3. 끝점을 초기화 해준다.

떼면

  1. bool 변수에 false를 넣는다.
  2. 끝점 저장

1강 - 기본 윈도우창 Part.1

API란? = 함수 집합. 굉장히 많은 windows의 기능들을 이용하기 위한 함수 집합

Windows API 는 C와 C++ 언어를 기반으로, 함수와 구조체를 기반으로 기능을 제공

방대한 기능을 모두 외우고 사용할 수는 없다. 따라서 필요한 기능들을 선택적으로 공부해야한다.

중요한 것은 C++ 프로그래밍 능력이다.

1. 프로젝트 생성 방법

'Win32 콘솔 응용 프로그램'이 아닌 'Win32 프로젝트'로 프로젝트 생성

프로젝트를 생성하면 미리 컴파일된 파일(precompiled file)들이 많이 있다.

stadfx.h 라는 헤더는 한번 컴파일한 후, 수정이 되기 전까지 컴파일이 실행되지 않음. 따라서 성능향상에 도움이 된다.

(프로젝트 이름).cpp 에 미리 생성된 코드들이 windows에 프로그램을 만들기 위해 미리 생성 된 코드이다.

F5로 실행하게 되면 콘솔이 아닌 실제 Windows에서 동작되는 창이 뜬다.

그렇다면 어떻게 이것이 가능한가?

windows.h에 기닝들이 포함되어 있으므로 반드시 include되어야한다.

기존에 했던 코딩의 진입점은 int main()이다. 그러나 windows에서는

int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
                     _In_opt_ HINSTANCE hPrevInstance,
                     _In_ LPWSTR    lpCmdLine,
                     _In_ int       nCmdShow)

여기에 있는 여럿 인자들을 살펴보자.

우선, APIENTRY은 define 되어있는 걸 볼 수 있는데

#define WINAPI    __stdcall
#define APIENTRY  WINPAI

__stdcall = 함수 호출 규약
(함수 호출하는 방법에 대해서 여러 방법이 있는데 그 중 하나임을 명사하는 것이다.)

인자의 종류 중 HINSTANCE hInstance를 살펴 보자

HINSTANCE

  • windows에서 앞에 h가 붙어있으면 handle을 의미한다. handle이란 가는 방향을 조종한다... instace는 class를 기준으로 설멍하자면 class를 만든다고 동작되는 것이 아니고 class를 변수로 선언하고 초기값을 설정한 후 사용가능하다. class 형태를 제공하는 것이 interface, 제공되는 class를 사용해서 실제 사용하는 객체를 Instace라고 한다.
  • 운영체제는 운영체제 안에 있는 모든 프로그램들을 관리해야 한다. 그리고 프로그램들이 어떤 프로그램들인지 구분해야 한다. 그러기 위해서는 프로그램마다 그 프로그램이 무엇인지 식별번호를 저장하는 변수가 필요하다. 이것이 HINSTANCE이다.
  • exe 파일을 실행하면 exe의 코드가 메모리의 코드영역에 올라간다. 그러면 CPU가 그 코드영역의 코드를 동작시킨다. 이 때 운영체제는 이 프로그램에 식별번호(HINSTANCE)를 부여한다. 결과적으로 우리가 수정하는 영역이 아니다.
  • 커널이라는 개념... 커널도 운영체제가 사용하는 기능이다. 커널에서 사용되는 오브젝트를 커널 오브젝트라고 한다. handle 종류가 커널 오브젝트에 속한다.

LoadString

  • 실행하면 타이틀 바에 제목이 있다. 이 타이틀 바의 제목을 문자열로 지정가능하다. 내부적으로 string table에 이 제목 변수를 넣어놓았다. LoadString이 그 string table을 읽어주는 방식이다. map 컨테이너처럼 key와 value로 구성되어 있다.
LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
  • 여기에서 IDS_APP_TITLE이 id(key)이고 szTitle은 위에 전역변수로 선언되어 있는데,
> WCHAR szTitle[MAX_LOADSTRING];
  • 이렇게 선언되어 있거 WCHAR을 F12번을 눌러 들어가보자.
typedef wchar_t WCHAR;
  • 를 발견할 수 있다. 주석에는 16-bit UNICODE character라고 되어 있는데 다양한 언어를 지원하기 위해 나온게 2byte UNICODE이다.

  • 이걸 바꾸기 위해서는 '프로젝트 이름'에 우클릭 하고 '속성'에 간 다음 '일반'에 '문자 집합' 부분을 '멀티바이트 문자 집합'으로 바꿔주면 된다.

ATOM MyRegisterClass(HINSTANCE hInstance)
{
    WNDCLASSEXW wcex;
  • WNDCLASSEXW는 구조체이다. 운영체제에는 registry가 존재하는데, 실행되는 프로그램에 대한 key값이 저장되어 있다. 실행 전에 registry를 저장하는 함수가 MyRegisterClass이다.

WndProc

wcex.lpfnWndProc    = WndProc;        
  • 중요!!! : 메시지 기반 운영체제가 Windows... 즉 마우스, 키보드 등의 입력을 이벤트라고 부른다. 또는 화면보호기를 띄워라 등. 이런 이벤트가 발생하면 어떤 이벤트냐에 따라서 이것을 메시지로 만들어 준다. 만들어 준 메시지를 내부에 존재하는 메시지 큐에 저장한다. 즉 이벤트가 발생한 순서대로 꺼내서 처리한다. 이벤트가 발생하는 것을 while로 계속 감시하고 이것을 메시지 루프라고 한다.

  • 메시지에 따른 사용자 정의 기능을 만들어 주어야한다. 즉 큐에 어떤 메시지가 들어있는지 정확히 알아야한다. 이 기능을 하는 것이 바로 WndProc이다.

  • WndProc이라는 함수 이름은 즉 함수 포인터와 같은 의미이다. 함수포인터를 왜 전달했을까? 포인터를 사용하는 이유는 '주소에 접근해서 값을 변경하기 위해서'이다. 따라서 그 주소에 있는 함수에 접근해서 실행하라는 뜻이다. Windows운영체제는 사용자가 만든 함수를 모른다. 그래서 함수의 포인터를 전달해서 실행시킨다. 즉 사용자 정의 함수를 만들고 사용하기 위해서이다.

hIcon, hIconSm

  • 윈도우에서 사용하는 아이콘 이미지이고 sm이 들어가면 윈도우 bar에 들어가는 작은 아이콘 이미지이다.

hCursor

  • 마우스 커서의 모양이다.

hbrBackground

  • 화면 배경 색

lpszMenuName

  • 리소스에 이미 만들어져 있다. 윈도우 바 밑에 있는 탭다운 메뉴이다. 그러나 사용되지 않을 예정이다.

lpszClassName

  • 레지스터에 등록할 클래스 이름.

+ Recent posts