2장. 윈도우즈 입출력 1/211 1
내용 출력화면 얻기 텍스트 출력 키보드 메시지 처리 카렛(커서) 이용하기 직선, 원, 사각형, 다각형 그리기 2/211 2장에서는 1장에서 만들어본 기본 윈도우에 다양한 출력을 해 보는 과정을 배우게 된다. 윈도우에 글자를 출력하거나 그림을 그리기 위해서는 커널로부터 먼저 화면을 얻는 과정이 필요하다. 화면을 얻은 후에는 텍스트와 도형을 출력할 수 있다. 본 장에서는 텍스트 출력과 도형 출력을 위해 키보드와 마우스로부터 메시지를 받아서 처리하는 방법을 배운다. 2/211 2
디바이스 컨텍스트 DC 그래픽 관련한 선택정보를 모아 놓은 구조체 간단한 출력은 디폴트 사양 이용 세밀한 출력은 관련 선택정보(option) 변경 윈도우에서 출력장치에 무언가 출력하기 위해서는 반드시 DC가 필요 DC의 유형 화면출력을 위한 디스플레이 DC 프린터나 플로터 출력을 위한 프린터 DC 비트맵 출력을 위한 메모리 DC 디바이스 정보를 얻기 위한 정보 DC
디바이스 컨텍스트 DC를 얻고 해제하기 BeginPaint()와 EndPaint() GetDC()와 ReleaseDC() WM_PAINT 메시지 부분에서 사용 GetDC()와 ReleaseDC() 잠시 출력할 때 사용 CreateDC()와 DeleteDC() DC를 만들어서 사용 출력목적이 아니라 DC의 정보를 얻고자 할 때 사용
디바이스 컨텍스트 핸들 HDC HDC hdc; hdc 디바이스 컨텍스트 핸들이라고 부르고, 출력할 영역을 지정할 수 있는 타입 화면의 경우 윈도우로부터 얻어 옴 HDC hdc; hdc 변수는 출력할 영역을 얻어오면 얻어온 영역을 지정할 수 있음 출력하기 위하여 얻어온 화면 영역을 디바이스 컨텍스트라고 부른다. 얻어온 디바이스 컨텍스트는 변수에 저장해야 하는데 타입은 HDC이다. 엄밀히 말하면 HDC타입은 메모리 영역을 관리하며 메모리 영역에는 얻어온 화면 영역에 대한 여러 속성값을 저장할 수 있다. 만약 HDC타입의 변수를 hdc라고 선언하였다면 hdc는 어떤 영역을 얻어다 저장할 수 있게 된다. 화면을 얻어오는 함수를 이용하여 화면을 얻어 오면 그것을 hdc에 저장할 수 있다. hdc 5/211 5
출력화면 얻기 윈도우에 출력하기 위해서는 출력할 영역을 얻어 와야 한다. 기본적으로 얻을 수 있는 영역은 윈도우의 프레임내의 하얀 사각형 영역이다. 위의 그림에서는 검은색 테두리로 영역을 표시 하였다. 얻어온 출력화면에서 좌표는 좌측상단 점을 원점으로 하고 X축은 오른쪽 방향이고 Y축은 아래쪽 방향이다. 기본적으로 단위는 픽셀이다. 하지만 좌표계의 원점도 변경 가능하고 Y축의 방향도 아래방향이 아닌 위 방향으로 변경이 가능하다. 또한, 픽셀 단위가 아닌 다른 단위로 나타낼 수도 있다. 6/211 6
2-1 출력할 영역 얻어오기 HDC hdc ; PAINTSTRUCT ps ; switch (iMsg) { case WM_CREATE: break; case WM_PAINT: hdc = BeginPaint (hwnd, &ps) ; TextOut(hdc, 0,0,”HelloWorld”,10); EndPaint (hwnd, &ps) ; break; case WM_DESTROY: PostQuitMessage (0) ; } 위의 화면은 출력할 영역을 얻어온 다음 “HelloWorld”라는 문장을 화면에 출력한 예이다. 이상의 코드는 당연히 메시지 처리 함수인 WndProc에 들어가야 한다. HDC타입의 hdc변수를 만들어 놓고 PAINTSTRUCT타입의 구조체변수 ps를 만들어 놓는다. ps는 얻어오는 화면에 관한 다양한 정보를 얻어오는데 사용하는 변수인데 여기서는 특별히 사용하지 않기 때문에 설명은 생략한다. 앞에서 설명했다시피 다양한 메시지가 WndProc에게 전달된다. 먼저 오는 메시지는 윈도우가 만들어 졌을 때 오는 메시지로 WM_CREATE이다. 위의 코드에서는 WM_CREATE에 대해 아무것도 하지 않고 switch문을 끝내고 있다. 그리고 윈도우가 화면에 등장하며 화면영역을 나타내기 위해 WM_PAINT가 오게 된다. 이때 화면을 얻어오고 TextOut을 이용하여 화면인 hdc에 “HelloWorld”를 출력한다. 출력을 마친 이후에는 화면영역 출력이 마쳤음을 알려야 한다. 마지막으로 윈도우의 닫기 버튼을 누르거나 종료를 시키면 WM_DESTROY가 오게 된다. 위의 코드에서는 PostQuitMessage(0)를 수행하고 있는데 이것은 WinMain의 while문 내에 있는 GetMessage함수가 0의 값을 리턴 하게 한다. 즉, while문이 거짓이 되기 때문에 루프는 종료되게 되고 WinMain은 끝이 나게 된다. 이것은 전체 프로그램을 종료 시킨다. 7/211 7
실행결과 위의 그림은 앞장 코드의 실행 결과이다. 화면영역의 좌측상단에 HelloWorld가 출력 되었다. 8/211 8
헝거리언 표기법 Hungarian Notation의 배경 및 개념 변수명 생성 시 사용되는 접두어 마이크로소프트사의 프로그래머가 오류를 줄이기 위해 사용
한 점 기준 텍스트 출력 함수 BOOL TextOut( HDC hdc, int x, // 좌표 int y, // 좌표 LPCTSTR lpString, // 출력 문자열 int nLength // 문자열 길이 ); 텍스트를 출력하기 위한 가장 간단한 함수는 TextOut이다. TextOut 함수에 전달해야 하는 파라메터는 다음과 같다. HDC hdc: BeginPaint나 GetDC를 통해 얻어온 화면영역을 말한다. int x, y: 텍스트를 출력할 좌표의 x값과 y값을 말한다. LPCTSTR lpString: 출력할 텍스트를 스트링 값으로 쓴다. int nLength: 출력할 텍스트의 길이를 말한다. 예를 들어, TextOut(hdc, 0, 0, “HELLOWORLD”, 10);은 화면 (0,0)위치에 HelloWorld를 출력한다. 출력하는 스트링의 길이는 10이다. 10/211 10
박스영역에 텍스트 출력 함수 int DrawText( HDC hdc, LPCSTR lpString, int nLength, LPRECT lpRect, // 문자열 사각형 구조체 UINT Flags ); DrawText함수는 TextOut과 마찬가지로 텍스트를 화면에 출력하는 함수이지만 한 점의 좌표를 주고 출력하게 하는 것이 아니라 박스영역을 지정하고 그 안에 출력하게 한다. 여기서 사용된 파라메터들은 TextOut의 파라메터들과 유사하지만 차이점은 영역 좌표를 전달하기 위한 파라메터가 있다는 것과 그 영역에서 어느 위치에 출력할 것인지를 알리는 플래그 값이 추가로 있다는 것이 다르다. HDC hdc: 화면영역을 가리키는 변수이다. LPCSTR lpString: 출력할 텍스트를 스트링으로 나타낸 것이다. int nLength: 출력할 스트링의 길이이다. LRRECT lpRect: 스트링을 출력할 영역으로 박스영역 좌표이다. UNIT Flags: 영역 내에 어느 위치에 어떻게 출력할지 알려주기 위한 플래그 값으로 미리 정의된 상수를 이용한다. 11/211 11
박스영역 typedef struct tagRECT { LONG left; // x1 LONG top; // y1 LONG right; // x2 LONG bottom; // y2 } RECT; (x1, y1) (x2, y2) 박스 영역을 지정하기 위해 사용하는 구조체 타입으로 RECT가 있다. 이것은 수평 수직이 되는 직사각형 모양으로 위의 그림과 같다. 사각형 모양의 좌표를 저장하기 위해서는 마주보는 2개 좌표가 필요하다. RECT에서는 좌측상단 꼭지점과 우측하단 꼭지점의 좌표를 이용하여 RECT 변수를 만든다. 따라서, RECT구조체 안에는 4개의 정수 필드인 left, top, right, bottom이 있다. x1의 값은 박스영역에서 가장 왼쪽 경계 값을 나타내기 때문에 left에 저장하고 y1은 박스영역에서 가장 위쪽 경계를 나타내기 때문에 top에 저장한다. 마찬가지로 right에는 x2를 bottom에는 y2를 저장한다. 12/211 12
2-2 DrawText() 사용 예 HDC hdc ; PAINTSTRUCT ps ; RECT rect; switch (iMsg) { case WM_CREATE : break ; case WM_PAINT : hdc = BeginPaint (hwnd, &ps) ; rect.left = 50; // 사각형 정의 rect.top = 70; rect.right = 500; rect.bottom = 400; DrawText(hdc,"HelloWorld",10,&rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER); // 한 라인, 수직/수평 중앙 EndPaint (hwnd, &ps) ; 다음은 DrawText함수를 이용하여 HelloWorld를 출력한 예이다. 마찬가지로 위의 코드도 메시지 처리 함수인 WndProc에 있는 것이다. hdc와 ps 변수는 앞에서 설명한 것과 동일하다. rect변수는 RECT 구조체로 텍스트 출력 영역 지정에 사용된다. 먼저 오는 메시지는 앞의 예와 마찬 가지로 WM_CREATE이다. 위의 코드에서도 break문에 의해 switch문을 빠져나가 WndProc을 마치고 있다. 그 다음 WM_PAINT가 오면 BeginPaint함수에 의해 화면영역을 얻어오고 있다. 그리고 rect변수의 left, top, right, bottom 필드에 50, 70, 500, 400 값을 부여 하였다. 그 다음 DrawText를 이용하여 rect영역에 HelloWorld를 출력하고 있다. 출력할 때 사용하는 플래그의 의미는 다음과 같다. DT_SINGLELINE: 박스 영역 안에 한 줄로 출력 DT_CENTER: 박스 영역 내에서 가운데 정렬 DT_VCENTER: 박스 영역의 상하에서 가운데 출력 그리고 나서 EndPaint를 이용하여 출력을 마쳤음을 알린다. 13/211 13
실행화면 설명 앞의 DrawText를 이용한 예를 실행하면 위의 그림과 같이 나온다. 여기서 주의 할 점은 위의 사각형 박스는 박스 영역을 표시해 주기 위해 그린 것일 뿐 실제 출력에서는 나오지 않는다. 설명한대로 박스 내에 한 줄로 정 가운데 텍스트가 출력되고 있음을 확인할 수 있다. 14/211 14
키보드 메시지 WM_KEYDOWN: 키보드에서 키를 눌렀을 때 발생하는 메시지 WM_KEYUP: 키보드에서 키를 눌렀다가 띄어지면 발생하는 메시지 WM_CHAR: 문자 키를 눌렀을 때 발생하는 메시지 지금까지는 지정된 스트링을 화면에 일방적으로 출력하는 방법을 배웠다. 여기서는 키보드를 통하여 입력되는 정보를 받는 방법을 배운다. 먼저 키보드에 의해 발생되는 주요 메시지를 살펴본다. WM_KEYDOWN은 키보드의 아무 키나 눌렀을 때 발생하는 메시지이다. 두 번째로 WM_CHAR은 키보드의 키들 중 문자 키를 눌렀을 때 발생하는 메시지이다. 그러면 ‘a’키를 누르면 어떤 메시지가 발생할 것인가? 그것은 먼저 WM_KEYDOWN이 발생하고 그 이후 바로 WM_CHAR가 발생하는 형태이다. 마지막으로 WM_KEYUP은 키에서 손을 띌 때 발생하는 메시지이다. 이외에도 여러 메시지가 있지만 주로 3가지 메시지를 많이 사용한다. 15/211 15
2-3 키보드 눌렀을 때 출력처리 HDC hdc ; PAINTSTRUCT ps ; switch (iMsg) { case WM_CREATE: break; case WM_KEYDOWN: // 어떤 키라도 누르면 처리 hdc = GetDC(hwnd); TextOut(hdc, 0,0,”HelloWorld”,10); ReleaseDC(hwnd,hdc); break; case WM_DESTROY: PostQuitMessage (0) ; } 위에 보이는 코드는 WM_KEYDOWN 메시지를 처리하는 코드를 작성한 예이다. 다른 부분은 똑 같고 WM_KEYDOWN 메시지를 처리하기 위한 case문만 다르기 때문에 그 부분만 설명한다. 여기서는 화면을 얻어오기 위해 GetDC함수를 이용하고 있다. 앞서 WM_PAINT메시지 처리하는 case문에서는 BeginPaint를 이용하여 HDC를 얻어오고 EndPaint를 이용하여 출력을 마쳤다. 하지만 WM_PAINT가 아닌 다른 메시지에서는 GetDC함수를 이용하여 HDC를 얻어와야 한다. 그리고 출력을 마칠 때는 ReleaseDC함수를 이용한다. 사용하는 함수는 다르더라도 기능은 같기 때문에 같다고 봐도 된다. 단지, 윈도우가 다른 윈도우에 의해 가렸다가 치우거나 최소화되었다가 원상복귀 되면 WM_PAINT메시지 처리하는 case문에서 출력한 내용들은 계속 볼 수 있지만 다른 곳에서 출력한 것들은 모두 사라지고 만다. 위의 예에서는 키보드를 눌렀을 때 hdc에 화면을 얻어오고 화면의 (0,0)좌표에 “HelloWorld”를 출력하고 있다. 이것은 키보드 상의 어떤 키를 눌렀을 때나 동일하다. 여기서는 눌러진 키가 어떤 문자 키인지를 구분하지는 않는다. 어떤 키를 누르던지 동일하게 “HelloWorld”를 출력할 뿐이다. 16/211 16
입력된 한 문자 출력 HDC hdc ; PAINTSTRUCT ps ; char str[100]; switch (iMsg) { case WM_CREATE : return 0 ; case WM_CHAR: hdc = GetDC(hwnd); str[0] = wParam; // 입력문자 : : WinProc의 매개변수로 들어 옴 str[1] = '\0'; TextOut(hdc,0,0,str,strlen(str)); ReleaseDC(hwnd,hdc); break; case WM_DESTROY : PostQuitMessage (0) ; } 여기서는 키보드를 눌렀을 때 눌러진 키의 문자를 알아내서 그 문자를 화면에 출력하는 예이다. 앞장의 코드와 달리 여기서는 WM_CHAR메시지를 처리하는 case문이 있음을 알 수 있다. 이미 말한 것처럼 WM_CHAR는 문자 키를 누르면 발생하는 메시지이다. 키보드 상에서 아무 키가 아닌 문자 키를 눌렀을 때 hdc에 화면을 얻어온다. 그리고 미리 선언해 둔 문자배열 str에 입력된 문자를 저장하여 스트링을 만든다. 코드를 보고 짐작할 수 있겠지만 키를 눌렀을 때 키의 코드 값은 wParam에 저장되어 온다. 즉, WM_CHAR메시지의 번호는 iMsg에 저장되고 문자의 코드 값은 wParam에 저장되어 오는 것이다. ‘a’라는 문자 키를 눌렀다면 배열 str에는 “a”라는 스트링이 들어가게 된다. 따라서 화면의 (0,0)좌표에는 “a”가 출력되게 된다. 앞장에서 말한 것처럼 이상에서 출력된 내용도 윈도우가 다른 윈도우에 의해 가렸다가 나타나거나 최소화 되었다가 원상복귀 되면 출력되었던 내용들은 사라지게 된다. 그 이유는 이런 일이 발생할 때마다 화면을 깨끗이 지우고 WM_PAINT 메시지를 발생시키기 때문이다. 그리고, 여기서는 눌러진 문자만 보여줄 뿐 그 전에 입력된 문자는 사라지게 된다. 다음에 입력된 문자를 연속해서 보여주는 예를 설명한다. 17/211 17
wParam/lParam Switch(message) { case WM_KEYDOWN: // 키를 누름 Switch(wParam) // 어느 키인가의 키 값을 가짐 case VK_LEFT: // 왼쪽 키 … ; case WM_TIMER: // 타이머가 하나 종료됨 : 여러 개의 타이머가 가능 if(wParam == 1) // 종료된 타이머의 ID는 ? case WM_LBUTTONDOWN: // 마우스 좌측 버튼을 누름 pt.x = LOWARD(lParam); // 마우스 누른 좌표의 x값 pt.y = HIWORD(lParam); // 마우스 누른 좌표의 y값
2-5 입력된 문자열 출력 HDC hdc ; PAINTSTRUCT ps ; static char str[100]; static int count; switch (iMsg) { case WM_CREATE : count = 0; return 0 ; case WM_CHAR: hdc = GetDC(hwnd); str[count++] = wParam; str[count] = '\0'; TextOut(hdc,0,0,str,strlen(str)); ReleaseDC(hwnd,hdc); break; case WM_DESTROY : PostQuitMessage (0) ; } 앞장에서는 키보드를 통해 입력된 하나의 문자만을 보여 주었다. 여기서는 입력되는 문자들을 계속하여 저장하고 그 모든 것을 화면에 출력함으로 간단한 텍스트 입력 프로그램을 구현해 보았다. 앞의 예와 달리 여기서는 str 문자배열 변수와 count 정수변수를 static으로 선언하였다. 그 이유는 메시지 발생시에 저장했던 내용들을 다음 메시지 발생시에도 계속해서 유지하기 위해서이다. 하나의 메시지가 와서 그 메시지를 처리하고 나면 그 메시지 때문에 저장했던 내용들은 static변수나 전역변수에 저장하지 않는 한 모두 잃어 버리게 된다. 이것은 메시지 처리가 끝나면 WndProc함수가 종결되기 때문이다. 윈도우가 처음 만들어졌을 때 count변수를 0으로 초기화 해 주고 있다.count변수는 입력된 문자의 개수를 알려주는 용도로 사용하게 된다. 문자 키가 눌러질 때마다 WM_CHAR메시지가 발생할 것이고 입력된 문자는 wParam에 저장되어 온다. 이것을 str[count]에 저장하고 count변수를 증가시켜 준다. 그리고 나서 (0,0)좌표에 str을 출력해 준다. 이 예제를 실행시켜보면 화면 좌측 상단에서부터 입력된 문자가 순서대로 나오는 것을 볼 수 있을 것이다. 이것은 윈도우의 상단에만 출력될 분 엔터키나 백스페이스 키 등과 같은 컨트롤키 입력은 작동하지 않음을 알 수 있다. 19/211 19
문제점 다른 윈도우에 의해 가렸다가 나타나면 출력되었던 내용이 사라짐 Enter, Backspace 등과 같은 문자 처리 불가 원인: WM_PAINT 발생 해결방법: WM_PAINT 처리하는 case를 만들어야 함 Enter, Backspace 등과 같은 문자 처리 불가 가상키 사용하여 처리 필요 지금까지 실습해 본 예제는 기본적인 기능은 하지만 몇 가지 문제점이 있었다. 가장 시급한 두 가지 문제만 살펴보면 다음과 같다. 첫 번째는 다른 윈도우에 의해 우리 윈도우가 가려졌다가 치워지거나 우리 윈도우를 최소화 했다가 원상복귀 시키거나 하면 윈도우에 출력된 모든 내용이 사라진다는 것이다. 이것의 원인은 이와 같은 상황에서 WM_PAINT메시지가 발생한다는 것이다. WM_PAINT가 발생하면 윈도우의 모든 내용은 지워지며 WndProc함수의 WM_PAINT메시지를 처리하는 case문에서 출력하는 것만 화면에 남게 된다. 따라서, 출력되었던 내용들이 계속해서 화면에 남길 원한다면 출력을 WM_PAINT에서 해야 한다. 두 번째로 엔터키나 백스페이스와 같은 콘트롤 문자에 대한 처리가 안되고 있다. 물론, 이 외에도 탭도 안되고 Delete키도 사용할 수 없다. 하지만 가장 시급한 것은 백스페이스와 엔터키이다. 이것은 엔터키나 백스페이스 키를 눌렀을 때 두 키에 해당하는 문자코드 값이 wParam에 저장되고 이것은 단지 하나의 문자로 str에 저장되기 때문이다. TextOut이나 DrawText는 자신이 출력하는 str 배열 내에 엔터문자나 백스페이스문자가 있어도 달리 처리하지 않고 그냥 출력할 뿐이다. 20/211 20
2-6 첫 번째 문제 해결 switch (iMsg) { case WM_CREATE : count = 0; return 0 ; case WM_PAINT: // 화면이 가렸다 보일 때(자동 메시지 생성), 다시 그림 hdc = BeginPaint(hwnd, &ps); TextOut(hdc,0,0,str,strlen(str)); EndPaint(hwnd, &ps); break; case WM_CHAR: hdc = GetDC(hwnd); str[count++] = wParam; str[count] = '\0'; TextOut(hdc,0,0,str,strlen(str)); // 차후 개선 : 그리는 대신 WM_PAINT 발생 ReleaseDC(hwnd, hdc); 첫 번째 WM_PAINT메시지 발생으로 인한 화면 삭제 문제를 해결한 예이다. 앞의 예제와 비슷하지만 여기서 추가된 것은 WM_PAINT메시지를 처리하는 case문이다. 즉, WM_PAINT메시지가 발생해서 윈도우의 내용이 전부 사라졌다면 (0,0)좌표에 str에 저장된 스트링을 출력해 주는 것이다. str 배열변수 같은 경우는 static으로 선언 되어 있기 때문에 이전에 저장되었던 모든 내용이 그대로 유지되고 있게 된다. 다시 한번 더 말하지만 WM_PAINT메시지 발생시에는 화면을 얻어오기 위해 BeginPaint를 사용해야 하고 다른 메시지에서는 GetDC함수를 이용해야 한다. 반대로 화면출력을 마쳤을 때는 각각 EndPaint와 ReleaseDC함수를 이용하여 알린다. 여기 예제에서는 텍스를 출력하는 부분이 두 곳이다. 만약 두 개의 TextOut을 하나로 모을 수 있을까 ? 대답은 “할 수 있다”이다. 다음 장에서는 입력되는 문자를 저장하는 곳과 출력하는 곳을 나눈 예를 보여 주도록 하겠다. 21/211 21
2-7 문자키 입력 시 WM_PAINT 강제 발생 switch (iMsg) { case WM_CREATE : count = 0; return 0 ; case WM_PAINT: hdc = BeginPaint(hwnd, &ps); TextOut(hdc,0,0,str,strlen(str)); EndPaint(hwnd, &ps); break; case WM_CHAR: str[count++] = wParam; str[count] = '\0'; InvalidateRgn(hwnd, NULL, TRUE); // 다시 그리는 대신에, 강제 처리 위의 코드에서 WM_CHAR메시지 처리 부분에서는 입력된 문자를 str배열에 저장하는 역할을 담당하고 저장된 내용을 출력하는 것은 WM_APINT메시지 처리 부분에서 담당한다. 그러면 문자가 하나 들어 올 때마다 str에 저장한 후 출력하도록 해야 한다. 이를 위해 위에서는 InvalidateRgn함수를 이용하였다. 이 함수는 화면을 다시 그리도록 하는 WM_PAINT메시지를 강제 발생시킨다. 즉, 다른 윈도우가 우리 윈도우를 가렸다가 치워지는 효과를 강제로 내는 것이다. InvalidateRgn을 만나면 WM_PAINT메시지 처리 부분을 수행하게 하는 것과 동일한 효과를 가진다. 하지만, 이 방법도 문제를 가지고 있다. 화면을 강제로 지우기 때문에 출력되는 내용이 많을 경우 화면이 깜박거리는 현상을 볼 수 있게 될 것이다. 이것은 이후에 비트맵을 배우면 더블버퍼링이라는 기법으로 해결될 수 있음을 알게 된다. 22/211 22
두 번째 문제 해결 화면에 표시되지 않는 제어문자는 가상키 테이블 이용 가상키 내용 VK_CANCEL Ctrl+Break 내용 VK_CANCEL Ctrl+Break VK_END End VK_BACK Backspace VK_HOME Home VK_TAB Tab VK_LEFT 좌측 화살표 VK_RETURN Enter VK_UP 위쪽 화살표 VK_SHIFT Shift VK_RIGHT 우측 화살표 VK_CONTROL Ct기 VK_DOWN 아래쪽 화살표 VK_MENU Alt VK_INSERT Insert VK_CAPITAL Caps Lock VK_DELETE Delete VK_ESCAPE Esc VK_F1 ~ VKF10 F1-F10 VK_SPACE Space VK_NUMLOCK Num Lock VK_PRIOR Page Up VK_SCROLL Scroll Lock VK_NEXT Page Down 여기서는 두 번째 문제에 대한 해결 방법을 배운다. 두 번째 문제는 백스페이스 키나 엔터키 또는 방향키와 같이 많이 사용되지만 문자 키가 아닌 콘트롤용 키에 대한 처리 문제이다. 이들 키에 대한 문자는 볼 수 있도록 표시할 수 없기 때문에 가상 키라는 상수로 처리를 한다. 위의 표를 보면 가상 키에 해당하는 매크로와 내용을 볼 수 있다. 각 내용에 해당하는 키 입력이 있는지 확인하는 방법은 WM_KEYDOWN 메시지 또는 WM_CHAR 메시지가 올 때 wParam에 저장되어 오는 내용이 가상 키와 같은 값인지 비교하는 방법을 사용한다. 예를 들어, 백스페이스 키를 누르면 wParam의 내용은 VK_BACK과 같은 내용이다. 따라서, 가상키 처리는 WM_CHAR나 WM_KEYDOWN 메시지 처리하는 case문에서 체크하기 원하는 가상 키와 wParam이 같은지 비교하는 방식을 사용한다. 23/211 23
2-8 Backspace 입력 처리 static char str[100]; static count; switch (iMsg) { case WM_CREATE : count = 0; return 0 ; case WM_PAINT: hdc = BeginPaint(hwnd, &ps); TextOut(hdc,0,0,str,strlen(str)); EndPaint(hwnd, &ps); break; case WM_CHAR: if (wParam == VK_BACK) count--; else str[count++] = wParam; str[count] = '\0'; InvalidateRgn(hwnd, NULL, TRUE); 백스페이스 키 입력이 들어 왔을 때 마지막에 입력된 글자를 삭제하는 예를 프로그램으로 작성한 것이다. 앞에서 보았던 입력된 문자열을 출력하는 프로그램과 기능은 똑 같고 단지 WM_CHAR 메시지 처리 case문에 wParam 내용과 VK_BACK이 같은지 체크하는 부분이 추가 되었다. 만약 입력된 문자가 VK_BACK이라면 count값을 하나 감소 시키고 있다. 이것은 마지막에 들어온 문자가 저장된 곳을 가리키므로 연이어 나오는 str[count] = ‘\0’;에 의해 마지막에 입력된 글자가 삭제 되게 된다. 그리고 나서 InvalidateRgn함수를 호출하고 이것은 화면을 삭제하며 문자배열 str의 새로운 내용을 출력하게 된다. 24/211 24
Caret 만들기(커서) Caret: 키보드 입력 시 깜박거리는 커서 Caret 만들고 보이기 Caret의 위치 설정하기 CreateCaret(hwnd,NULL,width,height); ShowCaret(hwnd); Caret의 위치 설정하기 SetCaretPos(x,y); Caret 감추기 HideCaret(hwnd); Caret 삭제하기 DestroyCaret(); width height 지금까지는 문자가 입력될 때 caret이 나타나지 않았다. Caret은 메모장과 같은 워드 프로세서에서 글자가 입력될 위치에 깜박이는 커서이다. Caret이 있으면 어느 위치에 문자가 입력되고 있는지를 알 수 있게 해 준다. Caret을 만드는 함수는 CreateCaret이다. 함수의 첫 번째 인자는 caret이 나타날 윈도우의 핸들 값이다. 이것은 WinMain에서 만들어진 윈도우의 핸들을 말하는 것으로 메시지가 올 때 WndProc에 값이 전달 된다. 두 번째 인자는 Caret의 내부를 칠할 내용에 관한 것이 들어 간다. 비트맵으로 칠하기 원하면 비트맵핸들을 넣어주면 되고 NULL을 넣으면 그림에서와 같이 검은색으로 칠한다. 세 번째와 네 번째 값은 정수로 caret의 폭과 높이 값이다. Caret의 위치를 설정하기 위해서 사용하는 함수는 SetCaretPos로서 인자는 Caret이 위치할 좌표이다. 윈도우의 포거스가 다른 윈도우로 넘어가 caret을 감추어야 할 때는 HideCaret함수를 이용한다. 마지막으로 프로그램이 종료될 때는 만들었던 Caret을 삭제해 준다. 25/211 25
Caret 위치 정하기 Abijc 문자열 “Abijc”를 굴림체로 출력하고 ‘c’뒤에 caret 위치를 정한다고 가정 X의 길이를 알아야 caret 위치 정함 예를 들어 문자열 출력 위치가 (100,200)이라고 하면 caret의 위치는 (100+x, 200)이다. A b i j c 문자열을 저장하고 있는 문자 배열 화면상에 출력 Abijc x y 문자열을 출력하고 특정 문자 뒤에 caret을 출력하기 원할 때 어떻게 해야 하는지를 살펴보도록 한다. 문자열 “Abijc”를 특정 크기의 굴림체로 출력하고 문자 ‘c’뒤에 caret을 출력한다고 가정하자. 문자열을 저장하고 있는 배열변수의 모양을 그림으로 도식화 하면 위와 같아진다. 각 공간마다 하나의 문자가 저장되는 형태이다. 이 문자열을 화면상에 출력하면 각 문자가 차지하는 공간은 서로 다르다. 위의 그림에서 보는 바와 같이 대문자일 경우에는 차지하는 영역이 크다. 소문자일지라도 문자의 종류에 따라 서로 다름을 알 수 있다. 그렇기 때문에 특정 문자 뒤에 caret을 출력할 때 문자가 몇 번째 문자인가만을 가지고 위치를 계산하는 것은 틀릴 수 있다. 화면상에서의 실제 길이를 측정해야 한다. 위의 예에서는 “Abijc”라는 문자열을 특정크기의 굴림체로 출력한다고 가정하면 높이가 y픽셀, 폭이 x픽셀 차지한다. 만약 이 문자열을 화면상에 (100,200)좌표에 출력한다면 문자 ‘c’뒤의 위치는 (100+x, 200)이다. 이곳에 caret을 출력하면 caret은 문자 ‘c’뒤에서 깜박이게 된다. 26/211 26
출력될 문자열 폭 구하기 BOOL GetTextExtentPoint( HDC hdc, LPCTSTR lpString, int cbString, LPSIZE lpSize // 문자열의 크기(폭, 높이) ); LPSIZE SIZE *; struct tagSIZE { // 문자열의 폭과 높이 저장 LONG cx; LONG cy; } SIZE; 그러면, Caret의 위치를 얻기 위해서 문자열이 차지하는 크기를 측정할 수 있어야 한다. 이때 사용하는 함수가 GetTextExtentPoint이다. 이 함수를 이용하면 현재 화면에 설정된 폰트로 주어진 스트링을 측정하여 스트링이 화면에 차지하는 공간이 얼마나 큰지를 측정할 수 있다. 함수의 첫 번째 인자는 Caret을 출력할 화면이다. 화면을 함수에게 알리는 이유는 화면에 현재 설정된 폰트와 폰트의 크기 정보가 저장되어 있기 때문이다. 두 번째 인자는 측정하기 원하는 스트링이다. 지난번에 말한 것처럼 LPCTSTR은 char *와 같기 때문에 문자배열의 시작주소나 문자열 상수를 쓸 수 있다. 세 번째 인자는 정수 값으로 문자열의 몇 번째 문자까지 크기를 측정하기 원하는지를 알리는 값이다. 문자열을 모두 측정하기 원할 수도 있지만 앞에서 부터 일부만 측정하기 원할 수도 있기 때문에 이 정수 값을 이용한다. 값이 2이면 앞에서부터 두 번째 문자까지 크기를 측정하는 것이다. 측정된 값은 문자열의 폭과 높이를 알리는 두 개의 정수 값이다. 이들 정수 값을 받아오기 위해 여기서는 SIZE하는 구조체를 이용한다. 구조체에는 cx와 cy라는 정수 필드가 있고 구조체 변수의 주소를 함수의 네 번째 인자로 주게 된다. 위에서 LPSIZE와 SIZE *와는 같은 의미이다. 27/211 27
“Abijc” 폭 구하기 Abijc SIZE size; GetTextExtentPoint(hdc,"Abijc",5,&size); TextOut(hdc, x,y,”Abijc”,5); SetCaretPos(x+size.cx,y); (0,0) x y Abijc size.cx size.cy 위의 그림은 문자열 “Abijc”을 출력하고 문자 ‘c’뒤에 caret을 출력하는 예이다. 먼저 SIZE 구조체 변수 size를 선언하였다. 이 변수에 문자열의 폭과 높이 정보가 저장되게 된다. GetTextExtentPoint 함수에게 “Abijc”를 주고 5 값을 준다. 그러면, 문자의 폭 값을 size.cx에 저장하고 높이를 size.cy에 저장하여 돌아온다. TextOut을 이용하여 화면의 (x,y) 좌표에 스트링을 출력한다. 문자 ‘c’의 바로 뒤는 좌표가 (x+size.cs, y)가 되게 된다. 그림에서 보는 바와 같이 y좌표는 아래로 내려간 것이 아니기 때문에 변함이 없고 x좌표만 스트링의 폭만큼 오른쪽으로 이동한 것이다. 이제 그 위치에 Caret을 그려주기만 하면 된다. 28/211 28
Caret 표시 static char str[100]; static count; static SIZE size; switch (iMsg) { case WM_CREATE : CreateCaret(hwnd, NULL, 5, 15); ShowCaret(hwnd); // 빈 화면에 캐럿 표시 count = 0; return 0 ; case WM_PAINT: hdc = BeginPaint(hwnd, &ps); GetTextExtentPoint(hdc, str, strlen(str), &size); TextOut(hdc,0,0,str,strlen(str)); SetCaretPos(size.cx, 0); EndPaint(hwnd, &ps); break; 앞장에서 배운 내용을 기반으로 한 줄짜리 텍스트를 입력할 수 있는 프로그램에 caret을 넣었다. 위의 프로그램에서 WM_CHAR 메시지 처리하는 case문이 누락되었는데 다음 장에 나와 있다. 위의 프로그램에서는 WM_CREATE메시지를 처리하는 case문에서 제일 먼저 Caret을 만들고 있다. Caret의 크기는 폭이 5픽셀, 높이가 15픽셀이다. 좀 더 정확하게 하기 위해서는 화면에 설정된 폰트 정보를 얻어와서 Caret의 크기를 정해야 한다. 이 과정을 여기서 다루면 좀 더 복잡해 지기 때문에 생략하도록 하겠다. 위의 프로그램을 실행시켰을 때 글자 크기보다 Caret이 너무 작으면 수치를 크게 함으로 조정해 주면 된다. 그리고 나서, 정수변수 count를 초기화 해 준다. WM_PAINT 메시지 처리 case문에서는 str에 저장된 내용을 출력하고 Caret을 출력된 내용 뒤에 그려주고 있다. 처음에는 str배열의 내용이 아무것도 없기 때문에 Caret만이 윈도우 왼쪽 상단 모서리에서 깜박거리게 된다. 문자가 입력됨에 따라 WM_CHAR메시지가 발생하고 해당 case문에서 str문자 배열에 입력되어 들어온 문자를 저장하게 된다. 그리고 나서 WM_PAINT 메시지를 강제 발생시켜 str내용을 출력하고 Caret의 위치를 변경시킨다. 29/211 29
Caret 표시(계속) case WM_CHAR: if (wParam == VK_BACK) count--; else str[count++] = wParam; str[count] = '\0'; InvalidateRgn(hwnd, NULL, TRUE); break; case WM_DESTROY : HideCaret(hwnd); DestroyCaret(); PostQuitMessage (0) ; return 0 ; } 키보드 입력을 통해 문자들이 입력되면 WM_CHAR 메시지가 도착하게 된다. 먼저 입력된 문자가 백스페이스인지를 검사한다. 만약 백스페이스라면 count변수를 감소시켜주고 그렇지 않다면 입력된 문자를 str배열 끝에 넣어준다. 그리고 나서 스트링의 끝을 알리는 NULL을 넣어주고 InvalidateRgn을 호출한다. 이것으로 인해 WM_PAINT메시지가 발생하게 된다. 마지막으로 윈도우가 종료되면 WM_DESTROY메시지가 오게 되고 만들었던 Caret을 삭제해 준다. 앞에서 설명한 것과 같이 PostQuitMessage(0)는 WinMain함수의 GetMessage함수가 0의 값을 강제로 리턴 하게 만들어 준다. 이것은 while반복문을 멈추게 하고 프로그램은 종료되게 된다. 30/211 30
직선 그리기 직선의 시작점으로 이동하기 BOOL MoveToEx( HDC hdc, int X1, int Y1, LPPOINT lpPoint // 이전의 좌표, 사용안함 ); 직선의 종착점까지 직선 그리기 BOOL LineTo( int X2, int Y2 (X1, Y1) (X2, Y2) 직선은 두 점과 그 사이에 연결된 선분으로 이루어 진다. 따라서 화면에 직선을 그리기 위해서는 시작점과 끝점의 좌표를 알려야 한다. Win32 SDK를 이용하여 직선을 그리는 방법은 두 단계를 이용한다. 첫 번째 단계는 시작점으로 포인트를 옮기는 것이다. 이 과정에서 가시적인 효과는 없다. 이때 사용하는 함수가 MoveToEx()이다. 함수의 첫 번째 파라메터는 화면영역을 가리키는 hdc이고 두 번째와 세 번째 파라메터는 정수 값으로 옮겨갈 위치의 좌표 값이다. 네 번째 파라메터는 LPPOINT 타입으로 POINT *와 같다고 보면 된다. POINT타입은 한 점의 좌표를 저장할 수 있는 구조체타입으로 필드는 정수로 x, y 두 개가 존재한다. 그러므로, MoveToEx함수의 네 번째 파라메터는 한 점 좌표를 저장 할 수 있는 POINT 변수의 주소 값이다. 이렇게 주어진 변수에는 이전에 포인트가 위치했던 좌표를 저장해 주게 되는데 프로그램에서는 거이 사용하지 않는다. 따라서, 네 번째 파라메터에는 NULL값을 넣어줘도 무방하다. 두 번째 단계는 직선을 그리는 것이다. 이것은 현재 위치부터 지정된 점까지 직선을 그리게 한다. 이 때 사용하는 함수 LineTo()는 3개의 파라메터를 가진다. 첫 번째는 화면영역을 가리키는 hdc이고 두 번째와 세 번째 파라메터는 종착점 좌표이다. 31/211 31
원 그리기 두 점의 좌표를 기준으로 만들어진 가상의 사각형에 내접하는 원을 그림 BOOL Ellipse( HDC hdc, int X1, int Y1, int X2, int Y2 ); (X1, Y1) (X2, Y2) 원은 통상적으로 중심과 반지름으로 구성된다. 하지만 반지름이 있는 원은 타원을 배제한 동그란 원만을 포함한다. 여기서는 타원까지 그릴 수 있도록 원 그리는 함수를 제공하고 있다. 원을 그리는 함수는 Ellipse()함수이다. 이 함수에게는 기본적으로 두 개의 좌표 값을 제공해 주어야 한다. 두 개의 좌표를 기준으로 가상의 사각형이 그려지고 실제 나타나는 원은 그 사각형에 내접하는 형태의 원이다. 가상의 사각형의 모습이 정사각형이라면 원은 동그란 정원이 될 것이다. 반면에 가상의 사각형이 정사각형이 아닌 직사각형이라면 원은 타원 모습으로 나타날 것이다. 직선을 그리거나 텍스트 출력과 마찬가지로 원을 그리는 Ellipse함수에게도 화면 영역을 알리는 hdc값이 주어져야 한다. 32/211 32
2-10 원 그리기 HDC hdc ; PAINTSTRUCT ps ; switch (iMsg) { case WM_CREATE : return 0 ; case WM_PAINT : hdc = BeginPaint (hwnd, &ps) ; Ellipse(hdc, 0, 0, 40, 40); // 박스의 좌표, 중심점 (20,20) EndPaint (hwnd, &ps) ; case WM_DESTROY : PostQuitMessage (0) ; } 위의 프로그램은 원 그리기 함수를 이용하여 윈도우에 간단한 원을 그려보는 예이다. 원을 그리기 위해 화면 영역을 hdc에 얻어오고 두 좌표 (0,0), (40,40)을 기준으로 원을 그린다. 이것은 가상의 사각형이 정사각형이기 때문에 타원이 아닌 정원을 그리게 된다. 중심점의 좌표는 (20,20)이고 반지름은 20인 원이 나타나게 된다. 33/211 33
사각형 그리기 두 점의 좌표를 기준으로 수평수직 사각형을 그림 BOOL Rectangle( HDC hdc, int X1, int Y1, int X2, int Y2 ); (X1, Y1) 사각형을 그리는 방법은 원을 그리는 방법과 유사하다. 그러나 여기서 그리는 사각형은 수평 수직이 되는 직사각형이다. 다른 형태의 일반적인 사각형은 다각형을 그리는 함수를 이용해야 가능하다. 수평, 수직이 되는 직사각형은 RECT 구조체와 같이 두 점을 주면 만들 수 있다. 주어진 두 점은 직사각형의 마주보는 꼭지점 역할을 한다. 사각형을 그리는 함수는 Rectangle()이고, 첫 번째는 화면영역에 해당하는 hdc를 주고 두 번째부터 다섯 번째 까지는 정수 값으로 좌표 값이다. 위의 그림에서 보는 바와 같이 (X1, Y1)이 하나의 점이고 (X2, Y2)가 마주보는 상대 점이다. 그림은 이 두 점을 기준으로 그려진 수평, 수직이 되는 직사각형을 보여주고 있다. (X2, Y2) 34/211 34
2-11 사각형 그리기 예제 HDC hdc ; PAINTSTRUCT ps ; switch (iMsg) { case WM_CREATE : return 0 ; case WM_PAINT : hdc = BeginPaint (hwnd, &ps) ; Rectangle(hdc, 0, 0, 40, 40); EndPaint (hwnd, &ps) ; case WM_DESTROY : PostQuitMessage (0) ; } 사각형을 그리는 함수 Rectangle을 이용한 예제이다. 프로그램이 시작하여 WM_CREATE메시지 이후에 WM_PAINT메시지가 도착하면 BeginPaint함수를 이용하여 화면 영역을 hdc에 얻어오고 있다. 이제 화면 영역을 얻어 왔기 때문에 그림을 그리거나 글씨를 출력할 수 있다. 위의 예제에서는 (0,0)과 (40,40)을 기준으로 하는 사각형을 그리게 하고 있다. 이것은 한 변의 길이가 40인 정사각형을 화면에 그릴 것이다. 사각형 그리기를 마친 후 EndPaint를 통해 화면영역을 돌려주게 된다. 35/211 35
다각형 그리기 연속되는 여러 점의 좌표를 직선으로 연결하여 다각형을 그림 BOOL Polygon( HDC hdc, CONST POINT *lppt, int cPoints // 꼭지점의 수 ); typedef struct tagPOINT { LONG x; LONG y; } POINT; X1 Y1 X2 Y2 Xn Yn (X1,Y1) (X2,Y2) (Xn,Yn) 다각형 또는 다각라인은 복수개의 점으로 구성된다. 각 이웃하는 점 사이에는 직선이 그려진다. 다각형과 다각라인의 차이점은 마지막 점과 시작 점이 연결되느냐 그렇지 않느냐 차이이다. 앞에서 간단하게 언급하였듯이 한 점의 좌표를 저장하기 위한 구조체는 POINT이다. 따라서, POINT구조체 내에는 정수형으로 두 개의 필드인 x와 y가 존재한다. 다각형을 그리기 위한 함수는 Polygon이고 다각라인을 그리기 위한 함수는 Polyline이다. Polyline함수의 형태는 위의 Polygon과 이름만 다를 뿐 같기 때문에 따로 설명하지 않겠다. Polygon 함수에게는 먼저 화면 영역에 해당하는 hdc를 제공해야 하고 두 번째는 다각형 꼭지점 좌표들을 제공해야 한다. 꼭지점 좌표들은 POINT타입 배열에 꼭지점 순서대로 저장된다. Polygon함수의 두 번째 파라메터는 저장된 꼭지점 배열의 시작주소를 제공한다. 즉, 제공된 주소부터 POINT타입의 꼭지점들이 저장되어 있다는 뜻이다. 그러나, 지정된 주소부터 몇 개의 POINT값을 다각형 그릴 때 이용해야 하는지는 아직 나타나지 않았다. Polygon함수의 세 번째 파라메터를 이용하여 몇 개의 꼭지점을 이용할 지를 알리게 된다. 36/211 36
2-12 다각형 그리기 예제 HDC hdc ; PAINTSTRUCT ps ; POINT point[10] = {{10,20}, {100,30}, {500,200}, {600, 300}, {200, 300}}; switch (iMsg) { case WM_CREATE : return 0 ; case WM_PAINT : hdc = BeginPaint (hwnd, &ps) ; Polygon(hdc, point, 5); // 5각형 EndPaint (hwnd, &ps) ; case WM_DESTROY : PostQuitMessage (0) ; } 함수 Polygon을 이용하여 화면에 다각형을 그려보자. 먼저 꼭지점 좌표들을 저장할 구조체 POINT타입 배열변수를 선언해야 한다. 위의 예에서는 최대 10개 좌표를 저장할 수 있는 배열 변수 point를 지역변수로 선언 하였다. 그렇지만 초기값으로 5개만 설정하였다. WM_PAINT 메시지 발생했을 때 화면을 얻어오고 화면인 hdc에 5개 꼭지점 다각형을 그리게 하고 있다. 이때 다각형은 면을 가지고 있기 때문에 내부를 특정 색으로 채우게 된다. 이때 사용하는 색은 하얀색인데 특별히 언급하지 않았을 때 사용하는 기본 색이 하얀색이기 때문이다. 반면에 기본이 되는 선의 색은 검은색이다. 37/211 37
선 굵기, 색상 지정 선의 굵기 정보와 색상정보를 가지는 펜 핸들을 만들어 준다 HPEN CreatePen( int fnPenStyle, int nWidth, COLORREF crColor); 그림그릴 화면인 디바이스컨텍스트에 펜 핸들을 등록한다 HPEN SelectObject(HDC hdc, HPEN pen); 그림 그리기를 마친 후 생성된 펜 핸들은 삭제한다 DeleteObject(HPEN); 지금까지 선의 굵기나 색 등 다양한 속성들을 지정하지 않은 채 기본 선을 이용해서 직선, 사각형, 원, 다각형을 그렸다. 여기서는 선의 속성을 변경하는 방법을 배운다. 선에 대한 다양한 속성 정보들은 펜 핸들 변수에 저장 될 수 있다. 저장하기 위해 먼저는 펜 핸들 변수를 만들고 CreatePen이라는 함수를 이용하여 만들어진 펜 핸들 변수에 저장한다. CreatePen함수에는 3가지 파라메터를 통해 속성을 지정한다. 지정할 수 있는 속성은 펜의 스타일, 펜의 굵기, 펜의 색상이다. 펜 핸들 변수에 선의 속성값들이 지정되면 펜 핸들변수를 화면인 디바이스컨텍스트에 등록을 해 줘야 한다. 디바이스컨텍스트는 하나의 펜을 등록할 수 있게 되어 있다. 디바이스컨텍스트에 직선, 사각형, 원, 다각형 등을 그리게 되면 등록된 펜을 이용하여 그리는 것이다. 펜을 등록할 때 사용하는 함수는 SelectObject라는 함수이다. 첫 번째 파라메터는 화면을 가리키는 디바이스컨텍스트핸들변수이고 두 번째는 등록하기 원하는 펜 핸들 변수이다. 꼭 명심해야 할 것은 펜을 이용한 그리기를 마친 후에는 반드시 DeleteObject함수를 이용하여 펜 핸들을 삭제해 주어야 한다. 38/211 38
CreatePen 첫 번째 인자: 펜 스타일로 다음 중 하나를 입력한다. 두 번째 인자: 펜의 굵기로 단위는 픽셀이다. 세 번째 인자: 색상을 표현하기 위해 COLORREF 값을 제공해야 하는데 RGB()함수로 만들 수 있다. COLORREF RGB(int Red,int Green,int Blue) Red, Green, Blue에는 0~255 사이의 정수 값을 사용한다. PS_SOLID PS_DASH PS_DOT PS_DASHDOT PS_DASHDOTDOT 펜 핸들을 만들기 위한 함수 CreatePen함수에는 3가지 파라메터가 들어간다. 첫 번째는 펜 스타일로써 위에 나열된 매크로 상수 중 하나를 사용한다. 각 매크로 상수의 스타일은 오른쪽에 그려진 선의 스타일이다. 두 번째 파라메터는 펜의 굵기를 표시하는 정수로써 단위는 픽셀이다. 이 값의 크면 펜의 굵기도 커진다. 센 번째 파라메터는 선의 색상을 표현하기 위한 타입이다. 세 번째 파라메터가 요구하는 타입은 COLERREF이고 이를 만들기 위해서 RGB함수를 이용한다. RGB함수는 이름에서 알 수 있듯이 빨강, 초록, 파랑을 위해 3개의 정수 값을 요구한다. 정수 값은 0~255 사이의 값으로 0이면 해당 색상을 이용하지 않는다는 것이고 255이면 최대로 사용한다는 것이다. 주어진 3개의 정수 값 만큼씩 3가지 색상을 모아서 하나의 색을 만들게 된다. 255값이 최대인 이유는 위의 색이 24비트 칼라이기 때문이다. 24비트를 3등분 하면 빨강, 초록, 파랑을 위해 각각 8비트씩 할당해 주게 된다. 8비트가 표현할 수 있는 최대 정수 값이 255이다. 39/211 39
2-13 빨간 점선으로 원 그리기 HDC hdc ; PAINTSTRUCT ps ; HPEN hPen, oldPen; switch (iMsg) { case WM_CREATE : return 0 ; case WM_PAINT : hdc = BeginPaint (hwnd, &ps) ; hPen = CreatePen(PS_DOT, 1, RGB(255,0,0)); oldPen = (HPEN)SelectObject(hdc, hPen); // 새로운 펜의 사용 선언 Ellipse(hdc, 20,20, 300,300); SelectObject(hdc, oldPen); // 이전의 펜으로 돌아감 DeleteObject(hPen); EndPaint (hwnd, &ps) ; 위의 예에서는 펜을 만들고 만들어진 펜을 이용하여 화면에 원을 그리고 있다. 펜을 만들기 위해서는 펜 핸들변수를 선언해 주어야 한다. 위에서는 HPEN타입 변수 hPen, oldPen 두 개를 지역변수로 선언하고 있다. 이 변수를 만들었다고 해서 펜이 만들어 진 것은 아니다. 앞장에서 설명한 것과 같이 CreatePen함수를 이용하여 펜의 다양한 속성값을 가진 펜 핸들을 만들어야 하는 것이다. 위의 예에서는 WM_PAINT 메시지가 발생할 때 CreatePen함수를 이용하여 펜을 만들고 있다. 만들어진 펜은 점선이고 굵기는 1픽셀이며 펜의 색상은 빨간색이다. 만들어진 펜은 펜 핸들 변수 hPen에 저장된다. 이것은 SelectObject함수를 이용하여 화면 hdc에 등록된다. 새로운 펜을 hdc에 등록하면 SelectObject함수는 hdc에 이전에 등록되었던 펜을 반환하게 된다. 위에서는 반환된 펜을 oldPen에 저장해 주고 있다. 이제 직선, 원, 사각형,다각형을 그리면 만들어진 빨간 펜으로 그리게 된다. 그림 그리기를 마치면 화면 hdc에 받아 두었던 펜 핸들을 다시 등록해 주어야 한다. 이때도 마찬가지로 SelectObject함수는 등록되었던 빨간 펜을 반환해 주지만 그 값을 받을 필요가 없기 때문에 저장하지 않고 있다. 그리고 나서, 만들었던 hPen은 DeleteObject함수를 이용하여 삭제하고 있다. 40/211 40
면 색상 지정 면의 색상정보를 가지는 브러쉬핸들을 만들어 준다HBRUSH CreateSolidBrush( COLORREF crColor ); 그림그릴 화면인 디바이스컨텍스트에 브러쉬핸들을 등록한다 HBRUSH SelectObject(HDC hdc, HBRUSH brush); 그림 그리기를 마친 후 생성된 브러쉬핸들은 삭제한다 DeleteObject(HBRUSH); 면을 가지는 도형은 원, 사각형, 다각형이다. 이와 같이 면을 가진 도형은 펜을 이용하여 테두리를 그리지만 내부는 브러쉬를 이용하여 칠해 준다. 면을 칠하기 위해 API에서는 색상정보를 가지는 브러쉬 핸들을 만들어 주어야 한다. 이때 사용하는 함수는 CreateSolidBrush이다. 내부를 칠하는 브러쉬의 속성은 색상이면 충분하기 때문에 CreateSolidBrush함수의 파라메터는 COLORREF타입의 값 하나이다. 펜을 만들어서 펜 핸들변수에 넣었던 것과 같이 브러쉬도 만들면 브러쉬 핸들변수에 저장해 주어야 한다. SelectObject함수를 이용하여 브러쉬 핸들 변수를 화면인 디바이스컨텍스트에 등록해 준다. 펜에서와 마찬가지로 SelectObject함수는 디바이스컨텍스트에 이전에 등록되었던 브러쉬를 반환해 준다. 이것도 저장할 필요가 있으면 브러쉬 핸들 변수를 만들어 저장해 두지만 그럴 필요가 없을 때는 저장 하지 않는다. 그림 그리기를 마쳐서 더 이상 브러쉬 핸들이 필요 없으면 DeleteObject를 이용하여 삭제 해 준다. 여기서 주의 할 것은 펜 핸들을 등록하거나 삭제하는 함수나 브러쉬 핸들을 등록하거나 삭제하는 함수가 똑 같다는 것이다. 이것은 함수 SelectObject나 DeleteObject의 파라메터 타입이 void형태로 어떤 것이든 받을 수 있도록 해 주었기 때문이다. 41/211 41
빨간색 원 그리기 HDC hdc ; PAINTSTRUCT ps ; HBRUSH hBrush, oldBrush; switch (iMsg) { case WM_CREATE : return 0 ; case WM_PAINT : hdc = BeginPaint (hwnd, &ps) ; hBrush = CreateSolidBrush(RGB(255,0,0)); oldBrush = (HBRUSH)SelectObject(hdc, hBrush); Ellipse(hdc, 20,20, 300,300); SelectObject(hdc, oldBrush); DeleteObject(hBrush); EndPaint (hwnd, &ps) ; 빨간색 면을 가지고 검정색 테두리를 가진 원을 그리는 예제 프로그램이다. 여기서는 빨간색 브러쉬를 만들기 위한 브러쉬 핸들 변수만을 선언하였다. 펜 핸들 변수를 사용할 때와 마찬 가지로 oldBrush도 선언하였다. WM_PAINT메시지에서 빨간색 브러쉬를 만들어 hBrush 변수에 저장하였다. SelectObject를 이용하여 화면인 hdc에 등록하였고 이전에 등록되었던 브러쉬는 oldBrush에 받아 두었다. 이제 화면인 디바이스컨텍스트 hdc에 빨간색 브러쉬가 등록 되었기 때문에 면을 가진 도형을 그리면 내부는 빨간색으로 칠하게 된다. 위에서는 원을 그리고 있다. 여기서 특별히 펜을 만들어 등록을 하지는 않았기 때문에 기본 펜인 검은색 1픽셀 두께의 실선 펜을 이용한다. oldBrush에 받아 두었던 브러쉬는 디바이스컨텍스트 hdc에 있었던 기본 브러쉬인데 기본 브러쉬는 하얀색 브러쉬이다. 그림 그리기를 마친 후 SelectObject함수를 이용해서 받아 두었던 oldBrush를 등록해 주고 만들었던 브러쉬 hBrush는 삭제해 준다. 삭제를 하지 않으면 WM_PAINT 메시지 발생시마다 브러쉬를 만들기 때문에 메모리 소비가 점점 커지게 된다. 42/211 42
실습안내서 2-1 실습제목 실습 내용 텍스트 출력하는 프로그램 작성하여 실행하기 실습 1-1에서 작성한 코드를 불러온다. 윈도우의 (100,100)위치에 “I love you”를 출력한다. TextOut()과 DrawText()를 각각 이용한다. TextOut함수와 DrawText함수를 이용하여 텍스트를 출력하는 프로그램을 작성하는 문제이다. 여기서는 실습 1-1에서 작성했던 코드를 불러와 사용함으로 반복되는 부분에 대한 입력을 줄일 수 있다. 좌표 (100, 100) 위치에 “I love you”를 출력하는 프로그램을 작성하는데 TextOut함수와 DrawText함수를 각각 이용한다. 43/211 43
실습안내서 2-2 실습제목 실습 내용 10라인까지 입력 받을 수 있는 메모장 작성 한 줄은 최대 문자 99자이고 최대 10라인까지 입력 받을 수 있는 메모장 프로그램을 작성한다. Backspace입력에 의해 마지막 입력된 문자가 삭제되어야 한다. Enter입력 의해 아랫줄 입력을 받을 수 있어야 한다. 여기서는 지금까지 배운 내용을 기반으로 10라인까지 입력 받을 수 있는 메모장을 작성해 보도록 한다. 입력 받을 때 한 줄은 최대 99자 까지 저장 가능해야 하고 최대 줄 수는 10라인 이다. 지금까지 배우지 않은 것 중에 하나는 Enter키 입력에 대한 처리이다. 이것을 처리하기 위해서는 wParam==VK_RETURN 조건이 참인지 체크하고 참이면 아래 라인으로 내려가서 출력할 수 있도록 해야 한다. 앞장의 예제에서 출력은 TextOut(hdc,0,0,str,strlen(str));와 같이 (0,0)좌표에 언제나 출력하였지만 여기서는 Enter입력이 있을 때 마다 y좌표의 값을 증가 시켜 주어야 한다. 가장 먼저 해야 할 일은 현재 출력될 줄이 몇 번째 줄인지를 알리는 변수가 필요하다. 그 변수의 값을 기반으로 좌표는 재설정 된다. 예를 들어, Enter 입력이 한번 있어서 두 번째 줄에 문자를 입력하고 있는 중이라면 줄을 알리는 변수의 내용은 1이 된다. 그리고 두 번째 줄 문자열 출력 좌표는 한 줄의 높이를 20픽셀이라고 가정할 때 (0,1*20)이 된다. 마찬가지로 세 번째 줄 출력하려면 (0, 2*20)에 출력하면 된다. 각 줄의 내용이 계속 저장되어 유지하기 원한다면 문자열 저장 변수도 1차원 문자배열이 아니라 2차원 문자배열로 선언하여야 한다. 44/211 44
실습안내서 2-3 실습제목 실습 내용 10라인까지 입력 받을 수 있는 caret이 있는 메모장 작성 실습 2-2를 기반으로 Caret이 있는 10라인 입력 에디터를 작성한다. 실습 2-2의 기능은 기본적으로 모두 구현되어야 하고 추가로 Caret이 나와서 사용자 입력을 받기 쉽게 한다. 여기서 주의해야 할 것은 Backspace키나 Enter키의 입력 시 Caret의 위치에 대해 주의해야 한다. 그렇지만 현재 입력된 문자의 개수를 기억하는 count변수를 이용하면 쉽게 해결 할 수 있다. 또한, Enter키에 의해 입력되는 문장 출력이 아래로 내려올 때 Caret도 똑같이 내려와야 한다. 이것은 Caret의 y좌표 값을 변경해야 한다는 것을 요구한다. 45/211 45
실습안내서 2-4 실습제목 실습 내용 선색과 면색을 활용하여 원 그리기 중심이 (20,20)이고 반지름이 20인 원을 그린다. 테두리 선은 빨간색 2픽셀 굵기의 실선이고 면은 파란색으로 그린다. 지금까지 배운 펜 핸들과 브러쉬 핸들을 이용하여 원을 그리는 실습을 수행한다. 여기서 만들어야 하는 펜은 빨간색, 2픽셀 굵기의 실선이고 브러쉬는 파란색 브러쉬이다. 그리고, 그려야 하는 도형은 중심 좌표가 (20,20)이고 반지름이 20인 원이다. 46/211 46