5-4 액셀러레이터
액셀러레이터(Accelerator)는 아주 쉽게 말해서 단축키이다. 아래 한글을 예로 든다면 Alt+L을 누르면 문자 꾸미기 기능, Ctrl+P를 누르면 프린터 설정 기능이 곧바로 실행되도록 하는 키보드 조합키를 말한다. 그런데 왜 단축키라는 말을 쓰지 않고 어렵게시리 영어를 쓰는가 하면 윈도우즈에서 단축키(ShortCut)라는 말은 다른 의미로 사용되고 있기 때문이다. 메뉴 이름에 &를 넣어 Alt키와 함께 사용하는 키를 단축키라고 하며 여기서 말하는 액셀러레이터와는 의미가 조금 다르다. 단축키는 반드시 Alt키와 함께 사용해야 하며 메뉴에 있는 항목을 키보드로 선택하는 빠른 방법이지만 액셀러레이터는 메뉴와 상관없이 언제든지 사용할 수 있다는 점에 있어서 차이가 있다. 여기서는 Menu.dsw에서 만들었던 예제를 변형하여 메뉴 항목을 액셀러레이터로 곧바로 실행할 수 있도록 변형해 볼 것이다. 각 메뉴 항목에 대해 Ctrl+A, Ctrl+B, Ctrl+C의 액셀러레이터를 할당하여 메뉴를 선택하지 않고 키보드로 메뉴 기능을 실행하도록 해 본다. 단계가 조금 복잡하므로 다음 단계를 따라 Menu.dsw를 계속 만들어 보자. 1일단 Menu.dsw 프로젝트를 다시 연다. 단축키를 지원하기 위해 메뉴 리소스를 수정한다. 리소스 뷰에서 IDR_MENU1을 더블 클릭하면 메뉴 편집기가 열릴 것이다. File 메뉴 아래 세 개의 메뉴 항목이 있는데 각 항목의 캡션을 다음과 같이 수정한다. 메뉴 편집기에서 메뉴 항목을 더블클릭하여 속성 윈도우를 연 다음 Caption 속성을 편집하면 된다.
캡션에 포함된 &문자가 단축키를 지정하는데 &다음에 있는 문자가 단축키가 되며 단축키로 지정된 문자 밑에는 밑줄이 그어진다. 단축키는 그 메뉴 항목을 대표하는 문자 하나로 지정하는 것이 보통인데 Menu1은 1을 Menu2는 2를 Exit는 E를 단축키로 지정하였다. 앞 문자를 단축키로 지정하는 것이 보편적이지만 Copy, Cut 등과 같이 앞문자가 중복될 경우는 적당한 다른 문자를 지정해야 한다. 액셀러레이터는 캡션 뒤에 \t로 적당히 칸을 띄운 후 키조합을 써 주면 된다. 여기서 사용된 \t는 탭 문자인데 액셀러레이터가 메뉴 리스트의 오른쪽으로 가지런하게 정렬되도록 해 준다. 위에서부터 순서대로 Ctrl+A, Ctrl+B, Ctrl+C를 액셀러레이터로 지정하였다. 캡션을 변경한 후의 메뉴 모양은 다음과 같다. 2여기까지만 작업하고 예제를 다시 실행해 보자. 단축키가 지정되었으므로 이제 마우스를 사용하지 않고도 메뉴 항목을 선택할 수 있다. File 메뉴는 F문자가 단축키로 지정되어 있으므로 언제든지 Alt+F를 누르면 File 메뉴가 열린다. 메뉴 리스트에서 밑줄 그어진 문자를 누르면 해당 메뉴가 선택되는데 예를 들어 1을 누르면 Menu1이 선택되며 E를 누르면 Exit가 선택되어 프로그램이 종료된다. 그러나 액셀러레이터는 표시만 되어 있지 아직 동작하지는 않는다. Ctrl+A, Ctrl+B를 눌러도 프로그램은 아무 반응이 없을 것이다. 메뉴 리스트에 출력되는 액셀러레이터는 이 메뉴 항목의 액셀러레이터에 대한 설명을 해 주는 문자열일뿐이며 실제로 단축키로 동작하지는 않는다. 액셀러레이터를 만들려면 별도의 리소스를 만들어 주어야 한다. 3이제 액셀러레이터를 작성해 보자. 메뉴를 만들 때와 같은 방법으로 Insert/Resource를 선택하고 리소스 리스트에서 Accelerator를 선택한다. 다음과 같은 액셀러레이터 편집기가 열릴 것이다. 새로 만든 액셀러레이터 테이블이므로 아직 정의되어 있는 액셀러레이터가 없으며 빈칸만 하나 있다. 이 빈칸을 더블클릭하면 액셀러레이터를 편집할 수 있는 속성 편집기가 열린다. 각 란을 다음과 같이 입력한다. 액셀러레이터의 ID이며 프로그램 소스에서 액셀러레이터를 참조할 때 이 값을 사용한다. 메뉴의 ID를 작성하는 방법과 동일한 규칙대로 ID를 작성하되 이 예제의 경우는 메뉴에 작성되어 있는 ID를 그대로 사용하면 된다. 드롭다운 리스트를 열어 ID에 ID_FILE_MENU1을 선택한다. Key액셀러레이터로 사용할 키를 선택한다. 펑션키나 특수키를 사용하려면 드롭다운 리스트를 열어 선택하고 알파벳키나 숫자키를 선택하려면 바로 아래쪽의 Next Key Typed버튼을 누른 후 원하는 키를 키보드에서 누르면 된다. Next Key Typed를 누른 후 키보드에서 "A"키를 누른다. ModifierKey와 함께 눌러질 조합키를 선택한다. Ctrl, Alt, Shift를 개별적으로 또는 여러개를 한꺼번에 선택할 수 있다. Ctrl키만 선택한다. TypeKey값이 아스키 코드값인지 가상키 코드값인지를 설정한다. 보통 가상키 코드를 많이 사용한다. 이런식으로 다음과 같이 세 개의 액셀러레이터를 만든다. 액셀러레이터가 메뉴의 기능을 대신할 수 있도록 메뉴와 같은 ID를 사용하도록 해 두었다. 4다음은 만들어진 리소스를 코드에서 사용하도록 소스를 변경해 주어야 한다. 액셀러레이터의 ID를 메뉴의 ID와 같도록 해 두었기 때문에 WndProc의 WM_COMMAND는 수정할 필요가 없다. WinMain의 선두에 hAccel 변수를 선언해 주고 메시지 루프만 다음과 같이 수정해 주면 된다. HACCEL hAccel; hAccel=LoadAccelerators(hInstance,MAKEINTRESOURCE(IDR_ACCELERATOR1)); while(GetMessage(&Message,0,0,0)) { if (!TranslateAccelerator(hWnd,hAccel,&Message)) { TranslateMessage(&Message); DispatchMessage(&Message); } } 못 보던 함수 두 개가 새로 등장했다. 우선 LoadAccelerators 함수부터 알아 보자. HACCEL LoadAccelerators(HINSTANCE hInstance, LPCTSTR lpTableName );이 함수는 리소스로부터 액셀러레이터 테이블을 읽어들인다. 두번째 인수 lpTableName은 액셀러레이터 테이블의 이름 문자열 포인터이되 우리가 작성한 액셀러레이터 테이블 IDR_ACCELERATOR1은 정수값이므로 MAKEINTRESOURCE 매크로를 사용해야 한다. 이 함수는 리소스에서 액셀러레이터 테이블을 읽은 후 그 핸들값을 리턴해 준다. 이 핸들값을 hAccel이라는 변수에 대입해 두면 다음부터 hAccel을 통해 액셀러레이터 테이블을 읽을 수 있다. 메시지 루프안에는 다음 함수가 삽입되었다. int TranslateAccelerator(HWND hWnd, HACCEL hAccTable, LPMSG lpMsg );이 함수는 키보드 메시지를 WM_COMMAND 메시지로 변경해 주어 액셀러레이터가 동작할 수 있도록 해 준다. 액셀러레이터 Ctrl+A가 입력되었다고 해 보자. Ctrl+A는 액셀러레이터이기 이전에 키보드로부터의 입력이므로 먼저 WM_KEYDOWN 메시지가 발생할 것이고 그대로 내버려 두면 WndProc의 WM_KEYDOWN 메시지 처리 루틴에서 먼저 이 키값을 처리해 버릴 것이다. 그래서 TranslateAccelerator 함수는 키보드 입력값을 읽어 이 키값이 지정한 액셀러레이터 테이블에 있는지를 먼저 살펴보고 있을 경우 WM_COMMAND 메시지를 발생시키고 TRUE를 리턴해 버린다. 그래서 액셀러레이터가 입력되었을 경우 TranslateMessage, DispatchMessage 함수가 실행되지 못하도록 막아 버리며 다음번의 WM_COMMAND메시지가 처리되도록 해 준다. 물론 액셀러레이터 입력이 아니면 FALSE를 리턴하여 다른 메시지들은 정상적으로 처리되도록 해 준다. 이제 예제를 완성했다. 컴파일시켜 보고 예제를 실행해보자. 프로그램 실행중의 모습은 다음과 같다. 단축키와 액셀러레이터가 정의되었으므로 다음 세가지 방법중 어떤 방법을 쓰더라도 Menu1메뉴 항목을 호출할 수 있게 되었다. ① 마우스로 File 메뉴를 열고 Menu1 메뉴 항목을 선택한다. ② Alt+F를 눌러 File 메뉴를 열고 1을 눌러 Menu1을 선택한다. ③ 곧바로 Ctrl+A를 누른다. |
엑셀레이터는 단축키를 말한다. 다른거랑 비슷하다. 예제를 쳐보자.
메뉴를 눌러도 WM_COMMAND 가 메시지 큐에 메시지가 쌓이고 단축키를 눌려도 메시지 큐에 WM_COMMAND가 쌓인다.
완성된 코드는 이러하며,
#include <windows.h> |
WinMain에
HACCEL hAccel; .
. hAccel=LoadAccelerators(hInstance,MAKEINTRESOURCE(IDR_ACCELERATOR1)); while(GetMessage(&Message,0,0,0)) { if (!TranslateAccelerator(hWnd,hAccel,&Message)) { TranslateMessage(&Message); DispatchMessage(&Message); } }
이 부분이 수정 되었다라는 것을 볼 수 있다.
새로히 보이는 함수가 있는데LoadAccelerators() ,TranslateAccelerator() 이다. 어디 소스를 분석하며 살펴보자.
HACCEL hAccel;
.
.
.
hAccel = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDR_ACCELERATOR1));
while (GetMessage(&Message, 0, 0, 0))
{
if (!TranslateAccelerator(hWnd, hAccel, &Message))
{
TranslateMessage(&Message);
DispatchMessage(&Message);
}
}
1. LoadAccelerators() 는 응용 프로그램의 리소스에 정의된 액셀러레이터 테이블을 읽어온다. (직접 정의한 엑셀레이터) 액셀러레이터 테이블은 응용 프로그램이
사용하는 단축키의 목록을 가지는 리소스이다. 이 함수로 읽어온 액셀러레이터 테이블은 메시지 루프에서 TranslateAccelerator() 에 의해 해석되어
WM_COMMAND 메시지로 변환된다. 이 함수로 읽어온 액셀러레이터 테이블은 응용 프로그램이 종료될 때 자동으로 파괴되므로 직접 파괴해주지 않아도
된다. (Kill~() 동적 할당 해제하듯이 사용했던 애들.)
함수 원형
:HACCEL LoadAccelerators(HINSTANCE hInstance, LPCTSTR lpTableName );
여기 보이는 HINSTANCE 는 자료형의 일종이다. 16비트에서는 Uint 로써 32비트에서는 Ulong 으로써 작용한다. 알아두자.
또한, MAKEINTRESOURCE() 이것은 매크로 함수 인데,
말그대로 LPSTR로 형변환하는 겁니다
굳이 MAKEINTRESOURCE()만든 이유는 가독성이죠
그렇다.
2. TranslateAccelerator() 는 액셀러레이터 명령을 만든다. 이 함수는 hAccTable(LoadAccelerators() 로 부터 읽혀 들여진 액셀러레이터.) 을 참조하여
WM_KEYDOWN, WM_SYSKEYDOWN으로부터
WM_COMMAND, WM_SYSCOMMAND 메시지를 만들어 낸다. 눌러진 키가 액셀러레이터 테이블에 정의된 명령일 경우 명령 메시지로
변환하여 메시지 큐에 붙여주며 이 메시지는 다음번 GetMessage나 PeekMessage에 의해 읽혀져 처리되며 이 메시지가 완전히 처리되기 전에는
리턴하지 않는다.
함수 원형
:int TranslateAccelerator(HWND hWnd, HACCEL hAccTable, LPMSG lpMsg );
일반적으로 액셀러레이터는 메뉴 항목에 대한 단축키를 제공하기 위해 작성한다. 이 경우 액셀러레이터키가 눌러지면 마치 메뉴가 선택된 것처럼
WM_INITMENU, WM_INITPOPUPMENU 메시지가 전달된다. 단 윈도우가 사용금지 되어 있거나 메뉴 항목이 사용금지된 경우 , 마우스가 캡쳐된 경우는
제외된다. WM_COMMAND 메시지는 명령이 액셀러레이터로부터 온 경우 wParam의 상위 워드로 1이 전달 되며 메뉴로 부터 온 경우 0이 전달되는데
보통 이 구분은 무시하지만 메뉴로부터의 명령과 액셀러레이터로부터의 명령을 구분하려면 HIWORD(wParam)을 참고하도록 한다.
hAccel = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDR_ACCELERATOR1));
while (GetMessage(&Message, 0, 0, 0))
{
if (!TranslateAccelerator(hWnd, hAccel, &Message))
{
TranslateMessage(&Message);
DispatchMessage(&Message);
}
}
함수 분석이 끝났으니 소스 분석을 해보자.
GetMessage로 메시지를 조사한 후 먼저 TranslateAccelerator 함수가 이 메시지를 검사하여 액셀러레이터표에 있는 키보드 입력인지 조사한다.
만약 그렇다면 이 메시지는 WM_COMMAND로 변환되어 메시지 처리 함수로 보내지며 이 경우 TranslateMessage, DispatchMessage 함수는 호출되지
말아야 한다.
즉, 액셀러레이터 값이라면 TranslateMessage, DispatchMessage 함수 는 호출되지 말아야 한다. 그대로 while 문만 탈출한다.
실행 화면은 전과 같을테니 생략하도록 하겠다. 물론 액셀~ 값과 마우스로 눌렀을 경우, 로 wParam의 상위 워드 0과 1로 나눌수야 있겠지만 우선 생략하도록 하자.
5-5 문자열 테이블
윈도우즈에서는 문자열들도 리소스의 일종으로 취급된다. 대량의 문자열을 사용하는 프로그램은 리소스에 문자열을 정의해 두고 필요할 때마다 리소스에 문자열을 읽어와 사용한다. 일단 문자열 리소스를 사용하는 간단한 예제를 같이 만들어 보도록 하자. StrTable이라는 이름으로 프로젝트를 만들고 ApiStart.txt 파일을 복사해 온다. lpszClass 문자열을 "StrTable"로 변경한 후 소스 파일을 프로젝트에 포함시켜 표준적인 프로젝트를 먼저 만든다. 이 상태에서 문자열 리소스를 정의하기 위해 Insert/Resource 항목을 선택하고 리소스 종류에서 String Table을 선택한다. 다음과 같은 문자열 리소스 편집기가 열릴 것이다. 액셀러레이터 편집기와 유사하게 생겼는데 아직 정의된 문자열이 없으므로 비어 있다. 새 문자열을 삽입하기 위해 빈칸을 더블클릭하여 속성 편집기를 열고 다음과 같이 입력한다. ID는 디폴트로 제시되는 IDS_STRING1을 선택하고 Caption에 적당한 문자열을 입력하였다. 리소스 스크립트를 StrTable.rc로 저장하고 프로젝트에 포함시키면 문자열 리소스는 다 만든 것이다. 이제 이 문자열 리소스를 소스에서 사용하기 위해 코드를 작성한다. #include "resource.h" LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam) { HDC hdc; PAINTSTRUCT ps; char str[256]; switch(iMessage) { case WM_PAINT: hdc=BeginPaint(hWnd, &ps); LoadString(g_hInst, IDS_STRING1, str, 256); TextOut(hdc,10,10,str,strlen(str)); EndPaint(hWnd, &ps); return 0; case WM_DESTROY: PostQuitMessage(0); return 0; } return(DefWindowProc(hWnd,iMessage,wParam,lParam)); } 리소스 ID를 인식하기 위해 resource.h를 먼저 포함시켰으며 WM_PAINT에서 문자열 리소스를 읽어와 화면으로 출력하였다. 문자열 리소스를 읽을 때는 다음 함수를 사용한다. int LoadString( HINSTANCE hInstance, UINT uID, LPTSTR lpBuffer, int nBufferMax);인수가 네 개나 되지만 별로 복잡하지 않다. 첫번째 인수는 문자열 리소스를 가진 인스턴스 핸들인데 이 값은 WinMain의 첫번째 인수로 전달되며 ApiStart.txt에서 이 인수를 전역 변수 g_hInst에 대입해 두었으므로 g_hInst를 써 주면 된다. 두번째 인수로 읽어올 문자열의 ID를 주고 세번재 인수로 문자열을 읽을 버퍼, 네번째 인수로 버퍼의 길이를 주면 된다. LoadString(g_hInst, IDS_STRING1, str, 256); 이 함수 호출문의 의미를 해석해 보면 g_hInst 인스턴스에 정의된 IDS_STRING1 문자열을 길이 256의 str 문자 배열로 읽어 오라는 뜻이다. 문자열 리소스는 최대 255문자까지 가능하므로 버퍼 길이는 256이면 충분하다. 이렇게 읽은 문자열 str을 TextOut으로 화면에 출력하였다. 문자열 리소스에 정의된 문자열이 화면으로 출력되었다. 별로 어려운 내용은 없으므로 문자열 리소스를 정의하고 사용하는 방법에 대해서는 쉽게 이해가 갈 것이다. 그런데 이렇게 문자열 리소스를 정의해서 쓰는 이유는 뭘까? 문자열을 출력할 단순한 목적이라면 다음과 같이 코드를 작성하는 것이 훨씬 더 쉬워 보인다. char str[256]="String Resource Test"; switch(iMessage) { case WM_PAINT: hdc=BeginPaint(hWnd, &ps); TextOut(hdc,10,10,str,strlen(str)); ........ 문자 배열에 바로 문자열을 초기화시켜 이 문자열을 화면으로 출력하면 그만이다. 물론 단순한 문자열 출력이 목적이라면 이런 방법이 더 편하겠지만 문자열 리소스는 여러가지 면에서 이점을 준다. 우선 첫째로 문자열 자체가 코드와 분리됨으로써 문자열만 따로 관리할 수 있으며 프로젝트를 유지하는데도 큰 도움을 준다. 프로그램은 실행중에 사용자가 조작을 잘못하면 에러 메시지를 보여주며 간단한 안내문이나 도움말을 보여주기도 한다. 이런 메시지 문자열들이 많을 경우 수백개가 되는데 이 문자열들이 몽땅 소스 코드에 다 들어가 있다고 한다면 정말 끔직할 것이다. 메시지 문자열이나 안내문들도 사용자에 대한 세심한 배려이기 때문에 문장을 바르게 예의있게 작성해야 하는데 프로그래머가 코드에 신경쓰다 보면 이런 메시지들에 제대로 신경을 쓸 수가 없다. 문자열들이 리소스로 분리되어 있다면 전문 디자이너가 메시지들을 한꺼번에 모아 작성할 수 있을 것이다. 만약 어떤 이유로 메시지들을 전부 수정해야 한다고 해보자. 이때 코드의 여기저기에 박혀있는 메시지를 찾아 고치는 경우와 리소스 편집기에서 메시지를 일괄적으로 고치는 것 중 어떤 것이 더 빠르고 정확할 것인가는 쉽게 상상이 갈 것이다. 문자열 리소스를 사용하는 두번째 이점은 다국어 버전을 쉽게 만들 수 있다는 점이다. 리소스는 조건에 따라 교체할 수 있기 때문에 경우에 따라 다른 문자열을 사용할 수 있다. 그래서 영어 리소스 한벌, 한글 리소스 한벌을 각각 따로 만들어 놓으면 소스 코드는 건드릴 필요없이 리소스만 교체함으로써 영문 프로그램, 한글 프로그램을 쉽게 만들 수 있다. 리소스만 만들면 일본이나 중국 어디든지 팔아먹을 수 있게 되는 것이다. 물론 문자열이 소스에 작성되어 있어도 찾아서 고쳐주면 가능이야 하겠지만 무척 어려운 일이다. 또한 문자열이 소스와 분리되어 있으면 문자열을 고쳐도 소스를 다시 컴파일할 필요가 없어 개발 기간도 빨라진다. 이런 여러가지 장점이 있기 때문에 아주 간단한 문자열인 경우를 제외하고 좀 길다 싶은 문자열은 왠만하면 문자열 리소스로 만들어 쓰는 것이 좋다. 필자는 한때 이런 원칙을 무시한 나머지 "그라모 안돼는데예"라고 장난으로 메시지를 써 놨다가 파이널 베타에서 이 메시지를 찾아 고친 적이 있었는데 만약 그대로 제품으로 나갔다가는 큰 웃음거리가 될 뻔 했었다. 메모리 사용면에 있어서도 코드에 문자열을 바로 기입하는 것보다 문자열 리소스를 사용하는 것이 유리하다. 운영체제는 꼭 필요한 문자열만 메모리로 읽어들일만큼 지능적으로 메모리를 관리해주기 때문이다. 이상으로 윈도우즈에서 사용되는 몇가지 리소스에 대해 알아 보았는데 이외에도 중요한 리소스로 비트맵과 대화상자가 있다. 비트맵에 대해서는 다음장인 6장에서, 대화상자에 대해서는 8장에서 별도로 공부할 것이다. |
(주쌤)
{
여기있는 내용을 수정을 한다면 번역을 할 때 많이 쓰인다.
이 테이블만 고치면 프로그램 수정을 안해도 한글화가 된다라는 뜻이다.
프린트에프에 들어있는 메시지들 수정할 것들을 한글화로 전부 하는 식이다.
게임을 몰라도 테이블에 있는 것만 보고 바꿔도 수정이 손쉬워 지므로
게임에 필요한 것들을 대부분 여기에 등록하고 쓰면 편하게 할 수 있다.
보통은 잘 안쓰지만 쓰일 수도 있으니 알아두자.
}
이번엔 문자열 리소스이다. 해보자.
이번엔 문자열 리소스이다. 해보자.
#include <windows.h> |
이거 뭔가 자꾸 깨지게 나온다..
해결방안은 없을까.
6-1-가. GDI오브젝트
GDI 오브젝트(GDI Object)란 그래픽 출력에 사용되는 도구를 말하며 펜, 브러시, 비트맵, 폰트 등등이 모두 GDI 오브젝트이다. 사람이 그림을 그릴 때 연필, 붓 등의 도구를 사용하는 것과 마찬가지로 GDI가 그래픽을 출력할 때는 GDI 오브젝트를 사용한다. 즉 선을 그을 때는 펜을 사용하며 면을 채울 때는 브러시를 사용하고 문자열을 출력할 때는 폰트를 사용한다. GDI 오브젝트를 모아놓은 것이 DC이며 GDI는 현재 DC에 선택된 GDI 오브젝트를 사용한다. 그래서 사용자는 그래픽을 출력하기 전에 DC에 원하는 오브젝트를 선택해 줌으로써 그래픽을 다른 모양으로 변경할 수 있다. 예를 들어 그냥 선을 그으면 디폴트 펜인 검정색 펜으로 그려지지만 파란색 펜을 만들어 DC에 선택한 후 선을 그으면 GDI는 이 파란색 펜을 사용하여 선을 긋게 되므로 파란색 선이 그려지게 된다. 마찬가지로 브러시나 폰트를 변경하면 채워지는 색상이나 문자열의 글꼴 모양을 변경할 수 있다. GDI 오브젝트는 GDI가 그래픽 출력을 위해 사용하는 도구임과 동시에 사용자가 GDI의 출력을 조정할 수 있는 도구이기도 하다. GDI 오브젝트는 내부적으로 일종의 구조체이겠지만 우리가 사용할 때는 모두 핸들로 관리된다. GDI 오브젝트를 만들 때 핸들을 발급받으며 선택하거나 삭제할 때는 이 핸들만 가지고 GDI 오브젝트를 사용하게 된다. DC가 BeginPaint나 GetDC 함수에 의해 처음 만들어졌을 때 디폴트로 선택된 GDI 오브젝트는 다음과 같다.
|
(주쌤)
{
선을 그을떄 사용된다. 페인트통이라고 생각하면 된다.
비트맵을 들고올수도, 그릴수도 있다. 파일 읽어가 출력할 수도있다
윈api는 비트맵을 그리고 수동으로 점찍어주는것도 가능 하다. 윈도우가 다 자동으로 해준다. 사용방법을 익히자.
}
6-1-나. 스톡 오브젝트
스톡 오브젝트(Stock Object)는 윈도우즈가 기본적으로 제공해 주는 GDI 오브젝트를 말한다. 운영체제가 제공해 주므로 일부러 만들지 않아도 언제든지 사용할 수 있으며 사용하고 난 후에 파괴시켜 줄 필요도 없다. 다음 함수로 핸들을 얻어 사용하기만 하면 된다. HGDIOBJ GetStockObject( int fnObject );fnObject 인수에 사용하고자 하는 스톡 오브젝트를 기입해 주면 된다. 사용 가능한 스톡 오브젝트는 다음과 같다. 주로 브러시와 펜이 스톡 오브젝트로 제공된다. 2장에서 처음 공부했던 WHITE_BRUSH도 알고보면 스톡 오브젝트의 일종이다.
그러면 스톡 오브젝트를 사용하는 예제를 만들어 보고 GDI오브젝트를 어떤 식으로 사용하는가를 살펴 보자. GdiObj.dsw라는 프로젝트를 만들고 다음과 같이 Gdiobj.cpp를 작성한다. LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam) { HDC hdc; PAINTSTRUCT ps; switch(iMessage) { case WM_PAINT: hdc=BeginPaint(hWnd,&ps); Rectangle(hdc,50,50,300,200); EndPaint(hWnd,&ps); return 0; case WM_DESTROY: PostQuitMessage(0); return 0; } return(DefWindowProc(hWnd,iMessage,wParam,lParam)); } 4장 끝에서 말했지만 노파심에서 한번 더 얘기하자면 앞으로는 WndProc의 소스만 보인다. 이때까지의 실습을 통해 알수 있겠지만 WinMain은 아주 특별한 경우가 아니면 변하지 않으므로 굳이 소스 리스트를 보일 필요가 없을 것이다. 괜히 지면만 낭비하고 독자들의 머리와 눈만 괴롭힐 것 같으므로 앞으로도 꼭 필요한 경우가 아니면 WinMain은 보이지 않는다. WndProc외에는 WinMain 앞에 선언된 lpszClass 문자열값만 프로젝트의 이름으로 변경해 주면 된다. LPSTR lpszClass="GdiObj"; 이 문자열은 윈도우 클래스의 이름과 윈도우의 타이틀 바에 사용되는데 당연히 바꿔 주어야 할 부분이므로 앞으로 이 부분에 대해서도 설명을 생략하도록 한다. 이 소스에서 WM_PAINT 메시지를 보면 BeginPaint로 DC를 얻은 후 Rectangle 함수로 사각형을 그리고 EndPaint로 그리기를 종료하고 있다. DC를 만든 후 어떠한 GDI 오브젝트도 만들거나 선택하지 않고 곧바로 사각형을 그렸으므로 이 때 사용되는 GDI 오브젝트는 모두 디폴트 GDI 오브젝트이다. 즉 선은 검정색의 가는 선으로 그려지고 사각형 안쪽의 채워지는 면은 단순한 흰색일 뿐이다. 실행 결과는 다음과 같다. 보다시피 아주 썰렁하다. 이 상태에서 브러시를 변경하여 채워지는 면을 흰색이 아닌 다른 색으로 바꾸어 보도록 하자. 브러시를 직접 만들어 사용하는 실습은 잠시 후에 해 보기로 하고 일단 스톡 오브젝트를 사용한다. 다음 문장들을 추가해 보아라. LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam) { HDC hdc; PAINTSTRUCT ps; HBRUSH MyBrush,OldBrush; switch(iMessage) { case WM_PAINT: hdc=BeginPaint(hWnd,&ps); MyBrush=(HBRUSH)GetStockObject(GRAY_BRUSH); OldBrush=(HBRUSH)SelectObject(hdc,MyBrush); Rectangle(hdc,50,50,300,200); SelectObject(hdc,OldBrush); EndPaint(hWnd,&ps); return 0; case WM_DESTROY: PostQuitMessage(0); return 0; } return(DefWindowProc(hWnd,iMessage,wParam,lParam)); } 4줄이 추가되었다. 이제 다시 컴파일하고 실행해 보자. 결과는 다음과 같다. 흰색 사각형 대신 회색의 사각형이 그려졌다. 어째서 이런 그림이 그려졌는지 코드를 살펴보도록 하자. 우선 브러시 핸들을 저장할 변수 두 개를 선언한다. 그리고 GetStockObject 함수로 회색의 브러시 핸들을 얻은 후 이 브러시를 DC에 선택하되 MyBrush 변수에 대입하기 위해 (HBRUSH)형으로 캐스팅 해 주어야한다. GDI 오브젝트를 DC에 선택할 때는 다음 함수를 사용한다. HGDIOBJ SelectObject( HDC hdc, HGDIOBJ hgdiobj );첫번째 인수로 DC의 핸들을 주고 두번째 인수로 GDI 오브젝트의 핸들을 주면 DC에 해당 오브젝트를 선택해 준다. 이후부터 GDI는 그래픽을 출력할 때 선택된 오브젝트를 사용하게 된다. SelectObject가 리턴하는 값은 새로 선택되는 오브젝트 이전에 선택되어 있던 같은 종류의 오브젝트 핸들이다. 이 핸들값은 복구를 위해 반드시 별도의 변수에 저장해 두어야 한다. 위 예제에서는 이전 브러시의 핸들을 OldBrush 변수에 대입해 두었다. 회색 스톡 브러시를 선택한 후 Rectangle 함수로 사각형을 그렸으므로 사각형의 내부는 회색으로 채워지게 된다. 사각형을 그리고 난 후는 SelectObject를 한번 더 호출하여 원래의 GDI 오브젝트인 OldBrush를 복구해 주어야 한다. 그 이유에 대해서는 잠시 후에 다시 논해 보도록 하자. |
자동으로 다 해준다.
LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam) { HDC hdc; PAINTSTRUCT ps; HBRUSH MyBrush,OldBrush; switch(iMessage) { case WM_PAINT: hdc=BeginPaint(hWnd,&ps); MyBrush=(HBRUSH)GetStockObject(GRAY_BRUSH); OldBrush=(HBRUSH)SelectObject(hdc,MyBrush); Rectangle(hdc,50,50,300,200); SelectObject(hdc,OldBrush); EndPaint(hWnd,&ps); return 0; case WM_DESTROY: PostQuitMessage(0); return 0; } return(DefWindowProc(hWnd,iMessage,wParam,lParam)); }
(주쌤)
{
되돌리기 위해 OldBrush가 잠시 가지고 있는 것이다.
기존것을 원래대로 되돌려 놔야지 , 콘텍스트 스위칭 할때도 레지스터 값들을 마치 호출안한듯이 전부 되돌리는 작업을 했듯이 이도 똑같다.
SelectObject 는 기존의 것들을 반환한다라는 것이다.
그레이로 잠시 바뀌어 졌다라는 것이다.
}
완성된 코드를 보자. 커서와 이것저것 설정이 되어있는 윈 메인은 잠시 눈길을 피해주길 바란다.
#include <windows.h> |
현재 실행화면은,
이와 같고 저기 주석이 되있는 부분을 풀면,
이와 같다.
6-1-다. 색상
잠시 후에 다양한 색상과 모양의 펜, 브러시를 만들어 볼텐데 그 전에 윈도우즈에서 색상을 표현하는 방법에 대해 알아보자. 도스에서는 WHITE, YELLOW, RED 등의 매크로 상수로 색상을 표현했고 이 매크로들의 실제값은 0~15까지의 정수였었다. 도스에서야 기껏 16색상까지 사용할 수 있었으므로 각 색상마다 이름을 줄 수 있었지만 최대 천육백만가지 색상을 사용할 수 있는 윈도우즈 환경에서는 이런 간단한 방법을 쓸 수 없다. 윈도우즈에서는 색상값을 표현하기 위해 COLORREF라는 데이터형을 사용하는데 이는 다음과 같이 정의되어 있다. typedef DWORD COLORREF; 보다시피 COLORREF 형은 부호없는 32비트 크기의 정수형이며 8비트씩 빨간색, 초록색, 파란색의 농도를 나타내며 상위 8비트는 사용되지 않는다. 각 색상 요소는 1바이트의 크기를 가지므로 0~255까지의 농도를 표현할 수 있다. COLORREF형은 32비트 정수일 뿐이므로 직접 16진수로 표현할 수도 있다. 예를 들어 0는 검정색이 되며 0xff는 빨간색, 0xff0000은 파란색이 된다. 하지만 이 방법은 너무 기계적이고 초보자가 쓰기는 힘들므로 색상값을 만들 때는 통상 RGB 매크로 함수를 사용하며 이 매크로는 다음과 같이 정의되어 있다. #define RGB(r,g,b) ((COLORREF)(((BYTE)(r) | ((WORD)((BYTE)(g))<<8)) | (((DWORD)(BYTE)(b))<<16))) 세 개의 인수를 가지는데 각각 빨간색, 초록색, 파란색의 농도이며 이 세값을 조립하여 하나의 32비트 색상값을 만들어내는 간단한 비트 연산을 하고 있다. 비트 쉬프트, 비트 OR 연산자를 적절히 혼합한 문장인데 그리 어렵지 않게 이해가 될 것이다. 각 색상 요소가 얼마만큼 혼합되어 있는가에 따라 실제 색상이 결정되는데 RGB(255,0,0)는 빨간색, RGB(0,0,255)는 파란색이다. 세 요소가 모두 다 최대치인 RGB(255,255,255)는 흰색이며 반대로 RGB(0,0,0)는 검정색이다. 윈도우즈에서는 이와같이 RGB 매크로를 사용하여 색상값을 표현하며 COLORREF 형의 인수 자리에는 RGB 매크로를 사용하면 된다. 다음 세 매크로 함수는 COLORREF 형 변수값에서 각 색상요소의 농도를 분리해내는 함수이다. #define GetRValue(rgb) ((BYTE)(rgb)) #define GetGValue(rgb) ((BYTE)(((WORD)(rgb)) >> 8)) #define GetBValue(rgb) ((BYTE)((rgb)>>16)) |
6-1-라. 펜
펜은 선을 그을 때 사용되는 GDI 오브젝트이다. 펜을 변경하면 그려지는 선의 모양을 마음대로 변경할 수 있다. 그런데 윈도우즈가 제공하는 스톡 펜은 흰색, 검정색, 투명색 세 가지 뿐이며 파란색, 노란색 등의 원색 펜은 없다. 이런 펜을 사용하고자 할 때는 직접 만들어서 사용해야 한다. 펜을 만들 때는 다음 함수를 사용한다. HPEN CreatePen( int fnPenStyle, int nWidth, COLORREF crColor );세 개의 인수를 가지며 각 인수의 의미는 다음과 같다. fnPenStyle그려질 선의 모양을 정의한다. 이 값을 변경하면 실선뿐만 아니라 다양한 형태의 선을 만들 수 있다. 실선, 점선, 일점 쇄선 등등의 선 모양이 있다.
선의 폭을 지정한다. 디폴트 선의 굵기는 1이지만 이 값을 2나 3으로 변경해 주면 두꺼운 선을 그릴 수 있다. 단 이 값이 0일 경우는 맵핑 모드에 상관없이 무조건 1픽셀 두께의 선이 만들어진다. crColor선의 색상을 지정한다. COLORREF 형이므로 RGB 매크로 함수를 사용하면 된다. 리턴값으로는 만들어진 펜의 핸들을 돌려주므로 이 값을 잘 보관해 두어야 만들어진 펜을 사용할 수 있다. 모양, 굵기, 색상 세 가지 속성을 조합하면 아주 다양한 형태의 펜을 만들 수 있을 것이다. 그럼 펜을 만들어 사용해 보도록 하자. 별도의 예제를 만들 필요없이 GdiObj 프로젝트를 다음과 같이 수정한다. LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam) { HDC hdc; PAINTSTRUCT ps; HPEN MyPen, OldPen; switch(iMessage) { case WM_PAINT: hdc=BeginPaint(hWnd,&ps); MyPen = CreatePen(PS_SOLID, 5, RGB(0,0,255)); OldPen = (HPEN)SelectObject(hdc, MyPen); Rectangle(hdc,50,50,300,200); SelectObject(hdc, OldPen); DeleteObject(MyPen); EndPaint(hWnd,&ps); return 0; case WM_DESTROY: PostQuitMessage(0); return 0; } return(DefWindowProc(hWnd,iMessage,wParam,lParam)); } 실행 결과는 다음과 같다. CreatePen 함수를 호출하여 굵기 5의 파란색 실선 펜을 만든 후 이 펜의 핸들을 MyPen에 대입하였다. 그리고 SelectObject로 이 펜을 DC에 선택한 후 사각형을 그렸으므로 사각형의 테두리는 분명히 굵은 파란색으로 그려질 것이다. CreatePen 함수 자체가 그다지 어렵지 않으므로 이 코드는 아주 쉽게 이해가 갈 것이다. GDI 오브젝트는 사용한 후 반드시 삭제해 주어야 한다. 왜냐하면 GDI 오브젝트도 메모리를 사용하기 때문이다. 메모리를 할당한 후 반드시 해제해 주어야 하는 것과 마찬가지로 GDI 오브젝트도 사용이 끝나면 해제해 주어야 하는 것이 원칙이다. 만약 해제해 주지 않으면 시스템의 메모리를 갉아먹게 될 것이다. GDI 오브젝트를 삭제할 때는 다음 함수를 사용한다. BOOL DeleteObject( HGDIOBJ hObject );삭제하고자 하는 GDI 오브젝트의 핸들만 인수로 넘겨주면 된다. 단, 이때 주의할 것은 DC에 현재 선택되어 있는 GDI 오브젝트는 삭제할 수 없다는 점이다. 현재 사용되고 있는 객체를 함부로 삭제하도록 내버려 둘 수는 없기 때문에 생긴 일종의 안전 장치 역할을 하는 규정이다. 그래서 삭제를 하기 전에 먼저 DC에 선택된 객체를 선택 해제해 주어야 하는데 선택을 해제시켜주는 별도의 함수는 제공되지 않으므로 다른 GDI 오브젝트를 선택해 주는 방법을 사용한다. 이런 이유로 OldPen이라는 핸들을 만든 후 이 핸들에 MyPen이 선택되기 전의 펜 핸들을 저장해 두고 MyPen을 삭제하기 전에 OldPen을 다시 선택해 주는 것이다. SelectObject(hdc, OldPen); DeleteObject(MyPen); OldPen이 DC에 선택되면 자연히 현재 선택되어 있는 MyPen이 선택해제될 것이고 따라서 MyPen을 안전하게 삭제할 수 있다. 이 두 줄은 다음과 같이 한줄로도 작성할 수 있다. DeleteObject(SelectObject(hdc, OldPen)); SelectObject가 이전 핸들값을 리턴해 주므로 OldPen을 선택함과 동시에 리턴되어져 나오는 MyPen을 삭제하는 것이다. 이때 사용되는 OldPen은 이전에 선택되어 있던 펜을 보존한다기보다는 단순히 MyPen을 선택해제하기 위한 용도로 사용된 것이다. GDI 오브젝트를 만들고 사용하는 일반적인 절차는 다음과 같다. 펜뿐만 아니라 브러시, 폰트 등 모든 GDI 오브젝트는 일반적으로 이런 절차를 거쳐 만들어지고 사용된다. 물론 속도를 위해 약간의 다른 형식을 사용하는 방법이 있기는 하지만 여기서는 원론적인 내용만 이해하도록 하자. |
완성된 코드는 이러하며,
#include <windows.h> |
실행 화면은 이러하다. (PS_SOLID)
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
MyPen = CreatePen(PS_SOLID, 5, RGB(0, 0, 255));
OldPen = (HPEN)SelectObject(hdc, MyPen);
Rectangle(hdc, 50, 50, 300, 200);
SelectObject(hdc, OldPen);
DeleteObject(MyPen);
EndPaint(hWnd, &ps);
return 0;
자 이부분, 해석 해보자.
1. BeginPaint() 이제 슬슬 지겹다. DC를 구하느라 쓴다. 또한 WM_PAINT 내부에서만 사용해야 한다.
2. CreatePen() 는 펜은 GDI가 선을 그릴 때 사용하는 오브젝트 이며 DC에 선택된 펜의 속성대로 선이 그어진다. 디폴트 펜은 굵기 1의 검정색 실선이나
펜을 만들어 DC로 전송하면 만들어진 펜대로 선이 그어진다. 다 사용하고 난 후에는 브러쉬와 마찬가지로 DeleteObject()로 펜을 삭제해야 한다.
함수 원형
: HPEN CreatePen( int fnPenStyle, int nWidth, COLORREF crColor );
첫 번째 인자에 어떤 펜의 형태를 선택할 것인가를 선택할 수 있는데, 디파인 되어있다.
fnPenStyle
그려질 선의 모양을 정의한다. 이 값을 변경하면 실선뿐만 아니라 다양한 형태의 선을 만들 수 있다. 실선, 점선, 일점 쇄선 등등의 선 모양이 있다.
![]() | ![]() | ![]() | ![]() | ![]() |
PS_SOLID | PS_DASH | PS_DOT | PS_DASHDOT | PS_DASHDOTDOT |
3. Rectangle() 는 지정한 사각형을 그린다.
함수 원형
: BOOL Rectangle(HDC hdc, int nLeftRect, int nTopRect, int nRightRect, int nBottomRect);
사각형의 변은 현재 DC에 선택된 펜으로 그려지며 내부는 현재 DC에 선택된 브러쉬로 채워진다. 터보C의 Rectangle() 와는 달리 내부를 채우므로
도스 프로그래밍을 해 본 사람은 주의해야 한다. 내부를 채우지 않으려면 NULL_BRUSH 스톡 오브젝트를 선택한 후 사각형을 그려야 한다.
Ellipse의 예제를 참고하면 더 자세히 알 수 있다.
<순서>
6-1-마. 브러시
브러시는 채워지는 면을 채색하는 용도로 사용된다. 사각형의 안쪽이나 원의 내부 또는 다각형의 내부를 채색할 때 현재 DC에 선택된 브러시가 사용된다. 스톡 브러시에는 회색, 흰색, 검정색 등의 단색 브러시가 있으므로 이 브러시들은 별도로 만들지 않아도 사용할 수 있다. 이 외의 브러시는 직접 만들어 사용해야 한다. 만드는 함수만 다를 뿐 사용 방법은 앞에서 살펴본 펜과 동일하므로 길게 설명하지 않기로 하자. 브러시를 만드는 함수는 다음과 같다. HBRUSH CreateSolidBrush( COLORREF crColor ); HBRUSH CreateHatchBrush( int fnStyle, COLORREF clrref );첫번째 함수는 단색의 브러시만을 만들 수있으며 브러시의 색상만 인수로 전달해 주면 된다. 두번째 함수는 색상뿐만 아니라 무늬도 같이 지정할 수 있다. 지정할 수 있는 무늬의 종류는 다음과 같다.
두 함수 모두 리턴하는 값은 만들어진 브러시의 핸들이다. 그럼 이제 브러시를 만드는 함수도 배웠으니 펜과 브러시를 동시에 만들어서 사용해 보도록 하자. GdiObj 프로젝트의 WM_PAINT 메시지를 다음과 같이 수정한다. LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam) { HDC hdc; PAINTSTRUCT ps; HBRUSH MyBrush,OldBrush; HPEN MyPen, OldPen; switch(iMessage) { case WM_PAINT: hdc=BeginPaint(hWnd,&ps); MyBrush=CreateHatchBrush(HS_BDIAGONAL, RGB(255,255,0)); OldBrush=(HBRUSH)SelectObject(hdc,MyBrush); MyPen=CreatePen(PS_SOLID, 5, RGB(0,0,255)); OldPen=(HPEN)SelectObject(hdc, MyPen); Rectangle(hdc,50,50,300,200); SelectObject(hdc,OldBrush); SelectObject(hdc, OldPen); DeleteObject(MyBrush); DeleteObject(MyPen); EndPaint(hWnd,&ps); return 0; case WM_DESTROY: PostQuitMessage(0); return 0; } return(DefWindowProc(hWnd,iMessage,wParam,lParam)); } 파란색의 굵은 MyPen과 노란색의 줄무늬를 가지는 MyBrush를 각각 만들고 DC에 선택해 준 후 사각형을 그렸다. 실행 결과는 다음과 같다. 이런 식으로 펜과 브러시를 바꾸어 가며 그림을 그리면 얼마든지 다양한 모양의 그래픽을 그릴 수 있다. 코드가 워낙 간단하기 때문에 더 이상의 설명이 필요없을 것이다. 이 외에도 비트맵, 패턴 브러시를 만드는 함수들과 브러시에 관련된 좀 더 복잡한 이론들이 있지만 다음 기회에 배우기로 한다. |
소스코드는 이러하며,
#include <windows.h> |
실행 화면은 이러하다.
(HS_CROSS)
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
MyBrush = CreateHatchBrush(HS_BDIAGONAL, RGB(255, 255, 0));
OldBrush = (HBRUSH)SelectObject(hdc, MyBrush);
MyPen = CreatePen(PS_SOLID, 5, RGB(0, 0, 255));
OldPen = (HPEN)SelectObject(hdc, MyPen);
Rectangle(hdc, 50, 50, 300, 200);
SelectObject(hdc, OldBrush);
SelectObject(hdc, OldPen);
DeleteObject(MyBrush);
DeleteObject(MyPen);
EndPaint(hWnd, &ps);
return 0;
이 부분을 보면, 브러쉬와 펜을 같이 이용하여 사각형을 만들고 있는 모습이다.
앞에서 설명이 되어있으니 넘어가도록 하겠다.
6-2-가. 흑백에서의 그리기 모드
화면에 무엇인가가 그려져 있는 상황에서 그 위에 다른 무엇인가를 출력하면 원래 그려져 있던 그림은 새로 그려지는 그림에 덮여 지워진다. 디스플레이 표면은 2차원적인 평면이므로 새로운 그림이 그려지면 그 아래에 있던 그림이 지워질 수밖에 없으며 아주 당연하게 생각될 것이다. 그러나 이런 당연한 현상도 그리기 모드를 변경하면 달라진다. 그리기 모드란 도형이 그려질 때 원래 그려져 있던 그림과 새로 그려지는 그림과의 관계를 정의하는 것이다. 그리기 모드를 개념적으로 이해하기 위해 아주 단순한 흑백의 그래픽 환경을 가정해 보자. 무엇인가를 화면에 그린다는 것은 비디오 메모리에 그림의 이미지를 기록해 넣는 동작을 말하며 이 때 원래 비디오 메모리에 있던 값과 새로 써지는 값 사이의 관계를 생각해 볼 수 있다. 가장 단순하게는 새로 그려지는 값으로 원래 있던 값을 덮어 버리는 경우가 있으며 비트 논리 연산에 의해 두 값을 적당히 혼합하는 방법들도 있다. 다음 그림은 두 개의 그림을 4가지 비트 연산으로 합쳐본 것이다. 여기서 가로로 놓인 막대가 원래 그려져 있던 그림이며 세로로 놓여져 있는 막대가 새로 그려지는 그림이다. 첫번째의 COPY는 새로 그려지는 그림이 기존 그림을 덮어 버리는 것이다. 두번째의 OR은 두 그림의 대응되는 비트를 OR연산하여 새로 값을 써 넣는다. 즉 두 비트가 모두 1이거나 둘 중 하나라도 1이면 1이 쓰여지며 둘 다 0일 경우에만 0을 써 넣는다. 그래서 마치 새로 그려지는 그림이 셀로판지에 그려져 기존 그림위에 얹히는 것과 같은 효과를 낸다. AND연산을 할 경우 두 그림의 교집합 영역만 그려지며 XOR연산을 할 경우 두 그림 중 겹쳐지는 부분이 반전되는 효과를 가져온다. 흑백에서의 비트 연산은 0(검정색) 또는 1(흰색)만 있기 때문에 이렇게 이해하기 쉽지만 여러 가지 색상을 사용하는 컬러 그래픽 환경에서의 비트 연산은 이보다 훨씬 더 복잡하다. 하지만 개념적으로는 대응되는 비트끼리 흑백에서와 같은 형태의 연산을 하기 때문에 엄격하게 계산해 본다면 결과를 예측해 볼 수도 있다. |
6-2-나. 그리기 모드의 종류
윈도우즈에서 사용하는 디폴트 그리기 모드는 R2_COPY 모드이다. 그래서 그려지는 그림이 기존 그림을 덮어 버린다. 그리기 모드를 변경하는 함수와 현재 설정된 그리기 모드를 구하는 함수는 다음과 같다. int SetROP2( HDC hdc, int fnDrawMode );int GetROP2( HDC hdc ); 첫번째 인수는 그리기 모드를 변경(또는 조사)하고자하는 DC의 핸들이며 SetROP2 함수의 두번째 인수에 다음과 같은 그리기 모드값을 넘겨준다.
이 외에도 몇가지 그리기 모드가 더 있지만 주로 NOT연산자를 중간 중간에 넣은 것들이며 현실적으로 거의 사용되지 않는다. GetROP2 함수는 DC에 설정되어 있는 현재 그리기 모드값을 리턴해 준다. |
6-2-다. Ropmode
<ㅖ>그러면 반전모드를 사용하는 예제를 만들어 보자. 마우스로 선을 그리도록 하되 선이 그려지는 중간 과정을 보여주도록 한다. 즉 마우스 버튼을 누른 위치에서부터 시작해서 다시 마우스 버튼을 놓는 자리까지 선을 긋되 버튼을 누른채로 마우스를 움직이면 중간에 그려지는 선의 모양을 보여주도록 하는 것이다. 페인트 브러시 등의 그래픽 프로그램을 써 본 사람이라면 무슨 말인지 쉽게 이해할 수 있을 것이다. 소스는 다음과 같다.
실행중의 모습을 보이면 다음과 같다. 마우스 버튼을 누른 위치에서부터 놓은 위치까지 선을 긋되 버튼을 놓기전까지 선의 중간 모양을 계속해서 보여준다. WndProc의 선두부터 분석을 해 보도록 하자. 이 프로그램에서 사용하는 변수들은 다음과 같다.
이 중 ex,ey를 제외한 변수들은 계속 값을 보관해야 하므로 static 기억 부류로 선언되었다. 마우스 버튼을 누르면 bNowDraw를 TRUE로 만들어 선 그리기를 시작하며 버튼이 눌러진 좌표를 시작점, 끝점에 모두 대입해 준다. 즉 처음 선을 그리기 시작할 때는 시작점과 끝점이 같은 상태에서 시작하며 이 상태에서 마우스를 움직이면 끝점이 움직이면서 선이 늘어나게 된다. 마우스가 이동할 때는 먼저 bNowDraw값을 점검해 보고 이 값이 TRUE일 경우만 선을 긋는다. SetROP2 함수를 호출하여 그리고 모드를 R2_NOT로 변경하고 (sx,sy)-(oldx,oldy) 선을 먼저 지운다. 왜 이 좌표에 선을 그으면 선이 지워지는가 하면 이미 그려진 선 위에 반전 모드로 다시 선을 출력하면 선이 지워지는 XOR연산의 특수성때문이다. 이전 선을 지운 후 현 마우스 좌표를 구해 ex, ey에 대입하고 다시 새로운 선을 그린다. 새 선을 그린 후 oldx, oldy에 현재의 끝점을 대입해 주어 다음 마우스 이동시에 이 선을 지울 수 있도록 해준다. 마우스 버튼을 놓으면 확정된 선을 다시 그리고 bNowDraw를 FALSE로 변경하여 그리기를 끝낸다. 이렇게 설명을 읽어도 코드를 직접 분석해 보지 않으면 이해가 잘 되지 않으므로 코드를 자세히 뜯어 보기 바란다. WM_MOUSEMOVE 메시지의 SetROP2 함수 호출문을 삭제해 보면 왜 그리기 모드를 변경해야 하며, 그리기 모드가 왜 필요한가를 알 수 있을 것이다. 다음은 SetROP2 호출문을 삭제한 후의 실행 모습이다. 한번 출력한 선을 지우지 못하기 때문에 마우스가 움직이는 족족 새로운 선이 그려질 것이다. 이것은 분명히 우리가 바라는 결과와는 다른 것이다. 이동중에 계속 선의 모양을 보여주기 위해서는 그리기 모드라는 것이 반드시 필요하다. 선 하나가 그려진 상태에서 다음 위치로 마우스를 이동하는 과정을 그림으로 정리해 보았다. 이런 과정이 처음 마우스 버튼을 누르고부터 시작하여 bNowDraw가 FALSE가 될 때까지 계속 반복되며 마우스 버튼을 놓으면 비로소 종료된다. 주의하여 살펴볼 것은 새 위치에 선이 그려진 후 oldx, oldy가 ex,ey 즉, 현재 그려진 선의 끝 좌표를 대입받음으로써 다음번 마우스가 움직일 때 이 선이 지워지도록 해 준다는 점이다. |
원점을 찍고 마우스를 움직였다. 선이 따라왔을 것이다.
위로 다시 움직으면 첫 번째 선은 지워진다.
그 부분에 xor을 하면 선이 지워진다.
기존에 있는 선을 한 번 더 긋는데 그을 떄 xor을 하고 움직이는 것이다. 그러므로 우리 눈에는 선을 지우면서 움직이게 되는 것이다.
XOR 연산은 두 값의 각 자릿수를 비교해, 값이 같으면 0, 다르면 1을 계산한다.
0101 XOR 0011 = 0110
x = y ^ z;
그러하다.
즉, 선을 다시 그으니 xor 로 1과 1 값이 만나 0 이 나오는 것이다.
그러므로 지워진다.
완성된 소스는 이러하며,
#include <windows.h> |
6-3-가. 윈도우즈의 좌표체계
윈도우즈는 그래픽 기반의 GUI운영체제이며 모든 출력은 점 단위로 이루어진다. 픽셀(Picture Element)이란 그래픽을 이루는 최소단위이며 우리말로 번역하면 화소이다. 윈도우의 위치를 지정하거나 문자열을 출력하거나 반드시 출력 위치를 지정하는 좌표가 있어야 하며 좌표는 X,Y 두 축의 오프셋, 즉 원점으로부터의 거리로 구성된다. 예를 들어 화면의 (100,100)에 문자열을 출력한다면 이는 윈도우의 작업 영역 좌상단에서 X축으로나 Y축으로 100픽셀 만큼 떨어진 거리에 문자열이 출력된다는 뜻이며 원점과 출력된 문자열 사이에는 100개의 픽셀이 있다. 결론을 말하자면 윈도우즈의 좌표체계는 픽셀 단위를 사용하며 이는 실수 단위가 아닌 정수 단위로 계산이 이루어지는 디지털 컴퓨터의 특성상 불가피한 일이다. 그러나 때로는 이런 픽셀 단위의 좌표 체계가 프로그램에서 응용하기에 부적합할 수도 있다. 대표적으로 프린터로 출력을 보낼 때가 이에 해당한다. 화면은 72dpi의 낮은 저해상도를 가지지만 프린터는 보편적으로 600dpi의 높은 고해상도를 사용하며 어떤 프린터는 4800dpi까지 지원하기도 한다. 이런 해상도의 차이에 의해 두 장치의 출력 결과가 엄청나게 달라진다. 화면에 반지름 100픽셀의 원을 그렸을 때는 안경알만한데 이 원을 똑같은 반지름 100을 사용하여 프린터로 출력하면 콩알만하게 보일 것이다. 같은 100픽셀이라도 해상도가 낮은 화면에서는 크기가 크고 거칠게 그려지지만 해상도가 높은 프린터에서는 작고 섬세하게 그려지기 때문이다. 쉽게 이해가 가지 않으면 모눈 종이를 생각해 보면 된다. 눈이 큰 모눈 종이의 10칸과 눈이 작은 모눈 종이의 10칸의 크기가 같을 수 없지 않은가? 좌표의 증가 방향도 문제가 있다. 모니터의 좌표 체계는 좌상단이 (0,0)의 좌표가 되어 원점으로 사용되며 X축 좌표는 오른쪽으로 갈수록 증가하고 Y축 좌표는 아래쪽으로 갈수록 증가한다. 즉 모니터의 좌표계는 4/4분면에 위치하며 Y축의 증가 방향이 우리가 학교 다닐 때 배웠던 그래프와 거꾸로 되어 있다. 왜 이렇게 되어 있는지 굳이 이유를 댄다면 사람들은 오랫동안 글을 읽을 때 왼쪽에서 오른쪽으로, 위에서 아래로 읽는 습관을 가지고 있었기 때문에 여기에 맞추다 보니 그렇게 된 것이다. 이런 좌표계가 아주 자연스러워 보이겠지만 경우에 따라서는 4/4분면의 좌표 공간이 적합하지 못한 경우가 있는데 예를 들어 수학적인 그래프를 화면으로 출력하고자 할 경우가 이에 해당한다. 수학 좌표계는 대개 1/4분면에 그려지며 좌하단이 (0,0)의 원점이 되고 X축은 오른쪽으로 갈수록 증가하지만 Y축은 위쪽으로 갈수록 증가한다. 즉 모니터상의 좌표계와 수학 좌표계는 원점과 Y축 증가 방향이 다르다. 모니터에서의 좌표 단위와 좌표 공간이 실생활에서의 그것들과 차이가 있음으로 인해 좌표 체계를 변경해 주어야 할 경우가 있다. 이럴 때는 보통 출력 함수에서 Y축 좌표에 -1을 곱해 주어 증가 방향을 바꾸거나 원점에 일정한 값을 더해 평행 이동시켜 인위적으로 옮겨주는 방법을 사용했다. 하지만 윈도우즈는 이런 차이점을 맵핑 모드라는 매커니즘을 통해 쉽게 해결할 수 있도록 해 준다. 즉 좌표 체계를 바꾸는 방법을 운영 체제가 제공해 줌으로써 프로그래머의 부담을 덜어 주고 있는 것이다. 맵핑 모드를 꼭 변경해 주어야 할 경우는 그다지 흔하지 않지만 윈도우즈 시스템의 주요한 부분이므로 잘 알아두도록 하자. |
(주 쌤)
{
아래로 내려오면서 증가한다. 윈도우즈 대부분이 이렇다.
}
6-3-나. 맵핑 모드
맵핑 모드(mapping mode)란 주어진 좌표가 화면상의 실제 어디에 해당하는지를 결정하는 방법을 말한다. 윈도우즈에서 사용하는 좌표는 논리 좌표와 물리 좌표 두가지가 있다. 논리 좌표 : 윈도우즈의 내부에서 사용되는 좌표를 말한다. TextOut (100,100,...)에서 지정한 (100,100)이 곧 논리 좌표이며 논리 좌표의 실제 위치는 경우에 따라 달라진다. 그래픽 함수들이 사용하는 모든 좌표는 논리 좌표이며 좀 더 현실적으로 얘기한다면 DC핸들을 인수로 받아들이는 모든 함수는 논리 좌표를 사용한다. 물리 좌표 : 실제 화면에 출력되는 좌표이며 픽셀 단위를 사용한다. 물리적인 모니터의 픽셀이 단위이므로 물리 좌표 (100,100)은 그 위치가 정해져 있다. 윈도우를 관리하는 함수(또는 메시지) 에서 사용하는 좌표는 물리 좌표이다. 이 두가지 좌표의 관계를 정의하는 것이 맵핑 모드이다. 맵핑(Mapping)이란 용어는 두가지 사물의 일대일 대응 관계를 정의하는 공식 내지는 함수라고 할 수 있으며 윈도우즈에서의 맵핑 모드는 논리 좌표를 물리 좌표로 변환하는 방법을 의미한다. 어떠한 맵핑 모드가 사용되는가에 따라 (100,100)의 논리 좌표는 물리적으로 (10,10)이 될 수도 있고 (20,30)이 될 수도 있다. 그러나 우리는 이때까지 프로그래밍을 하면서 이런 대응관계를 전혀 느끼지 못했다. TextOut(100,100,...)하면 화면에 출력되는 위치는 어김없이 X축으로 100픽셀만큼 떨어지고 Y축으로 100픽셀만큼 떨어진 위치였다. 왜 그런가하면 윈도우즈가 디폴트로 사용하는 맵핑 모드에서는 논리 좌표와 물리 좌표가 일치되어 있기 때문에 어떠한 변환도 일어나지 않았기 때문이다. 물론 맵핑 모드를 변경하면 화면에 출력되는 실제좌표는 달라진다. 윈도우즈에서 사용되는 맵핑 모드에는 다음과 같은 것들이 있다.
디폴트 맵핑 모드는 픽셀 단위인 MM_TEXT이며 나머지 맵핑 모드는 밀리미터나 인치 등의 논리적인 단위를 사용한다. 끝에 있는 두 개의 맵핑 모드는 약간 특수한 맵핑 모드이며 별도로 연구해 볼 것이다. 맵핑 모드를 변경할 때는 SetMapMode 함수를 사용하며 현재 설정된 맵핑 모드를 알고 싶을 때는 GetMapMode함수를 사용한다. int SetMapMode( HDC hdc, int fnMapMode );int GetMapMode( HDC hdc ); 첫번째 인수는 DC의 핸들이며 SetMapMode의 인수 fnMapMode로 변경하고자 하는 맵핑 모드를 지정해 주면 된다. GetMapMode 함수는 DC에 설정되어 있는 맵핑 모드를 조사해 준다. |
(주쌤)
{
거의 안쓴다. 오락만들때 쓰인다.
]
나중에 써보자.
6-3-다. 윈도우와 뷰포트
맵핑 모드를 제대로 이해하려면 몇가지 용어에 대해 이해해야 한다. 우선 윈도우와 뷰포트에 대해 알아야 하며 원점과 확장에 대해서도 알아야 한다. 윈도우(Window)는 논리 좌표가 사용되는 표면을 말하며 그래픽 출력 함수는 윈도우에 그래픽을 출력한다. 뷰포트(Viewport)는 물리 좌표가 사용되는 영역을 말하며 실제로 사용자의 눈에 보이는 좌표 영역이다. TextOut(100,100,...) 함수는 윈도우 영역의 (100,100)에 문자열을 출력하며 이렇게 출력된 문자열은 뷰포트를 통해 우리 눈에 보이게 된다. 물론 뷰포트에서의 좌표는 맵핑 모드에 따라 달라진다. 요컨데 윈도우는 논리 좌표를 사용하는 영역이며 뷰포트는 물리 좌표를 사용하는 영역을 말한다. 원점(Origin)이란 좌표의 기준이 되는 점, 즉 (0,0)의 좌표를 말한다. 디폴트로 원점은 맵핑 모드에 상관없이 화면의 좌상단에 위치하고 있다. 그러나 수학좌표계를 표현하고자 할 경우는 원점이 화면의 좌상단에 있는 것보다 중앙에 있는 것이 더 적합하다. 그래서 필요에 따라 원점을 변경시킬 수 있도록 다음 두 함수를 제공한다. BOOL SetViewportOrgEx( HDC hdc, int X, int Y, LPPOINT lpPoint );BOOL SetWindowOrgEx( HDC hdc, int X, int Y, LPPOINT lpPoint ); 각각 뷰포트와 윈도우의 원점을 인수로 지정한 좌표 (X,Y)로 이동시킨다. 4번째 인수 lpPoint는 변경하기 전의 원래 원점값을 돌려받기 위해 사용하는데 원래의 원점값이 필요없을 경우는 NULL을 사용하면 된다. 두 원점을 동시에 이동시킬 필요는 없으며 둘 중 하나의 원점만을 옮겨도 원하는 결과를 얻을 수 있지만 보통 뷰포트의 원점을 이동시키는 것이 더 편리하다. 왜냐하면 윈도우의 원점은 논리 단위로 지정되며 뷰포트의 원점은 픽셀 단위로 표현되기 때문이다. 아무래도 논리 단위보다는 픽셀 단위가 더 사용하기는 쉽다. 원점을 이동시켜 수학좌표계와 완전히 동일한 좌표계를 가지도록 만들어 보고 여기에 부드러운 사인 곡선을 그려보도록 하자. Sine.cpp의 소스는 다음과 같다.
사인값을 구하는 수학 함수를 사용하므로 math.h를 포함시켰다. 두개의 변수 f와 y를 가지고 f를 -500도에서 1000도까지 변화시키며 y좌표를 구한 후 이 좌표에 점을 찍어 사인 곡선을 만든다. 출력 결과는 다음과 같다. 모든 일은 WM_PAINT 안에서 일어나므로 WM_PAINT 메시지를 분석해 보면 된다. 우선 WM_PAINT의 선두에서 맵핑 모드를 MM_LOENGLISH로 변경하였다. 그래서 그래픽 출력 함수에서 지정하는 단위는 모두 0.01인치가 되며 Y축이 위로 증가하므로 수학 좌표계와 같은 증가방향을 가지게 된다. 바로 아래에서 뷰포트의 원점을 (200,150)으로 변경하였다. 그래서 우리가 보는 화면은 (200,150)이 원점이 되는 수학좌표계와 동일한 공간이 된다. 맵핑 모드를 변경한 후 선을 두 개 그어 수학 좌표계의 축을 표시하도록 하였다. 좌표값에 음수가 사용되는 것이 이상하게 보일지도 모르겠지만 맵핑 모드와 원점이 변경되면 공간 자체가 실수 공간이기 때문에 음수도 당연히 사용될 수 있다. 축을 그린 후 사인 함수의 입력값인 f를 -500에서 1000까지 루프를 돌며 각 값의 사인값을 구해 점을 찍는다. sin 함수가 받아들이는 값이 라디안 값이므로 이 값을 각도로 바꾸기 위해 3.14를 곱하고 180으로 나누었다. 간단한 수학 공식이므로 혹시 이 식이 이해가 되지 않으면 책꽃이에 꽂아둔 정석이나 해법등의 수학 참고서를 보기 바란다. 아뭏든 우리는 이 예제를 통해 수학 좌표계를 만들었고 사인 곡선을 그렸다. 그럼 이제 이 예제를 조금씩 수정해 가면서 결과가 어떻게 달라지는지 보자. 먼저 맵핑 모드를 MM_HIENGLISH로 변경해 보자. 그러면 단위가 0.001인치로 더 작아지며 따라서 그려지는 그림은 훨씬 더 작게 그려진다. MM_TEXT나 MM_LOMETRIC 등의 맵핑 모드로도 바꾸어 보면 각 맵핑모드별로 그림의 크기가 달라진다는 것을 알 수 있다. 이번에는 원점을 변경해 보아라. SetViewportOrg 함수의 인수를 변경하면 원점이 변경한 곳으로 가 있게 될 것이다. 이때 원점이 변경되었다고 해서 축의 좌표가 달라지거나 점의 좌표가 달라져야할 필요는 전혀없다. 왜냐하면 모든 그래픽 함수들은 변경된 원점의 영향을 받기 때문이다. sin 함수를 cos난 tan 함수로도 변경해 보면 삼각함수 그래프들을 볼 수 있을 것이다.
맵핑 모드의 개념에 대해 알아 보았는데 지금까지 배운 내용들에 비해서는 다소 어렵다는 생각이 들 것이다. 오래전에 잊어버린 수학 얘기도 나오고 새로운 용어도 여러 개 등장해서 혼란스럽지나 않았는지 모르겠다. 아마 대부분의 사람이 "대충은 알겠는데... 글쎄" 하는 반응을 보일 것 같은데 그 정도면 충분하다. 다행히 맵핑 모드는 자주 사용되지 않으므로 당장 몰라도 되며 그다지 상세하게 알 필요도 없으므로 대충 읽어보고 다음에 필요할 때 더 공부해 보기 바란다. |
새로히 등장한 함수는 세개다.
BOOL SetViewportOrgEx( HDC hdc, int X, int Y, LPPOINT lpPoint );
BOOL SetWindowOrgEx( HDC hdc, int X, int Y, LPPOINT lpPoint );
이것과
int SetMapMode(HDC hdc, int fnMapMode);
SetMapMode() 는 조금의 부가적인 설명이 뒷받침되어야 한다.
윈도우즈에서 사용되는 좌표는 논리 좌표와 물리좌표 두가지가 존재한다.
※ 논리 좌표 : 윈도우즈 내부에서 사용되는 좌표를 말한다. TextOut(100, 100,...) 에서 지정한 (100, 100)이 곧 논리 좌표이며 논리 좌표의 실제 위치는
경우에 따라 달라진다. 그래픽 함수들이 사용하는 모든 좌표는 논리좌표이며 좀 더 현실적으로 얘기한다면 DC핸들을 인수로 받아들이는
모든 함수는 논리 좌표를 사용한다
※ 물리 좌표 :
완성된 소스는 이러하며,
#include <windows.h> |
실행 화면은 이렇다.
6-3-라. 가변 비율
윈도우즈에 포함된 시계 프로그램을 보면 윈도우의 크기에 비해 항상 일정한 비율을 유지하며 윈도우의 크기가 변경되면 시계의 크기도 같이 변경되어 항상 윈도우의 일정 영역을 차지한다.
이런 프로그램은 도대체 어떤 방법으로 작업 영역에 그려지는 크기를 일정하게 유지하는 것일까? 윈도우의 크기가 변할 때마다 그림을 그리는 코드를 바꾸거나 좌표를 일정 비율로 곱해주는 것이 아닐까 하고 추측되겠지만 그렇지는 않다. 그림을 그리는 코드나 좌표는 항상 일정하지만 윈도우 확장을 변경함으로써 전체 좌표계의 범위를 조정해 주는 방법을 사용한다. 윈도우 확장을 조정할 수 있는 맵핑 모드에는 MM_ISOTROPIC과 MM_ANISOTROPIC 두 가지가 있으며 나머지 맵핑 모드에서는 윈도우 확장을 변경할 수 없다. 시계 프로그램과 똑같은 원리로 항상 일정한 크기를 유지하는 프로그램을 Aniso.dsw라는 이름으로 만들고 WndProc에 다음 코드를 작성해 보자.
그리고 프로그램을 컴파일한 후 실행해 보면 이 프로그램도 과연 시계 프로그램과 마찬가지로 윈도우 크기를 변경함에 따라 그림의 크기도 변경된다. 프로그램 실행중의 모습은 다음과 같다.
이렇게 BOOL SetViewportExtEx( HDC hdc, int nXExtent, int nYExtent, LPSIZE lpSize ); 윈도우 확장은 논리적인 좌표 범위를 지정하며 이 프로그램의 경우 (160,100)으로 설정하였다. 그래서 그림을 그리는 함수들은 모두 (160,100) 안쪽의 좌표를 사용하며 (160,100)이 우하단의 좌표가 된다. 뷰포트의 확장은 그림이 화면으로 출력되는 뷰포트의 좌표 범위를 말한다. 이 프로그램에서는 뷰포트 확장을 작업 영역의 우하단 점까지로 확장한다. 뷰포트의 우하단이 윈도우의 우하단과 맵핑되므로 윈도우의 (160,100)은 항상 뷰포트의 우하단과 대응되며 그 중간 점들은 적당한 비율의 좌표와 맵핑되어 윈도우의 크기에 상관없이 항상 일정한 비율의 크기를 유지하는 것이다. MM_ANISOTROPIC 맵핑 모드는 이런식으로 X,Y 어느 방향으로나 확장을 임의 설정할 수 있는 맵핑 모드이다. 이에 비해 MM_ISOTROPIC은 확장을 마음대로 변경할 수 있도록 해 주기는 하되 항상 가로, 세로 종횡비를 일정하게 유지시켜준다. 그래서 확장에 따라 그림이 작아지거나 커지기는 하지만 찌그러지지는 않도록 해 준다. 위 코드에서 맵핑 모드를 MM_ISOTROPIC으로 변경해 보면 무슨 말인지 쉽게 이해할 것이다. 윈도우즈의 시계 프로그램도 사실은 MM_ISOTROPIC 맵핑 모드를 사용하기 때문에 시계의 크기가 변할지라도 항상 원모양을 유지하며 타원이 되지는 않는다. 여기까지 그리기 모드, 맵핑 모드, 윈도우와 뷰포트에 대해 알아보았고 관련 함수들에 대해서도 소개했다. 그런데 이 즈음에서 Win32 API 함수들의 이름을 잘 관찰해 보면 Set~ 함수가 있으면 Get~ 함수가 반드시 존재한다는 법칙을 발견할 수 있을 것이다. 즉 SetMapMode 함수가 있으면 GetMapMode 함수가 있고 SetROP2 함수가 있으면 GetROP2 함수도 존재한다. 물론 각 함수의 쌍은 같은 대상을 설정/조사하는 함수쌍인데 대부분의 API 함수들은 Get/Set 함수가 쌍으로 존재한다. |
이건 지금은 넘어간다.
6-4-가. bitmap.dsw
점, 선, 원 등을 그리는 작도 함수를 사용하면 어떤 그래픽이든지 그릴 수 있다. 사실 극단적으로 말하자면 점을 찍는 SetPixel만 사용해도 못그릴 그림이 없는 셈이다. 그러나 작도로 그리는 그래픽은 그 속도는 둘째로 치더라도 복잡한 그림을 나타내기에는 무리가 많다. 특히 사진같은 정밀한 그래픽을 프로그램 실행중에 일일이 그린다는 것은 굉장히 비합리적이다. 다음과 같은 그림을 작도 함수로 직접 그린다고 생각해 보라. 과연 가능하겠는가? 그래서 복잡한 그림을 출력해야 할 경우는 미리 그려진 비트맵을 사용한다. 페인팅 툴을 사용해 출력하고자 하는 그림을 미리 그려 두거나 아니면 스케너와 같은 장비로 그림을 입력받아 두고 프로그램에서는 이 그림을 사용하는 것이 일반적이다. 복잡한 그림을 출력하는 용도 외에도 비트맵은 넓은 활용 범위를 가지고 있다. 화면의 일정영역을 복사해서 옮기기도 하고 잠시 보관해 두기도 하며 화려한 애니메이션에 활용되기도 한다. 그럼 일단 비트맵을 읽어와 화면으로 출력하는 프로그램을 한번 만들어 보자. 리소스를 임포트하는 생소한 실습을 해야 하므로 단계를 따라 프로젝트를 만들어 보자. 1Bitmap.dsw 프로젝트를 만들고 ApiStart.txt를 복사하여 Bitmap.cpp를 만든 후 프로젝트에 포함시킨다. 일단 Bitmap.cpp의 lpszClass만 "Bitmap"으로 변경시키고 컴파일해 보자. 그러면 First.exe와 같은 실행 파일이 만들어질 것이다. 2비트맵 출력에 사용될 실습용 비트맵을 준비한다. 아무 비트맵이나 상관없지만 여기서는 윈도우즈 3.1에 포함되어 있는 Arches.bmp 파일을 사용한다. 사정에 따라 아무 비트맵이나 사용해도 상관없다. 단 그래픽 카드에는 맞아야 하므로 16색상 비트맵을 준비하는 것이 좋을 것 같다. 준비한 비트맵 파일을 Bitmap 프로젝트 디렉토리에 복사해 둔다. 3비트맵 리소스를 만든다. 비트맵 편집기를 사용할 경우 직접 비트맵을 만들 수도 있지만 여기서는 이미 만들어져 있는 비트맵을 임포트(Import)해와 사용할 것이다. 임포트란 남이 만들어 놓은 리소스를 영원히 빌려오는 동작이다. Insert/Resource를 선택하면 다음 대화상자가 나타난다. 이 대화상자에서 리소스 타입을 선택하면 새로운 리소스를 만드는 것이므로 그렇게 하지말고 Import 버튼을 누른다. 그러면 다음과 같은 임포트 대화상자가 나타날 것이다. 이 대화상자에서 미리 준비해 두었던 비트맵 파일을 선택한다. 그러면 이 비트맵을 IDB_BITMAP1이라는 ID로 임포트해오며 Script1.rc라는 리소스 스크립트 파일을 만들어 줄 것이다. 이 스크립트 파일을 Bitmap.rc로 저장하고 Project/Add To Project/Files메뉴 항목을 사용하여 프로젝트에 포함시킨다. 비트맵 파일을 임포트하고 리소스 파일을 만든 것이다. 만약 직접 비트맵을 만드려면 Insert/Resource 메뉴에서 Bitmap을 선택한 후 New 버튼을 눌러 새 비트맵을 그리면 된다. 4비트맵 리소스를 읽어와 출력하도록 코드를 수정한다.
리소스를 만들었으므로 resource.h를 포함시켜 주어야 한다. 일단 실행 결과를 보이면 다음과 같다. (0,0)위치에 비트맵이 출력되었다. 코드를 보면 다소 생소한 함수들이 사용되고 있는데 이 코드의 의미는 잠시 후에 알아보자. |
메뉴와 같은 원리로 비트맵도 숫자로 디파인 되어있다.
101 하면 현재 그림이 뜬다. 라는 소리이다.
#include "resource.h" LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam) { HDC hdc,MemDC; PAINTSTRUCT ps; HBITMAP MyBitmap, OldBitmap; switch(iMessage) { case WM_PAINT: hdc = BeginPaint(hWnd, &ps); MemDC=CreateCompatibleDC(hdc); MyBitmap=LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BITMAP1)); OldBitmap=(HBITMAP)SelectObject(MemDC, MyBitmap); BitBlt(hdc, 0,0,123,160,MemDC,0,0,SRCCOPY); SelectObject(MemDC,OldBitmap); DeleteObject(MyBitmap); DeleteDC(MemDC); EndPaint(hWnd, &ps); return 0; case WM_DESTROY: PostQuitMessage(0); return 0; } return(DefWindowProc(hWnd,iMessage,wParam,lParam)); }
(주쌤)
{
LoadBitmap() 가 메모리에 비트맵을 올리고
BitBlt() 로 화면에 표시하는 함수 이다. 용량이 크기 때문에 띄우는데 오랜 시간이 걸린다.
이 인자 네개는 0, 0 memDc를 찍어줄 시작 좌표이고 그 뒤의 0, 0 가로, 세로 크기 이다. 우리가 숫자 바꿔보자.
메모리 올리고 DC 화면에 출력하고 다시 딜리트 시키고. 느리다.
올리는 작업을 WM_PAINT에 보내고 딜리트 작업을 디스트로이에 보낸다.
즉, 종료할때까지 해제를 안시킨다. 즉, 프로그램이 실행될때 메모리는 잡아먹지만 속도를 얻는다.
}
완성된 코드는 이러하며,
#include <windows.h>
#include "resource.h"
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
HINSTANCE g_hInst;
LPSTR lpszClass = L"First";
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdParam, int nCmdShow)
{
HWND hWnd;
MSG Message;
WNDCLASS WndClass;
g_hInst = hInstance;
WndClass.cbClsExtra = 0;
WndClass.cbWndExtra = 0;
WndClass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
WndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
WndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
WndClass.hInstance = hInstance;
WndClass.lpfnWndProc = (WNDPROC)WndProc;
WndClass.lpszClassName = lpszClass;
WndClass.lpszMenuName = NULL;
WndClass.style = CS_HREDRAW | CS_VREDRAW;
RegisterClass(&WndClass);
hWnd = CreateWindow(lpszClass, lpszClass, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
NULL, (HMENU)NULL, hInstance, NULL);
ShowWindow(hWnd, nCmdShow);
while (GetMessage(&Message, 0, 0, 0))
{
TranslateMessage(&Message);
DispatchMessage(&Message);
}
return Message.wParam;
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam)
{
HDC hdc, MemDC;
PAINTSTRUCT ps;
HBITMAP MyBitmap, OldBitmap;
switch (iMessage)
{
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
MemDC = CreateCompatibleDC(hdc);
MyBitmap = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BITMAP1));
OldBitmap = (HBITMAP)SelectObject(MemDC, MyBitmap);
BitBlt(hdc, 0, 0, 1000, 1000, MemDC, 0, 0, SRCCOPY);
SelectObject(MemDC, OldBitmap);
DeleteObject(MyBitmap);
DeleteDC(MemDC);
EndPaint(hWnd, &ps);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return(DefWindowProc(hWnd, iMessage, wParam, lParam));
}
실행 화면은 이렇다.
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
MemDC = CreateCompatibleDC(hdc);
MyBitmap = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BITMAP1));
OldBitmap = (HBITMAP)SelectObject(MemDC, MyBitmap);
BitBlt(hdc, 0, 0, 1000, 1000, MemDC, 0, 0, SRCCOPY);
SelectObject(MemDC, OldBitmap);
DeleteObject(MyBitmap);
DeleteDC(MemDC);
EndPaint(hWnd, &ps);
return 0;
이 부분을 해석해 보자.
(주쌤)
{
현재 우리는 캡스톤 프로젝트롤 하는 중이다.
}
6-4-나. 메모리 DC
윈도우즈는 비트맵을 곧바로 화면 DC로 출력하는 함수는 제공하지 않는다. 비트맵을 출력하는 함수라면 아마 다음과 같은 형태를 상상할 수 있을 것이다. OutBitmap(hdc,x,y,비트맵 이름); 이런 함수가 있다면 무척 편리하게 비트맵을 출력할 수 있겠지만 안타깝게도 이런 함수는 없다. 왜냐하면 비트맵은 크기가 큰 데이터 덩어리이며 따라서 출력 속도가 형편없이 느리기 때문에 화면으로 곧바로 출력할 경우 여러 가지 꼴사나운 현상이 발생할 수 있기 때문이다. 게다가 다른 좋은 대안이 있기 때문에 직접 화면으로 출력하는 방법은 쓰지 않는다. 그 대안이 바로 여기서 논하고자 하는 메모리 DC이다. 메모리 DC란 화면 DC와 동일한 특성을 가지며 그 내부에 출력 표면을 가진 메모리 영역이다. 메모리에 있기는 하지만 화면 DC에서 사용할 수 있는 모든 출력을 메모리 DC에서도 할 수 있다. 선, 면, 원 등의 작도 함수는 물론 화면 DC에서는 불가능한 것까지도 가능하다. 그래서 메모리 DC에 먼저 그림을 그린 후 사용자 눈에 그려지는 과정은 보여주지 않고 메모리 DC에서 작업을 완료한 후 그 결과만 화면으로 고속 복사하는 방법을 많이 사용한다. 사용자 눈에 화면이 그려지는 과정을 보여주는 것은 그리 깔끔한 모양은 아니다. 여기서 우리가 하고자 하는 비트맵 출력을 위해서도 반드시 메모리 DC를 사용해야 한다. 비트맵도 일종의 GDI 오브젝트이지만 화면 DC에서는 선택할 수 없으며 메모리 DC만이 비트맵을 선택할 수 있다. 그래서 메모리 DC에서 먼저 비트맵을 읽어온 후 화면 DC로 복사하는 것이다. 메모리 DC를 만들 때는 다음 함수가 사용된다. HDC CreateCompatibleDC( HDC hdc );인수로 화면 DC의 핸들을 주면 이 화면 DC와 동일한 특성을 가지는 DC를 메모리에 만들어 그 핸들값을 리턴해 준다. 동일한 특성을 가진다(=호환된다)는 말은 사용하는 색상수, 색상면(plane)이 같다는 뜻이다. 호환되지 않는 DC끼리는 정보를 공유할 수 없기 때문에 화면 DC와 호환되는 메모리 DC를 만들어야 한다. 메모리 DC를 만든 후에는 비트맵을 읽어온 후 이 비트맵을 메모리 DC에 선택해 준다. 선택하는 방법은 여타의 GDI 오브젝트와 마찬가지로 SelectObject 함수를 사용하며 비트맵을 읽어올 때는 LoadBitmap 함수를 사용한다. HBITMAP LoadBitmap( HINSTANCE hInstance, LPCTSTR lpBitmapName );첫번째 인수는 비트맵 리소스를 가진 인스턴스의 핸들이며 두번째 인수는 비트맵 리소스의 이름이다. 읽어온 비트맵을 SelectObject 함수로 메모리 DC에 선택하면 메모리 DC의 표면에는 리소스에서 읽어온 비트맵이 그려져 있을 것이다. 이제 남은 일은 메모리 DC에 그려진 비트맵을 화면으로 복사하기만 하면 된다. |
(주쌤)
{
메모리 속도가 굉장히 빠르므로 화면에 표시되는 속도는 굉장히 느린다ㅔ
메모리에 써지니까 굉장히 빠른거다,.
한꺼번에 메모리를 푸는 것이다.
그래픽이 개입하면 무조건 느리므로 이런식으로 조금이라도 느린것을 상쇄시킬수 있다라는 것이다.
일종의 버퍼링이라고 한다 이것을.
메모리에 비트맵을 복사해놓고 복사해오는 것이다.
}
(주쌤)
{
진짜 복사한다라는 말이 아니고,
가상의 공간을 현재 화면 크기에 맞춰 생성한다라는 뜻이다.
}
(주쌤)
{
로드 비트맵을 호출해야 비트맵이 진짜로 적재가 된다.
}
앞에 분석에서 확인 되었다.
6-4-다. BitBlt
BitBlt 함수는 DC간의 영역끼리 고속 복사를 수행한다. 메모리 DC의 표면에 그려져 있는 비트맵을 화면 DC로 복사함으로써 비트맵을 화면으로 출력한다. 원형은 다음과 같다. BOOL BitBlt( HDC hdcDest, int nXDest, int nYDest, int nWidth, int nHeight, HDC hdcSrc, int nXSrc, int nYSrc, DWORD dwRop );첫번째 인수는 복사 대상 DC이며 다음 네 개의 인수는 복사 대상의 XYWH이며 PSrcDC가 복사원의 DC이다. xSrc, ySrc는 복사원의 좌표이되 BitBlt는 비트맵의 크기를 변경시키지 않고 복사를 수행하므로 폭과 높이는 복사 대상에서 한번만 지정하고 복사원에서는 이 값을 그대로 사용한다. 소스를 보면 MemDC의 0,0위치를 복사 대상의 0,0위치에 폭 123, 높이 160만큼 복사한다. BitBlt의 마지막 인수 dwRop는 레스터 연산 방법을 지정하며 SRCCOPY를 쓰면 복사원을 그대로 복사 대상으로 복사한다. dwRop에 다른 값을 사용하면 기존 그림에 겹친다거나 반전시킬 수도 있다. 앞에서 배운 그리기 모드와 개념적으로 유사하며 다음과 같은 값들이 가능하다.
비트맵 출력이 끝난 후에는 비트맵 자체와 메모리 DC를 해제해 주어야 한다. 비트맵은 GDI 오브젝트이므로 DeleteObject 함수로 지우면 되고 메모리 DC는 DeleteDC라는 별도의 함수를 사용하여 지운다. |
BitBlt(hdc, 0, 0, 1000, 1000, MemDC, 0, 0, SRCCOPY);
BOOL BitBlt( HDC hdcDest, int nXDest, int nYDest, int nWidth, int nHeight, HDC hdcSrc, int nXSrc, int nYSrc, DWORD dwRop );
BitBlt(그림 x 좌표, 그림 y 좌표, 그림 넓이, 그림 높이, 그림 그려진 메모리 DC, 그림 시작 x 좌표, 그림 시작 y 좌표, 스타일);
(주쌤)
{
확대 축소라는 개념은 없고 그냥 짤려서 복사 해오는 것이다.
확대 축소하는 함수는 뒤에 나온다.
BLACKNESS
화면에 안나오는 부분이 검은색으로 나온다라는 소리이다.
}
6-4-라. StretchBlt
BOOL StretchBlt( HDC hdcDest, int nXOriginDest, int nYOriginDest, int nWidthDest, int nHeightDest, HDC hdcSrc, int nXOriginSrc, int nYOriginSrc, int nWidthSrc, int nHeightSrc, DWORD dwRop ); BitBlt와 마찬가지로 DC간의 복사를 수행하지만 복사 후에 크기가 변경된다는 점이 다르다. 인수를 보면 복사 대상과 복사원이 모두 폭과 높이를 가지고 있다. 복사원의 지정한 영역이 복사대상의 지정한 영역의 크기만큼 확대되어 출력된다. 물론 복사대상의 영역이 복사원보다 더 좁다면 축소가 발생할 것이다. 예제의 BitBlt 호출문을 다음과 같이 변경해 보자. StretchBlt(hdc,0,0,246,320,MemDC,0,0,123,160,SRCCOPY); 이 코드에서는 123,160의 폭을 가지는 영역을 246, 320 영역에 복사했으므로 비트맵이 두배의 크기로 확장된다. |
{
얘가 확대 축소하는 애다.
뒤에 있는 애가 원본 좌표다.
다섯개로 나누면된다.
맨앞에는 무조건 dc 고
그룹이
(dc, 5 개(화면) , 5 개(비트맵) , )
원본그림의 가로 세로 등
확대 축소가 가능하다.
}
완성된 코드는
#include <windows.h> |
실행화면은
바꾸어서,
StretchBlt(hdc, 0, 0, 800, 800, MemDC, 0, 0, 400, 400, SRCCOPY);
6-4-마.비트맵 만들기
비트맵은 보통 페인트 샵이나 포토샵 또는 그림판 등의 그래픽 편집 툴로 만들어 사용하거나 아니면 미리 만들어져 있는 이미지를 구해 사용하지만 간단한 비트맵이라면 개발자 스튜디오에서 직접 만들 수도 있다. Insert/Resource 항목을 선택한 후 리소스 목록에서 Bitmap을 선택하거나 리소스 뷰가 있을 경우 리소스 뷰의 팝업 메뉴에서 Insert Bitmap 항목을 선택하면 새 비트맵을 만들 수 있다. IDB_BITMAP2라는 디폴트 ID로 48*48 크기의 16색상 비트맵을 만들 수 있도록 비트맵 편집기가 열리며 도구 툴바와 색상 팔레트가 같이 열릴 것이다. 펜, 브러시, 문자툴, 선택툴 등 그림판에서 볼 수 있는 각종 툴들이 준비되어 있어 마치 그림판을 사용하듯이 비트맵을 그리면 된다. 비트맵의 크기나 색상, ID를 변경하려면 팝업 메뉴 편집기의 빈 여백을 더블클릭하여 속성 편집 윈도우를 열어 수정하면 된다. 이렇게 리소스에 추가된 비트맵은 언제든지 LoadBitmap 함수로 읽어올 수 있고 BitBlt 함수로 화면으로 출력할 수 있다. 비트맵을 만드는데는 별다른 기술이 필요한 것은 아니며 단지 약간의 예술적 소질만 있으면 된다. 이상으로 비트맵 리소스를 화면으로 출력하는 방법에 대해 아주 간단하게 알아 보았다. 비트맵은 단순한 장식외에도 여러 가지 활용용도가 있으며 그 기법들도 아주 고난도에 해당한다. 화면 영역 보관, 가상화면, 화면 확대, 더블 버퍼링, 투명 비트맵, 그래픽 파일 입출력 등등의 기법들이 있는데 비트맵에 관한 고급 기법들에 대해서는 18장에서 본격적으로 연구해 볼 것이다. |
내가 직접 비트맵을 그려서 윈도우 화면에 띄워보자.
완성 시킨 소스는 이러하며
#include <windows.h> |
픽셀단위로 그려서 굉장히 작으므로 8배 확대 시킨 화면이다.
폰트도 펜이나 브러시와 마찬가지로 GDI 오브젝트이다. 폰트에 대한 지정을 하지 않고 문자열을 출력하면 디폴트로 시스템 폰트를 사용하지만 폰트를 만들고 DC로 전송한 후 문자열을 출력하면 DC에 전송된 폰트를 사용하여 문자열을 출력한다. 그래서 문자열을 원하는 폰트로 출력하고자 한다면 먼저 폰트 오브젝트를 만들어 DC로 전송해 주어야 한다. 6-5-가. CreateFont폰트를 만들기 위해서는 CreateFont 함수를 사용하며 이 함수가 리턴해 주는 핸들을 HFONT형의 변수에 대입해 주면 된다. CreateFont 함수는 다음과 같이 아주 복잡한 모양을 가지고 있다. HFONT CreateFont( int nHeight, int nWidth, int nEscapement, int nOrientation, int fnWeight, DWORD fdwItalic, DWORD fdwUnderline, DWORD fdwStrikeOut, DWORD fdwCharSet, DWORD fdwOutputPrecision, DWORD fdwClipPrecision, DWORD fdwQuality, DWORD fdwPitchAndFamily, LPCTSTR lpszFace );인수만 해도 자그마치 14개나 될 정도로 복잡하다. 그만큼 글꼴이라는 것이 복잡한 모양을 가질 수 있다는 뜻이다. 개별 인수의 의미를 보기 쉽게 표로 정리하였다.
인수가 많기도 하지만 개별 인수의 의미도 무척 복잡하다. 하지만 이중 실질적으로 변경해 주어야할 필요가 있는 인수는 문자의 크기를 지정하는 nHeight와 글꼴 모양을 지정하는 lpszFacename 정도이며 나머지 인수는 디폴트를 사용하면 일단은 큰 무리 없이 사용할 수 있다. 각 인수의 좀 더 정확한 의미에 대해서는 도움말을 참조하기 바란다. |
이 부분은 넘어 간다.
리눅스 에서
gg = G
비쥬얼 스튜디오 에서
4-1-다. WM_KEYDOWN
키보드로부터 문자를 입력받고자 할 경우는 WM_CHAR 메시지를 사용하면 된다는 것을 배웠다. 문자 이외의 키를 입력 받으려면 WM_CHAR 메시지만으로는 입력을 받을 수 없다. 예를 들어 커서 이동키라든가 Ins, Del, PgUp, 펑션키 등의 키는 문자키가 아니기 때문에 WM_CHAR 메시지로는 검출해 낼 수 없다. 이때는 WM_KEYDOWN 메시지를 사용해야 한다. WM_KEYDOWN 메시지는 wParam에 문자 코드가 아닌 가상 키코드라는 것을 전달해 준다. 가상키코드(Virtual Key Code)란 시스템에 장착된 키보드의 종류에 상관없이 키를 입력받기 위해 만들어진 코드값이며 다음과 같이 정의되어 있다.
WM_KEYDOWN 메시지가 발생했고 wParam으로 VK_HOME이 전달되었으면 사용자가 Home키를 누른 것이다. 숫자 및 영문자의 가상 키코드는 아스키 코드와 같으며 매크로 상수는 정의되어 있지 않으므로 아스키 코드와 wParam을 바로 비교하면 된다. 가상 키 코드는 지금까지 나온 모든 키보드는 물론이고 앞으로 만들어질 키보드까지 고려하여 만들어진 범용적인 코드이다. 코드표를 보면 VK_SELECT, VK_EXECUTE, VK_HELP 등과 같이 현재 101키에 없는 키값도 미리 정의되어 있으며 펑션키도 F16까지 미리 만들어 놓았다. 이외 한국과 일본 등의 2바이트 문자를 지원하기 위한 특수한 가상키까지 포함되어 있는데 가상 키 코드를 이렇게 범용적으로 만들어 놓은 이유는 앞으로 윈도우즈를 다른 시스템으로 이식하더라도 키 코드를 그대로 쓸 수 있도록 하기 위한 배려이다. WM_KEYDOWN 메시지 처리 루틴에서 wParam의 값과 가상 키코드값을 비교해 봄으로써 어떤 키가 눌러졌는지를 구분한다. lParam으로 전달되는 값은 WM_CHAR와 동일하나 역시 잘 사용되지 않는다. 다음의 KeyDown예제는 커서 이동키를 검출하여 문자 "A"를 화면에서 상하좌우로 이동하는 예제이다. 소스를 입력해볼 필요없이 눈으로 보기만 해도 이해할 수 있을 것이다.
x,y라는 정수형 변수 두 개를 100으로 초기화 하고 WM_PAINT 메시지에서 이 변수의 위치에 문자 "A"를 출력한다. WM_KEYDOWN 메시지에서는 wParam을 읽어 커서 이동키일 경우 x,y값을 커서 이동키의 방향에 따라 조정함으로써 "A"를 화면에서 이동시킨다. 물론 WM_KEYDOWN에서 좌표를 조정한 후는 반드시 화면에 좌표 조정이 반영되도록 InvalidateRect 함수를 호출해 주어야 한다. 프로그램 실행중의 모습은 다음과 같다. 키보드를 누를 때마다 가상 키코드를 점검하여 커서 이동키에 따라 x,y 좌표값을 조정한 후 화면을 다시 그리고 있다. 이때 화면을 다시 그리기 위해 InvaludateRect 함수를 호출하는데 세번째 인수에 TRUE를 주어 배경을 지우도록 하였다. 만약 이 인수를 FALSE로 변경해 주면 어떻게 될까? 직접 실행해 보면 알겠지만 A문자가 이동은 하지만 기존 출력된 A문자가 지워지지는 않는다. 이 예를 통해 InvalidateRect의 세번째 인수가 어떤 역할을 하는지 쉽게 이해할 수 있을 것이다. WM_KEYDOWN의 반대 메시지는 WM_KEYUP이며 키가 떨어질 때 발생한다. wParam, lParam의 의미는 WM_KEYDOWN과 동일하다. 별로 잘 사용되지 않는 메시지이다. |
난 이 수업을 제대로 못들었지만 아주 간단하므로 잘 따라만 하면 되겠다.
현재 우리가 그림 그림을 이동하는 즉, 게임을 만드렁 보려고 소스코드를 수정중이다.
#include <windows.h> |
1. 경계 검사
2. 창 모양 고정 시킨다. 스크롤바도 없애고 가로 세로 크기를 사용자가 바꾸지 못하도록 고정시킨다.
'코스웨어 > 15년 스마트컨트롤러' 카테고리의 다른 글
20151117_안향진_API_4 (6) | 2015.11.18 |
---|---|
20151117 임현수 업무일지 WIN32API #4 (6) | 2015.11.18 |
20151117 - 강동조 개인업무일지 API 4일차(작성중) (5) | 2015.11.17 |
20151117_박서연_WinAPI(4) (6) | 2015.11.17 |
20151117 22번 업무일지 우대희 API (7) | 2015.11.17 |
20151117 - WinAPI 네번째 시간 일지 엄민웅 (수업진도 : 5-4 액셀러레이터 부터) (6) | 2015.11.17 |
20151116_안향진_API_3 (7) | 2015.11.16 |
20151116 김태현 WinAPI 3일차 CALLBACK함수,메뉴 만들기 (6) | 2015.11.16 |