Presentation is loading. Please wait.

Presentation is loading. Please wait.

3장. 제어 메시지 처리하기 1/211 1.

Similar presentations


Presentation on theme: "3장. 제어 메시지 처리하기 1/211 1."— Presentation transcript:

1 3장. 제어 메시지 처리하기 1/211 1

2 내용 키보드 입력으로 도형 이동 타이머 메시지 마우스 메시지 래스터 연산 2/211
먼저 1장에서 다룰 내용을 설명하면 다음과 같다. 본 강의에서 다룰 전체적인 강의 개요를 먼저 설명할 것이다. 두 번째로 API 프로그래밍을 위한 개발도구와 개발방법을 다룬다. C언어 프로그래밍을 위한 기존의 개발도구를 그대로 사용하고 있지만 방법적인 측면에서는 약간 다르기 때문에 실습 중심으로 다룰 것이다. 윈도우 프로그램은 콘솔기반의 C언어 프로그램과 기본 구조에서 약간 차이가 있다. 본 강의에서는 먼저 큰 그림에서 보고 차이점을 보고 세부적으로 다른 부분들을 다루는 방식을 따를 것이다. 윈도우 프로그램에서는 윈도우클래스를 만들어서 등록을 해야 한다. 이것은 생성하기 원하는 윈도우의 기본 설정을 저장한 구조체이다. 이를 기반으로 윈도우는 생성되고 생성된 윈도우 상에서 이벤트가 발생하면 우리의 윈도우 프로그램에서는 발생한 이벤트가 보내온 메시지를 처리한다. 이 과정을 간단한 실습문제를 통하여 익숙해 진다. 2/211 2

3 3-1 오른쪽 화살표키로 원 이동 시키기 예제 HDC hdc ; PAINTSTRUCT ps ;
static int x, y; // 눌렀을 때 저장한 값이 출력할 때도 갖고 있도록 switch (iMsg) { case WM_CREATE : x = 20; y = 20; // 원 중심점 return 0 ; case WM_PAINT : hdc = BeginPaint (hwnd, &ps) ; Ellipse(hdc, x-20, y-20, x+20, y+20); EndPaint (hwnd, &ps) ; case WM_KEYDOWN: if (wParam == VK_RIGHT) x += 40; InvalidateRgn(hwnd, NULL, TRUE); // WM_PAINT 메시지 발생 break; case WM_DESTROY : PostQuitMessage (0) ; } 윈도우에 그려진 원의 위치를 오른쪽 화살표키를 누를 때마다 오른쪽으로 이동시키는 프로그램을 작성하여 볼 것이다. 지금까지 그렸던 원이나 사각형은 좌표를 상수로 지정했기 때문에 정해진 위치에 나타났었다. 여기 예제에서는 위치가 변할 것이기 때문에 상수를 사용하면 안되고 변수를 사용해야 한다. 원의 중심점 좌표를 저장하기 위한 정수변수 x, y를 선언하였다. 그리고 초기 좌표를 WM_CREATE메시지가 발생할 때 각각 20과 20을 x, y에 부여 하였다. 값을 변수에 넣은 후 메시지 처리 함수인 WndProc는 종료되게 된다. 이제 지정된 위치에 반지름 20인 원을 그려야 하는데 x,y의 값이 다음 메시지가 올 때 계속하여 저장돼 있기 위해서는 전역변수이거나 static변수이어야 한다. 위에서는 static 변수로 x,y를 지정하였다. WM_CREATE메시지 이후에 바로 WM_PAINT메시지가 올 것이다. WM_PAINT메시지 처리에서 화면을 hdc에 얻어오고 x, y를 중심으로 반지름 20인 원을 그렸다. 그리고는 WndProc를 마쳐서 윈도우 상에는 중심이 (20,20)이고 반지름 20인 원이 나타나게 된다. 그 이후에 우리의 프로그램은 메시지가 오기만을 기다릴 것이다. 그러다가, 키보드가 눌러지면 WM_KEYDOWN메시지가 발생하고 입력된 키가 오른쪽 방향 화살표키인지 if문에서 체크하게 된다. 참이라면 x의 값에 40을 더해주고 거짓이라면 아무것도 하지 않는다. 그 이후 InvalidateRgn함수를 호출함으로 화면을 지우고 WM_PAINT메시지를 발생 시킨다. WM_PAINT가 자시 발생했을 때 달라진 것은 x의 값이 20에서 60으로 변한 것 뿐이다. 따라서, 원은 (60,20)을 중심으로 그려지게 된다. 이 과정의 반복을 보면 알 수 있듯이 x의 값이 오른쪽 방향 화살표가 눌러질 때 마다 40씩 증가하며 화면에 원을 다시 그리게 하고 있다는 것을 알 수 있다. 이것은 원이 오른쪽으로 이동 하는 것과 같은 효과를 낼 수 있다. 3/211 3

4 3-2 오른쪽 경계 안에서 원 이동 시키기 HDC hdc ; PAINTSTRUCT ps ; static int x, y;
static RECT rectView; // 윈도우 크기정보를 갖는 구조체 switch (iMsg) { case WM_CREATE : GetClientRect(hwnd, &rectView); // 윈도우 크기정보 얻어오기 x = 20; y = 20; return 0 ; case WM_PAINT : hdc = BeginPaint (hwnd, &ps) ; Ellipse(hdc, x-20, y-20, x+20, y+20); EndPaint (hwnd, &ps) ; 앞에서 작성된 프로그램에서는 화살표키에 의해 원이 움직이기는 하지만 원도우의 경계를 만나도 무시하고 지나가고 있음을 볼 수 있었다. 윈도우 경계에서 더 이상 진행하지 않도록 프로그램을 변경해 보자. 먼저 윈도우의 경계좌표를 얻어내야 한다. 윈도우의 경계좌표 즉, 윈도우의 크기를 알 수 있는 함수는 GetClientRect이다. 여기에는 측정하기 원하는 윈도우의 핸들과 윈도우의 크기를 저장할 RECT 구조체 변수가 필요하다. RECT 구조체는 알다시피 수평수직이 되는 직사각형의 좌측 상단과 우측 하단 꼭지점 좌표를 저장할 수 있다. GetClientRect는 윈도우의 클라이언트 영역, 즉 윈도우 프레임을 제외한 하얀 사각형 영역의 좌측 상단 모서리와 우측 하단 모서리의 좌표를 측정한다. 좌측 상단 모서리의 좌표는 언제나 (0,0)이다. 하지만 우측 하단 모서리 좌표는 윈도우의 크기에 따라 달라진다. 만약 우측 하단 점의 좌표를 (r, b)라고 한다면 r은 클라이언트 영역의 폭이 되고 b는 높이가 된다. 위의 예에서는 WM_CREATE메시지 발생시 클라이언트 영역의 크기를 측정하여 rectView 변수에 저장하고 있다. 그리고, 원의 중심점 좌표를 초기값으로 (20, 20)이라고 정하였다. 여기서도 마찬가지로 rectView에 측정값을 넣는 것은 WM_CREATE메시지 발생 시에 하지만 이를 이용하여 원의 경계가 윈도우 경계를 벗어나려 하는지 검사는 다른 메시지에서 체크하기 때문에 static변수로 선언하였다. 4/211 4

5 오른쪽 경계 안에서 원 이동 시키기(계속) case WM_KEYDOWN: if (wParam == VK_RIGHT) {
x += 40; // 원의 중심점을 오른 쪽으로 이동 if (x + 20 > rectView.right) // 원이 경계선 우측에 x -= 40; // 닿으면 다시 원상복귀 } InvalidateRgn(hwnd, NULL, TRUE); break; case WM_DESTROY : PostQuitMessage (0) ; return 0 ; 오른쪽 방향 화살표키를 누르면 WM_KEYDOWN메시지가 발생하게 된다. 이에 따라 입력된 킷값이 VK_RIGHT인지 체크하고 맞는다면 x값을 40 증가 시켜 준다. 하지만, 증가를 시켜도 되는지 즉, 변화된 위치에 원을 그려도 윈도우 경계를 벗어나지 않는지 체크는 아직 하지 않았다. 원이 경계에 걸치기만 해도 안되기 때문에 원의 오른쪽 경계 부분과 윈도우의 오른쪽 경계를 비교하였다. 원의 오른쪽 경계는 중심에서 20만큼 더해진 값이다. 따라서, 여기서는 x+20이 윈도우 오른쪽 경계 값인 rectView.right보다 큰지를 검사하는 것이다. 만약 크다면 원이 경계에 걸쳐지거나 넘어가는 상황이 발생할 것이기 때문에 40만큼 더했던 것을 취소한다. 만약 조건이 거짓이라서 아직 윈도우가 오른쪽 경계에 도달 하지 않았다면 변화된 좌표를 중심으로 원을 그리게 한다. 5/211 5

6 타이머 컴퓨터에 시간 간격을 설정하여 WM_TIMER 메시지를 프로그램에 보내도록 하는 기능 설정함수
WORD SetTimer(HWND, int, WORD, FARPROC); 매개변수 HWND : 윈도우 핸들 nIDEvent : 타이머 ID, 여러 개의 타이머를 동시에 사용 가능 uElapse : 시간간격 milisec lpTimerFunc : 시간간격 마다 수행할 함수 키보드나 마우스 버튼의 입력이 아닌 다른 신호를 주기적으로 줄 수 있는 것은 타이머 메시지이다. 타이머는 컴퓨터에 있는 시계로 타이머에게 일정 시간 간격으로 메시지 WM_TIMER를 우리 프로그램에게 보내도록 셋팅할 수 있다. 셋팅하는 함수는 SetTimer이다. 첫 번째 파라메터는 윈도우핸들 값이고 두 번째 파라메터인 정수는 타이머의 ID이다. 이 숫자는 여러 개의 타이머가 존재 할 수 있기 때문에 사용되는 것이다. 세 번째 파라메터는 시간 간격을 나타낸 것으로 단위는 밀리초이다. 즉, 1000이라고 쓰면 1초이다. 네 번째 파라메터에는 타이머가 보내는 WM_TIMER를 받을 함수 이름을 쓰는 곳으로 NULL이라고 쓰면 WndProc가 메시지를 받을 수 있다. 위에 있는 SetTimer(hwnd, 1, 70, NULL)은 숫자 1을 ID로 가지는 타이머를 셋팅하는 문장으로 메시지는 0.07초마다 오게 되며 메시지는 WndProc함수가 처리하게 된다. 위의 프로그램에서는 윈도우가 생성되어 WM_CREATE메시지가 오면 두 개의 타이머를 셋팅하고 있다. 1번 타이머는 0.07초, 2번 타이머는 0.1초 간격으로 WM_TIMER를 우리 프로그램에 보내주고 메시지 처리는 WndProc에서 한다. 도착한 메시지 WM_TIMER가 1번 타이머에게서 온 것인지 2번 타이머에게서 온 것인지는 wParam의 값을 보고 판단한다. 그렇기 때문에 case WM_TIMER:에 wParam의 값을 조사하는 switch문이 있는 것이다. 6/211 6

7 타이머 처리방법 case WM_CREATE: SetTimer(hwnd, 1, 70, NULL);
break; case WM_TIMER: switch(wParam) { case 1: // 0.07초 간격으로 실행 case 2: // 0.1초 간격으로 실행 키보드나 마우스 버튼의 입력이 아닌 다른 신호를 주기적으로 줄 수 있는 것은 타이머 메시지이다. 타이머는 컴퓨터에 있는 시계로 타이머에게 일정 시간 간격으로 메시지 WM_TIMER를 우리 프로그램에게 보내도록 셋팅할 수 있다. 셋팅하는 함수는 SetTimer이다. 첫 번째 파라메터는 윈도우핸들 값이고 두 번째 파라메터인 정수는 타이머의 ID이다. 이 숫자는 여러 개의 타이머가 존재 할 수 있기 때문에 사용되는 것이다. 세 번째 파라메터는 시간 간격을 나타낸 것으로 단위는 밀리초이다. 즉, 1000이라고 쓰면 1초이다. 네 번째 파라메터에는 타이머가 보내는 WM_TIMER를 받을 함수 이름을 쓰는 곳으로 NULL이라고 쓰면 WndProc가 메시지를 받을 수 있다. 위에 있는 SetTimer(hwnd, 1, 70, NULL)은 숫자 1을 ID로 가지는 타이머를 셋팅하는 문장으로 메시지는 0.07초마다 오게 되며 메시지는 WndProc함수가 처리하게 된다. 위의 프로그램에서는 윈도우가 생성되어 WM_CREATE메시지가 오면 두 개의 타이머를 셋팅하고 있다. 1번 타이머는 0.07초, 2번 타이머는 0.1초 간격으로 WM_TIMER를 우리 프로그램에 보내주고 메시지 처리는 WndProc에서 한다. 도착한 메시지 WM_TIMER가 1번 타이머에게서 온 것인지 2번 타이머에게서 온 것인지는 wParam의 값을 보고 판단한다. 그렇기 때문에 case WM_TIMER:에 wParam의 값을 조사하는 switch문이 있는 것이다. 7/211 7

8 3-3 자동으로 원 이동 시키기 HDC hdc ; PAINTSTRUCT ps ; static int x, y;
static RECT rectView; switch (iMsg) { case WM_CREATE : GetClientRect(hwnd, &rectView); x = 20; y = 20; return 0 ; case WM_PAINT : hdc = BeginPaint (hwnd, &ps) ; Ellipse(hdc, x-20, y-20, x+20, y+20); EndPaint (hwnd, &ps) ; 이 부분은 앞에서 사용했던 예제와 동일하다. 윈도우가 처음 만들어져서 WM_CREATE가 오면 윈도우의 크기를 측정하고 원의 기본중심 좌표인 (20,20)을 변수 x,y에 저장한다. WM_PAINT메시지가 오면 화면 디바이스 컨텍스트에 (x,y)좌표를 중심으로 반지름 20인 원을 그려준다. 다음 장에서는 x,y좌표가 어떻게 해서 변경되고 변경된 후 어떻게 WM_PAINT가 발생하는 지를 볼 것이다. 8/211 8

9 자동으로 원 이동 시키기 예제(계속) case WM_KEYDOWN:
if (wParam == VK_RIGHT) // 오른쪽 키를 누를 때 SetTimer(hwnd, 1, 70, NULL); // 타이머 설정 break; case WM_TIMER: // 시간간격이 지나면 자동 생성 x += 40; if (x + 20 > rectView.right) x -= 40; InvalidateRgn(hwnd, NULL, TRUE); case WM_DESTROY : KillTimer(hwnd, 1); // 윈도우 종료시 타이머도 종료 PostQuitMessage (0) ; return 0 ; } 오른쪽 화살표키가 눌러지면 화면에 있는 원이 자동으로 오른쪽으로 이동하게 된다. 키보드 눌러진 후 오른쪽 화살표 키인지 체크한 후 맞는다면 타이머를 셋팅한다. 1번 타이머에 0.07초라는 시간간격을 셋팅한다. 이제 0.07초마다 WM_TIMER메시지가 올 것이고 메시지는 WndProc에서 처리한다. WM_TIMER메시지가 오면 x좌표의 값을 40씩 증가 시킨다. 그 값이 증가하여 원이 윈도우 오른쪽 경계를 넘어 가려고 하면 증가 했던 40을 빼서 윈도우 내부에 있게 만든다. 그리고, InvalidateRgn을 통해 WM_PAINT메시지가 발생함으로 바뀐 좌표에 원을 다시 그리게 된다. 전에는 오른쪽 화살표 키를 누름으로 x좌표가 증가하고 WM_PAINT메시지가 발생했었는데 여기서는 WM_TIMER메시지가 올 때마다 하게 된다. 즉, 0.07초마다 x좌표는 증가되고 WM_PAINT메시지가 발생되는 것이다. 이 때문에 자동으로 원이 오른쪽으로 이동하는 것처럼 보인다. 타이머의 동작을 멈추기 원하면 KillTimer함수를 이용하면 된다. 위에서 보는 바와 같이 KillTimer(hwnd, 1)이라고 해 주면 1번 타이머는 동작을 멈추게 된다. 9/211 9

10 마우스 메시지 WM_LBUTTONDOWN: 왼쪽 마우스 버튼을 눌렀을 때 발생하는 메시지
WM_LBUTTONUP: 왼쪽 마우스 버튼을 떼었을 때 발생하는 메시지 WM_RBUTTONDOWN: 오른쪽 마우스 버튼을 눌렀을 때 발생하는 메시지 WM_RBUTTONUP: 오른쪽 마우스 버튼을 떼었을 때 발생하는 메시지 WM_MOUSEMOVE: 마우스를 움직일 때 발생하는 메시지 마우스는 개인용 컴퓨터나 터미널에서 없어서는 안될 만큼 많이 사용되는 입력기이다. 요즘은 마우스에 버튼도 많아지고 기능도 아주 다양해 지고 있다. 하지만 우리는 마우스의 좌, 우 2개 버튼에 대해서만 처리 방법을 배운다. 그 중에서도 가장 간단한 사건에 대해서만 처리할 예정이기 때문에 좀 더 복잡한 상황을 처리하기 원한다면 참고도서를 찾아봐야 한다. 여기서 다룰 기본적인 사건은 마우스의 왼쪽과 오른쪽 버튼을 누르거나 떼는 사건과 마우스를 움직이는 사건이다. 각 사건이 발생하였을 때 발생하는 메시지의 매크로 상수는 위에서 정의한 바와 같다. 모든 메시지는 Window Message란 의미에서 WM으로 시작한다. 매크로 상수에 이용한 메시지 명은 영어단어를 이용하여 쉽게 알 수 있도록 하였다. 10/211 10

11 마우스 좌표 구하기 int y = HIWORD(lParam) int x = LOWORD(lParam) x y 11/211
마우스 메시지가 발생했을 때 프로그램은 단지 마우스에 어떤 사건이 발생했다는 사실만 알고 거기에서 그치기를 원하지 않는다. 정작 중요한 것은 마우스 사건이 발생한 위치일 것이다. 메시지 번호는 WndProc함수의 iMsg 파라메터에 저장되어 오지만 마우스 좌표는 lParam 파라메터에 저장되어 온다. lParam은 32비트 크기의 변수로 상위 2바이트에는 y좌표 값이 하위 2바이트에는 x좌표 값이 저장되어 있다. 상위 2바이트를 얻는 함수는 HIWORD이고 하위 2바이트를 얻는 함수는 LOWORD이다. 11/211 11

12 3-4 마우스 클릭으로 원 선택하기 static int x, y; static BOOL Selection;
int mx, my; switch (iMsg) { case WM_CREATE : x = 50; y = 50; Selection = FALSE; // 원이 선택되었나, FALSE : 아직 안되었음 return 0 ; case WM_PAINT : hdc = BeginPaint (hwnd, &ps) ; // 만약 원이 선택되었다면, 4각형을 그린다. 아니면 원만 그린다. if (Selection) Rectangle(hdc, x-SIZE, y-SIZE, x+SIZE, y+SIZE); Ellipse(hdc, x-SIZE, y-SIZE, x+SIZE, y+SIZE); EndPaint (hwnd, &ps) ; 원의 중심점 좌표는 변수 x,y에 저장된다. 초기 값으로 각각 50, 50을 넣었다. 그리고, BOOL변수 Selection을 선언하여 마우스 왼쪽버튼 클릭으로 원을 선택했는지 여부를 저장한다. 처음에는 선택되지 않은 상태이기 때문에 FALSE값을 저장하고 있다. 프로그램이 시작되고 WM_CREATE메시지가 발생했을 때는 원의 중심좌표 변수 x, y, 워 선택여부를 저장할 변수 Selection에 대한 초기화만 수행한다. 그 이후에 WM_PAINT메시지가 오면 먼저 디바이스컨텍스트를 hdc에 얻어온다. 여기에서 원을 그리게 되고 선택적으로 사각형을 그릴 것이기 때문이다. 그리고 나서, Selection값이 참인지 체크한다. 그것은 원이 선택되었는지를 검사하는 것이다. 선택되어 참이라면 사각형을 그린다. 거짓이면 사각형은 그리지 않게 된다. 그 다음 원을 그린다. 원은 Selection변수의 내용에 상관없이 무조건 그린다. 그릴 때 좌표는 (x,y)를 중심으로 그리게 된다. 그림을 다 그리면 디바이스컨텍스트를 돌려준다. 12/211 12

13 마우스 클릭으로 원 선택하기(계속) case WM_LBUTTONDOWN : // 왼쪽 버튼 누르면
mx = LOWORD(lParam); my = HIWORD(lParam); if (InCircle(x, y, mx, my)) // 원의 중심점, 마우스 좌표 비교 Selection = TRUE; // 원 안에 있으면 ‘참’ InvalidateRgn(hwnd, NULL, TRUE); break; case WM_LBUTTONUP : // 왼쪽 버튼 띠면 Selection = FALSE; 앞에서는 변수 초기화 하는 시점과 변수의 내용을 기준으로 화면에 그려줄 내용에 대해 설명하였다. 여기서는 특정 사건이 발생했을 때 변수의 내용을 바꿔 줌으로 화면에 그려줄 내용을 변경시키는 효과를 설명할 것이다. 먼저 마우스 왼쪽버튼이 눌러졌을 때 일어나는 일이다. 이 사건이 발생하면 WM_LBUTTONDOWN메시지가 오게 되고 lParam으로 부터 마우스의 좌표를 mx, my에 얻어온다. 원의 중심좌표가 (x,y)이기 때문에 변수 x,y와 마우스 좌표인 mx,my를 이용하여 마우스 좌표가 원의 내부에 있는지 체크하게 된다. 만약 원의 내부에 있다면 Selection변수에는 TRUE를 넣어준다. 만약 그렇지 않다면 Selection은 변하지 않기 때문에 그대로 FALSE가 된다. 그리고 나서, InvalidateRgn함수를 부르고 WM_PAINT메시지를 발생시켜준다. 두 번째 사건은 마우스 왼쪽 버튼을 뗄 때 발생하는 사건이다. 이때는 당연히 WM_LBUTTONUP메시지가 발생하여 온다. 먼저는 Selection변수에 FALSE를 넣어준다. 원에 대한 선택이 끝났기 때문이다. 그리고 나서 InvalidateRgn을 통해 WM_PAINT를 다시 발생시켜 준다. 13/211 13

14 마우스 클릭으로 원 선택하기(계속) #include <math.h> #define SIZE 40 // 반지름
BOOL InCircle(int x, int y, int mx, int my) { // 마우스 위치(mx, my)가 원 안에 있나 ? if (LengthPts(x, y, mx, my) < SIZE) return TRUE; else return FALSE; } float LengthPts(int x1, int y1, int x2, int y2) { return(sqrt((x2-x1)*(x2-x1) + (y2-y1)*(y2-y1))); (x2, y2) 화면에 원을 그리고 그 원 내부에 마우스 왼쪽 버튼을 누르면 원 주위에 사각형이 나타나는 프로그램을 예제로 공부한다. 여기서 원의 반지름은 40으로 정하였지만 차후 원의 크기 변경을 위하여 반지름을 SIZE라고 매크로를 이용하여 정의하였다. 특정 좌표가 원 내부에 있는 것인지 아닌지를 판단하기 위해 InCircle함수를 정의하였다. 파라메터 x, y는 원의 중심 좌표이고 파라메터 mx, my는 체크하기 원하는 좌표 값이다. 가장 간단하게는 (x,y)좌표에서 (mx,my)좌표까지의 거리가 반지름 SIZE보다 작으면 좌표 (mx, my)는 원의 내부에 위치하는 것이다. 위의 코드에서는 LengthPts라는 함수를 이용에서 (x,y)좌표에서 (mx,my)좌표까지의 거리를 구하고 있다. 그 값이 SIZE보다 작으면 TRUE를 반환함으로 원 내부에 있다는 것을 알리고 그렇지 않을 경우 FALSE를 반환함으로 원밖에 있다는 것을 알린다. LengthPts함수에서는 두 점 사이의 거리를 구하고 있으며 이때 사용하는 sqrt함수 때문에 math.h파일을 include해 주고 있다. c b (x1, y1) a 14/211 14

15 3-5 마우스로 선택된 원 이동하기 case WM_LBUTTONDOWN : mx = LOWORD(lParam);
my = HIWORD(lParam); if (InCircle(x, y, mx, my)) Selection = TRUE; InvalidateRgn(hwnd, NULL, TRUE); break; case WM_LBUTTONUP : Selection = FALSE; case WM_MOUSEMOVE: if (Selection) { x = mx; y = my; } 마우스의 버튼을 누른 채 마우스를 움직이는 행동을 드래그라고 한다. 여기서는 그려진 원을 선택하여 드래그 한다면 드래그 되는 위치에 원을 그려주는 예제를 살펴 볼 것이다. 앞에서 보았던 예제와 중복되는 부분이 많기 때문에 여기서는 프로그램의 모든 코드를 전부 나열하지는 않겠다. 앞에서 원을 선택할 때 사용한 코드와 달라진 것은 WM_MOUSEMOVE메시지 처리하는 부분이 추가 되었다는 것이다. 마우스를 움직이면 WM_MOUSEMOVE메시지가 오게 된다. 이때도 마찬가지로 움직이는 마우스의 좌표는 lParam에 저장된다. 마우스를 움직임으로 WM_MOUSEMOVE가 발생하면 먼저 lParam에서 마우스의 위치 좌표를 얻어낸다. 얻어낸 좌표는 임시로 mx, my에 저장하게 된다. 그리고 나서 Selection값이 참이라면 즉, 원이 선택된 상태에서 마우스가 움직이는 것이라면 원의 중심 좌표인 x,y에 mx,my의 값을 각각 넣어준다. 그리고, InvalidateRgn함수를 이용해서 WM_PAINT메시지를 발생시키면 원은 새로운 좌표 (x,y)를 중심으로 그려지게 된다. 그러나, Selection값이 거짓 즉, 원이 선택되지 않은 상태에서 단지 마우스가 움직이는 것이라면 원의 중심좌표 x,y는 변하지 않는다. 15/211 15

16 래스터연산으로 그리기 SetRop2(hdc,R2_XORPEN); XOR =
검정색 RGB(0,0,0) = 하얀색 RGB(255,255,255) = 지금까지는 바탕에 특정 색상의 펜이나 브러쉬로 그림을 그리면 색상 그대로 그려지는 과정을 살펴 보았다. 이와 같이 색상 그대로 나타나는 것은 화면인 디바이스컨텍스트에 설정되어 있는 기본 래스터 연산이 R2_COPYPEN으로 설정되어 있기 때문이다. 래스터 연산이라는 것은 그림을 그리거나 색을 칠하면 바탕색과 그리고자 하는 색 사이에 어떤 연산을 할 것인가를 정하기 위해 사용된다. R2_COPYPEN은 바탕색을 무시하고 그리고자 하는 색만 남는 것을 말한다. 가령 위의 예제와 같이 R2_XORPEN으로 설정이 되었다면 바탕색과 그리는 색 사이에는 XOR연산을 수행하게 된다. 하얀색 바탕에 검은색 브러쉬와 흰색 펜을 가지고 원을 그린다고 가정을 하겠다. 검정색과 하얀색 사이에 XOR연산을 하면 하얀색이 나온다. 하얀색과 하얀색 사이에 XOR연산을 수행하면 검정색이 나온다. 따라서, 우리는 하얀색 펜과 검정색브러쉬를 가지고 그렸지만 결과적으로는 그 반대인 검정색 테두리와 하얀색 면을 가진 원이 남게 된다. 16/211 16

17 래스터연산으로 지우기 SetRop2(hdc,R2_XORPEN); XOR =
검정색 RGB(0,0,0) = XOR 하얀색 RGB(255,255,255) = 앞장에서 그려진 검은색 테두리와 하얀색 면을 가진 원 위에 다시 한번 더 하얀색선과 검은색 면을 가진 원을 그려 보겠다. 두 개의 그림이 선과 면에서 각각 색이 검정색과 하얀색으로 상반되는 것을 알 수 있다. 검정색과 하얀색 사이에 XOR연산을 수행하면 결과는 하얀색이다. 그러므로 검정색선도 하얀색으로 변하고 하얀색면도 하얀색으로 그려지게 된다. 이 과정을 이용하여 우리는 그림을 그리기도 지우기도 한다. 그림을 그리고 지우는 과정을 래스터 연산을 이용하여 반복하면 움직이는 모양을 쉽게 나타낼 수 있으며 화면이 깜박거리는 현상도 피할 수 있다. 예를 들어 좌표(100,100) 중심으로 원을 위와 같이 그렸다가 지웠다고 가정한다. 그 다음 좌표(100,101), (100,102), (100,103),…중심으로 원을 그렸다가 지우는 과정을 반복하면 원이 움직이는 효과를 볼 수 있을 것이다. InvalidateRgn함수를 이용했을 때와 다른 것은 화면을 WM_PAINT로 지우지 않기 때문에 화면이 깜박거리지 않는다. 하얀색 RGB(255,255,255) = 17/211 17

18 3-6 고무줄 효과 직선그리기 static int startX, startY, oldX, oldY;
static BOOL Drag; int endX, endY; switch (iMsg) { case WM_CREATE : startX = oldX = 50; startY = oldY = 50; Drag = FALSE; return 0 ; case WM_PAINT : hdc = BeginPaint (hwnd, &ps) ; MoveToEx(hdc, startX, startY, NULL); LineTo(hdc, endX, endY); EndPaint (hwnd, &ps) ; case WM_LBUTTONDOWN : Drag = TRUE; break; case WM_LBUTTONUP : 앞장에서와 같이 래스터 연산으로 XOR연산을 지정함으로 직선을 그릴 때 rubber band 효과를 나타낼 것이다. 직선을 그릴 때 대부분의 워드프로세서에서는 마우스 왼쪽버튼을 누름으로 시작점을 지정하고 버튼을 누른 채 드래그를 함으로 원하는 직선 모양을 가늠하며 원하는 모양이 되었을 때 눌렀던 버튼을 뗌으로 직선을 완성한다. 이때 드래그 하는 도중에 직선의 시작점과 마우스 커서 사이에는 마치 고무줄이 연결된 것처럼 직선이 그려진다. 이것을 rubber band라고 부른다. 고무줄 효과를 내기 위해서는 직선을 그리고 지우는 과정을 반복한다. 직선을 그리기 위해서는 시작점과 종착점을 저장할 변수가 필요하다. 이것을 위해 (startX, startY)와 (endx, endY)를 선언하였다.원래 마우스 왼쪽 버튼을 누른 점을 시작점으로 하는 것이 맞지만 여기서는 (50,50)을 시작점으로 부여 하였다. 마우스 커서가 움직일 때 새로운 좌표를 구하게 되는데 이전에 마우스 커서가 있던 위치는 oldX, oldY에 저장한다. 그리고 현재 마우스가 드래그 중인지를 나타내기 위해 Drag라는 BOOL변수를 선언하였다. 윈도우가 처음 만들어져서 WM_CREATE메시지가 도착하면 이들 변수에 초기화를 시행한다. 초기좌표는 시작점과 종착점 모두 (50,50)이고 Drag에는 FALSE를 부여한다. 그 다음 WM_PAINT메시지가 오면 화면인 디바이스컨텍스트를 얻어오고 시작점에서 종착점까지 직선을 그린다. 그러나 두 좌표가 동일하기 때문에 현재 화면에 나오는 직선은 없다. 마우스 왼쪽버튼을 누르면 Drag변수는 TRUE가 되어 드래그 시작을 알리고 버튼을 떼면 Drag변수에 FALSE를 넣음으로 드래그가 끝났음을 알린다. 18/211 18

19 고무줄 효과 직선 그리기(계속) case WM_MOUSEMOVE: hdc = GetDC(hwnd); if (Drag) {
SetROP2(hdc, R2_XORPEN); SelectObject(hdc, (HPEN)GetStockObject(WHITE_PEN)); endX = LOWORD(lParam); endY = HIWORD(lParam); MoveToEx(hdc, startX, startY, NULL); LineTo(hdc, oldX, oldY); // 지우기 LineTo(hdc, endX, endY); // 그리기 oldX = endX; oldY = endY; } ReleaseDC(hwnd, hdc); break; 마우스를 움직이면 WM_MOUSEMOVE메시지가 오게 된다. 이것은 드래그 중일 수도 있고 아닐 수도 있다. 드래그 중이라면 직선을 그려야 하고 그렇지 않다면 아무것도 할 필요가 없다. 일단 화면인 디바이스컨텍스트를 GetDC함수를 이용하여 얻어온다. 그리고 나서 Drag변수를 통해 마우스가 드래그 중인지 아닌지를 판단한다. 드래그 중이라면 래스터연산을 통해 XOR연산을 설정한다. 디바이스컨텍스트에 하얀색펜을 SelectObject를 이용하여 등록한다. 이제 직선을 그리면 하얀색 선과 하얀색 바탕 사이에 XOR연산을 수행하기 때문에 검은색 선만 남게 된다. 즉, 검은색으로 그리는 효과를 얻게 되는 것이다. 그려진 검은색 선위에 하얀색 펜으로 직선을 다시 그리게 되면 검은색 선과 하얀색 선 사이에 XOR연산을 수행하기 때문에 하얀색이 남아서 그려진 검은색 선이 사라지게 된다. 위의 코드에서는 마우스 커서의 현재 좌표를 얻기 위하여 lParam의 LOWORD와 HIWORD를 구해서 직선이 종착점인 endX, endY에 저장한다. 그리고, 시작점 (startX, startY)에서 이전점 (oldX, oldY)사이에 직선을 그린다. 이것은 사실 그리는 행위라기 보다는 이미 그려진 직선 위에 덧 그리는 것이므로 지우는 효과를 나타낸다. 그 다음 종착점인 (endX,endY)로 직선을 그리는데 결과적으로 이번에 그린 것이 화면에 남게 된다. 그리고, 다음에 마우스 커서를 움직이면 종착점이 이전 점이 되기 때문에 endX, endY를 oldX, oldY로 각각 복사하여 준다. 19/211 19

20 고무줄 효과 원 그리기 static int startX, startY, oldX, oldY; static BOOL Drag;
int endX, endY; switch (iMsg) { case WM_CREATE : startX = oldX = 50; startY = oldY = 50; Drag = FALSE; return 0 ; case WM_PAINT : hdc = BeginPaint (hwnd, &ps) ; Ellipse(hdc, startX, startY, endX, endY); EndPaint (hwnd, &ps) ; case WM_LBUTTONDOWN : Drag = TRUE; break; case WM_LBUTTONUP : Rubber band를 이용하여 직선을 그리는 기법을 이용하여 rubber band를 이용한 원 그리기를 시작해 본다. 원을 그리는 방법도 직선을 그리는 방법과 동일하다. 마우스 왼쪽 버튼을 눌러서 원을 그리기 시작하고 드래그 도중에는 원의 모양이 마우스 커서 위치에 따라 계속 변하다가 마우스 왼쪽 버튼을 뗄 때 원이 비로소 완성된다. 직선에서와 마찬가지로 시작점이 있을 것이고 마우스가 움직임에 따라 종착점은 계속 변하게 된다. 원도 마찬가지로 지우고 그리는 과정이 반복된다. 직선을 그릴 때와 마찬가지로 원을 그리기 시작한 점은 (50,50)으로 가정하였다. 이 가정 좌표는 WM_CREATE메시지가 도착하였을 때 시작점 저장 변수인 startx,startY와 이전 점 저장 변수인 oldX, oldY에 저장하였다. 또한, 아직 드래그를 하고 있지 않기 때문에 BOOL변수 Drag에 FALSE를 저장하였다. WM_PAINT메시지 발생하였을 때는 화면인 디바이스컨텍스트를 얻어오고 시작점과 종착점을 기준으로 원을 그린다. 여기서 endX,endY변수에는 쓰레기 값이 들어 있기 때문에 이상한 원을 그릴 것이다. 그러나, 나중에 드래그 도중에 종착점이 마우스 현재 위치를 얻어오면 원은 (50,50)좌표와 마우스 현재 좌표를 기준으로 원을 그리게 될 것이다. 마우스 왼쪽 버튼을 누르면 드래그의 시작을 알리기 위해 Drag에 TRUE값을 저장하고 버튼을 떼면 FALSE를 저장한다. 20/211 20

21 고무줄 효과 원 그리기(계속) case WM_MOUSEMOVE: hdc = GetDC(hwnd); if (Drag) {
SetROP2(hdc, R2_XORPEN); SelectObject(hdc, (HPEN)GetStockObject(WHITE_PEN)); SelectObject(hdc, (HBRUSH)GetStockObject(HOLLOW_BRUSH)); endX = LOWORD(lParam); endY = HIWORD(lParam); Ellipse(hdc, startX, startY, oldX, oldY); //지우기 Ellipse(hdc, startX, startY, endX, endY); //그리기 oldX = endX; oldY = endY; } ReleaseDC(hwnd, hdc); break; 마우스를 움직이는 WM_MOUSEMOVE메시지가 발생하면 직선을 그릴 때와 마찬가지로 GetDC함수를 이용하여 디바이스컨텍스트를 얻어온다. 그리고, Drag값을 조사함으로 드래그 중인지 아닌지를 판단한다. 드래그 중이라면 래스터 연산을 XOR연산을 하도록 설정하고 하얀색펜을 등록해 준다. 이제 원을 그린다면 하얀색 펜과 하얀색브러쉬로 바탕색과 XOR연산을 한 후 그리게 될 것이다. 마우스 현재 좌표를 lParam으로 부터 구해서 endX, endY에 저장한다. 시작점인 (startX, startY)와 이전점인 (oldX, oldY)를 기준으로 원을 그린다. 처음 그릴 때는 두 좌표가 (50,50)으로 같기 때문에 화면에 나타나는 것은 없다. 그 다음 줄에 시작점인 (startX, startY)와 마우스 현재 점인 (endX, endY)를 기준으로 원을 그린다. 이때 하얀색 펜과 하얀색 브러쉬로 하얀 바탕에 XOR연산 후에 그리기 때문에 검은색 펜과 검은색 브러쉬로 그리는 효과를 얻게 된다. 그림을 그린 후 마우스 현재 좌표를 저장한 (endX, endY)의 값은 이전 점을 저장하는 변수 (oldX, oldY)에 저장한다. 그 다음에 계속 드래그를 한다면 WM_MOUSEMOVE메시지가 발생하고 Drag는 TRUE이다. 달라진 것은 endX, endY의 값이다. oldX, oldY의 값은 이전 endX, endY의 값과 같기 때문에 이전에 그려진 원 위에 다시 그리게 된다. 검은색 선, 검은색면인 원 위에 하얀색 선, 하얀색 면을 XOR로 그리게 되면 모두다 하얀색으로 되기 때문에 원은 지워지게 된다. 그리고 나서 하얀 바탕 위에 시작점 (startX,startY)에서 마우스 커서의 새로운 위치인 (endX, endY)까지 원을 그리고 이것이 결과적으로 남게 된다. 21/211 21

22 연습문제 1 실습제목 : 실습 3-1 이용 실습 내용 방향화살표를 활용하여 원 이동 시키기
중심이 (20,20)이고 반지름이 20인 원을 그린다. 방향화살표를 활용하여 원을 4방향으로 이동 시킨다. 이동 단위는 40씩 움직인다. 앞에서 다루었던 예제들을 기반으로 원이 윈도우 내에만 머물도록 하는 프로그램을 작성한다. 여기서도 4개의 방향화살표는 원을 40씩 이동하게 하는 제어기 역할을 한다. 기본적으로 원은 중심 좌표 (20,20), 반지름 20으로 출발한다. 방향 화살표는 원을 상, 하, 좌, 우 4방향으로 움직일 수 있다. 움직이는 단위는 40씩이다. 하지만 원은 절대로 윈도우 경계를 벗어나면 안 된다. 22/211 22

23 연습문제 2 실습제목 : 실습 3-1 확장 실습 내용 방향화살표를 활용하여 윈도우 안에서만 원 이동 시키기
다른 내용은 1번과 동일. 단, 원이 만들어진 윈도우의 밖으로 나가지 않게 한다. 앞에서 다루었던 예제들을 기반으로 원이 윈도우 내에만 머물도록 하는 프로그램을 작성한다. 여기서도 4개의 방향화살표는 원을 40씩 이동하게 하는 제어기 역할을 한다. 기본적으로 원은 중심 좌표 (20,20), 반지름 20으로 출발한다. 방향 화살표는 원을 상, 하, 좌, 우 4방향으로 움직일 수 있다. 움직이는 단위는 40씩이다. 하지만 원은 절대로 윈도우 경계를 벗어나면 안 된다. 23/211 23

24 연습문제 3 실습제목(실습 3-3 이용) 실습 내용 방향화살표를 활용하여 윈도우 안에서만 원을 자동으로 이동 시키기
중심이 (20,20)이고 반지름이 20인 원을 그린다. 엔터키를 한번 누르면 오른쪽으로 움직이기 시작한다. 다시 누르면 멈추고 또다시 누르면 움직인다. 방향화살표를 활용하여 원의 이동 방향을 변경하며 방향이 변경되어도 계속 움직인다. 방향이 설정되면 원은 40씩 움직인다. 원은 만들어진 윈도우의 밖으로 나가지 않는다. 앞에서 다루었던 예제들을 기반으로 원이 윈도우 내에만 머물면서 원이 계속 자동으로 움직이는 프로그램을 작성한다. 여기서 4개의 방향화살표는 원이 이동할 방향을 선택하는데 사용된다. 기본적으로 40씩 이동한다. 기본적으로 원은 중심 좌표는 (20,20)이고 반지름은 20이다. 프로그램이 시작하면 원은 화면에 나타나지만 움직임이 없다. 엔터키를 한번 누르면 원은 오른쪽 방향을 향해 자동으로 움직이기 시작한다. 엔터키를 다시 누르면 움직이던 원은 제자리에 멈춘다. 그러나, 또다시 엔터키를 누르면 원은 원래 움직이던 방향으로 계속 움직인다. 즉, 엔터키를 홀수 번째 누르면 원은 움직이고 짝수 번째 누르면 멈춘다. 원은 절대로 윈도우 경계를 벗어나면 안 된다. 24/211 24

25 연습문제 4 실습제목 실습 내용 선택한 원의 색상을 변경하기 중심이 (20,20)이고 반지름이 20인 원을 그리시오.
마우스 클릭으로 원이 선택되면 원의 색상을 변경하시오.(검정  흰색) 원의 색상이 변하는 것은 왼쪽 마우스버튼이 눌러진 동안만 변하게 하시오. 지금까지 배운 내용을 가지고 실습 2-7을 수행한다. 앞에서 보았던 예제는 마우스 왼쪽 버튼을 누름으로 선택한 원 주위에 사각형을 그려주는 것이었다. 여기서 해야 할 일은 선택된 원의 색상을 변경함으로 선택되었다는 것을 알리는 것이다. 먼저 좌표 (20,20)을 중심으로 반지름 20인 원을 그려준다. 마우스 왼쪽 버튼이 눌러지는 사건이 발생하면 눌러진 마우스 위치가 원의 내부인지를 검사하여 만약 원의 내부라면 Selection변수에 TRUE를 넣어준다. WM_PAINT메시지를 처리하는 부분에서는 Selection의 값을 보고 참이라면 새로운 펜과 브러쉬를 만들어 등록하고 원을 그려준다. 만약 거짓이라면 펜과 브러쉬를 만드는 일은 하지 않고 단지 원만 그린다. 마우스 왼쪽 버튼이 눌러 졌다가 떼어지면 Selection변수는 다시 FALSE로 돌아가게 된다. 25/211 25

26 연습문제 5 실습제목 실습 내용 선택한 원의 중심에서 마우스 커서까지 직선 그리기
중심이 (20,20)이고 반지름이 20인 원을 그리시오. 왼쪽 마우스버튼을 눌러서 원이 선택되면 원의 중심부터 드래그 중인 마우스의 커서까지 직선을 그리시오. 마우스버튼에서 손을 놓으면 직선은 사라지게 하시오. 이제 래스터 연산을 이용하여 rubber band그리는 것을 실습한다. 먼저 화면에 원을 그린다. 원의 좌표는 중심이 (20,20)이고 반지름은 20이다. 마우스의 왼쪽 버튼을 이용하여 원을 선택하여 드래그를 하면 원의 중심좌표인 (20,20)에서 드래그 중인 마우스 현재 좌표까지 직선을 그려준다. 마우스를 계속하여 드래그를 하면 (20,20)좌표부터 마우스 커서까지는 rubber band 직선을 그려준다. 그러나 드래그를 마치면 그려지던 직선은 화면에서 사라져야 한다. 26/211 26


Download ppt "3장. 제어 메시지 처리하기 1/211 1."

Similar presentations


Ads by Google