GDI란 Graphics Device Interface의 약자입니다. 설명한다면 윈도 내부에 설정되어 있는 그래픽 디바이스와 상호 연결한다는 의미입니다. 윈도를 설치하면 비디오 카드와 맞는 드라이버를 윈도가 설정해 줍니다. 또한 비디오 카드를 생산하는 업체들은 자신의 비디오 카드와 함께 윈도에서 사용할 수 있는 드라이버를 제공해줍니다. 자신의 윈도 안에 어떤 비디오 드라이버가 들어 있는가를 확인해보려면, 제어판에서 디스플레이를 선택하고 설정을 누른 다음 고급 등록 정보를 클릭하면 그림 1-3-1과 비슷한 화면이 나타날 것입니다.

그림 1-3-1에서 보면 현재 비디오 카드는 S3 호환 디스플레이 어댑터이며, 이 비디오 카드를 핸들링하는 드라이버 파일들에 s3.sys, s3.dll 등등이 있다는 정보를 볼 수 있습니다. 이 상황에서 비디오 카드를 새로 바꾸었으면 변경이라는 버튼을 클릭하고 새로 구입한 비디오 카드와 함께 제공되는 드라이버 파일을 설치하면 그 비디오 카드와 맞게 화면이 갱신될 것입니다. 그냥 표준을 정하고 모든 카드를 그 표준에 맞추면 되지 왜 이렇게 각 회사 비디오 카드별로 따로 드라이버를 설정해야 하는가? 이 문제에 대해서는 필자가 보기에 어쩔 수 없다고 생각됩니다. 요즘 나오는 비디오 카드들은 보통 1024×768에 24비트 컬러를 기본으로 제공해주고 기본 메모리가 2메가 정도 하는 것들입니다. 1024×768이란 화면에 가로 점이 1024개, 세로 점이 768개라는 의미입니다. 또한 어떤 카드들은 800×600모드에서 24비트 컬러를 제공하며 1024×768에서는 256밖에 지원하지 못하는 카드들도 있습니다. 또한 어떤 카드들은 비디오 메모리가 4메가에 1280×1024 이상에서 24비트 컬러를 제공하여 주는 카드도 있으며, 비디오 카드이면서 동영상을 지원하는 다중 카드도 나오고 있습니다.

이런 모든 카드들의 동작법이 같을 수는 없습니다. 모든 카드는 자신의 특징에 따라 동작이 다르기 때문에 카드의 구동 방법은 카드 제작사에게 맡기고, 윈도는 단지 그 디바이스 드라이버를 로드하여 화면 출력은 디바이스 드라이버에 의존합니다. 윈도 시스템은 현재 화면의 전체 크기는 어떻게 출력되는가와 현재 화면에 출력되는 컬러수가 어떤 것이라는 정보를 디바이스 드라이버를 통해서 얻고, 또한 디바이스 드라이버에 설정해 주는 역할을 합니다. 이렇게 보면 윈도 시스템은 디바이스 드라이버와 서로 인터페이스를 한다고 볼 수 있습니다. 그래서 GDI, 즉 Graphics Device Interface라고 볼 수 있습니다.

그림 1-3-2는 GDI 커널이 구동하는 형태를 보여줍니다. GDI 커널은 스크린에 그래픽을 출력하고자 할 경우 해당 명령을 비디오 드라이버에 설정하여 주고, 이 설정된 명령을 받아서 ② 항목의 비디오 드라이버가 해당 내용을 화면에 출력하여 주는 것입니다.

그래픽 디바이스 콘텍스트란 쉽게 보면 윈도 화면을 컨트롤하는 것을 말합니다. 윈도에서는 여러 개의 애플리케이션을 출력할 수 있습니다. 그림 1-3-3는 윈도에 여러 개의 윈도가 출력된 형태입니다.

윈도에서는 화면 영역이 모든 윈도의 출력 영역은 아닙니다. 화면 영역은 윈도 시스템에서 관리하는 영역이고 실제 출력 영역은 각 애플리케이션마다 다르게 할당됩니다. 이럴 수밖에 없는 것이 보통 애플리케이션 프로그램들은 윈도 화면과 상관없이 자신의 영역을 마음대로 설정할 수 있으며, 이 화면들이 다른 애플리케이션 프로그램에 의해서 가려지고 다시 출력되기 때문입니다. 예를 들어 가로 2000과 세로 2000의 크기의 그림 파일을 출력하는 프로그램이 있다고 가정합시다. 그러나 윈도 스크린 크기가 1024×768이라고 가정할 때, 우리는 화면 영역보다 큰 그림을 출력할 수 있습니다. 즉, 스크롤바를 이용하여 일부 영역만 화면에 보이고 나머지 영역은 가려져서 출력할 수 있는 것입니다. 이렇다면 윈도 화면과는 다르게 애플리케이션만의 가상 화면과 이 가상 화면을 컨트롤하는 어떤 핸들러가 있을 것입니다. 이 가상 화면을 핸들링하는 핸들러가 HDC입니다. 이것은 화면 출력용 핸들이라는 의미로 그래픽 디바이스 핸들이라고 합니다. 이 핸들은 단순히 모니터 출력만 담당하는 것이 아니고 화면을 출력하는 모든 디바이스를 관리합니다.

예를 들어 출력을 프린터로 한다면 프린터 디바이스 핸들이 로드되고, 모니터로 한다면 모니터 디바이스 핸들이 로드되는 것입니다.

현재 데이터를 출력하기 위해서는 먼저 출력할 디바이스 콘텍스트 핸들을 얻고, 즉 HDC를 얻고 이 HDC에 문자나 여러 글자를 출력하는 게 SDK의 형태였습니다.

MFC에서는 이 HDC 멤버를 내장하는 하나의 클래스 CDC가 바로 디바이스 콘텍스트 핸들을 포함하는 클래스입니다. CDC에는 HDC를 내장하고 있을 뿐만 아니라 HDC를 이용하여 화면에 여러 요소를 출력하는 모든 함수들이 멤버로 저장되어 있습니다. 따라서 CDC 클래스 변수를 하나 받으면 그 변수의 멤버 함수만 이용하여 모든 내용을 출력할 수 있습니다.

CDC 클래스

CDC 클래스는 지금까지 설명한 것처럼 애플리케이션 프로그램에서 화면을 컨트롤하는 핸들을 포함하고 화면에 관계되는 여러 함수를 내장하고 있는 클래스입니다. 이 클래스에서 기본적으로 수행하는 기능은 표1과 같습니다.

표1에서 보는 것과 같이 화면 및 프린터에 출력하는 모든 함수들이 CDC에 내장되어 있습니다. 도움말을 이용하여 CDC를 찾아보면 표1과 같은 리스트가 나옵니다. 이때  필요한 부분을 클릭하면 해당 기능에 대한 함수 리스트가 나옵니다. 이 리스트 중에서 여러분이 필요한 함수를 얻을 수가 있습니다. CDC 클래스에서 파생된 클래스가 몇 개 있습니다. 그림 1-3-4는 CDC에서 파생된 클래스들입니다.

표1    CDC 클래스 기능 리스트

기능	내용

Initialization	DC를 새로 만들거나 삭제하고 다른 DC로부터 핸들을 얻는 기능 등 DC 초기화

Device-Context Functions	HDC 핸들을 얻고 설정하는 것에 관계되는 기능

Drawing-Tool Functions	그리기 객체인 브러시, 펜 등의 정보를 얻는 기능

Type-Safe Selection Helpers	그리기 객체를 설정하는 기능

Color and Color Palette Functions	색을 설정하거나 팔레트를 설정하는 기능

Drawing-Attribute Functions	그리기 속성을 설정하는 기능

Mapping Functions	매핑 모드 설정 기능

Coordinate Functions	좌표 변환 기능

Region Functions	영역을 설정하는 기능

Clipping Functions	화면 클리핑 기능

Line-Output Functions	라인 그리기 기능

기능	내용

Simple Drawing Functions	단순한 그래픽 그리기 기능

Ellipse and Polygon Functions	원과 다각형 그리기 기능

Bitmap Functions	비트맵 출력 기능

Text Functions	텍스트 출력 기능

Font Functions	폰트 설정 기능

Printer Escape Functions	프린터 출력 기능

Scrolling Functions	스크롤 기능

Path Functions	패스 기능

CDC는 일반적으로 사용되는 클래스로서 MFC로 프로젝트를 만들었을 경우 View 클래스의 OnDraw 함수나 GetDC와 같은 함수에 의해서 얻어지는 클래스입니다. 이 클래스를 상속받은 CClientDC는 현재 클라이언트 영역을 얻는 클래스입니다. 이 클래스는 화면을 출력하는 현재 클래스 어느 곳에서든지 설정하면 바로 현재 화면을 얻을 수 있습니다. CPaintDC는 클래스 위저드를 이용하여 WM_PAINT 메시지에 의해서 만들어진 함수 OnPaint에 내장된 클래스입니다. 이 클래스의 기능은 CClientDC와 같습니다. 차이점은 CPaintDC는 OnPaint 함수에서만 설정된다는 것입니다. CWindowDC는 윈도 전체를 받는 클래스입니다. CPaintDC, CClientDC나 OnDraw 함수에 의해서 얻어지는 CDC, GetDC 함수에서 얻어지는 CDC 등은 윈도 전체가 아닌 클라이언트 영역만 의미합니다. 캡션바, 외곽 경계선을 포함한 전체 윈도의 DC를 받고자 할 경우에는 CWindowDC를 사용하면 됩니다.

화면 출력 방법

윈도에서 화면 출력 방법은 그림 1-3-5와 같습니다.

그림 1-3-5는 CString 클래스의 변수 m_str에 특정 문자를 설정하고 이것을 화면에 출력하는 예입니다. 이 예에서, 키보드에서 문자키를 입력하여 이 값이 화면에 출력되도록 하고 마우스 좌측 버튼을 클릭했을 때 화면에 “반갑습니다”라는 것을 출력하며, 처음 윈도가 출력될 때는 “안녕하세요”가 출력되는 형태를 예로 설정한 것입니다. 처음 윈도가 만들어질 때는 해당 클래스의 생성자에 그림 1-3-5의 ② 항목처럼 m_str에 “안녕하세요”를 설정합니다. 이렇게 하면 윈도가 출력되면서 자동적으로 OnDraw 함수를 호출하게 됩니다. 따라서 처음 윈도가 출력되었을 때는 화면에 “안녕하세요”가 출력됩니다. 이후에 ③ 항목처럼 좌측 마우스 버튼을 클릭하였을 때 m_str에 “반갑습니다”라는 내용을 설정하고 Invalidate 함수를 호출하면 다시 OnDraw 함수가 호출되고 화면에는 “반갑습니다”라는 문자열로 출력됩니다. ④ 항목처럼 키보드에서 특정키를 눌렀을 때 이 키값을 m_str에 저장하고 다시 Invalidate 함수를 호출하면 다시 OnDraw 함수가 호출되고 입력된 키가 호출됩니다. 윈도즈 프로그래밍에서 화면 출력 방법은 기본 형태가 그림 1-3-5와 같은 루틴을 갖습니다. 즉 화면에 데이터를 출력하는 함수가 한 개이고 이 함수에 모든 출력 변수를 설정한 후에 특정 이벤트가 발생되었을 때 출력 변수의 데이터를 바꾸는 형태입니다.

화면 출력을 할 경우 두 가지 형태로 분리할 수 있습니다. 첫 번째는 고정된 위치에 데이터만 변경되는 형태이며, 두 번째는 화면에 그림이나 글자 등이 이동되는, 즉 애니메이션 효과가 발생되는 형태입니다. 고정된 위치에 데이터만 변경되는 형태는 그림 1-3-6과 같은 방식을 사용할 수 있습니다.

그림 1-3-6은 이름과 전화번호, 그리고 주소를 화면의 고정된 위치에 출력하는 예입니다. 이럴경우 이 고정된 위치에 출력할 내용을 가지고 있는 변수를 ② 항목처럼 설정한 후에 화면에 출력을 갱신할 필요가 있을 때 ③ 항목처럼 변수에 데이터를 변경한 다음 Invalidate 함수를 호출하여 화면 출력 항목 ①를 호출하면 화면이 바뀝니다.

두 번째 방법은 애니메이션 효과를 줄 경우입니다.

그림 1-3-7은 애니메이션 효과를 줄 경우 이에 해당하는 방법을 도시한 그림입니다.

그림 1-3-7은 화면에 사자 그림을 m_nX와 m_nY 위치에 출력시킬 때의 예입니다. 이때 사자가 출력되는 위치는 m_nX와 m_nY의 값에 따라서 변하게 됩니다. 즉 화면 에서 그림의 이동이나 변경이 있을 경우에 m_nX와 m_nY의 값을 변경시키고 Invalidate 함수를 호출하여 화면을 갱신시킵니다.

그림 1-3-7에서는 단순하게 위치만 변경하는 방법에 대한 설명입니다. 만일 여러 개의 그림의 위치를 이동하면서 설정할 경우 해당되는 그림을 정보를 가진 하나의 변수로 설정합니다. 이 값을 변화시키면 위치와 그림 형태가 변경되면서 애니메이션 효과를 줄 수가 있습니다.

CDC 클래스 얻기

지금까지는 그래픽 출력의 일반적인 형태에 대해서, 그리고 화면을 출력하기 위해서는 HDC라는 핸들을 받아야 하는데, 이 핸들을 가지고 있는 클래스가 CDC라는 것을 설명하였습니다. 본 항목은 CDC 클래스를 어떻게 받는가에 대한 설명입니다. CDC 클래스를 받는 방법은 크게 네 가지입니다. 일반적인 OnDraw나 OnPaint 함수에 의해서 DC를 받는 방법과 GetDC 함수를 이용하여 CClientDC를 받는 방법입니다. 다음은 CDC 클래스를 얻는 방법의 설명입니다.

OnDraw나 OnPaint를 이용

OnDraw 함수에서 인자로 넘어온 CDC를 받거나 OnPaint에서 CPaintDC를 받습니다. 이 방법은 윈도의 크기가 변하거나 다른 프로그램에 의해서 가려졌다가 다시 출력이 되어도 화면 출력이 변하지 않습니다. 쉽게 말하면 화면이 변화가 생겼을 때는 계속적으로 OnPaint나 OnDraw 함수를 호출하기 때문에 자동적으로 재출력되므로 화면 변화가 없다는 말입니다. 전 항목에서 설명한 루틴(그림 1-3-6 참조)은 모두 이 방법에 의해서 CDC 클래스를 받고 화면에 데이터를 출력한 것입니다.

GetDC를 이용하는 방법

CWnd 클래스의 멤버 함수인 GetDC를 이용합니다. 다음은 GetDC를 이용하여 DC를 얻는 예입니다.

CDC *pDC = GetDC();

: 그래픽 출력

ReleaseDC(pDC);

GetDC는 ReleaseDC와 쌍으로 이루어져 있습니다. GetDC를 호출하여 얻는 CDC는 출력이 끝난 후에는 ReleaseDC를 이용하여 해제를 해주어야 합니다. 이 방법은 OnDraw 함수나  OnPaint 함수 이 외의 곳에서 DC를 받을 때 사용합니다. 이 방법으로 DC를 얻은 것은 일시적인 출력입니다. GetDC를 이용하여 DC를 얻고 “안녕하세요”라는 문자열을 출력한 후 윈도를 축소하였다가 다시 크기를 확대하면 “안녕하세요”라는 글자가 사라집니다. 그 이유는 윈도가 변했을 때 WM_PAINT 메시지가 호출되고 이 메시지에 의해서 OnDraw 함수나 OnPaint 함수가 수행되면서 화면 영역이 갱신되기 때문입니다.

CClientDC를 이용하는 방법

CClientDC를 이용하는 것은 GetDC()를 얻는 것과 같습니다. 다른 점은 CClientDC는 Release라는 함수를 사용할 필요없이 그냥 설정만 하면 된다는 것입니다. 다음은 CClinetDC를 이용하여 DC를 얻는 예입니다.

CClientDC cdc(this);

윈도 DC를 얻는 방법

지금까지 설명한 세 개의 방법은 클라이언트 영역을 얻는 방법입니다. 만일 윈도 DC를 얻고자 할 때는 다음과 같이 합니다.

CWindowDC *pDC= GetWindowDC();

: // 화면 출력

ReleaseDC(pDC);

윈도 DC를 얻고자 할 때는 CWindowDC라는 CDC 파생 클래스를 사용합니다. 이때의 좌표 0,0은 윈도 시작점이 됩니다. 즉 타이틀바 상단 끝 좌측이 되는 것입니다.

디바이스 정보 얻기

DC에는 현재 DC가 어떤 형태이며 어떤 기능을 가졌는가에 대한 정보들이 내장되어 있습니다. DC의 정보를 얻기 위한 함수는 CDC의 멤버 함수로 GetDeviceCaps입니다.

int GetDeviceCaps( int nIndex ) const;

nIndex는 얻고자 하는 정보 플러그로서, 설정되는 값의 리스트는 표2에 나타냈습니다.

표2    GetDeviceCap의 nIndex 리스트

설정값	내용

HORZSIZE	밀리미터의 가로 크기

VERTSIZE	밀리미터의 세로 크기

HORZRES	수평 픽셀수

VERTRES	수직 픽셀수

LOGPIXELSX	가로 방향으로 로직컬 인치당 픽셀수

LOGPIXELSY	세로 방향으로 로직컬 인치당 픽셀수

BITSPIXEL	각 픽셀에 대한 컬러 비트수

설정값	내용

PLANES	컬러 플레인

NUMBRUSHES	장치에 설정된 브러시의 수

NUMPENS	장치에 설정된 펜의 수

NUMFONTS	장치에 설정된 폰트의 수

NUMCOLORS	장치의 색상 테이블의 항목수

ASPECTX	선 그리기를 위한 장치 픽셀의 상대적인 폭

ASPECTY	선 그리기를 위한 장치 픽셀의 상대적인 높이

ASPECTXY	선 그리기를 위한 장치 픽셀의 대각선 크기

CLIPCAPS	장치의 클리핑 특성을 나타내는 플래그

GetDeviceCaps 함수는 다양한 용도에 쓰일 수 있습니다. 예를 들어서 24비트 전용의 프로그램을 제작하였는데 현재 화면이 256일 경우에 화면 출력을 약간 변경하거나 어떤 경고 메시지를 알려주어야 합니다. 이럴 경우 현재 화면 모드가 어떤지 알기 위해서는 BITSPIXEL를 GetDeviceCaps의 nIndex 인자로 넘겨주어야 합니다. GetDeviceCaps는 화면 출력 외에 프린터 출력에도 자주 사용됩니다. 현재 프린터에서 출력할 수 있는 도트 단위와 가로 크기, 세로 크기를 알고 이에 대응하여 프린트하고자 할 때 GetDeviceCaps를 이용하여 프린터 DC의 정보를 얻고 이에 따라 인쇄를 하기도 합니다. 표2에서 설명하지 않은 다양한 정보들을 GetDeviceCaps에서 얻을 수 있습니다. 곡선을 출력하는 기능, 문자를 출력하는 기능, 다각형을 출력하는 기능이 어느 정도인가 등의 정보 또한 GetDeviceCaps에서 얻을 수 있습니니다.

DC 얻기와 디바이스 정보 얻기 예제 ExDcGet

ExDcGet 예제는 위에서 설명한 네 개의 DC를 이용하여 화면에 문자를 출력하는 경우와 GetDeviceCaps 함수를 이용하여 현재 DC 정보를 얻는 경우입니다. 이 예제는 SDI 형태로 제작합니다. 프로그래밍 방법이 그림 1-3-8입니다.

ExDcGet이 출력되면 View 클래스에서 OnDraw 함수가 실행됩니다. OnDraw 함수가 실행될 때 GetDeviceCaps 함수를 이용하여 가로 픽셀, 세로 픽셀, 픽셀 비트, 폰트수, 펜수, 브러시수, 컬러수 등을 출력합니다. 그림 1-3-8의 ⓑ 항목이 OnDraw 함수에 의해서 기본적으로 화면에 문자가 출력되는 형태입니다. ⓑ 항목은 프로그램이 시작됨과 동시에 출력될 것입니다. 그 이유는 윈도가 화면에 나타나면 자동적으로 OnDraw 함수가 호출되기 때문입니다. 그림 1-3-8의 ⓐ 항목처럼 ExDcGet 프로그램은 “DC 얻기”라는 항목이 있고, 이 메뉴 항목의 부메뉴로는 “GetDC 이용”, “ClientDC 이용”, “CWindowDC 이용”, “OnDraw 호출”이라는 네 개의 항목이 있습니다. “OnDraw 호출” 항목을 제외하고 각각의 항목을 클릭하면 해당 DC를 이용하여 화면 (0,0) 좌표에 해당 DC를 이용하여 화면에 문자를 출력합니다. 이때 출력되는 결과가 ⓒ, ⓓ, ⓔ입니다. 마지막 항목 “OnDraw 호출”이라는 항목을 클릭하면 Invalidate 함수를 이용하여 OnDraw 함수를 호출하는 것입니다.

이와 같은 예제를 만드는 방법은 다음과 같습니다.

메뉴를 수정한다.

프로젝트 워크스페이스 윈도에서 ResourceView 항목을 클릭하고 Menu 항목을 선택하면 IDR_MAINFRAME이라는 ID가 있을 것입니다. 이 항목을 클릭하여 메뉴를 화면에 나타내고 그림 1-3-8의 ⓐ 형태와 같은 메뉴를 설정하고 각 메뉴 ID를 표3과 같이 설정합니다.

표3   ExDcGet의 메뉴 ID

메뉴명	ID

ID_GETDC	GetDC 이용

ID_CLIENTDC	ClientDC 이용

ID_WINDOWDC	CWindowDC 이용

ID_CALLONDRAW	OnDraw 호출

클래스 위저드를 이용하여 각 메뉴를 선택하였을 때의 함수를 View 클래스에 만든다.

메뉴 항목을 선택하고 클래스 위저드를 실행시키면 해당 메뉴를 클릭했을 때의 함수를 만들 수 있습니다. 이 함수들을 View 클래스에 만듭니다. 프로젝트명이 ExDcGet이므로 클래스명은 CExDcGetView일 것입니다. 이 함수를 만든 형태가 그림 1-3-8의 ①과 같습니다. 이렇게 함수를 만든 다음 다음과 같이 코딩합니다.

// “GetDC 이용” 메뉴를 클릭했을 때

void CExDcGetView::OnGetdc()

{

CDC *pDC = GetDC();

CString text=“GetDC를 이용하여 화면에 문자를 출력합니다.”;

pDC->TextOut(0,0,text);

ReleaseDC(pDC);

}

같은 방법으로 “ClientDC 이용”이라는 메뉴를 선택하였을 때의 함수를 만듭니다. 이 함수를 만든 형태가 그림 1-3-8의 ②와 같습니다. 이렇게 함수를 만든 다음 이 함수에서 위에 설명한 방법대로 클라이언트 DC를 얻은 다음 문자를 출력합니다.

// “ClientDC 이용” 메뉴를 클릭했을 때

void CExDcGetView::OnClientdc()

{

CClientDC cdc(this);

CString text=“CClientDC를 이용하여 화면에 문자를 출력합니다.”;

cdc.TextOut(0,0,text);

}

같은 방법으로 그림 1-3-8의 ③과 같은 함수를 만든 다음 이 함수에서 윈도 DC를 얻고 이곳에 문자를 출력하는 내용을 다음과 같이 코딩합니다.

// “CWindowDC 이용” 메뉴를 클릭했을 때

void CExDcGetView::OnWindowdc()

{

CWindowDC *pDC=(CWindowDC *)GetWindowDC();

CString text=“GetWindowDC를 이용하여 화면에 문자를 출력합니다.”;

pDC->TextOut(0,0,text);

ReleaseDC(pDC);

}

그림 1-3-8의 ④ 항목은  “OnDraw 함수 호출”이라는 항목을 클릭했을 때의 함수입니다. 이 함수에서는 Invalidate 함수를 이용하여 OnDraw 함수를 호출합니다.

// “OnDraw 호출” 메뉴를 클릭했을 때

void CExDcGetView::OnCallondraw()

{

// Invalidate 함수를 실행하면 OnDraw 함수가 호출된다.

Invalidate();

}

위의 소스 내용 중에 설명하지 않은 TextOut 함수는 CDC 멤버 함수로써 다음과 같은 형태를 가집니다.

CDC::TextOut

virtual BOOL TextOut( int x, int y, LPCTSTR lpszString, int nCount );

BOOL TextOut( int x, int y, const CString& str );

x, y 값은 출력된 좌표 위치를 의미합니다. TextOut 함수를 이용하여 문자열을 출력할 때는 두 가지 방법이 있습니다. 첫 번째 방법은 char* 형태의 문자열을 출력하는 것입니다. lpszString이 바로 이 형태입니다. 이때는 nCount라는 인자가 하나 더 필요한데, 이것은 출력할 문자열의 수를 의미합니다. 두 번째 방법으로는 CString 함수를 이용하는 방법입니다. 이것은 그냥 CString 클래스를 넘겨주면 됩니다. 이때는 nCount와 같이 출력할 문자열 수를 설정하지 않아도 됩니다.

OnDraw 함수를 수정한다.

OnDraw 함수에서는 GetDeviceCaps를 이용하여 화면의 가로 픽셀, 세로 픽셀 등등의 여러 정보를 설정하여 현재 디바이스 정보를 알 수 있도록 합니다. 그림 1-3-8의 ⓑ 그림을 보면 GetDeviceCaps에 의해서 얻어진 정보를 출력하는 내용을 볼 수 있을 것입니다. 이렇게 OnDraw 함수를 수정한 형태는 다음과 같습니다.

void CExDcGetView::OnDraw(CDC* pDC)

{

CExDcGetDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

// GetDeviceCaps를 이용하여 여러 정보를 출력한다.

CString text;

text.Format(“가로 픽셀: %d”, pDC->GetDeviceCaps(HORZRES));

pDC->TextOut(0,20,text);

text.Format(“세로 픽셀: %d”, pDC->GetDeviceCaps(VERTRES));

pDC->TextOut(0,40,text);

text.Format(“픽셀 비트: %d”, pDC->GetDeviceCaps(BITSPIXEL));

pDC->TextOut(0,60,text);

text.Format(“브러시수: %d”, pDC->GetDeviceCaps(NUMBRUSHES));

pDC->TextOut(0,80,text);

text.Format(“펜수: %d”, pDC->GetDeviceCaps(NUMPENS));

pDC->TextOut(0,100,text);

text.Format(“폰트수: %d”, pDC->GetDeviceCaps(NUMFONTS));

pDC->TextOut(0,120,text);

text.Format(“색상수: %d”, pDC->GetDeviceCaps(NUMCOLORS));

pDC->TextOut(0,140,text);

}

CString 클래스

애플리케이션 프로그램에서 가장 많이 사용하는 것이 문자열일 것입니다. MFC에서는 이 문자열을 컨트롤하는 하나의 클래스를 만들었습니다. 이 클래스가 CString입니다. 이 클래스의 멤버 함수들은 표4와 같습니다.

표4  CString의 멤버 함수

함수	내용

GetLength	설정된 문자열의 길이를 리턴한다.

IsEmpty	현재 클래스에 문자열 버퍼가 비워져 있는가? 비워져 있으면 TRUE, 그렇지 않으면 FALSE를 리턴한다.

Empty	문자열을 삭제하여 버퍼를 비워둔다.

GetAt	문자열의 특정 위치의 문자값을 얻는다.

SetAt	문자열의 특정 위치에 새로운 문자를 삽입한다.

Compare	문자열과 인자의 문자열을 비교한다. 같으면 0, 그렇지 않으면 0보다 크거나 작은 값 리턴

함수	내용

Mid,Left,Right	문자열 중간, 좌측, 우측부터 특정 위치의 문자열을 CString 형태로 추출한다.

MakeUpper	문자열을 소문자 -> 대문자로 바꾼다.

MakeLower	문자열을 대문자 -> 소문자로 바꾼다

Format	sprintf 형태로 스트링에 문자를 넣는다.

Find	문자열에서 특정 문자나 문자열을 찾는다.

표4 외에 많은 CString 멤버 함수들이 있습니다. 이것은 도움말을 이용하여 참고하기 바랍니다.

위와 같이 하고 프로그램을 컴파일하면 그림 1-3-8의 ⓑ와 같은 화면이 출력될 것입니다. 여기에서 중요시 할 것은 메뉴에서 “CWindowDC 이용”이라는 항목을 선택하였을 때의 출력 결과입니다. 그림 1-3-8의 ⓔ 항목이 바로 이 결과인데, ⓒ, ⓓ의 결과와 다르게 문자 출력이 윈도 경계선을(0,0) 기점으로 출력되는 것을 볼 수 있을 것입니다. 이것은 CWindowDC가 윈도 외곽 경계를 모두 포함하는 부분을 DC로 설정하기 때문입니다. 본 예제의 소스는 본 책에는 기록하지 않겠습니다. 그림 1-3-8의 형태를 참고하면 되고 지금까지 설명한 방법을 이해했다면 소스를 참조할 필요가 없기 때문입니다.

그래픽 객체라는 의미는 화면에 그림을 그리거나 문자를 출력할 때 사용하는 객체를 의미합니다. 즉 펜과 브러시, 폰트, 비트맵 등을 핸들링하는 핸들들을 그래픽 객체라고 하며, 이 핸들을 가지고 있는 클래스를 그래픽 객체 클래스라고 합니다. 화면을 하나의 캠버스라고(또는 도화지) 가정했을 경우, 이곳에 그래픽을 출력하고자 할 때 일반적으로 붓과 펜을 사용할 것입니다. 이와 같은 형태로 DC에 그래픽을 사용할 때 그 도구들이 그래픽 객체입니다.

그래픽 객체 클래스는 펜을 컨트롤하는 CPen, 브러시를 컨트롤하는 CBrush, 폰트를 컨트롤하는 CFont, 비트맵을 컨트롤하는 CBitmap 등이 있습니다. 이 그래픽 객체를 이용하는 방법은  그림 1-3-9와 같습니다.

그래픽 객체를 DC에 설정하기 위해서는 먼저 객체 클래스의 Create 계열 함수를 이용하여 객체를 만듭니다. 이처럼 객체를 만드는 순서가 그림 1-3-9의 ① 항목입니다. 이렇게 객체를 만든 다음 이 객체를 그림 1-3-9의 ② 항목처럼 SelectObject 함수를 이용하여 객체를 등록하면 됩니다. 그리고 원하는 그래픽을 출력한 후에 ⑤ 항목처럼 객체를 삭제하면 됩니다. 즉, 생성하고 등록하고 사용하고 삭제하는 4단계 루틴을 이용합니다. 그러나 여기에서 한 가지 주의할 점이 있습니다. 객체를 DC에 설정한 후 사용이 끝났다고 삭제를 하면 현재 DC에 설정된 객체가 삭제됩니다. DC에 설정된 객체를 삭제하였을 경우 화면이 깨지는 증상이 있을 수 있습니다. 그것은 DC에 설정된 객체 자체가 소멸되어 있기 때문에 DC에서 그 객체를 사용하려고 할 때 오류가 발생합니다. 예를 들어서 pen이라는 CPen 클래스의 변수를 사용하여 생성하고 등록하고 사용한 후 DeleteObject 함수를 이용하여 삭제를 하면 DC 안에 있는 펜이 삭제됩니다. 이렇게 삭제한 후 라인을 그리거나 상자를 그리면 DC는 자신이 가지고 있는 펜을 사용하게 되는데, 이때 삭제된 펜을 사용할 수도 있습니다. 따라서 보통 새로운 그래픽 객체를 DC에 등록할 때(그림 1-3-9의 ② 항목) DC에 등록되어 있던 이전의 객체의 포인터를 받아두었다가 새로 만든 객체를 삭제하기 이전에 이전의 객체를 다시 DC에 설정하는 루틴이 삽입되어야 합니다. 그림 1-3-9의 ④ 루틴이 바로 이런 루틴입니다. 이렇게 한 후 각 클래스의 멤버 함수인 DeleteObject를 이용하여 삭제하면 됩니다.

그림 1-3-9의 루틴은 그래픽 객체를 사용할 때 많이 사용하는 루틴이므로 유념해서 보기 바랍니다.

색

윈도에서는 2컬러, 흑백부터 트루컬러, 즉 24비트 컬러까지 다양한 색을 사용할 수 있습니다.(요즘은 32비트 컬러를 지원하는 비디오 카드도 나왔기에 32비트도 지원한다고 합니다.) 윈도 시스템에서 기본적으로 사용하는 색은 세 가지입니다. 적색, 녹색, 청색 이 세 개의 색을 이용하여 화면에 색을 출력합니다. RGB color라는 의미가 바로 이 의미입니다. 이 3원색을 혼합하여 2컬러부터 트루컬러까지 모든 색을 표현하는 것입니다. 각각의 값이 클수록 밝아지며 작을수록 어두워지는 형태의 색입니다. 예를 들어 R, G, B 각각의 값이 0이라면 이것은 가장 어두운 색으로 검정색이 됩니다. 이 3원색을 각각 256 레벨로 나누어서 사용합니다. 그렇기 때문에 각각의 가장 큰 값은255,255,255입니다. 이때의 색은 흰색이 됩니다. 256 레벨을 사용하기 때문에 각 색에 필요한 값은 8비트입니다. 결국 시스템에 출력할 모든 색을 사용하기 위해서는 24비트가 필요하기 때문에 24비트를 트루컬러라고 하는 것입니다. 이 3가지 색의 값을 가지고 있는 하나의 형이 있는데 이것이 COLORREF입니다. 이 형은 32비트 형으로 실제 24비트만 사용하고 나머지 6비트는 여분의 비트로 남겨져 있습니다. 이 형에 어떤 색을 설정하고자 할 경우 이때 사용하는 함수가 RGB입니다. 이 함수의 형태는 다음과 같습니다.

COLORREF RGB(  BYTE bRed,    // 적색

BYTE bGreen,  // 녹색

BYTE bBlue    // 청색);

만일 청색을 만들어 이 값을 COLORREF형으로 가지고 있고자 한다면 다음과 같이 할 수 있습니다.

COLORREF blue=RGB(0,0,255);

위의 예에서 B값은 최대값이 255이므로 가장 밝고 나머지 값은 0이므로 blue값은 청색이 됩니다. 그림 1-3-9의 ① 항목의 ex에서 펜을 만들 때 RGB 함수를 사용하는 것을 보았을 것입니다. 이것은 펜의 색을 설정할 때 COLORREF형의 변수를 설정하기 위한 것입니다. 화면에서는 RGB를 사용하나 프린터에서는 CMY를 사용합니다. 이것은 Yellow, Magenta, Cyan의 약자입니다. 실제 색의 형태를 보면 그림 1-3-10과 같습니다.

RGB 컬러의 형태는 색의 값이 더해지는 형태이고 CMY 컬러의 형태는 색의 값이 빠지는 형태입니다. RGB 형태는 색전등을 생각하면 됩니다. 파란 전등을 켜놓은 상태에서 빨간 전등을 다시 켜면 좀더 밝은 형태의 색(Magenta)이 됩니다. 여기에 다시 녹색 전등을 켜면 완전히 밝은 흰색 형태의 밝기가 됩니다. 이런 형태가 RGB를 사용했을 때입니다. 모니터에서 우리가 보는 어떤 색은 Red, Green, Blue의 새 개의 광선이 서로 세기를 달리하면서 한 점마다 출력되어 색을 보는 것이기 때문에 RGB 컬러 형태를 따르게 됩니다. 프린터에서는 이와 다르게 Subtrative 형태입니다. 이때는 색이 빠져 가는 것입니다. 즉, 자신의 색을 반사시키고 다른 색을 흡수하기 때문에 색을 혼합하면 점점 어두워지는 것입니다. 예를 들어, Red 셀로판지가 있을 경우 이곳에 빛을 투과시키면 Red는 그대로 통과되고 나머지 색은 흡수되기 때문에 Red 색만 나가게 됩니다. Red 셀로판지와 Green 셀로판지, 그리고 Blue 셀로판지를 모두 겹쳐 놓은 상태에서 빛을 투과시키면 각각의 셀로판지는 해당색을 제외하고 통과시키기 때문에 Red, Green, Blue 3원색 모두가 없어져서 검정색이 됩니다. 프린터에서 잉크로 색을 출력하였을 때 현재 색만 반사하고 나머지는 흡수하는 것과 같은 이론입니다. 이럴 경우에는 색을 더할수록 어두워집니다. 이것을 CMY color라고 합니다.

팔레트

윈도 98을 사용할 때 어떤 컴퓨터는 256 컬러로, 어떤 컴퓨터는 24비트 컬러, 또 어떤 컴퓨터는 16비트 컬러를 사용합니다. 전부 다 다르게 컬러값을 가지고 있다는 것입니다. 24비트 컬러는 모든 색을 다 출력하기 때문에 문제가 없지만 256 색만 사용되는 컴퓨터에서는 단순하게 256 색만 사용해야 합니다. 256 컬러만 사용하는 컴퓨터에서는 팔레트라는 개념을 사용하여 색을 출력합니다. 즉 R, G, B가 결합된 총색에서 256 컬러만 팔레트라는 객체에 설정하고 이 설정된 색을 이용하여 화면에 색을 출력한다는 것입니다. 그림 1-3-11은 24비트를 사용하는 형태와 256 컬러를 사용하는 형태의 차이점을 보여줍니다.

그림 1-3-11은 픽셀값이 101,102,103일 때 팔레트를 사용하느냐 안 하느냐에 따라 화면에 출력되는 형태가 달라지는 내용입니다. 그림 1-3-11의 ① 항목처럼 픽셀값이 101,102,103일 때 팔레트를 사용하지 않고 트루컬러에서 출력을 할 경우 그 색값 그대로 화면에 출력됩니다. 그림 1-3-11의 ⑤ 항목이 바로 이 경우입니다. 만일 팔레트를 사용할 경우(시스템에서 사용하는 컬러수가 트루컬러보다 작을 경우 팔레트를 사용하게 됩니다.) 어떤 팔레트 테이블을 사용하느냐에 따라 출력색이 달라집니다. 그림 1-3-11에서 ③ 항목과 같은 팔레트 테이블을 사용할 경우, 팔레트 테이블에서 101,102,103에 설정되어 있는 컬러값을 화면에 출력하기 때문에 54,67,127의 값이 출력됩니다. ④ 항목처럼 팔레트 테이블을 사용하면 픽셀값이 66,80,90의 색으로 출력됩니다. 즉, 팔레트를 사용하게 되면 어떤 팔레트를 사용하느냐에 따라 출력되는 색이 달라지는 것입니다.

팔레트 테이블은 화면에서 사용하는 컬러값과도 상관이 있을 뿐더러 그림 파일에도 매우 중요한 역할을 합니다. 그림 파일을 트루컬러 형태로 저장하여 한 개의 점을 출력하고자 할 경우 24비트의 메모리가 필요합니다. 그러나 256 컬러로 저장하여 한 개의 점을 출력할 때는 8비트밖에 필요하지 않습니다. 그림 파일의 크기를 작게 하고자 할 경우 그림 파일에 설정되는 픽셀 비트를 줄이는 경우도 많으며, 일반적으로 사용하는 그림 파일 중에서 256 컬러를 사용하는 파일 포맷도 있습니다. 이럴 경우에도 팔레트가 필요합니다.

그림 1-3-12는 그림 파일을 출력하고자 할 때 팔레트를 사용하는 예입니다.

256 컬러 그림은 보통 그림 파일 안에 현재 그림에서 사용하는 팔레트 테이블을 포함하고 있습니다. 즉, 그림 안에 팔레트 테이블과 그림 데이터가 함께 있다는 의미입니다. 그림 1-3-12에서 의 ①이 그림 파일을 의미합니다. 이 그림 파일을 화면에 출력하고자 한다면 그림 파일의 픽셀값을 읽어서 그림 파일 안에 있는 팔레트 테이블에서 픽셀값에 해당하는 팔레트값을 화면에 출력합니다. 그림 1-3-12의 ② 항목이 바로 이런 기능을 합니다. 이렇게 하여 실제값이 출력되는 형태가 ③ 항목입니다. 이렇게 팔레트 테이블을 가지고 있고, 이 팔레트 테이블을 시스템에 등록할 때 사용하는 클래스가 CPalette입니다.

CPalette 클래스를 이용하여 팔레트를 만들고 이것을 시스템에 설정하는 루틴은 그림 1-3-13과 같습니다.

CPalette 클래스 변수를 하나 설정한 다음(그림 1-3-13의 ①) CreatePalette 함수를 이용하여 팔레트를 만듭니다. 이 항목은 그림 1-3-9의 ② 항목에 해당됩니다.

CreatePalette 함수는 다음과 같습니다.

CPalette::CreatePalette

BOOL CreatePalette( LPLOGPALETTE lpLogPalette );

lpLogPalette는 LOGPALETTE 클래스의 포인터입니다. LOGPALETTE는 다음과 같습니다.

typedef struct tagLOGPALETTE { // lgpl

WORD         palVersion;

WORD         palNumEntries;

PALETTEENTRY palPalEntry[1];

} LOGPALETTE;

palVersion은 팔레트 버전을 의미합니다. 보통 0x300값을 그대로 대입하면 됩니다. pal NumEntries는 팔레트 테이블의 수를 의미합니다. 만일 256 컬러 팔레트일 경우에는 이 값이 256으로 설정됩니다. palPalEntry[1]은 팔레트 배열의 포인터입니다. 배열은 한 개이지만 필요에 따라서 여러 개의 팔레트 배열이 설정될 수 있습니다. PALETTEENTRY 구조체의 형태는 다음과 같습니다.

typedef struct tagPALETTEENTRY { // pe

BYTE peRed;     // 적색값

BYTE peGreen;  // 녹색값

BYTE peBlue;    // 청색값

BYTE peFlags; // 팔레트 엔트리를 어떻게 사용하는가의 플러그

// 보통 NULL값을 설정한다.

} PALETTEENTRY

LOGPALETTE를 만들기 위한 하나의 예는 다음과 같습니다.

LPLOGPALETTE lplgPalette;

lplgPalette = new char [sizeof(LOGPALTEET)+sizeof(PALETTEENTRY)*256];

먼저 팔레트가 설정될 메모리를 확보합니다. 이 메모리는 LOGPALETTE의 크기와 팔레트 엔트리 수만큼 해당하는 PALETTEENTRY 구조체의 크기의 합입니다. 위의 예는 256개의 팔레트가 필요할 때 이에 해당하는 LOGPALETTE 구조체의 변수를 설정한 것입니다. 이와 같이 설정한 후 LOGPALETTE 구조체의 정보와 함께 각각의 팔레트 엔트리에 값을 설정하면 됩니다.

lplgPalette->palVersion=0x300;

lplgPalette->palNumEntries=256;

// 해당 팔레트 엔트리에 값을 설정한다.

lplgPalette->palPalEntry[0].peRed=0;

lplgPalette->palPalEntry[0].peGreen=0;

lplgPalette->palPalEntry[0].peBlue=0;

lplgPalette->palPalEntry[1].peRed=0;

lplgPalette->palPalEntry[1].peGreen=0;

lplgPalette->palPalEntry[1].peBlue=0;

: // 256개의 값을 반복해서 설정

위와 같이 하면 LOGPALETTE 변수인 lplgPalette에 값을 설정한 것입니다. 이렇게 값을 설정한 후에 이 구조체를 CreatePalette 함수에 설정합니다.

CPalette pPalette;

pPalette.CreatePalette(lplgPalette);

이렇게 하면 그림 1-3-12의 ② 항목이 완결된 것입니다.

팔레트를 설정할 때는 그림 1-3-9의 ② 항목처럼 SelectObject 함수가 아닌 SelectPalette 함수를 사용합니다. 이 함수의 형태는 다음과 같습니다.

CDC::SelectPalette

CPalette* SelectPalette( CPalette* pPalette, BOOL bForceBackground );

pPalette는 위에서 만든 팔레트 클래스 변수를 설정하는 것입니다. bForceBackground값이 1 이상일 경우 현재 팔레트가 배경 팔레트로 설정되며, 0일 경우에는 해당 윈도의 팔레트로 설정됩니다. 배경 팔레트로 설정할 경우 배경에 그려진 그림들 모두가 이 팔레트에 의존하여 색을 표현합니다.

이렇게 팔레트를 설정한 다음에는 이 팔레트를 활성화해야 합니다. DC에 설정된 팔레트를 활성화할 때 사용하는 함수가 RealizePalette( );입니다. 이 함수를 호출하려면 현재 설정된 팔레트를 활성화하여서, 비트맵으로 픽셀을 출력할 때는 픽셀값을 읽어서 이것을 해당 팔레트 엔트리의 값으로 전환한 다음 출력합니다. 즉, 그림 1-3-12의 형태대로 출력한다는 의미입니다.

이렇게 한 후에 그림을 출력하고 원래의 팔레트로 복원하기 위해서 다시 이전 팔레트를 그림 1-3-12의 ⑥ 항목처럼 설정합니다.

펜 클래스 CPen

CPen은 라인을 그릴 때 사용하는 라인의 특성을 설정하는 클래스입니다. 라인의 굵기와 라인의 형태, 그리고 라인의 색을 설정할 때 CPen 클래스를 사용합니다. CPen에 의해서 라인의 속성을 설정하면 그래픽에서 사용하는 모든 선들은 이 펜을 사용하여 그리게 됩니다. 즉, 직선뿐만 아니라 상자를 그릴 경우, 또는 원을 그릴 경우 이런 도형의 외곽선 또한 DC에 설정한 CPen 클래스의 변수값에 의거하여 출력됩니다.

CPen을 이용하여 DC에 펜을 설정하는 방법은 다음과 같습니다.

(1) 펜을 만든다.

펜을 만들고자 할 경우 먼저 클래스의 변수로 선언한 다음 CreatePen을 이용하여 펜을 만듭니다.

ex) CPen pen, *oldpen;

pen.CreatePen(PS_SOLID,2,RGB(0,0,0))

위의 예는 두께가 2이고 직선 형태의 검정색 라인을 설정한 것입니다.

CreatePen 함수는 다음과 같습니다.

CPen::CreatePen

BOOL CreatePen( int nPenStyle, int nWidth, COLORREF crColor );

BOOL CreatePen( int nPenStyle, int nWidth, const LOGBRUSH* pLogBrush, int nStyleCount = 0, const DWORD* lpStyle = NULL );

nPenStyle : 펜의 스타일

nWidth : 펜의 굵기

crColor : 펜의 색

pLogBursh : 펜에 설정할 브러시

nStyleCount : lpStyle 배열의 크기

lpStyle : 펜의 스타일을 정의한 포인터

nPenStyle은 다음과 같습니다.

PS_SOLID

PS_DASH

PS_DOT

PS_DASHDOT

PS_DASHDOTDOT

본 내용은 그림 1-3-9의 ①과 같습니다.

(2) 펜을 DC에 설정

펜을 설정할 때는 그림 1-3-9의 ②와 같은 형태로 SelectObject 함수를 이용합니다. 이 함수를 이용하여  펜을 DC에 설정하는 예는 다음과 같습니다.

oldpen=pDC->SelectObject(&pen);

(3) 라인을 그리고 난 후에 다시 이전 펜을 설정

그림 1-3-9의 ④와 같이 그래픽을 출력한 다음 SelectObject 함수를 이용하여 이전에 설정되어 있던 펜을 다시 DC에 설정합니다. 이 예는 다음과 같습니다.

pDC->SelectObject(oldpen);

(4) 만들었던 펜을 삭제

그림 1-3-9의 ⑤와 같이 만들었던 펜을 DeleteObject 함수를 이용하여 삭제합니다. 이 예는 다음과 같습니다.

pen.DeleteObject();

브러시 클래스 CBrush

브러시란 면을 칠할 때 사용하는 객체입니다. 이 객체를 핸들링하는 클래스가 CBrush입니다. 이 클래스의 변수를 선언하고 이것을 DC에 설정할 때 어떤 브러시를 만들 것인가에 따라서 다양한 브러시가 만들어집니다. 브러시를 만드는 Create 계열의 함수는 표5와 같습니다.

표5   브러시를 만드는 CBrush의 멤버 함수 리스트

함수	내용

CreateSolidBrush	일반적으로 단색을 설정하여 칠하는 브러시 만듦

CreateHatchBrush	패턴을 가지고 있으며 이 패턴을 이용하여 칠하는 브러시 만듦

CreateBrushIndirect	LOGBRUSH 구조체를 이용하여 브러시를 만드는 함수

CreatePatternBrush	특정 비트맵을 이용하여 브러시를 만드는 함수

CreateDIBPatternBrush	장치 독립 비트맵을 이용하여 브러시를 만드는 함수

CreateSolidBrush는 다음과 같습니다.

BOOL CreateSolidBrush( COLORREF crColor );

CreateSolidBrush는 단일색으로 그대로 칠하는 브러시를 말합니다. 예를 들어서 노랑색 브러시를 만들고자 하면 다음과 같이 할 수 있습니다.

CBrush cBrush;

cBrush.CreateSolidBrush(RGB(255,255,0));

CreateBrushIndirect 함수는 LOGBRSH 구조체를 이용하여 브러시 정보를 설정한 다음 이 구조체를 이용하여 브러시를 만드는 함수입니다. LOGBRUSH 구조체는 다음과 같습니다.

LOGBRUSH

typedef struct tag LOGBRUSH { /* lb */

UINT     lbStyle;

COLORREF lbColor;

LONG     lbHatch;

} LOGBRUSH;

lbStyle는 브러시의 스타일을 의미합니다. 이 값은 다음과 같은 값을 가질 수 있습니다.

BS_SOLID : 단일색으로 칠합니다.

BS_PATTERN : 비트맵의 패턴 형태로 칠합니다.

BS_HATCHED : 빗금 패턴에 의해서 칠합니다.

lbColor는 RGB 함수에 의해서 설정하는 브러시의 색상값입니다.

lbHatch는 lbStyle가 BS_HATCHED일 경우 이때 설정하는 빗금 패턴값입니다. 값은 다음과 같습니다.

HS_BDIAGONAL : 45도 우측에서 아래로 빗금

HS_CROSS : 십자가 형태

HS_DIAGCROSS : X자 형태의 빗금

HS_FDIAGONAL : 45도 좌측에서 우측 아래로 빗금

HS_HORIZONTAL : 수평으로 빗금

HS_VERTICAL : 수직으로 빗금

CreateBrushIndirect 함수를 이용하여 검정색으로 HS_BDIAGONAL 패턴의 브러시를 만드는 예는 다음과 같습니다.

LOGBRUSH lobru;

lobru.lbStyle=BS_HATCHED;

lobru.lbColor=RGB(0,0,0);

lobru.lbHatch=HS_BDIAGONAL;

CBrush brush;

brush.CreateBrushIndirect(&lobru);

브러시를 만든 다음 사용하고 삭제하는 방법은 그림 1-3-9의 ③, ④, ⑤ 항목과 같습니다.

폰트 클래스 CFont

폰트를 만들고자 한다면 CFont 클래스의 멤버 함수인 CreateFont 함수를 이용합니다.

BOOL CreateFont(

int nHeight, // 폰트의 높이

int nWidth, // 폰트의 가로 크기

int nEscapement, // escapement 벡터 각도

int nOrientation, // 기본 선(보통 수평선)에서의 각도

int fnWeight, // 폰트 가중치

DWORD fdwItalic, // 이탤릭 속성 플러그

DWORD fdwUnderline, // 밑줄 속성 플러그

DWORD fdwStrikeOut, // 중앙선 속성 플러그

DWORD fdwCharSet, // 문자열 세트 명칭

DWORD fdwOutputPrecision, // 출력 정밀도

DWORD fdwClipPrecision, // 클리핑 정밀도

DWORD fdwQuality, // 출력 문자의 질

DWORD fdwPitchAndFamily, // 글꼴 피치의 종류

LPCTSTR lpszFace // 폰트체의 이름

);

nHeight와 nWidth는 폰트의 가로와 세로의 높이를 말합니다. nEscapement란 글자의 출력 각도를 말합니다. 보통 0이면 일반 수평 방향으로 글자가 출력됩니다.

그림 1-3-14는 90의 값 방향으로 글자가 출력되는 형태입니다.

이때 90의 의미는 1/10의 각도 단위이므로 9도 정도 비껴서 출력이 된다는 것입니다.

nOrientation은 장치의 기본선(baseline)에서의 x축과의 기울어진 각도를 의미합니다. 이 각도는 1/10도 단위입니다.

fnWeight는 폰트의 가중치입니다. 굵게 또는 가늘게 등의 여러 옵션이 있습니다. 해당하는 옵션은 다음과 같습니다.

표6   fnWeight의 값들

값	내         용

FW_DONTCARE	상관하지 않음

FW_THIN	가늘게

FW_BOLD	진하게

FW_LIGHT	약간 밝게

FW_NORMAL	보통 문자

FW_HEAVY	매우 두껍게

이 외에도 FW_EXTRABOLD, FW_EXTRALIGHT, FW_MEDIUM, FW_MEDIUM 등의 플러그가 있습니다.

fdwItalic, fdwUnderline, fdwStrikeOut들은 각각 이탤릭 밑줄 중앙줄에 대한 플러그입니다. 해당 변수들의 값을 1로 설정하면 활성화되며 0으로 설정하면 비활성화됩니다. fdwCharSet은 문자열 세트의 명칭입니다. 문자 세트는 일반적으로 사용하는 DEFAULT_CHARSET, 안시 문자 코드를 사용하는 ANSI_CHARSET, OEM 문자 세트를 사용하는 OEM_CHARSET, 그리고 한글 문자 세트를 사용하는 HANGEUL_CHARSET 등이 있습니다. OEM 문자 세트란 IBM에서 만든 문자 세트를 말합니다. ANSI_CHARSET란 일반적으로 윈도즈에서 사용하는 문자 세트를 말합니다.

fdwOutputPrecision은 출력의 정밀도를 의미하는데, 출력 문자의 높이와 가로 크기, 문자의 각도와 피치 등의 정밀도를 어떻게 할 것인가를 정의하는 값입니다. 이 값은 다음의 값들 중 하나를 설정할 수 있습니다.

OUT_DEFAULT_PRECIS

OUT_STRING_PRECIS

OUT_CHARACTER_PRECIS

OUT_STROKE_PRECIS : 윈도 95에서는 벡터 폰트에서 사용됨

OUT_DEVICE_PRECIS

OUT_TT_PRECIS

OUT_OUTLINE_PRECIS : 윈도 95에서는 사용하지 않음

OUT_RASTER_PRECIS

fdwClipPrecision은 클리핑의 정밀도를 의미합니다. fdwQuality는 GDI가 논리적 글 형태의 속성을 맞출 때 사용하는 옵션입니다.

fdwPitchAndFamily는 글꼴 피치의 종류를 의미하는 것으로 DEFAULT_PITCH와 FIXED_PITCH, VARIABLE_PITCH를 설정할 수 있습니다.

lpszFace는 설정할 폰트 이름의 문자열입니다.

폰트 하나를 설정하는 데는 매우 많은 인자가 필요합니다. 그리고 옵션 또한 많습니다. 그만큼 다양하고 섬세하게 글자를 출력할 수 있다는 의미입니다. 이제 막 윈도 프로그램을 시작하여 폰트를 하나 DC에 등록하면서 이 모든 인자를 이해하고자 한다면 욕심일 것입니다. 위에서 중요한 인자는 nHeight와 nWidth, 그리고 lpszFace입니다. 나머지 옵션 중에서 많은 부분들은 DEFAULT 옵션을 사용하면 됩니다.

다음은 폰트를 하나 만드는 예입니다.

CFont cFont;

cFont.CreateFont(10,10,// 가로, 세로 크기

0,0,// 기울어진 각도

FW_DONTCARE,// 폰트의 굵기

FALSE,// 이탤릭 플러그

FALSE,// 언더라인 플러그

FALSE,// STRIKEOUT 플러그

DEFAULT_CHARSET,// CHAR SET

OUT_DEFAULT_PRECIS,

CLIP_DEFAULT_PRECIS,

DEFAULT_QUALITY,

DEFAULT_PITCH,

“바탕체”);

이탤릭이나 언더라인, 중앙선 등은 FALSE할 경우 비활성화되고 TRUE할 경우 활성화됩니다. 그 외의 내용은 처음에는 DEFAULT 플러그값을 선택하고 계속적으로 프로그램을 하면서 값을 변경시켜 가면서 확인해 보면 이해할 수 있을 것입니다.

CreateFont 함수 외에 CreateFontIndirect 함수를 이용하는 방법도 있습니다. 이 함수는 LOGFONT 구조체를 설정하여 폰트를 만듭니다. LOGFONT 구조체는 CreateFont 안의 인자의 값을 그대로 구조체화한 것입니다. 이 구조체는 다음과 같습니다.

typedef struct tagLOGFONT { // lf

LONG lfHeight;

LONG lfWidth;

LONG lfEscapement;

LONG lfOrientation;

LONG lfWeight;

BYTE lfItalic;

BYTE lfUnderline;

BYTE lfStrikeOut;

BYTE lfCharSet;

BYTE lfOutPrecision;

BYTE lfClipPrecision;

BYTE lfQuality;

BYTE lfPitchAndFamily;

TCHAR lfFaceName[LF_FACESIZE];

} LOGFONT;

각 멤버 변수의 설명은 CreateFont 함수를 참조하기 바랍니다.

CreateFontIndirect를 사용하여 바탕체 폰트를 만드는 예는 다음과 같습니다.

char temp[80];

strcpy(temp,”바탕체”);

LOGFONT logfont={10,10,0,0,FW_DONTCARE,FALSE,

FALSE,FALSE,

DEFAULT_CHARSET,

OUT_DEFAULT_PRECIS,

CLIP_DEFAULT_PRECIS,

DEFAULT_QUALITY,

DEFAULT_PITCH,

temp[0]};

Font.CreateFontIndirect(&logfont);

이렇게 폰트를 만들고 등록하여 사용하는 방법은 그림 1-3-9와 같습니다.

비트맵 클래스 CBitmap

비트맵 만들기

비트맵은 rc 파일을 프로젝트에 등록한 후에 Ctrl+5 키를 누르거나 Resource 도구바에서 비트맵 설정 버튼을 클릭함으로써 만들 수 있습니다. 이렇게 하여 새로운 비트맵을 만들면 아무것도 그려지지 않은 흰색의 비트맵이 만들어지므로 Graphics 도구바와 Color 도구바를 이용하여 그림을 그릴 수 있습니다. 그림 1-3-15는 새로운 비트맵을 만든 다음 이 비트맵에 그림을 그린 것입니다.

그림 1-3-15는 실제 그린 것이 아니고 다른 그래픽 에디터에서 클립보드로 복사한 다음 이것을 새로운 비트맵을 만든 곳에 복사한 것입니다. 실제로 비트맵을 저장할 경우 이것을 일일이 손으로 그리는 것보다 그래픽 에디터에서 그린 다음 복사하는 것이 편리합니다. Visual C++ 4.x 이전에는 16컬러만 지원되었으나 이제 비트맵은 트루컬러까지 지원됩니다. 새로운 비트맵을 만들고 이것을 256컬러로 세팅하고자 한다면 Alt+Enter 키를 눌러서 속성을 변환시켜 줄 필요성이 있습니다.

Alt+Enter 키를 누르면 그림 1-3-16과 같은 화면이 나타납니다. 이곳에는 그림의 ID와 저장되는 파일명, 그리고 Color의 수값들이 나옵니다. Colors가 256이므로 이 그림은 256 색으로 그려진 그림입니다. File name이란 이 그림이 저장되는 파일명을 의미합니다. 그림 1-3-16에서는 File name이 bitmap1.bmp로 설정되어 있는데, 이 파일은 현재 프로젝트 디렉토리의 RES 서브 디렉토리에 위치하게 됩니다. 프로그램이 컴파일되고 링크되면 bitmpa1.bmp 파일은 필요없게 됩니다. 프로그램은 리소스 자원을 프로그램의 특정 위치에 설정하기 때문에 완결된 프로그램은 자원의 정보가 필요없습니다.

비트맵 로드하기

비트맵을 로드하기 위해서는 LoadBitmap을 사용합니다. 이 함수를 이용하여 위의 IDB_BITMAP1을 로드하는 방법은 다음과 같습니다.

CBitmap cBitmap

cBitmap.LoadBitmap(IDB_BITMAP1);

비트맵 출력하기

비트맵을 출력하고자 할 경우에는 절차가 약간 복잡합니다. 문자를 출력하듯이 바로 DC에 출력하는 것이 아니라 특정 DC를 만든 다음 이 DC에 비트맵을 설정하고 이 DC를 현재 클라이언트 DC에 전송해야 합니다. 즉, 다음과 같은 로직으로 해야 합니다.

CDC memdc;

// 현재 DC와 호환적인 메모리 DC를 만든다.

memdc.CreateCompatibleDC(pDC);

// 메모리 DC에 비트맵을 설정한다.

memdc.SelectObject(cBitmap);

// 메모리 DC를 화면 DC에 전송한다.

pDC->BitBlt(0,0,320,320,memdc,0,0,SRCCOPY);

위의 예를 자세히 설명하자면 CreateCompatibleDC란 특정 DC를 만드는 데 인자로 넘겨주는 CDC와 호환성이 있는 DC를 만드는 것입니다. 이렇게 DC를 만드는 이유는 DC가 화면에만 해당되는 것이 아니고 프린터 출력 또한 DC로 얻기 때문입니다. 프린터와 화면은 그 DC의 성격이 다르기 때문에 비트맵을 출력하기 위한 어떤 DC를 만든다면 이것은 출력할 DC의 성격과 같은 형으로 만들어야 합니다.

이렇게 하면 memdc는 설정된 pDC와 같은 형태의 DC가 됩니다. 여기에 LoadBitmap 함수에 의해 로드된 cBitmap을 설정합니다. 이렇게 설정한 후 BitBlt 함수를 이용하여 memdc를 cdc에 전송합니다. BitBlt 함수의 형은 다음과 같습니다.

CDC::BitBlt

BOOL BitBlt(

int nXDest, // 출력할 DC의 x 위치

int nYDest, // 출력할 DC의 y 위치

int nWidth, // 출력할 내용의 가로 크기

int nHeight, // 출력할 내용의 세로 크기

CDC *Src, // 현재 DC에 전송할 DC

int nXSrc, // 전송할 DC의 시작 X 위치

int nYSrc, // 전송할 DC의 시작 Y 위치

DWORD dwRop // ROP 코드

);

비트맵이 설정된 Src를 출력할 화면에 보낼 때 Src의 시작 위치 정보는 nXSrc와 nYSrc이며, 출력할 비트맵의 x와 y, 가로 크기와 세로 크기는 nXDest와 nYDest, nWidth와 nHeight입니다. dwRop란 hdcSrc와 hdcDest의 화면 결합을 어떻게 할 것인가를 정의한 것입니다. 표7은 이 값들의 리스트입니다.

SRCCOPY라고 설정하면 화면에 Src가 그대로 출력됩니다. 만일 두 개를 XOR의 형태로 출력하고자 한다면 SRCINVERT를 사용하면 됩니다. 컬러 그래픽에서 이 연산은 매우 다양하게 쓰입니다. 예를 들어서 현재 화면의 (0,0)의 색이 100이라는 값을 가지고 있고 전송할 Src의 첫 값이 200이라고 할 경우, 그리고 이것을 NOTSRCERASE라는 형태로  출력하고자 한다면 다음과 같은 형태가 됩니다.

01100100 // 화면 (0,0)의 값 100

11001000 // 전송할 DC의 (0,0)값 200

------------------ OR 연산 (|)

11101100

----------------- ~ 연산

00010011 // 실제 출력하는 값 19

즉, 비트맵의 200이라는 색의 값을 화면(0,0)의 좌표에 있는 100이라는 색의 값에  NOTSRCERASE 모드로 전송하고자 한다면 출력되는 색의 값은 19입니다. 게임 프로그램을 만들 때 배경 화면과 지우지 않고 현재 화면을 출력하고자 할 경우 ROP 코드를 이용한 BitBlt 함수를 많이 이용합니다. BitBlt 함수와 같은 기능이면서 출력하는 내용을 확대 축소할 수 있는 함수가 있습니다. 이 함수는 StretchBlt입니다.

BOOL StretchBlt(

int nXOriginDest, // 출력 좌표 x

int nYOriginDest, // 출력 좌표 y

int nWidthDest, // 가로 크기

int nHeightDest, // 세로 크기

CDC *Src, // 현재 DC에 전송할 DC

int nXOriginSrc, // x 시작 좌표

int nYOriginSrc, // y 시작 좌표

int nWidthSrc, // 가로 크기

int nHeightSrc, // 세로 크기

DWORD dwRop // ROP 코드

);

표7   dwRop값의 리스트

dwRop	부울 함수	ROP코드

BLACKNESS	0	0x000042

NOTSRCERASE	~(Src | Des)	0x1100A6

NOTSRCCOPY	~Src	0x330008

SRCERASE	Src & ~Des	0x440328

DSTINVERT	~Des	0x550009

PATINVERT	Pattern ^ Des	0x5A0049

SRCINVERT	Src ^ Des	0x660046

SRCAND	Src & Des	0x8800C6

MERGEPAINT	~Src | Des	0xBB0226

MERGECOPY	Pattern & Src	0xC00CA

SRCCOPY	Src	0xCC0020

SRCPAINT	Src | Des	0xEE0086

PATCOPY	Pattern	0xF00021

PATPAINT	Pattern | ~Src | Des	0xFB0A09

WHITENESS	1	0xFF0062

StretchBlt는 현재 DC에 전송할 Src의 nXOriginSrc와 nYOrginSrc, 그리고 nWidthSrc와 nHeightSrc의 영역을 nXOrignDest, nYOriginDes, nWidthDest, nHeightDest 영역의 크기에 맞게 축소 또는 확대하여 출력하는 함수입니다. 이 함수를 이용하면 현재 비트맵을 원하는 크기로 확대 축소하여 출력할 수가 있습니다.

래스터 연산(ROP)

래스터 연산이란 현재 설정된 화면의 그림 형태와 새로 출력할 그림이 연산에 의해서 출력되는 것을 의미합니다. 예를 들어 (0,0)과 (0,100) 사이에 100값의 색으로 라인이 그려졌을 때 다시 이 위에 200의 값의 라인을 그리게 될 때 이전의 색을 무시하고 라인을 출력하는 것이 아니라 특정 연산에 의해서 출력하는 것을 말합니다.

이 래스터 연산에 사용되는 함수는 SetRop2이며 이 함수에 설정한 연산 코드는 표8과 같습니다.

표8   ROP 코드표

연산 코드	부울 함수	내용

R2_BLACK	0	모든 값을 0으로 처리

R2_NOTMERGEPEN	~(Src | Des)	배경과 설정할 값을 OR 연산을 하고 난 후에 그 값을 Inverse한다.

R2_MASKNOPEN	~Src & Des	설정값을 Inverse한 다음 배경과 AND 연산을 한다.

R2_NOTCOPYPEN	~Src	설정값을 Inverse하여 출력한다.

R2_MASKPENNOT	Src & ~Des	배경값을 Inverse한 다음 설정값과 AND 연산을 한다.

R2_NOT	~Des	배경을 Inverse한다.

R2_XORPEN	Src ^ Des	배경과 설정값을 XOR 연산하여 출력한다.

R2_NOTMASKPEN	~(Src & Des)	배경과 설정값을 AND 연산을 한 다음 이것을 Inverse한다.

R2_NOP	Des	배경값을 그대로 출력

R2_MERGENOPTPEN	~Src | Des	설정값을 Inverse한 다음 배경값과 OR 연산을 한다.

R2_COPYPEN	Src	소스를 그래로 출력한다.

R2_MERGEPENNOT	Src | ~Des	배경값을 Inverse한 다음 설정값과 OR 연산을 한다.

R2_MERGEPEN	Src | Des	설정값과 배경값을 OR 연산을 한다.

R2_WHITE	1	배경에 흰색을 출력한다.

예를 들어서 100의 값이 설정되어 있는 배경에 200의 값을 R2_MERGEN OPTPEN으로 한다면 다음과 같습니다.

11001000 // 200의 값

00110111 // 200의 값의 Inverse값

01100100 // 100의 값

------------------ OR 연산

01110111 // 119값

200의 2진수인 11001000이 Src이므로 이 값을 Inverse합니다.  Inverse란 0은 1로, 1은 0으로 전환하는 것을 의미합니다. 이 값에 100의 2진수 01100100을 OR 연산하면 1110111, 즉 119의 값이 나오므로 119의 색이 화면에 출력됩니다.

이런 래스터 연산이 필요한 이유에는 여러 가지가 있습니다. 배경에 글자가 출력될 때 배경과 교차되어 배경 위에 글자를 다양하게 출력하고자 할 경우에 이 연산을 이용합니다. 또 하나 예를 들면, 그래픽 에디터에서 상자 상태의 영역을 설정할 때 상자가 커지고 작아질 경우 화면이 깜빡임이 없이 출력되는 것을 보았을 것입니다. 이것은 R2_XORPEN으로 설정하였기 때문입니다. XOR 연산은 한 번 출력하면 배경과 교차된 색으로 출력되었다가 다시 출력하면 출력한 것은 사라지고 이전의 배경이 나오는 형태입니다.

SetRop2 함수를 이용하여 R2_XORPEN을 사용한 연산을 하고 라인을 그리고자 한다면 다음과 같이 할 수 있습니다.

pDC->SetRop2(R2_XORPEN);

: 그래픽을 출력한다.

지금까지는 화면을 얻는 DC, 그리고 그래픽을 출력하기 위한 여러 객체들을 설정하는 내용을 설명하였습니다. 본 항목에서는 이 객체와 DC를 이용하여 화면에 선을 그리는 방법에 대해서 설명하겠습니다.

라인 그리기

라인을 그릴 때는 두 가지 함수를 사용합니다. 이 함수들 또한 CDC의 멤버 함수입니다. 먼저 라인을 그리기 위해서는 시작 위치로 이동해야 합니다. 이동하는 함수가 MoveTo입니다. 시작 위치에서 그릴 위치로 선을 그리는 함수가 LineTo입니다. 이 두 함수는 다음과 같습니다.

라인을 그릴 시작 위치로 이동하는 함수

CPoint MoveTo( int x, int y );

CPoint MoveTo( POINT point );

x, y: 이동할 위치 좌표

point: x, y 좌표를 가지고 있는 구조체 POINT

BOOL LineTo( int x, int y );

BOOL LineTo( POINT point );

x,y: 라인을 그릴 끝점 좌표

point: 라인을 그릴 끝점 좌표 (x,y)를 가지고 있는 POINT

예를 들어서 (0,0) 위치에서 (100,100)까지 라인을 그리고자 한다면 다음과 같이 할 수가 있습니다.

pDC->MoveTo(0,0);

pDC->LineTo(100,100);

CPen과 라인 그리기 예제 ExPen 라인 그리기

ExPen은 CPen 클래스를 이용하여 라인 스타일을 설정하고 이 객체를 DC에 설정한 후에 해당 라인을 그리는 예제입니다. 이 예제의 전체 구성도는 그림 1-3-17과 같습니다.

ExPen 예제는 그림 1-3-17과 같이 세 개의 메뉴 항목으로 설정되어 있습니다. 그리기, 선굵기, 색상입니다. 그리기 항목은 그림 1-3-17의 ②와 같이 선을 그릴 때 선의 스타일을 설정하는 항목이며, 선굵기는 이 선의 굵기를 설정하는 항목입니다. 최대 4 두께의 선을 그릴 수 있도록 네 개의 메뉴가 설정되어 있습니다. 색상은 그리는 선의 색상을 의미합니다. 총 네 개의 색상을 설정하였습니다. 이 메뉴의 ID는 그림 1-3-17의 ⑤ 항목과 같습니다.

처음에 마우스 좌측 버튼을 클릭한 채로 마우스를 드래깅하면 영역이 상자 형태로 설정됩니다. 이렇게 설정된 상태에서 마우스 버튼을 놓으면 해당 영역이 세 개의 메뉴 항목에서 설정한 형태로 상자가 그려집니다. 상자가 그려진 상태에서 새로운 항목으로 설정하면 현재 그려진 상자가 새롭게 설정된 스타일로 다시 그려집니다. 실제 예제는 다음과 같은 방법으로 만듭니다.

메뉴를 설정한다.

AppWizard를 이용하여 SDI 형태로 프로젝트를 만든 후에 메뉴를 그림 1-3-17과 같은 형태의 메뉴와 ID를 만듭니다.

마우스 드래깅을 만든다.

본 프로그램은 마우스 버튼을 누르고 있는 상태에서 마우스를 이동하면 상자가 설정되면서 마우스 위치에 따라 영역이 바뀌어집니다. 이 프로그램을 만들어야 합니다. 이 프로그램은 그림 1-3-18과 같은 형태입니다.

마우스 드래깅에 필요한 변수는 세 개입니다. CRect 클래스 형태의 변수 m_rect와 m_oldrect, 그리고 BOOL형인 m_bDown입니다. CRect 클래스는 사각형 영역의 좌표를 가지고 있는 클래스입니다. 이 클래스의 기능은 본 예제를 만들면서 설명하겠습니다. 그림 1-3-18의 ① 항목이 바로 이 변수를 View 클래스 헤더에 설정한 형태입니다. 이렇게 설정한 후에 View 클래스 생성자에 각 값을 초기화합니다. ② 항목이 초기화되는 루틴입니다. 이렇게 초기화한 후에 좌측 마우스 버튼을 클릭하면 m_rect와 m_oldrect에 좌측 상단과 우측 하단을 가지고 있는 멤버 변수 left, top, right, bottom에 각각 현재 마우스 위치 x, y를 설정합니다. 이렇게 설정하면 점 m_rect와 m_oldrect는 점 영역을 가지고 있는 게 됩니다. 좌측 마우스 버튼을 클릭했을 때의 함수는 클래스 위저드를 이용하여 WM_LBUTTONDOWN 메시지를 설정하면 만들어집니다. 이 함수를 View 클래스에 만듭니다.(본 예제는 모두 View 클래스에서만 작업합니다.) 이렇게 만든 함수에 코딩한 내용은 다음과 같습니다.

void CExPenView::OnLButtonDown(UINT nFlags, CPoint)

{

m_bDown=TRUE;// 버튼 눌림 플러그

m_rect=CRect(point.x,point.y,point.x,point.y);

m_oldrect=m_rect;

CView::OnLButtonDown(nFlags, point);

}

OnLButtonDown 함수의 인자 nFlags는 버튼이 눌리면서 키보드에서 특정 키가 눌려졌을 때의 값과 마우스 눌림값이 설정됩니다. 이 값은 다음과 같습니다.

MK_CONTROL : Ctrl 키가 눌림

MK_LBUTTON : 좌측 마우스 버튼 눌림

MK_MBUTTON : 가운데 버튼 눌림

MK_RBUTTON : 우측 마우스 버튼 눌림

MK_SHIFT : Shift값 눌림

두 번째 인자 CPoint는 좌표를 핸들링하는 클래스입니다. 이 클래스의 멤버 x, y는 바로 좌표값을 가지고 있는 멤버 변수입니다. 전의 OnLButtonDown 함수는 인자로 넘겨준 point의 x, y를 m_rect와 m_oldrect의 left, top, right, bottom 인자에 각각 대입한 것입니다. 이렇게 대입하고 좌측 마우스 버튼이 눌려져 있다는 것을 알려주기 위해서 m_bDown 함수를 TRUE로 설정합니다. 이 형태는 그림 1-3-18의 ⑤ 항목과 같습니다.

좌측 마우스 버튼을 누르고 있는 상태에서 마우스를 이동시켜 이동시킨 영역을 상자로 그려줍니다. 이 방법은 라인을 그릴 때 SetROP2 함수의 XOR 모드를 이용합니다. XOR 모드를 이용하여 한 번 라인을 그리면 라인이 그려지고 그 위에 다시 라인을 그리면 라인이 지워지고 배경이 나타납니다. 이 내용은 전에 설명하였습니다. 마우스가 이동될 때 먼저 현재 그려진 상자를 지우기 위해서 m_rectold에 설정한 영역으로 라인을 그려줍니다. 이렇게 라인을 그리면 이전에 설정한 라인이 지워지고 배경이 살아납니다. 이 상태에서 이동한 영역을 m_rectold에 저장하고 그 영역을 라인으로 그려줍니다. 그리고 m_rectold에 현재 그린 라인의 영역을 저장합니다. 마우스가 이동되면서 이 항목이 반복되면 이전 라인을 XOR 모드로 지워주기 때문에 이동한 현재 영역만 나타나게 됩니다. 이 형태가 그림 1-3-18의 ⑧, ⑨, ⑩입니다.

마우스 이동을 가로채는 함수는 클래스 위저드를 이용하여 WM_MOSUEMOVE 메시지가 발생되었을 때의 함수를 만들면 됩니다. 이 함수를 만들고 ⑦ 항목을 코딩한 내용은 다음과 같습니다.

void CExPenView::OnMouseMove(UINT nFlags, CPoint point)

{

if(m_bDown)

{

CDC *pDC=GetDC();

pDC->SetROP2(R2_XORPEN);

CPen pen,*oldpen;

// 펜을 만든다.

pen.CreatePen(PS_DOT,2,RGB(127,127,127));

// 펜을 등록한다.

oldpen=pDC->SelectObject(&pen);

// m_oldrect 영역에 라인을 그린다.

// 이때 m_rect로 그려진 영역이 지워진다.

pDC->MoveTo(m_oldrect.TopLeft());

pDC->LineTo(m_oldrect.right,m_oldrect.top);

pDC->LineTo(m_oldrect.BottomRight());

pDC->LineTo(m_oldrect.left,m_oldrect.bottom);

pDC->LineTo(m_oldrect.TopLeft());

// m_rect에 point의 x, y를 right, bottom에 설정한다.

m_rect=CRect(m_rect.left,m_rect.top,point.x,point.y);

// m_rect 영역을 그린다.

pDC->MoveTo(m_rect.TopLeft());

pDC->LineTo(m_rect.right,m_rect.top);

pDC->LineTo(m_rect.BottomRight());

pDC->LineTo(m_rect.left,m_rect.bottom);

pDC->LineTo(m_rect.TopLeft());

// 이전 펜으로 설정

pDC->SelectObject(oldpen);

pen.DeleteObject();

ReleaseDC(pDC);

// 현재 그려진 영역을 m_oldrect에 설정

m_oldrect=m_rect;

}

CView::OnMouseMove(nFlags, point);

}

CRect의 TopLeft 함수를 실행하면 현재 영역에서 left와 top값은 CPoint 형태로 리턴됩니다. MoveTo와 LineTo는 CPoint값을 받아서 라인을 그릴 수 있으며, 또한 x, y값으로 받아서 라인을 그릴 수가 있습니다. OnMouseMove 함수에 들어온 인자의 값은 OnLButtonDown 내용과 같습니다. 본 내용만 따로 ExRop2라는 예제로 만들었습니다. 제공되는 CD에서 EXRop2의 예제를 보면 본 마우스 드래깅시 영역 상자를 설정하는 부분만 따로 익힐 수 있을 것입니다.

좌측 마우스 버튼을 누르고 이동하면 이동한 형태에 따라 상자가 설정됩니다. 이렇게 한 후 좌측 마우스 버튼을 놓으면 영역 그리기가 해제되고 OnDraw 함수를 호출하면 메뉴 항목에서 설정한 스타일로 영역에 라인이 그려집니다. 이때버튼을 놓았다면 m_oldrect 영역만 XOR 모드로 다시 그려줍니다. 이렇게 그려주면 현재 그려진 라인이 지워집니다. 그리고 Invalidate 함수를 이용하여 OnDraw를 호출합니다. 좌측 마우스 버튼을 떼었을 때의 메시지는 WM_LBUTTONUP입니다. 클래스 위저드에서 이 메시지를 선택하여 View 클래스에 함수를 만들고 코딩한 것은 다음과 같습니다.

void CExPenView::OnLButtonUp(UINT nFlags, CPoint point)

{

// 버튼 떼어짐을 알리기 위해 m_bDown을 FALSE로 설정

m_bDown=FALSE;

// m_rectold 영역에 라인을 그린다.

CDC *pDC=GetDC();

CPen pen,*oldpen;

pen.CreatePen(PS_DOT,2,RGB(127,127,127));

oldpen=pDC->SelectObject(&pen);

pDC->SetROP2(R2_XORPEN);

pDC->MoveTo(m_oldrect.TopLeft());

pDC->LineTo(m_oldrect.right,m_oldrect.top);

pDC->LineTo(m_oldrect.BottomRight());

pDC->LineTo(m_oldrect.left,m_oldrect.bottom);

pDC->LineTo(m_oldrect.TopLeft());

pen.DeleteObject();

ReleaseDC(pDC);

// m_oldrect는 초기화

m_oldrect=CRect(0,0,0,0);

// OnDraw 함수 호출

Invalidate();

CView::OnLButtonUp(nFlags, point);

}

OnDraw 함수 수정하기

그림 1-3-17의 ⓐ, ⓑ, ⓒ에서 보듯이 해당 메뉴를 클릭하면 세 개의 변수 m_nPenStyle과 m_nWidth, m_color에 값이 설정됩니다. 이렇게 설정된 값으로 OnDraw 함수에서 라인을 그려줍니다. 먼저 생성자에 이 세 개의 값을 초기화합니다.

따라서 생성자에 초기화해야 할 것은 그림 1-3-17에서 사용한 세 개의 변수와 그림 1-3-18에서 사용한 세 개의 변수 모두 초기화합니다. View 클래스의 생성자를 다음과 같이 수정합니다.

CExPenView::CExPenView()

{

// 초기화

// 색은 검정색

m_color=RGB(0,0,0);

// 선 굵기는 1

m_nWidth=1;

// 그리기 스타일은 직선

m_nPenStyle=PS_SOLID;

m_bDown=FALSE;

m_oldrect=CRect(0,0,0,0);

m_rect=CRect(0,0,0,0);

}

생성자를 초기화한 후에 m_nWidth 굵기, m_color의 색으로 그리고, m_nPenStyle의 스타일로 영역을 라인으로 그려주는 것을 OnDraw 함수에 기입합니다. 수정된 OnDraw 함수는 다음과 같습니다.

void CExPenView::OnDraw(CDC* pDC)

{

CExPenDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

CPen pen,*oldpen;

// 메뉴에서 선택한 형태로 펜을 설정한다.

pen.CreatePen(m_nPenStyle,m_nWidth,m_color);

oldpen=pDC->SelectObject(&pen);

// 마우스로 움직이면 최종 영역이 m_rect에 저장되므로 이 영역을

// 라인으로 그린다.

pDC->MoveTo(m_rect.TopLeft());

pDC->LineTo(m_rect.right,m_rect.top);

pDC->LineTo(m_rect.BottomRight());

pDC->LineTo(m_rect.left,m_rect.bottom);

pDC->LineTo(m_rect.TopLeft());

// 이전 펜 복귀

pDC->SelectObject(oldpen);

// 펜 객체 삭제

pen.DeleteObject();

}

메뉴 항목 수정시 해당값 설정

각 메뉴 항목을 선택하였을 때의 함수를 View 클래스에 만듭니다. 이렇게 만든 후에 그림 1-3-17의 ⓐ, ⓑ, ⓒ처럼 해당 변수에 값을 대입하고 Invalidate 함수를 이용하여 OnDraw 함수를 호출하여 영역을 새로 설정한 형태로 출력하도록 합니다. 그림 1-3-17의 ② 항목에서 PS_SOLID 항목을 선택했을 때의 함수만 본 책에 기록하겠습니다. 나머지 부분은 직접 작성해 보기 바랍니다.(또는 소스를 참조하십시오.)

// PS_SOLD 항목 설정

void CExPenView::OnSolidline()

{

m_nPenStyle=PS_SOLID;

Invalidate();

}

예제 ExPen 프로그램 소스

다음은 ExPen의 View 클래스의 소스입니다. 본 예제는 View 클래스만 이용하였기 때문에 다른 소스는 생략합니다.

// ExPenView.h : interface of the CExPenView class

//

//////////////////////////////////////////////////////////////////////////

#if !defined(AFX_EXPENVIEW_H__9DA7682D_20B9_11D2_8B56_006097AD9DDC__INCLUDED_)

#define AFX_EXPENVIEW_H__9DA7682D_20B9_11D2_8B56_006097AD9DDC__INCLUDED_

#if _MSC_VER > 1000

#pragma once

#endif // _MSC_VER > 1000

class CExPenView : public CView

{

protected: // create from serialization only

CExPenView();

DECLARE_DYNCREATE(CExPenView)

// Attributes

public:

CExPenDoc* GetDocument();

// Operations

public:

// Overrides

// ClassWizard generated virtual function overrides

// {{AFX_VIRTUAL(CExPenView)

public:

virtual void OnDraw(CDC* pDC);  // overridden to draw this view

virtual BOOL PreCreateWindow(CREATESTRUCT& cs);

protected:

virtual BOOL OnPreparePrinting(CPrintInfo* pInfo);

virtual void OnBeginPrinting(CDC* pDC, CPrintInfo* pInfo);

virtual void OnEndPrinting(CDC* pDC, CPrintInfo* pInfo);

// }}AFX_VIRTUAL

// Implementation

public:

COLORREF m_color;

int m_nWidth;

int m_nPenStyle;

BOOL m_bDown;

CRect m_oldrect;

CRect m_rect;

virtual ~CExPenView();

#ifdef _DEBUG

virtual void AssertValid() const;

virtual void Dump(CDumpContext& dc) const;

#endif

protected:

// Generated message map functions

protected:

// {{AFX_MSG(CExPenView)

afx_msg void OnLButtonDown(UINT nFlags, CPoint point);

afx_msg void OnMouseMove(UINT nFlags, CPoint point);

afx_msg void OnLButtonUp(UINT nFlags, CPoint point);

afx_msg void OnPsDash();

afx_msg void OnPsDashdot();

afx_msg void OnPsDashdotdot();

afx_msg void OnPsDot();

afx_msg void OnBlue();

afx_msg void OnBlack();

afx_msg void OnRed();

afx_msg void OnGreen();

afx_msg void OnWidth1();

afx_msg void OnWidth2();

afx_msg void OnWidth3();

afx_msg void OnWidth4();

afx_msg void OnSolidline();

// }}AFX_MSG

DECLARE_MESSAGE_MAP()

};

#ifndef _DEBUG  // debug version in ExPenView.cpp

inline CExPenDoc* CExPenView::GetDocument()

{ return (CExPenDoc*)m_pDocument; }

#endif

//////////////////////////////////////////////////////////////////////////

// {{AFX_INSERT_LOCATION}}

// Microsoft Visual C++ will insert additional declarations immediately before the previous line.

#endif // !defined(AFX_EXPENVIEW_H__9DA7682D_20B9_11D2_8B56_006097AD9DDC__INCLUDED_)

// ExPenView.cpp : implementation of the CExPenView class

//

#include “stdafx.h”

#include “ExPen.h”

#include “ExPenDoc.h”

#include “ExPenView.h”

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

//////////////////////////////////////////////////////////////////////////// CExPenView

IMPLEMENT_DYNCREATE(CExPenView, CView)

BEGIN_MESSAGE_MAP(CExPenView, CView)

// {{AFX_MSG_MAP(CExPenView)

ON_WM_LBUTTONDOWN()

ON_WM_MOUSEMOVE()

ON_WM_LBUTTONUP()

ON_COMMAND(ID_PS_DASH, OnPsDash)

ON_COMMAND(ID_PS_DASHDOT, OnPsDashdot)

ON_COMMAND(ID_PS_DASHDOTDOT, OnPsDashdotdot)

ON_COMMAND(ID_PS_DOT, OnPsDot)

ON_COMMAND(ID_BLUE, OnBlue)

ON_COMMAND(ID_BLACK, OnBlack)

ON_COMMAND(ID_RED, OnRed)

ON_COMMAND(ID_GREEN, OnGreen)

ON_COMMAND(ID_WIDTH1, OnWidth1)

ON_COMMAND(ID_WIDTH2, OnWidth2)

ON_COMMAND(ID_WIDTH3, OnWidth3)

ON_COMMAND(ID_WIDTH4, OnWidth4)

ON_COMMAND(ID_SOLIDLINE, OnSolidline)

// }}AFX_MSG_MAP

// Standard printing commands

ON_COMMAND(ID_FILE_PRINT, CView::OnFilePrint)

ON_COMMAND(ID_FILE_PRINT_DIRECT, CView::OnFilePrint)

ON_COMMAND(ID_FILE_PRINT_PREVIEW, CView::OnFilePrintPreview)

END_MESSAGE_MAP()

//////////////////////////////////////////////////////////////////////////

// CExPenView construction/destruction

CExPenView::CExPenView()

{{

}}{

// 초기화

// 색은 검정색

m_color=RGB(0,0,0);

// 선 굵기는 1

m_nWidth=1;

// 그리기 스타일은 직선

m_nPenStyle=PS_SOLID;

m_bDown=FALSE;

m_oldrect=CRect(0,0,0,0);

m_rect=CRect(0,0,0,0);

}

CExPenView::~CExPenView()

{

}

BOOL CExPenView::PreCreateWindow(CREATESTRUCT& cs)

{

// TODO: Modify the Window class or styles here by modifying

//  the CREATESTRUCT cs

return CView::PreCreateWindow(cs);

}

//////////////////////////////////////////////////////////////////////////// CExPenView drawing

void CExPenView::OnDraw(CDC* pDC)

{

CExPenDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

CPen pen,*oldpen;

// 메뉴에서 선택한 형태로 펜을 설정한다.

pen.CreatePen(m_nPenStyle,m_nWidth,m_color);

oldpen=pDC->SelectObject(&pen);

// 마우스로 움직이면 최종 영역이 m_rect에 저장되므로 이 영역을

// 라인으로 그린다.

pDC->MoveTo(m_rect.TopLeft());

pDC->LineTo(m_rect.right,m_rect.top);

pDC->LineTo(m_rect.BottomRight());

pDC->LineTo(m_rect.left,m_rect.bottom);

pDC->LineTo(m_rect.TopLeft());

// 이전 펜 복귀

pDC->SelectObject(oldpen);

// 펜 객체 삭제

pen.DeleteObject();

}

//////////////////////////////////////////////////////////////////////////// CExPenView printing

BOOL CExPenView::OnPreparePrinting(CPrintInfo* pInfo)

{

// default preparation

return DoPreparePrinting(pInfo);

}

void CExPenView::OnBeginPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)

{

// TODO: add extra initialization before printing

}

void CExPenView::OnEndPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)

{

// TODO: add cleanup after printing

}

//////////////////////////////////////////////////////////////////////////// CExPenView diagnostics

#ifdef _DEBUG

void CExPenView::AssertValid() const

{

CView::AssertValid();

}

void CExPenView::Dump(CDumpContext& dc) const

{

CView::Dump(dc);

}

CExPenDoc* CExPenView::GetDocument() // non-debug version is inline

{

ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CExPenDoc)));

return (CExPenDoc*)m_pDocument;

}

#endif // _DEBUG

//////////////////////////////////////////////////////////////////////////// CExPenView message handlers

void CExPenView::OnLButtonDown(UINT nFlags, CPoint point)

{

m_bDown=TRUE;

m_rect=CRect(point.x,point.y,point.x,point.y);

m_oldrect=m_rect;

CView::OnLButtonDown(nFlags, point);

}

void CExPenView::OnMouseMove(UINT nFlags, CPoint point)

{

if(m_bDown)

{

CDC *pDC=GetDC();

pDC->SetROP2(R2_XORPEN);

CPen pen,*oldpen;

// 펜을 만든다.

pen.CreatePen(PS_DOT,2,RGB(127,127,127));

// 펜을 등록한다.

oldpen=pDC->SelectObject(&pen);

// m_oldrect 영역에 라인을 그린다.

// 이때 m_rect로 그려진 영역이 지워진다.

pDC->MoveTo(m_oldrect.TopLeft());

pDC->LineTo(m_oldrect.right,m_oldrect.top);

pDC->LineTo(m_oldrect.BottomRight());

pDC->LineTo(m_oldrect.left,m_oldrect.bottom);

pDC->LineTo(m_oldrect.TopLeft());

// m_rect에 point의 x, y를 right,bottom에 설정한다.

m_rect=CRect(m_rect.left,m_rect.top,point.x,point.y);

// m_rect 영역을 그린다.

pDC->MoveTo(m_rect.TopLeft());

pDC->LineTo(m_rect.right,m_rect.top);

pDC->LineTo(m_rect.BottomRight());

pDC->LineTo(m_rect.left,m_rect.bottom);

pDC->LineTo(m_rect.TopLeft());

// 이전 펜으로 설정

pDC->SelectObject(oldpen);

pen.DeleteObject();

ReleaseDC(pDC);

// 현재 그려진 영역을 m_oldrect에 설정

m_oldrect=m_rect;

}

CView::OnMouseMove(nFlags, point);

}

void CExPenView::OnLButtonUp(UINT nFlags, CPoint point)

{

// 버튼 떼어짐을 알리기 위해 m_bDown을 FALSE로 설정

m_bDown=FALSE;

// m_rectold 영역에 라인을 그린다.

CDC *pDC=GetDC();

CPen pen,*oldpen;

pen.CreatePen(PS_DOT,2,RGB(127,127,127));

oldpen=pDC->SelectObject(&pen);

pDC->SetROP2(R2_XORPEN);

pDC->MoveTo(m_oldrect.TopLeft());

pDC->LineTo(m_oldrect.right,m_oldrect.top);

pDC->LineTo(m_oldrect.BottomRight());

pDC->LineTo(m_oldrect.left,m_oldrect.bottom);

pDC->LineTo(m_oldrect.TopLeft());

pen.DeleteObject();

ReleaseDC(pDC);

// m_oldrect는 초기화

m_oldrect=CRect(0,0,0,0);

// OnDraw 함수 호출

Invalidate();

CView::OnLButtonUp(nFlags, point);

}

void CExPenView::OnPsDash()

{

m_nPenStyle=PS_DASH;

Invalidate();

}

void CExPenView::OnPsDashdot()

{

m_nPenStyle=PS_DASHDOT;

Invalidate();

}

void CExPenView::OnPsDashdotdot()

{

m_nPenStyle=PS_DASHDOTDOT;

Invalidate();

}

void CExPenView::OnPsDot()

{

m_nPenStyle=PS_DOT;

Invalidate();

}

// PS_SOLD 항목 설정

void CExPenView::OnSolidline()

{

m_nPenStyle=PS_SOLID;

Invalidate();

}

void CExPenView::OnBlue()

{

m_color=RGB(0,0,255);

Invalidate();

}

void CExPenView::OnBlack()

{

m_color=RGB(0,0,0);

Invalidate();

}

void CExPenView::OnRed()

{

m_color=RGB(255,0,0);

Invalidate();

}

void CExPenView::OnGreen()

{

m_color=RGB(0,255,0);

Invalidate();

}

void CExPenView::OnWidth1()

{

m_nWidth=1;

Invalidate();

}

void CExPenView::OnWidth2()

{

m_nWidth=2;

Invalidate();

}

void CExPenView::OnWidth3()

{

m_nWidth=3;

Invalidate();

}

void CExPenView::OnWidth4()

{

m_nWidth=4;

Invalidate();

}

본 항목에서는 사각형, 원, 다각형 등의 도형을 그리는 방법에 대해서 설명합니다. 도형을 그릴 경우 이것은 모두 CDC에 내장된 그래픽 함수들입니다. 본 항목에서는 필수적인 도형 그리기를 설명합니다. 나머지 부분들은 도움말을 참조하기 바랍니다.

상자 그리기

화면에 상자를 그리고자 할 경우 CDC의 멤버 함수인 Rectangle 함수를 사용할 수 있습니다. 이 함수는 다음과 같습니다.

BOOL Rectangle( int x1, int y1, int x2, int y2 );

BOOL Rectangle( LPCRECT lpRect );

x1, y1, x2, y2 : 상자를 그리는 좌표

lpRect : 상자를 그리는 CRect 클래스 변수

만일 화면의 0, 0, 100, 100에 좌표를 그리고자 한다면 다음과 같이 할 수 있습니다.

pDC->Rectangle(CRect(0,0,100,100));

이렇게 하면 외곽선은 현재 DC에 내장되어 있는 펜의 브러시를 이용하여 상자 영역을 칠할 수 있습니다. 펜과 브러시를 등록하지 않으면 기본적으로 등록되어 있는 검정색 펜과 흰색 브러시를 이용하여 상자를 그릴 수 있습니다.

모가 둥근 상자 그리기

모가 둥근 상자는 CDC의 멤버 함수인 RoundRect 함수를 이용합니다. RoundRect는 다음과 같습니다.

BOOL RoundRect( int x1, int y1, int x2, int y2, int x3, int y3 )

BOOL RoundRect( LPCRECT lpRect, POINT point );

x1, y1, x2, y2 : 상자 영역 좌표

x3 : 가로축으로의 지름

y3 : 세로축으로의 지름

lpRect : x1, y1, x2, y2의 값을 저장하고 있는 CRect 구조체

point : x3, y3의 값을 저장하고 있는 포인터

RoundRect 함수를 이용하여 상자를 그리는 방법은 다음과 같습니다.

pDC->RoundRect(0,0,100,100,20,20);

위의 내용은 화면 0, 0, 100, 100에 모서리가 x, y 지름 20, 20인 것으로 둥글게 되어 있는 형태입니다.

원 그리기

CDC 클래스에 내장되어 있는 원을 그리는 함수에는 Ellipse, Arc, Chord, Pie 등이 있습니다.

상자 영역 안에 원을 그리고자 할 경우에는 Ellipse를 사용합니다.

BOOL Ellipse( int x1, int y1, int x2, int y2 );

BOOL Ellipse( LPCRECT lpRect );

x1, y1, x2, y2 : 원을 그릴 상자 영역

lpRect : 원을 그릴 상자 영역을 가지고 있는 CRect 클래스

Ellipse 함수는 설정한 상자 영역에 맞는 원을 그려주며, Arc는 설정된 영역에 그리기 시작 좌표(x, y)에서 종료 지점(x, y)까지 원호를 그려줍니다. Chord 함수는 현을 그려주며 Pie는 부채꼴을 그려줍니다. Arc와 Chord와 Pie의 출력 형태는 그림 1-3-19와 같습니다.

Arc와 Chord, Pie 함수 형태는 다음과 같습니다.

BOOL Arc(

int nLeftRect, // 좌측 x좌표

int nTopRect, // 상단 y좌표

int nRightRect, // 우측 x좌표

int nBottomRect, // 하단 y좌표

int nXRadial1, // 그리기를 시작하는 x지점

int nYRadial1, // 그리기를 시작하는 y지점

int nXRadial2, // 그리기를 종료하는 x지점

int nYRadial2 // 그리기를 종료하는 y지점

);

BOOL Chord(

int nLeftRect, // 좌측 x좌표

int nTopRect, // 상단 y좌표

int nRightRect, // 우측 x좌표

int nBottomRect, // 하단 y좌표

int nXRadial1, // 그리기를 시작하는 x지점

int nYRadial1, // 그리기를 시작하는 y지점

int nXRadial2, // 그리기를 종료하는 x지점

int nYRadial2 // 그리기를 종료하는 y지점

);

BOOL Pie(

int nLeftRect, // 좌측 x좌표

int nTopRect, // 상단 y좌표

int nRightRect, // 우측 x좌표

int nBottomRect, // 하단 y좌표

int nXRadial1, // 그리기를 시작하는 x지점

int nYRadial1, // 그리기를 시작하는 y지점

int nXRadial2, // 그리기를 종료하는 x지점

int nYRadial2 // 그리기를 종료하는 y지점

);

다각형 그리기와 칠하기

다각형 그리기와 칠하기는 CDC 멤버 함수인 Polyline과 Polygon의 두 개 함수입니다. Polyline은 다각형의 외곽선을 그리는 함수이며, Polygon은 다각형을 칠하는 함수입니다.

다각형 선 그리기

BOOL Polyline( LPPOINT lpPoints, int nCount );

lpPoints : 다각형 좌표 Point 배열 포인터

nCount : 좌표의 수

다각형 선 그리기

BOOL Polygon( LPPOINT lpPoints, int nCount );

lpPoints : 다각형 좌표 Point 배열 포인터

nCount : 좌표의 수

베지어 곡선

베지어 곡선이란 다각형의 여러 점 사이를 원형으로 그리는 형태를 말합니다.

그림 1-3-20이 베지어 곡선의 형태입니다. 많은 점들로 이루어진  Polyline일 경우 그 점 사이가 매우 밀접하고 이 점 사이를 부드럽게 연결하고자 할 경우 베지어 곡선을 사용합니다. 베지어 곡선을 사용하고자 할 경우 최하 세 개의 좌표가 필요합니다. 그림 1-3-20은 네 개의 꼭지점 사이를 부드럽게 연결시켜 주는 베지어 곡선을 나타낸 것입니다. 베지어 곡선을 그리는 함수 또한 CDC에 있습니다. 이 함수는 PolyBezier입니다.

PolyBezier 함수는 다음과 같습니다.

BOOL PolyBezier( const POINT* lpPoints, int nCount );

lpPoints : 베지어 곡선을 그릴 Point의 배열

nCount : 배열의 수

도형 그리기 예제 ExDraw

ExDraw는 상자, 원, 라운드 상자, 다각형, 베지어 곡선 등을 그리는 예제입니다. 이예제의 전체 구성도는 그림 1-3-21과 같습니다.

ExDraw는 ExPen과 같은 루틴으로 제작되었습니다. 좌측 마우스 버튼을 클릭한 상태에서 드래깅하고 마우스 버튼을 놓으면 해당 영역에 각 메뉴에서 설정한 값에 의한 도형이 출력됩니다. ExDraw는 SDI 형태로 프로젝트를 만듭니다. 이 예제를 만드는 순서는 다음과 같습니다.

(1) 메뉴 항목에서 선택한 값을 저장할 변수 설정

그림 1-3-21에서 보듯이 메뉴 항목을 선택했을 때 선택한 값이 저장되는 변수는 네 개입니다. 이 변수는 그림 1-3-21의 ⑦, ⑧, ⑨, ⑩에 설명되어 있습니다. 이것과 함께 마우스 버튼을 누른 상태에서 이동할 때 영역을 그리는 세 개의 변수 m_rect, m_rectold, m_bDown을 View 클래스 헤더에 설정합니다.(본 예제도 View 클래스에서만 작성합니다.) 헤더에 멤버를 설정한 내용은 다음과 같습니다.

BOOL m_bDown;// 마우스 버튼 누른 상태 플러그

CRect m_rect;// 그리기 영역 좌표

CRect m_oldrect;// 마우스 드래깅시 이전 영역 좌표 설정

int m_nDrawStyle;// 그리기 항목

int m_nbStyle;// 브러시 항목

int m_nbPattern;// 패턴 항목

COLORREF m_nbColor;// 브러시 색상

COLORREF m_npColor;// 선 색상

위와 같이 멤버 변수를 설정한 후에 생성자에 다음과 같이 각 변수를 초기화합니다.

m_bDown=FALSE;

m_rect=CRect(0,0,0,0);

m_oldrect=CRect(0,0,0,0);

m_nDrawStyle=0;

m_nbStyle=BS_SOLID;

m_nbPattern=0;

m_nbColor=RGB(255,255,255);

m_npColor=RGB(0,0,0);

처음의 그리기 스타일은 상자이며 검정색으로 패턴 없이 칠하도록 설정하였습니다.

(2) 마우스 이동시 영역 그리기

마우스 이동시 영역 그리기는 ExPen과 같습니다. 자세한 내용은 ExPen 예제를 만들 때를 참조하기 바랍니다.

(3) OnDraw 함수 수정

OnDraw 함수는 멤버에 설정된 값에 따라 도형을 그리도록 설정합니다. 수정된 OnDraw 함수는 다음과 같습니다.

void CExDrawView::OnDraw(CDC* pDC)

{

CExDrawDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

LOGBRUSH logbr;

CPen pen,*oldpen;

CBrush brush,*oldbrush;

// LOGBRUSH 형에 메뉴 항목에서 선택된 값을 설정

logbr.lbStyle=m_nbStyle;

logbr.lbHatch=m_nbPattern;

logbr.lbColor=m_nbColor;

// 펜과 브러시를 만든다.

pen.CreatePen(PS_SOLID,1,m_npColor);

brush.CreateBrushIndirect(&logbr);

oldpen=pDC->SelectObject(&pen);

oldbrush=pDC->SelectObject(&brush);

// 베지어 곡선과 다각형을 위해서 포인터 배열을 만든다.

CPoint point[5];

point[0]=m_rect.TopLeft();

point[1]=CPoint(m_rect.right,m_rect.top);

point[2]=m_rect.BottomRight();

point[3]=CPoint(m_rect.left,m_rect.bottom);

point[4]=m_rect.TopLeft();

// 각 스타일에 따라 그림을 그린다.

switch(m_nDrawStyle)

{

case 0:// 상자

pDC->Rectangle(m_rect);

break;

case 1:// 원

pDC->Ellipse(m_rect);

break;

case 2:// 라운드 상자

pDC->RoundRect(m_rect,CPoint(20,20));

break;

case 3:// 다각형

pDC->Polygon((LPPOINT)point,5);

break;

case 4:// 베지어 곡선

pDC->PolyBezier(point,4);

break;

}

// 이전 브러시로 전환

pDC->SelectObject(oldpen);

pDC->SelectObject(oldbrush);

pen.DeleteObject();

brush.DeleteObject();

}

(4) 메뉴 항목을 선택하였을 때 멤버 변수에 값 설정

그림 1-3-21의 ②∼⑥ 항목을 선택하였을 때의 함수를 클래스 위저드를 이용하여 제작하고, 이 함수에 그림 1-3-21의 ⑦∼⑩ 항목에 설명한 값을 대입합니다. 그후에 Invalidate 함수를 실행시키고 OnDraw 함수를 호출하여 설정한 값으로 화면을 다시 그립니다. 다음은 그리기 메뉴에서 상자를 선택하였을 때의 함수 내용입니다.

void CExDrawView::OnDrawRectangle()

{

m_nDrawStyle=0;

Invalidate();

}

ExDraw 소스

ExDraw 예제에서 프로그램을 작성할 때 수정한 부분은 View 클래스입니다. View 클래스의 소스는 다음과 같습니다. 나머지 소스는 제공되는 CD를 참조하십시오.

// ExDrawView.h : interface of the CExDrawView class

//

//////////////////////////////////////////////////////////////////////////

#if !defined(AFX_EXDRAWVIEW_H__21237A1A_20B7_11D2_8B56_006097AD9DDC__INCLUDED_)

#define AFX_EXDRAWVIEW_H__21237A1A_20B7_11D2_8B56_006097AD9DDC__INCLUDED_

#if _MSC_VER > 1000

#pragma once

#endif // _MSC_VER > 1000

class CExDrawView : public CView

{

protected: // create from serialization only

CExDrawView();

DECLARE_DYNCREATE(CExDrawView)

// Attributes

public:

CExDrawDoc* GetDocument();

// Operations

public:

// Overrides

// ClassWizard generated virtual function overrides

// {{AFX_VIRTUAL(CExDrawView)

public:

virtual void OnDraw(CDC* pDC);  // overridden to draw this view

virtual BOOL PreCreateWindow(CREATESTRUCT& cs);

protected:

virtual BOOL OnPreparePrinting(CPrintInfo* pInfo);

virtual void OnBeginPrinting(CDC* pDC, CPrintInfo* pInfo);

virtual void OnEndPrinting(CDC* pDC, CPrintInfo* pInfo);

// }}AFX_VIRTUAL

// Implementation

public:

BOOL m_bDown;// 마우스 버튼을 누른 상태 플러그

CRect m_rect;// 그리기 영역 좌표

CRect m_oldrect;// 마우스 드래깅시 이전 영역 좌표 설정

int m_nDrawStyle;// 그리기 항목

int m_nbStyle;// 브러시 항목

int m_nbPattern;// 패턴 항목

COLORREF m_nbColor;// 브러시 색상

COLORREF m_npColor;// 선 색상

virtual ~CExDrawView();

#ifdef _DEBUG

virtual void AssertValid() const;

virtual void Dump(CDumpContext& dc) const;

#endif

protected:

// Generated message map functions

protected:

// {{AFX_MSG(CExDrawView)

afx_msg void OnLButtonDown(UINT nFlags, CPoint point);

afx_msg void OnMouseMove(UINT nFlags, CPoint point);

afx_msg void OnLButtonUp(UINT nFlags, CPoint point);

afx_msg void OnRoundrect();

afx_msg void OnPolybezier();

afx_msg void OnDrawEllipse();

afx_msg void OnDrawPolygon();

afx_msg void OnDrawRectangle();

afx_msg void OnBsSolid();

afx_msg void OnBsPattern();

afx_msg void OnBsHollow();

afx_msg void OnBsHatched();

afx_msg void OnHsBdiagonla();

afx_msg void OnHsCross();

afx_msg void OnHsDiagcross();

afx_msg void OnHsHorizontal();

afx_msg void OnHsVertical();

afx_msg void OnColorBlack();

afx_msg void OnColorBlue();

afx_msg void OnColorGreen();

afx_msg void OnColorRed();

afx_msg void OnColorWhite();

afx_msg void OnPcolorBlack();

afx_msg void OnPcolorBlue();

afx_msg void OnPcolorGreen();

afx_msg void OnPcolorRed();

afx_msg void OnFdiagonal();

// }}AFX_MSG

DECLARE_MESSAGE_MAP()

};

#ifndef _DEBUG  // debug version in ExDrawView.cpp

inline CExDrawDoc* CExDrawView::GetDocument()

{ return (CExDrawDoc*)m_pDocument; }

#endif

//////////////////////////////////////////////////////////////////////////

// {{AFX_INSERT_LOCATION}}

// Microsoft Visual C++ will insert additional declarations immediately

// before the previous line.

#endif // !defined(AFX_EXDRAWVIEW_H__21237A1A_20B7_11D2_8B56_006097AD9DDC__INCLUDED_)

// ExDrawView.cpp : implementation of the CExDrawView class

//

#include “stdafx.h”

#include “ExDraw.h”

#include “ExDrawDoc.h”

#include “ExDrawView.h”

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

//////////////////////////////////////////////////////////////////////////// CExDrawView

IMPLEMENT_DYNCREATE(CExDrawView, CView)

BEGIN_MESSAGE_MAP(CExDrawView, CView)

// {{AFX_MSG_MAP(CExDrawView)

ON_WM_LBUTTONDOWN()

ON_WM_MOUSEMOVE()

ON_WM_LBUTTONUP()

ON_COMMAND(ID_ROUNDRECT, OnRoundrect)

ON_COMMAND(ID_POLYBEZIER, OnPolybezier)

ON_COMMAND(ID_DRAW_ELLIPSE, OnDrawEllipse)

ON_COMMAND(ID_DRAW_POLYGON, OnDrawPolygon)

ON_COMMAND(ID_DRAW_RECTANGLE, OnDrawRectangle)

ON_COMMAND(ID_BS_SOLID, OnBsSolid)

ON_COMMAND(ID_BS_PATTERN, OnBsPattern)

ON_COMMAND(ID_BS_HOLLOW, OnBsHollow)

ON_COMMAND(ID_BS_HATCHED, OnBsHatched)

ON_COMMAND(ID_HS_BDIAGONLA, OnHsBdiagonla)

ON_COMMAND(ID_HS_CROSS, OnHsCross)

ON_COMMAND(ID_HS_DIAGCROSS, OnHsDiagcross)

ON_COMMAND(ID_HS_HORIZONTAL, OnHsHorizontal)

ON_COMMAND(ID_HS_VERTICAL, OnHsVertical)

ON_COMMAND(ID_COLOR_BLACK, OnColorBlack)

ON_COMMAND(ID_COLOR_BLUE, OnColorBlue)

ON_COMMAND(ID_COLOR_GREEN, OnColorGreen)

ON_COMMAND(ID_COLOR_RED, OnColorRed)

ON_COMMAND(ID_COLOR_WHITE, OnColorWhite)

ON_COMMAND(ID_PCOLOR_BLACK, OnPcolorBlack)

ON_COMMAND(ID_PCOLOR_BLUE, OnPcolorBlue)

ON_COMMAND(ID_PCOLOR_GREEN, OnPcolorGreen)

ON_COMMAND(ID_PCOLOR_RED, OnPcolorRed)

ON_COMMAND(ID_FDIAGONAL, OnFdiagonal)

// }}AFX_MSG_MAP

// Standard printing commands

ON_COMMAND(ID_FILE_PRINT, CView::OnFilePrint)

ON_COMMAND(ID_FILE_PRINT_DIRECT, CView::OnFilePrint)

ON_COMMAND(ID_FILE_PRINT_PREVIEW, CView::OnFilePrintPreview)

END_MESSAGE_MAP()

//////////////////////////////////////////////////////////////////////////// CExDrawView construction/destruction

CExDrawView::CExDrawView()

{

m_bDown=FALSE;

m_rect=CRect(0,0,0,0);

m_oldrect=CRect(0,0,0,0);

m_nDrawStyle=0;

m_nbStyle=BS_SOLID;

m_nbPattern=0;

m_nbColor=RGB(255,255,255);

m_npColor=RGB(0,0,0);

}

CExDrawView::~CExDrawView()

{

}

BOOL CExDrawView::PreCreateWindow(CREATESTRUCT& cs)

{

// TODO: Modify the Window class or styles here by modifying

//  the CREATESTRUCT cs

return CView::PreCreateWindow(cs);

}

//////////////////////////////////////////////////////////////////////////// CExDrawView drawing

void CExDrawView::OnDraw(CDC* pDC)

{

CExDrawDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

LOGBRUSH logbr;

CPen pen,*oldpen;

CBrush brush,*oldbrush;

// LOGBRUSH 형에 메뉴 항목에서 선택된 값을 설정

logbr.lbStyle=m_nbStyle;

logbr.lbHatch=m_nbPattern;

logbr.lbColor=m_nbColor;

// 펜과 브러시를 만든다.

pen.CreatePen(PS_SOLID,1,m_npColor);

brush.CreateBrushIndirect(&logbr);

oldpen=pDC->SelectObject(&pen);

oldbrush=pDC->SelectObject(&brush);

// 베지어 곡선과 다각형을 위해서 포인터 배열을 만든다.

CPoint point[5];

point[0]=m_rect.TopLeft();

point[1]=CPoint(m_rect.right,m_rect.top);

point[2]=m_rect.BottomRight();

point[3]=CPoint(m_rect.left,m_rect.bottom);

point[4]=m_rect.TopLeft();

// 각 스타일에 따라 그림을 그린다.

switch(m_nDrawStyle)

{

case 0:// 상자

pDC->Rectangle(m_rect);

break;

case 1:// 원

pDC->Ellipse(m_rect);

break;

case 2:// 라운드 상자

pDC->RoundRect(m_rect,CPoint(20,20));

break;

case 3:// 다각형

pDC->Polygon((LPPOINT)point,5);

break;

case 4:// 베지어 곡선

pDC->PolyBezier(point,4);

break;

}

// 이전 브러시로 전환

pDC->SelectObject(oldpen);

pDC->SelectObject(oldbrush);

pen.DeleteObject();

brush.DeleteObject();

}

//////////////////////////////////////////////////////////////////////////// CExDrawView printing

BOOL CExDrawView::OnPreparePrinting(CPrintInfo* pInfo)

{

// default preparation

return DoPreparePrinting(pInfo);

}

void CExDrawView::OnBeginPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)

{

// TODO: add extra initialization before printing

}

void CExDrawView::OnEndPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)

{

// TODO: add cleanup after printing

}

//////////////////////////////////////////////////////////////////////////// CExDrawView diagnostics

#ifdef _DEBUG

void CExDrawView::AssertValid() const

{

CView::AssertValid();

}

void CExDrawView::Dump(CDumpContext& dc) const

{

CView::Dump(dc);

}

CExDrawDoc* CExDrawView::GetDocument() // non-debug version is inline

{

ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CExDrawDoc)));

return (CExDrawDoc*)m_pDocument;

}

#endif // _DEBUG

//////////////////////////////////////////////////////////////////////////// CExDrawView message handlers

// 좌측 마우스 버튼 클릭 상태에서 마우스 이동시 영역 설정 함수들

void CExDrawView::OnLButtonDown(UINT nFlags, CPoint point)

{

m_bDown=TRUE;

m_rect=CRect(point.x,point.y,point.x,point.y);

m_oldrect=m_rect;

CView::OnLButtonDown(nFlags, point);

}

void CExDrawView::OnMouseMove(UINT nFlags, CPoint point)

{

if(m_bDown)

{

CDC *pDC=GetDC();

pDC->SetROP2(R2_XORPEN);

CPen pen,*oldpen;

pen.CreatePen(PS_DOT,2,RGB(127,127,127));

oldpen=pDC->SelectObject(&pen);

pDC->MoveTo(m_oldrect.TopLeft());

pDC->LineTo(m_oldrect.right,m_oldrect.top);

pDC->LineTo(m_oldrect.BottomRight());

pDC->LineTo(m_oldrect.left,m_oldrect.bottom);

pDC->LineTo(m_oldrect.TopLeft());

m_rect=CRect(m_rect.left,m_rect.top,point.x,point.y);

pDC->MoveTo(m_rect.TopLeft());

pDC->LineTo(m_rect.right,m_rect.top);

pDC->LineTo(m_rect.BottomRight());

pDC->LineTo(m_rect.left,m_rect.bottom);

pDC->LineTo(m_rect.TopLeft());

pDC->SelectObject(oldpen);

pen.DeleteObject();

ReleaseDC(pDC);

m_oldrect=m_rect;

}

CView::OnMouseMove(nFlags, point);

}

void CExDrawView::OnLButtonUp(UINT nFlags, CPoint point)

{

m_bDown=FALSE;

CDC *pDC=GetDC();

CPen pen,*oldpen;

pen.CreatePen(PS_DOT,2,RGB(127,127,127));

oldpen=pDC->SelectObject(&pen);

pDC->SetROP2(R2_XORPEN);

pDC->MoveTo(m_oldrect.TopLeft());

pDC->LineTo(m_oldrect.right,m_oldrect.top);

pDC->LineTo(m_oldrect.BottomRight());

pDC->LineTo(m_oldrect.left,m_oldrect.bottom);

pDC->LineTo(m_oldrect.TopLeft());

pen.DeleteObject();

ReleaseDC(pDC);

m_oldrect=CRect(0,0,0,0);

Invalidate();

CView::OnLButtonUp(nFlags, point);

}

// 그리기 메뉴 항목 선택시

void CExDrawView::OnRoundrect()

{

m_nDrawStyle=2;

Invalidate();

}

void CExDrawView::OnPolybezier()

{

m_nDrawStyle=4;

Invalidate();

}

void CExDrawView::OnDrawEllipse()

{

m_nDrawStyle=1;

Invalidate();

}

void CExDrawView::OnDrawPolygon()

{

m_nDrawStyle=3;

Invalidate();

}

void CExDrawView::OnDrawRectangle()

{

m_nDrawStyle=0;

Invalidate();

// 브러시 메뉴 항목 선택시

void CExDrawView::OnBsSolid()

{

m_nbStyle=BS_SOLID;

Invalidate();

}

void CExDrawView::OnBsPattern()

{

m_nbStyle=BS_PATTERN;

Invalidate();

}

void CExDrawView::OnBsHollow()

{

m_nbStyle=BS_HOLLOW;

Invalidate();

}

void CExDrawView::OnBsHatched()

{

m_nbStyle=BS_HATCHED;

Invalidate();

}

{{

}}// 패턴 메뉴 항목 선택시

void CExDrawView::OnHsBdiagonla()

{

m_nbPattern=HS_BDIAGONAL;

Invalidate();

}

void CExDrawView::OnFdiagonal()

{

m_nbPattern=HS_FDIAGONAL;

Invalidate();

}

void CExDrawView::OnHsCross()

{

m_nbPattern=HS_CROSS;

Invalidate();

}

void CExDrawView::OnHsDiagcross()

{

m_nbPattern=HS_DIAGCROSS;

Invalidate();

}

void CExDrawView::OnHsHorizontal()

{

m_nbPattern=HS_HORIZONTAL;

Invalidate();

}

void CExDrawView::OnHsVertical()

{

m_nbPattern=HS_VERTICAL;

Invalidate();

}

// 색상 항목 선택시

void CExDrawView::OnColorBlack()

{

m_nbColor=RGB(0,0,0);

Invalidate();

}

void CExDrawView::OnColorBlue()

{

m_nbColor=RGB(0,0,255);

Invalidate();

}

void CExDrawView::OnColorGreen()

{

m_nbColor=RGB(0,255,0);

Invalidate();

}

void CExDrawView::OnColorRed()

{

m_nbColor=RGB(255,0,0);

Invalidate();

}

void CExDrawView::OnColorWhite()

{

m_nbColor=RGB(255,255,255);

Invalidate();

// 선 색 메뉴 항목 선택시

void CExDrawView::OnPcolorBlack()

{

m_npColor=RGB(0,0,0);

Invalidate();

}

void CExDrawView::OnPcolorBlue()

{

m_npColor=RGB(0,0,255);

Invalidate();

}

void CExDrawView::OnPcolorGreen()

{

m_npColor=RGB(0,255,0);

Invalidate();

}

void CExDrawView::OnPcolorRed()

{

m_npColor=RGB(255,0,0);

Invalidate();

}

CDC를 얻고 문자를 출력하는 다양한 함수에 대해서 알아보겠습니다. TextOut은 가장 기본적인 문자 출력 함수입니다. 이 함수는 위에서 설명하였습니다.

본 항목에서는 TextOut 외에 CDC 클래스에 설정되어 있는 다양한 문자 출력 멤버 함수들을 설명합니다.

상자 영역에 문자 출력 DrawText

DrawText 함수는 상자 영역에 문자를 출력하는 함수입니다. DrawText 함수의 형태는 다음과 같습니다.

virtual int DrawText( LPCTSTR lpszString, int nCount, LPRECT lpRect, UINT nFormat );

int DrawText( const CString& str, LPRECT lpRect, UINT nFormat );

lpszString : 문자열 버퍼

nCount : 문자열의 길이

lpRect : 문자열을 출력할 상자 좌표

nFormat : 문자열을 출력하는 옵션 플러그

str : CString형의 문자열이 설정된 변수

DrawText 함수는 문자열 배열과 CString 클래스를 이용하는 두 개의 함수로 설정되어 있습니다. 문자열 배열을 이용할 경우에는 nCount라는 문자열의 길이를 얻는 한 개의 인자가 더 추가됩니다.

nFromat은 문자열을 출력하는 방법에 대한 플러그입니다. 표9는 이 플러그의 리스트입니다.

표9    nFormat 플러그 리스트

값	내   용

DT_TOP	설정된 lpRect 영역에 상단으로(DT_TOP), 하단으로(DT_BOT- TOM), 좌측으로(DT_LEFt), 우측으로(DT_RIGHT) 문자를 출력한다. DT_SINGLELINE이 함께 지정되어 있어야 한다.

DT_CENTER	설정된 영역의 가로 중앙에 정렬시킨다.

DT_VCENTER	설정된 영역의 세로 중앙에 정렬시킨다.

DT_EXPANDTABS	문자열에 탭이 포함되어 있을 때 공백으로 띄어 준다. 기본폭은 8이다.

DT_CALCRECT	텍스트가 출력할 사각형의 영역을 계산한다. 이때문자는 출력되지 않는다.

DT_EXTERNALLEADING	문자의 높이를 계산할 때 extenal leading sapce를 포함시킨다.

DT_SINGLELINE	행 바꿈과 라인 피드를 무시하고 한 줄로 출력한다.

DT_NOCLIP	클리핑 없이 문자를 출력한다.

DT_NOPREFIX	& 문자를 표식어로 사용하지 않는다. 일반적으로 & 코드는 출력하지 않고 다음 문자에 밑줄을 그린다.

DT_TABSTOP	Flat 상위 8비트에 값을 넣어서 탭 간격을 변화시킨다.

DT_WORDBREAK	문자열이 영역 밖으로 출력되면 개행하여 출력한다.

탭키에 대응하는 문자 출력 TabbedTextOut

문자열에 탭 코드 ‘\t’가 설정되어 있을 때 이 키값에 대응하여 설정된 탭 크기만큼 위치를 띄어주는 함수가 TabbedTextOut입니다. 이 함수의 형태는 다음과 같습니다.

virtual CSize TabbedTextOut( int x, int y, LPCTSTR lpszString, int nCount, int nTabPositions, LPINT lpnTabStopPositions, int nTabOrigin );

CSize TabbedTextOut( int x, int y, const CString& str, int nTabPositions, LPINT lpnTabStopPositions, int nTabOrigin );

x : 출력 x좌표

y : 출력 y좌표

lpszString : 문자열 버퍼

str : CString형의 문자열 클래스

nCount : 문자열 버퍼의 길이

nTabPositions : 탭값의 정수형 배열수

lpnTabStopPositions : 탭 폭의 정수형 배열

nTabOrigin : 탭이 시작되는 x좌표의 위치

x, y는 출력을 의미하며 lpszString과 str는 문자열입니다. nTabPositions는 탭값이 정수형 배열수입니다. 예를 들어 탭의 크기를 설정하는 배열을 다음과 같이 정의했다면

int tabstop[4]={8,16,24,32};

nTabPositions는 4가 됩니다. lpnTabStopPositions는 바로 tabstop값을 의미합니다. nTabOrigin은 탭이 시작되는 위치를 말합니다.

nTabPositions가 0일 경우에는 lpnTabStopPositions값이 무시되고 문자열에서 ‘\t’값을 만날 때마다 문자의 기본 가로 크기 곱하기 8값의 크기로 띄어줍니다. 이 값이 만일 1이라고 한다면 tabstop 배열의 첫 번째값 8만 유효하고 이 값 곱하기 문자열의 평균 가로 크기의 값이 출력됩니다. 4로 설정하였으며, 처음에는 8 곱하기 문자 가로 크기, 두 번째는 16 곱하기 문자 가로 크기, 세 번째는 24 곱하기 문자 가로 크기로 떨어지면서 탭 코드에 대응됩니다. 이 함수는 SetTextAlign 함수에서 TA_UPDATECP를 설정해주어야 정확한 탭 크기가 설정됩니다.

상자 영역 및 좌표 설정 문자 출력 ExtTextOut

ExtTextOut 함수는 좌표 영역과 상자 영역을 함께 사용하여 문자를 출력하는 함수입니다. 이 함수의 형태는 다음과 같습니다.

virtual BOOL ExtTextOut( int x, int y, UINT nOptions, LPCRECT lpRect, LPCTSTR lpszString, UINT nCount, LPINT lpDxWidths );

BOOL ExtTextOut( int x, int y, UINT nOptions, LPCRECT lpRect, const CString& str, LPINT lpDxWidths );

x : 출력 x좌표

y : 출력 y좌표

nOptions : 출력 옵션

lpREct : 상자 좌표

lpszString : 문자열

nCount : 문자의 수

st r: 문자가 설정되어 있는 CString 클래스

lpDxWidths : 문자열의 원점으로부터 출력값의 배열

nOptions를 ETO_CLIPPED로 설정하였을 경우에는 x, y 좌표가 무시되고 lpRect의 영역으로 설정되며, ETO_OPAQUE값을 설정하면 현재 설정된 배경색으로 상자 영역을 칠한 뒤에 문자를 출력합니다.

출력색 설정하기 SetTextColor와 배경색 설정하기 SetBkColor

SetTextColor는 출력색을 설정하는 것이고, SetBkColor는 배경색을 설정하는 함수입니다. 다음은 이 두 함수의 형태입니다.

virtual COLORREF SetTextColor( COLORREF crColor );

virtual COLORREF SetBkColor( COLORREF crColor );

예를 들어서 출력색은 청색으로, 배경색은 노랑색으로 한다면 다음과 같이 할 수 있습니다.

pDC->SetTextColor(RGB(0,0,255));

pDC->SetBkColor(RGB(255,255,0));

기타 문자 출력에 관한 함수들

배경과 투명한 문자 출력

문자를 출력하면 기본적으로 설정된 배경색과 출력색이 같이 출력됩니다. 일반적으로 배경색은 흰색이고 출력색은 검정색입니다.

우리는 때로 배경에 글자만 출력되고 배경색은 없는 형태를 출력하고자 할 경우가 있습니다. 이럴 경우 SetBkMode 함수를 사용합니다. SetBkMode를 이용하여 배경에 투명한 글자를 출력시키고자 할 경우 글자를 출력하기 전에 다음과 같은 함수를 사용합니다.

pDC->SetBkMode(TRANSPARENT);

텍스트 정렬 함수

텍스트를 출력할 때 출력 기본점을 정하고 이것에 의해 정렬하여 출력하는 함수가 있습니다. 우리는 일반적으로 좌표를 지정하면 좌측 상단과 문자의 좌측을 기준으로 출력합니다. 때로는 문자의 우측이 기준점이 되게 하고 싶을 경우도 있습니다.이렇게 문자의 정렬 방식을 설정한 함수가 CDC 멤버 함수 SetTextAlign입니다.

UINT SetTextAlign( UINT nFlags );

nFlag값은 표10과 같습니다.

예를 들어서 문자를 우측 하단에 맞추어서 출력하고자 한다면 다음과 같을 것입니다.

pDC->SetTextAlign(TA_BOTTOM|TA_RIGHT);

표 10    텍스트 정렬 플러그 리스트

모드	내용

TA_BASELINE	기본 베이스 라인에 의거하여 출력

TA_BOTTOM	설정된 좌표를 문자 하단부에 맞추어서 출력

TA_TOP	설정된 좌표를 문자 상단부에 맞추어서 출력

TA_CENTER	설정된 좌표를 문자 중앙에 맞추어서 출력

TA_LEFT	설정된 좌표를 문자 좌측에 맞추어서 출력

TA_RIGHT	설정된 좌표를 문자 우측에 맞추어서 출력

글자 정보 얻기

폰트를 설정할 때 그 크기가 고정되어 있는 형태의 폰트는 이제 거의 쓰지 않습니다. 글자에 따라 가변폭으로 변화됩니다. 예를 들면, I와 A의 가로 크기가 같다면 보기에 그렇게 좋지 않을 것이기 때문입니다. 어떤 글자를 출력할 때 한 글자 한 글자를 어떤 바둑판 모양에 출력할 필요가 있을 경우나 글자를 고정된 형태로 출력하고자 할 경우에 필요한 정보들입니다. 그림 1-3-22와 같은 정보를 얻고자 할 경우 GetTextMetrics를 사용합니다. 이것을 사용하면 위의 여섯 개의 값과 함께 tmAveCharWidth와 tmMaxCharWidth를 얻을 수 있습니다.

BOOL GetTextMetrics(

LPTEXTMETRIC lptm // 텍스트 메트릭스 구조체 정보

);

GetTextMetrics에 설정해주는 두 번째 인자 LPTEXTMETRIC 구조체는 다음과 같습니다.

typedef struct tagTEXTMETRIC { // tm

LONG tmHeight;

LONG tmAscent;

LONG tmDescent;

LONG tmInternalLeading;

LONG tmExternalLeading;

LONG tmAveCharWidth;

LONG tmMaxCharWidth;

LONG tmWeight;

LONG tmOverhang;

LONG tmDigitizedAspectX;

LONG tmDigitizedAspectY;

BCHAR tmFirstChar;

BCHAR tmLastChar;

BCHAR tmDefaultChar;

BCHAR tmBreakChar;

BYTE tmItalic;

BYTE tmUnderlined;

BYTE tmStruckOut;

BYTE tmPitchAndFamily;

BYTE tmCharSet;

} TEXTMETRIC;

위의 구조체에서 그림 1-3-22에 해당하는 값들 또한 얻을 수 있습니다.

글자 출력 예제 ExText

ExText는 지금까지 설명한 글자 출력과 CFont 클래스를 이용하여 폰트를 설정하는 예제입니다. 이 예제의 전체적인 구성도는 그림 1-3-23과 같습니다.

그림 1-3-23에서 보듯이 메뉴 항목은 출력 방법, 일반 폰트, 폰트 만들기, 크기 등으로 나누어져 있습니다. 출력 방법을 클릭하면 그림 1-3-23의 ①과 같은 항목이 나오고 전에 설명한 네 개의 문자 출력 함수가 항목으로 설정되어 있습니다. 이 항목을 클릭하면 멤버 변수의 m_nTextStyle에 순서대로 0, 1, 2, 3의 값을 가지게 됩니다. 이렇게 값을 가지고 있는 m_nTextStyle을 OnDraw에서 사용합니다. 그림 1-3-23의 ⓔ 항목이 바로 m_nTextStyle의 값에 의해서 switch문을 사용한 부분이며, 각 항목에 따라서 해당 함수를 사용하여 문자를 출력합니다. 이렇게 출력된 결과가 ⓐ, ⓑ, ⓒ, ⓓ입니다. ②, ③, ④ 메뉴 항목은 폰트를 만들 때 어떤 크기로 만들 것인가를 결정하는 항목입니다.

각 항목의 멤버 변수는 m_strFont, m_nMakeFont, m_nFontSize입니다. m_nMakeFont는 폰트를 만들 때 CreateFont 함수를 이용할 것인가 또는 CreateFontIndirect 함수를 이용할 것인가를 결정하는 변수입니다. 이 변수의 값이 0이면 CreateFont를 이용하며 1일 경우에는 CreateFontIndirect를 이용합니다. CreateFont를 이용할 때와 CreateFontIndirect를 이용할 때는 차이가 있습니다. CreateFontIndirect는 가로 크기와, 세로 크기를 각각 10으로 설정하였다 하더라도 그 문자의 스타일에 따라 보기 좋은 값으로 약간 변합니다. 즉, CreateFontIndirect 함수를 이용하면 글자체가 좀 보기 좋게 나타납니다. 실지로 폰트를 사용할 때는 CreateFontIndirect를 많이 사용합니다. 폰트 대화 상자에서 특정 폰트를 설정하였을 때(뒷 장에서 설명합니다.) LOGFONT가 값을 받으며, 이 값을 CreateFontIndirect에서 바로 이용할 수 있는 장점도 있습니다.

ExText 예제는 AppWizard를 이용하여 SDI 형태로 만듭니다. ExText는 다음과 같은 순서로 만듭니다.

(1) 메뉴를 바꿉니다.

메뉴 형태를 그림 1-3-23의 ①, ②, ③, ④와 같은 형태로 바꿉니다.

(2) 멤버 변수를 설정합니다.

본 예제에서는 멤버 변수가 네 개 필요합니다. 이 멤버 변수를 View 클래스 헤더에 설정합니다.(본 예제는 View 클래스에서만 내용을 코딩합니다.) 이 설정은 다음과 같습니다.

int m_nTextStyle;// 문자 출력 함수

int m_nFontSize;// 폰트 크기

CString m_strFont;// 폰트명

int m_nMakeFont;// CrateFont ? CreateFontIndirect?

(3) 생성자에 각 멤버 변수의 값을 초기화한다.

생성자에서 헤더에 설정한 멤버 변수를 초기화합니다. 멤버 변수를 만들어 놓았으면 생성자에서 해당 멤버 변수를 초기화하는 방법을 꼭 기억하기 바랍니다.

CExTextView::CExTextView()

{

m_strFont=”바탕체”;// 기본 폰트로 “바탕체” 설정

m_nMakeFont=0;// CreateFont 형태 설정

m_nFontSize=10;// 폰트 크기 10

m_nTextStyle=0; // 문자 출력은 TextOut 함수로 설정

}

(4) OnDraw 함수를 수정합니다.

OnDraw 함수를 그림 1-3-23의 ⑨와 같은 형태로 수정합니다.

이렇게 수정한 OnDraw 함수는 다음과 같습니다.

void CExTextView::OnDraw(CDC* pDC)

{

CExTextDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

CFont Font,*oldFont;

// 폰트 만드는 함수 결정

if(!m_nMakeFont)

{

Font.CreateFont(m_nFontSize,m_nFontSize,0,0,FW_DONTCARE,FALSE,

FALSE,FALSE,DEFAULT_CHARSET,

OUT_DEFAULT_PRECIS,

CLIP_DEFAULT_PRECIS,

DEFAULT_QUALITY,DEFAULT_PITCH,

m_strFont);

}

else

{

char fontname[80];

strcpy(fontname,m_strFont);

LOGFONT logfont={m_nFontSize,m_nFontSize,

0,0,FW_DONTCARE,FALSE,

FALSE,FALSE,DEFAULT_CHARSET,

OUT_DEFAULT_PRECIS,

CLIP_DEFAULT_PRECIS,

DEFAULT_QUALITY,DEFAULT_PITCH,

fontname[0]};

Font.CreateFontIndirect(&logfont);

}

oldFont=pDC->SelectObject(&Font);

CString test=”안녕하세요 이것은 테스트입니다.”;

// 출력 함수 결정

switch(m_nTextStyle)

{

case 0:

pDC->TextOut(0,0,test);

break;

case 1:

{

test=”DrawText 함수를 이용한 문자 출력”;

// 상자 영역을 설정하고

CRect rect= CRect(0,60,400,100);

// DrawText 함수를 호출

pDC->DrawText(test,rect,DT_SINGLELINE |

DT_CENTER | DT_VCENTER);

}

break;

case 2:

{

// 탭 크기 배열

int tabstop[4]={8,16,24,32};

test=”TabbedTextOut\t를\t이용한\t출력”;

// TabbedTextOut 함수 사용

pDC->TabbedTextOut(0,100,test,4,tabstop,0);

}

break;

case 3:

{

test=”ExtTextOut을 이용한 출력”;

// 문자 출력 색을 청색으로

pDC->SetTextColor(RGB(0,0,255));

// 배경색을 노랑색으로

pDC->SetBkColor(RGB(255,255,0));

// ExtTextOut 함수 사용

pDC->ExtTextOut(0,120,ETO_CLIPPED

|ETO_OPAQUE,CRect(0,120,400,200),

test,NULL);

}

break;

}

pDC->SelectObject(oldFont);

Font.DeleteObject();

}

(5) 각 메뉴 항목을 선택했을 때 선택한 값을 멤버 변수에 설정

각 메뉴 항목을 선택하였을 때 해당값을 설정하고 Invalidate 함수를 호출하여  OnDraw 함수를 호출합니다.  다음은 바탕체 폰트 항목을 선택했을 때의 함수를 클래스 위저드로 만든 다음 이 함수에 코딩한 내용입니다. 나머지 부분은 소스를 참조하기 바랍니다.

void CExTextView::OnFontBatang()

{

m_strFont=”바탕체”;

Invalidate();

}

ExText 소스

다음은 ExText의 View 클래스 소스입니다. 본 예제는 View 클래스에서만 작업하였습니다. 나머지 부분의 소스는 제공되는 CD를 참조하기 바랍니다.

// ExTextView.h : interface of the CExTextView class

//////////////////////////////////////////////////////////////////////////

#if !defined(AFX_EXTEXTVIEW_H__21237A42_20B7_11D2_8B56_006097AD9DDC__INCLUDED_)

#define AFX_EXTEXTVIEW_H__21237A42_20B7_11D2_8B56_006097AD9DDC__INCLUDED_

#if _MSC_VER > 1000

#pragma once

#endif // _MSC_VER > 1000

class CExTextView : public CView

{

protected: // create from serialization only

CExTextView();

DECLARE_DYNCREATE(CExTextView)

// Attributes

public:

CExTextDoc* GetDocument();

// Operations

public:

// Overrides

// ClassWizard generated virtual function overrides

// {{AFX_VIRTUAL(CExTextView)

public:

virtual void OnDraw(CDC* pDC);  // overridden to draw this view

virtual BOOL PreCreateWindow(CREATESTRUCT& cs);

protected:

virtual BOOL OnPreparePrinting(CPrintInfo* pInfo);

virtual void OnBeginPrinting(CDC* pDC, CPrintInfo* pInfo);

virtual void OnEndPrinting(CDC* pDC, CPrintInfo* pInfo);

// }}AFX_VIRTUAL

// Implementation

public:

int m_nTextStyle;// 문자 출력 함수

int m_nFontSize;// 폰트 크기

CString m_strFont;// 폰트명

int m_nMakeFont;// CrateFont ? CreateFontIndirect?

virtual ~CExTextView();

#ifdef _DEBUG

virtual void AssertValid() const;

virtual void Dump(CDumpContext& dc) const;

#endif

protected:

// Generated message map functions

protected:

// {{AFX_MSG(CExTextView)

afx_msg void OnFontBatang();

afx_msg void OnFontDotum();

afx_msg void OnFontMunjo();

afx_msg void OnMakeGeneral();

afx_msg void OnMakeDirect();

afx_msg void OnFontsize10();

afx_msg void OnFontsize12();

afx_msg void OnFontsize16();

afx_msg void OnFontsize20();

afx_msg void OnFontsize40();

afx_msg void OnTextTextout();

afx_msg void OnTextDrawtext();

afx_msg void OnTextExttextout();

afx_msg void OnTextTabbedtextout();

// }}AFX_MSG

DECLARE_MESSAGE_MAP()

};

#ifndef _DEBUG  // debug version in ExTextView.cpp

inline CExTextDoc* CExTextView::GetDocument()

{ return (CExTextDoc*)m_pDocument; }

#endif

//////////////////////////////////////////////////////////////////////////

// {{AFX_INSERT_LOCATION}}

// Microsoft Visual C++ will insert additional declarations immediately before the previous line.

#endif // !defined(AFX_EXTEXTVIEW_H__21237A42_20B7_11D2_8B56_006097AD9DDC__INCLUDED_)

// ExTextView.cpp : implementation of the CExTextView class

//

#include “stdafx.h”

#include “ExText.h”

#include “ExTextDoc.h”

#include “ExTextView.h”

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

//////////////////////////////////////////////////////////////////////////// CExTextView

IMPLEMENT_DYNCREATE(CExTextView, CView)

BEGIN_MESSAGE_MAP(CExTextView, CView)

// {{AFX_MSG_MAP(CExTextView)

ON_COMMAND(ID_FONT_BATANG, OnFontBatang)

ON_COMMAND(ID_FONT_DOTUM, OnFontDotum)

ON_COMMAND(ID_FONT_MUNJO, OnFontMunjo)

ON_COMMAND(ID_MAKE_GENERAL, OnMakeGeneral)

ON_COMMAND(ID_MAKE_DIRECT, OnMakeDirect)

ON_COMMAND(ID_FONTSIZE10, OnFontsize10)

ON_COMMAND(ID_FONTSIZE12, OnFontsize12)

ON_COMMAND(ID_FONTSIZE16, OnFontsize16)

ON_COMMAND(ID_FONTSIZE20, OnFontsize20)

ON_COMMAND(ID_FONTSIZE40, OnFontsize40)

ON_COMMAND(ID_TEXT_TEXTOUT, OnTextTextout)

ON_COMMAND(ID_TEXT_DRAWTEXT, OnTextDrawtext)

ON_COMMAND(ID_TEXT_EXTTEXTOUT, OnTextExttextout)

ON_COMMAND(ID_TEXT_TABBEDTEXTOUT, OnTextTabbedtextout)

// }}AFX_MSG_MAP

// Standard printing commands

ON_COMMAND(ID_FILE_PRINT, CView::OnFilePrint)

ON_COMMAND(ID_FILE_PRINT_DIRECT, CView::OnFilePrint)

ON_COMMAND(ID_FILE_PRINT_PREVIEW, CView::OnFilePrintPreview)

END_MESSAGE_MAP()

//////////////////////////////////////////////////////////////////////////// CExTextView construction/destruction

CExTextView::CExTextView()

{

m_strFont=”바탕체”;

m_nMakeFont=0;

m_nFontSize=10;

m_nTextStyle=0;

}

CExTextView::~CExTextView()

{

}

BOOL CExTextView::PreCreateWindow(CREATESTRUCT& cs)

{

// TODO: Modify the Window class or styles here by modifying

//  the CREATESTRUCT cs

return CView::PreCreateWindow(cs);

}

//////////////////////////////////////////////////////////////////////////// CExTextView drawing

void CExTextView::OnDraw(CDC* pDC)

{

CExTextDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

CFont Font,*oldFont;

// 폰트 만드는 함수 결정

if(!m_nMakeFont)

{

Font.CreateFont(m_nFontSize,m_nFontSize,0,0,FW_DONTCARE,FALSE,

FALSE,FALSE,DEFAULT_CHARSET,

OUT_DEFAULT_PRECIS,

CLIP_DEFAULT_PRECIS,

DEFAULT_QUALITY,DEFAULT_PITCH,

m_strFont);

}

else

{

char fontname[80];

strcpy(fontname,m_strFont);

LOGFONT logfont={m_nFontSize,m_nFontSize,0,0,FW_DONTCARE,FALSE,

FALSE,FALSE,DEFAULT_CHARSET,

OUT_DEFAULT_PRECIS,

CLIP_DEFAULT_PRECIS,

DEFAULT_QUALITY,DEFAULT_PITCH,

fontname[0]};

Font.CreateFontIndirect(&logfont);

}

oldFont=pDC->SelectObject(&Font);

CString test=”안녕하세요. 이것은 테스트입니다.”;

// 출력 함수 결정

switch(m_nTextStyle)

{

case 0:

pDC->TextOut(0,0,test);

break;

case 1:

{

test=”DrawText 함수를 이용한 문자 출력”;

// 상자 영역을 설정하고

CRect rect= CRect(0,60,400,100);

// DrawText 함수를 호출

pDC->DrawText(test,rect,DT_SINGLELINE |

DT_CENTER | DT_VCENTER);

}

break;

case 2:

{

// 탭 크기 배열

int tabstop[4]={8,16,24,32};

test=”TabbedTextOut\t를\t이용한\t출력”;

// TabbedTextOut 함수 사용

pDC->TabbedTextOut(0,100,test,4,tabstop,0);

}

break;

case 3:

{

test=”ExtTextOut을 이용한 출력”;

// 문자 출력색을 청색으로

pDC->SetTextColor(RGB(0,0,255));

// 배경색을 노랑색으로

pDC->SetBkColor(RGB(255,255,0));

// ExtTextOut 함수 사용

pDC->ExtTextOut(0,120,ETO_CLIPPED|

ETO_OPAQUE,CRect(0,120,400,200),

test,NULL);

}

break;

}

pDC->SelectObject(oldFont);

Font.DeleteObject();

//////////////////////////////////////////////////////////////////////////// CExTextView printing

BOOL CExTextView::OnPreparePrinting(CPrintInfo* pInfo)

{

// default preparation

return DoPreparePrinting(pInfo);

}

void CExTextView::OnBeginPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)

{

// TODO: add extra initialization before printing

}

void CExTextView::OnEndPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)

{

// TODO: add cleanup after printing

}

//////////////////////////////////////////////////////////////////////////// CExTextView diagnostics

#ifdef _DEBUG

void CExTextView::AssertValid() const

{

CView::AssertValid();

}

void CExTextView::Dump(CDumpContext& dc) const

{

CView::Dump(dc);

}

CExTextDoc* CExTextView::GetDocument() // non-debug version is inline

{

ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CExTextDoc)));

return (CExTextDoc*)m_pDocument;

}

#endif // _DEBUG

//////////////////////////////////////////////////////////////////////////// CExTextView message handlers

// 폰트 설정

void CExTextView::OnFontBatang()

{

m_strFont=”바탕체”;

Invalidate();

}

void CExTextView::OnFontDotum()

{

m_strFont=”돋움체”;

Invalidate();

}

void CExTextView::OnFontMunjo()

{

m_strFont=”굴림체”;

Invalidate();

}

// 폰트 만들기 설정

void CExTextView::OnMakeGeneral()

{

m_nMakeFont=0;

Invalidate();

}

void CExTextView::OnMakeDirect()

{

m_nMakeFont=1;

Invalidate();

}

// 폰트 크기 설정

void CExTextView::OnFontsize10()

{

m_nFontSize=10;

Invalidate();

}

void CExTextView::OnFontsize12()

{

m_nFontSize=12;

Invalidate();

}

void CExTextView::OnFontsize16()

{

m_nFontSize=16;

Invalidate();

}

void CExTextView::OnFontsize20()

{

m_nFontSize=20;

Invalidate();

}

void CExTextView::OnFontsize40()

{

m_nFontSize=40;

Invalidate();

}

// 출력 함수 설정

void CExTextView::OnTextTextout()

{

m_nTextStyle=0;

Invalidate();

}

void CExTextView::OnTextDrawtext()

{

m_nTextStyle=1;

Invalidate();

}

void CExTextView::OnTextExttextout()

{

m_nTextStyle=3;

Invalidate();

}

void CExTextView::OnTextTabbedtextout()

{

m_nTextStyle=2;

Invalidate();

}

비트맵은 장치 의존적인 비트맵과 장치 독립적인 비트맵으로 나누어집니다. 장치의존적인 비트맵은 본 장의 2항목에서 설명하였습니다. 장치 의존적인 비트맵의 로드는 CBitmap 클래스를 이용하는 것을 의미합니다. 장치 독립적인 비트맵은 장치에 관계없이 비트맵을 출력하는 것을 말합니다. 이 장치 독립적인 비트맵을 컨트롤하는 클래스는 없습니다. 장치 독립적인 비트맵은 그 종류가 많고 많은 형식으로 설정되어 있기 때문에 통합적인 장치 독립적인 비트맵을 설정할 수가 없는 것입니다. 장치 독립적인 비트맵을 보통 DIB(Device Independent Bitmap)라고 말합니다. 본 장 이후에서부터는 DIB라는 용어로 사용하겠습니다. DIB 형태로 출력되는 그림 파일들은 현재 데이터가 어떤 형식으로 되어 있는가에 대한 정보와 실제 데이터로 분리되어 저장되어 있습니다. 이렇게 저장되어 있는 데이터를 필요에 따라서 적절한 구조체를 이용하여 데이터를 화면에 출력합니다. 그림 1-3-24는 DIB의 출력에 대한 방법을 도표화한 것입니다.

DIB 파일은 보통 그래픽 파일들을 의미하며, 이 그래픽 파일들은 어떤 시스템이든 관계없이 출력이 됩니다. 여러분들이 잘 알고 있는 BMP 파일, GIF 파일, JPG 파일들을 출력하고자 할 경우 그림 1-3-24와 같은 방법으로 출력합니다. 일반적인 그림 파일들은 헤더에 현재 그림의 컬러수, 가로 크기와 세로 크기, 데이터 압축 형태 등이 정의되어 있으며 이에 해당하는 데이터를 함께 저장하고 있습니다. 일반적인 그림 파일들은 그림 1-3-24의 ①과 ②를 함께 가지고 있습니다. 만약 그림 파일의 컬러수가 트루컬러 미만이면 팔레트를 사용합니다.(본 Part의 2항목을 참조하세요.) 팔레트를 사용하려면 그림 1-3-24의 ⑤ 항목처럼 팔레트를 설정합니다. 팔레트를 설정하는 방법은 앞에서 설명하였습니다. 만약 트루컬러를 사용한다면 ⑤ 항목을 거치지 않고 넘어갑니다. 만약 현재 그림 파일 데이터가 압축되어 있다면 제공되는 데이터의 압축을 해제해야 합니다. ⑥ 항목이 바로 압축을 해제하는 루틴입니다. 압축 방식에 따라서 해제하는 루틴이 다릅니다. JPG 파일인 경우에는 보통 DCT와 Huffman 코드를 이용하여 압축하고 GIF일 경우에는 LZW 방식의 압축 방법을 이용하고 있습니다. 이런 압축 방법을 참고하여 압축을 해제하여 실제 출력 DIB를 만들면 됩니다. 만일 압축이 되지 않는 그림 파일이라면 ⑥ 항목을 거치지 않고 실제 DIB 형태를 설정하면 됩니다. 이렇게 DIB 형태를 만든 다음 이 DIB의 정보를 BITMAPINFO라는 구조체에 설정한 후에 DIB를 출력합니다.

DIB의 구조

DIB 파일의 기본적인 구조는 그림 1-3-25와 같습니다.

그림 1-3-25의 ① 항목은 출력되어야 할 그림 형태이며, ② 항목은 저장되어 있는 형태입니다. ① 항목처럼 출력하기 위해서는 DIB 데이터는 최상단에, 그림 파일은 하단부에 위치가 설정되어야 합니다. 즉, DIB 형태는 Y축이 감소되는 형태로 저장되어 있어야 한다는 것입니다. 또한 DIB 형태는 트루컬러일 경우 첫 번째 8비트는 Blue값, 두 번째는 Green, 세 번째는 Red 형태로 저장됩니다. 즉 RGB 순서로 컬러 정보가 설정되어 있는 것이 아니고 BGR 형태로 저장되어 있다는 의미입니다. DIB 파일은 또한 4배수 형태로 한 스캔 라인을 구성합니다. 예를 들어 가로 픽셀이 54, 세로 픽셀이 100인 그림이 있다고 가정해 보겠습니다. 이 그림이 트루컬러라면 한 픽셀에 필요한 데이터는 RGB 각각 8비트이므로 24비트, 즉 3바이트의 데이터가 필요합니다. 그렇기 때문에 한 라인이 필요한 데이터는 54×3=162, 즉 162바이트가 필요합니다. 그러나 DIB는 4배수 형태이기 때문에 4로 나누어서 0으로 떨어지는 크기를 가지게 됩니다. 즉 162가 아니라 162보다 크거나 같으면서 최소 4의 배수가 필요합니다. 즉, 한 라인을 출력할 때 필요한 바이트는 164바이트가 되는 것입니다. 이런 형태로 DIB 형태를 가지고 있습니다.

CBitmap을 이용한 비트맵 출력 예제 ExBitmap

ExBitmap은 본 장의 3항목에서 설명한 내용대로 비트맵을 만들고 이 비트맵을 출력하는 예제입니다. 본 예제는 AppWizard를 이용하여 SDI 형태로 만든 것입니다. 프로젝트를 만든 후 그림 1-3-15처럼 비트맵을 하나 만들고 이것의 ID를 IDB_BITMAP으로 설정한 후에 본 장의 3항목에서 설명한 비트맵 출력 방법을 이용하여 비트맵을 출력합니다. 먼저 Bitmap을 출력하기 위해서는 ID가 IDB_BITMAP인 비트맵 데이터를 로드해야 합니다. 이렇게 로드하기 위해서 비트맵 클래스를 View 클래스의 헤더에 다음과 같이 설정합니다.

CBitmap m_pBitmap;// 비트맵 로드 설정함

이제 m_pBitmap의 Resource에 있는 IDB_BITMAP을 로드합니다. 이 비트맵을 로드하는 부분은 View 클래스의 OnInitialUpdate 함수에 설정하였습니다. OnInitialUpdate 함수는 클래스 위저드에서 메시지 항목을 OnInitialUpdate로 설정하면 만들어집니다. View 클래스의 OnInitialUpdate 함수를 만들고 수정한 형태는 다음과 같습니다.

void CExBitmapView::OnInitialUpdate()

{

CView::OnInitialUpdate();

// IDB_BITMAP을 로드한다.

m_pBitmap.LoadBitmap(IDB_BITMAP);

}

이와 같이 비트맵을 로드하고 이 비트맵을 출력합니다. 출력하는 부분은 OnDraw에 설정합니다. 다음은 ExBitmap의 View 클래스 소스의 OnDraw 함수를 수정한 내용입니다.

void CExBitmapView::OnDraw(CDC* pDC)

{

CExBitmapDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

CDC cdc;

// 현재 DC와 호환성이 있는 DC로 설정

cdc.CreateCompatibleDC(pDC);

// 이 DC에 비트맵을 설정한다.

cdc.SelectObject(&m_pBitmap);

// cdc를 화면 DC pDC로 출력

pDC->BitBlt(0,0,275,407,&cdc,0,0,SRCCOPY);

}

지금까지의 코딩이 끝나고 컴파일하여 실행하면 그림 1-3-26과 같은 결과가 출력됩니다.

본 예제의 소스는 기재하지 않겠습니다. 위에 설명한 세 개의 항목에 코딩한 내용이 없기 때문에 소스를 볼 필요는 없을 것 같습니다. 소스는 제공되는 CD를 참조하기 바랍니다.

DIB 출력 예제 ExDib

ExDib는 DIB 형태를 출력하는 예제입니다. 이 예제는 프로그램 구성도가 필요없습니다. 단순하게 DIB 파일을 읽어서 화면에 출력하기 때문입니다. 본 예제는 AppWizard를 이용하여 SDI 형태의 프로젝트를 만듭니다. 프로젝트를 만든 다음 다음과 같은 순서로 프로그래밍을 합니다.

(1) DIB 형태의 데이터를 파일로 만든다.

그림 1-3-25의 ②번과 같이 데이터가 저장되어 있는 파일을 만듭니다. 이 파일을 읽게 되면 바로 DIB 형태의 데이터를 읽기 때문입니다. 필자는 그림 1-3-25의 ②번과 같은 파일을 Paint Shop Pro 프로그램을 이용하여 만들었습니다. DIB 형태의 파일을 만드는 방법은 그림 1-3-27과 같습니다.

그림 1-3-27의 ① 항목은 Paint Shop Pro 프로그램에서 DIB 형태의 파일을 만들기 위해서 그림을 로드한 형태입니다. 이때Image 메뉴에서 Mirror를 선택합니다. 이렇게 선택한 그림이 1-3-27의 ② 항목입니다. 좌우측이 바뀐 형태로 그림이 만들어집니다. 이 상태에서 Image 메뉴에서 Rotate 항목을 클릭합니다. 이때나타나는 대화상자의 형태는 그림 1-3-27의 ④ 항목과 같습니다. Rotate 대화 상자에서 180도 회전을 선택하고 OK 버튼을 클릭하면 그림 1-3-27의 ⑤와 같은 형태가 됩니다. 이 형태가 바로 DIB 형태입니다. 이 상태에서 이 그림을 저장합니다. 저장할 때 파일 형식을 RAW로 설정하고 원하는 파일명을 입력한 다음(그림 1-3-27의 ⑥) Option 버튼을 클릭하여 BGR 포맷으로 설정합니다. 이렇게 하면 DIB 형태의 파일로 저장됩니다. 본 예제에서는 이 DIB 파일을 kim.raw로 설정하였습니다. 다른 그래픽 에디터를 사용한다면 그림을 그림 1-3-25의 형태로 만들어 놓은 다음 BGR 형태의 RAW 파일로 저장하면 됩니다. RAW 파일 구조는 헤더가 없이 단순하게 그림 데이터만 저장한 파일을 의미합니다.

본 예제에서는 트루컬러만 사용합니다. 따라서 그림을 사용할 때는 24비트 영상을 사용하기 바랍니다.

(2) DIB 파일의 데이터를 읽는다.

항목에서 만든 kim.raw 파일을 읽어서 해당 데이터를 로드해 보겠습니다. 먼저 파일에 데이터를 로드할 변수를 만듭니다. 이 변수를 View 클래스 헤더에 다음과 같이 설정합니다.

BITMAPINFO szBitmapInfo;// 헤더 정보 설정

LPSTR m_pDib;// DIB 데이터 설정 변수

DIB를 출력하기 위해서는 BITMAPINFO 구조체와 실제 DIB 형태의 데이터가 필요하다고 전에 설명하였습니다. 그림 1-3-24의 ⑦과 ⑧ 항목을 참조하세요.

따라서 두 개의 변수가 필요합니다. BITMAPINFO 구조체의 변수와 DIB 데이터를 가지고 있는 변수, 이 두개의 변수를 설정한 것입니다.

BITMAPINFO

typedef struct tagBITMAPINFO {

BITMAPINFOHEADER    bmiHeader;

RGBQUAD             bmiColors[1];

} BITMAPINFO;

BITMAPINFO 구조체 안에는 두 개의 구조체가 있습니다. 하나는 BITMAPINFOHEADER로서 현재 DIB의 정보를 가지고 있는 구조체이며, 또 하나는 현재 DIB가 팔레트를 사용할 경우 필요한 팔레트를 설정하는 구조체입니다.

BITMAPINFOHEADER 구조체는 다음과 같습니다.

typedef struct tagBITMAPINFOHEADER{ // bmih

DWORD  biSize; // 헤더 크기

LONG   biWidth; // 그림 가로 크기

LONG   biHeight;// 그림 세로 크기

WORD   biPlanes; // 사용하는 비트플랜. 1을 설정

WORD   biBitCount;// 픽셀당 사용되는 비트수

DWORD  biCompression;// 압축 유무 플러그

DWORD  biSizeImage; // 영상의 크기

LONG   biXPelsPerMeter;// X 픽셀당 미터수

LONG   biYPelsPerMeter;// Y 픽셀당 미터수

DWORD  biClrUsed; // 사용된 컬러수

DWORD  biClrImportant; // 중요 컬러수

} BITMAPINFOHEADER;

BITMAPINFOHEADER 구조체는 현재 그림에 대한 모든 정보라고 할 수 있습니다. biSize값은 보통 40입니다. 이 값을 그대로 대입하여도 되고 sizeof(BITMAPINFOHEADER)로 설정해도 됩니다. biCompression이란 압축에 대한 플러그인인데 마이크로소프트 사에서 제공하는 압축 방식을 사용하였을 경우 이 플러그를 설정하여 주면 됩니다. 마이크로소프트 사에서는 RLE(Run Length Encoding) 방식의 압축을 지원합니다. 이 압축을 8비트당 할 경우 BI_RLE8로, 4비트당 할 경우 BI_RLE4로, 그리고 압축이 없을 경우 BI_RGB로 설정하면 됩니다. 미터수와 사용된 중요 컬러수는 그냥 0의 값을 넣고 사용해도 무방합니다.

RGBQUAD 구조체는 다음과 같습니다.

typedef struct tagRGBQUAD { // rgbq

BYTE    rgbBlue;

BYTE    rgbGreen;

BYTE    rgbRed;

BYTE    rgbReserved;

} RGBQUAD;

RGBQUAD 구조체는 본 장의 2항목에서 설명한 PALETTEENTRY와 같은 형태입니다. PALETTEENTRY 구조체를 참조하기 바랍니다.

이제 DIB 파일에서 데이터를 로드해 보겠습니다.

이것을 로드하는 부분은 View 클래스의 OnInitialUpdate() 함수입니다. 이 함수는 클래스 위저드를 이용하여 View 클래스에서 OnInitialUpdate를 선택하면 만들어집니다. 이 함수에서 다음과 같이 코딩해줍니다.

HFILE hfi;

int size;

// 파일을 열고

hfi=_open(“kim.raw”,_O_BINARY | _O_RDONLY);

if(hfi<0)

return;

// 파일 크기를 얻는다.

size=_lseek(hfi,0,SEEK_END);

// 파일 처음으로 가서

_lseek(hfi,0,SEEK_SET);

// 파일 데이터를 로드할 메모리 설정

m_pDib= new char[size];

// 파일에서 데이터를 읽는다.

_read(hfi,m_pDib,size);

// 파일을 닫는다.

_close(hfi);

위와 같이 하면 m_pDib 안에 kim.raw 파일의 데이터가 로드됩니다. 이때 include문을 설정해 주어야 합니다. _lopen,_lseek 등의 함수가 설정되어 있는 헤더가 io.h와 fcntl.h이기 때문에 View 클래스 상단부에 다음과 같이 두 개의 파일을 포함시킵니다.

#include <io.h>

#include <fcntl.h>

이제 BITMAPINFO 구조체의 변수인 szBitmapInfo에 해당값을 입력합니다. 이 입력하는 방법은 다음과 같습니다.

// bmiHeader의 구조체 크기 설정

szBitmapInfo.bmiHeader.biSize=sizeof(BITMAPINFOHEADER);

// 이미지 가로 크기

szBitmapInfo.bmiHeader.biWidth=380;

// 세로 크기

szBitmapInfo.bmiHeader.biHeight=380;

// 비트플렌은 1

szBitmapInfo.bmiHeader.biPlanes=1;

// 비트카운트, 즉 픽셀당 비트수. 트루컬러이므로 24

szBitmapInfo.bmiHeader.biBitCount=24;

// 압축이 없으므로 BI_RGB

szBitmapInfo.bmiHeader.biCompression=BI_RGB;

// 그림 크기는 가로 * 세로 * 3

szBitmapInfo.bmiHeader.biSizeImage=380*380*3;

// 나머지는 모두 0으로

szBitmapInfo.bmiHeader.biXPelsPerMeter=0;

szBitmapInfo.bmiHeader.biYPelsPerMeter=0;

szBitmapInfo.bmiHeader.biClrUsed =0;

szBitmapInfo.bmiHeader.biClrImportant =0;

szBitmapInfo에 데이터를 설정하기 위해서는 우리가 그림을 kim.raw로 저장하기 이전에 기본적인 정보를 알아야 합니다. 즉, 그림의 가로와 세로 크기를 알아야 하고 그림 자체를 트루컬러로 저장해야 합니다. 이렇게 정보를 알고 있는 상태에서 raw 파일을 로드할 수 있는 것입니다. 이 정보들을 szBitmapInfo에 설정합니다.

이렇게 해서 완성된 OnInitialUpdate()는 다음과 같습니다.

void CExDibView::OnInitialUpdate()

{

CView::OnInitialUpdate();

HFILE hfi;

int size;

// 파일을 열고

hfi=_open(“kim.raw”,_O_BINARY | _O_RDONLY);

if(hfi<0)

return;

// 파일 크기를 얻는다.

size=_lseek(hfi,0,SEEK_END);

// 파일 처음으로 가서

_lseek(hfi,0,SEEK_SET);

// 파일 데이터를 로드할 메모리 설정

m_pDib= new char[size];

// 파일에서 데이터를 읽는다.

_read(hfi,m_pDib,size);

// 파일을 닫는다.

_close(hfi);

// bmiHeader의 구조체 크기 설정

szBitmapInfo.bmiHeader.biSize=sizeof(BITMAPINFOHEADER);

// 이미지 가로 크기

szBitmapInfo.bmiHeader.biWidth=380;

// 세로 크기

szBitmapInfo.bmiHeader.biHeight=380;

// 비트플렌은 1

szBitmapInfo.bmiHeader.biPlanes=1;

// 비트카운트, 즉 픽셀당 비트수. 트루컬러이므로 24

szBitmapInfo.bmiHeader.biBitCount=24;

// 압축이 없으므로 BI_RGB

szBitmapInfo.bmiHeader.biCompression=BI_RGB;

// 그림 크기는 가로 * 세로 * 3

szBitmapInfo.bmiHeader.biSizeImage=380*380*3;

// 나머지는 모두 0으로

szBitmapInfo.bmiHeader.biXPelsPerMeter=0;

szBitmapInfo.bmiHeader.biYPelsPerMeter=0;

szBitmapInfo.bmiHeader.biClrUsed =0;

szBitmapInfo.bmiHeader.biClrImportant =0;

}

(3) OnDraw 함수에서 DIB 데이터를 출력한다.

OnDraw 함수를 수정하여 현재 로드된 m_pDib 데이터를 출력합니다. 수정한 내용은 다음과 같습니다.

void CExDibView::OnDraw(CDC* pDC)

{

CExDibDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

// 비트맵을 출력하는 모드를 색상 형태로

::SetStretchBltMode(pDC->m_hDC, COLORONCOLOR);

// DIB를 DC에 전송한다.

::SetDIBitsToDevice(pDC->m_hDC,

0,             // DestX

0,              // DestY

380,        // nDestWidth

380,       // nDestHeight

0,            // SrcX

0,   // SrcY

0,                          // nStartScan

(WORD)380,  // nNumScans

m_pDib,                  // lpBit

&szBitmapInfo,     // lpBitsInfo

DIB_RGB_COLORS);            // wUsage

}

SetDIBitsToDevice

int SetDIBitsToDevice(

HDC hdc,              // DC 핸들

int XDest,            // 출력할 DC의 시작 X좌표

int YDest,            // 출력할 DC의 시작 Y좌표

DWORD dwWidth,        // DIB의 가로 크기

DWORD dwHeight,       // DIB의 세로 크기

int XSrc,             // DIB의 출력 시작 X좌표

int YSrc,             // DIB의 출력 시작 Y좌표

UINT uStartScan,      // 배열에서 처음 스캔할 위치

UINT cScanLines,      // 스캔 라인

CONST VOID *lpvBits,  // DIB 데이터 버퍼 포인터

CONST BITMAPINFO *lpbmi,  // 비트맵 정보 헤더

UINT fuColorUse       // RGB 형태인가 팔레트 형태인가?

);

SetDIBitsToDevice 함수는 DIB 데이터를 화면에 전송하는 함수입니다. 이 함수에 우리가 지금까지 만든 m_pDib를 lpbmi의 BITMAPINFO에 szBitmapInfo를 설정한 것입니다. cScanLines는 스캔라인이라는 의미인데 그냥 세로 라인으로 생각하면 됩니다. uStartScan은 출력 시작의 줄을 의미합니다. fuColorUse값은 DIB_PAL_COLORS값과 DIB_RGB_COLORS값 중 하나의 값을 가질 수 있습니다. DIB_PAL_COLORS일 경우에는 팔레트 테이블에 있는 색을 출력하고, DIB_RGB_COLORS일 경우에는 DIB에 있는 색을 그대로 출력합니다.

SetDIBitsToDevice와 유사한 형태로 DIB를 확대 축소하여 출력하는 함수가 있습니다. 이 함수가 StretchDIBits입니다. 이 함수 내용은 도움말을 참조하기 바랍니다.

위와 같이 한 후에 프로그램을 컴파일하고 실행하면 저장한 그림이 출력됩니다.

ExDib 프로그램 소스

ExDib에서 수정한 부분은 View 클래스뿐입니다. 다음은 View 클래스의 소스입니다.

// ExDibView.h : interface of the CExDibView class

//

//////////////////////////////////////////////////////////////////////////

#if !defined(AFX_EXDIBVIEW_H__21237A6B_20B7_11D2_8B56_006097AD9DDC__INCLUDED_)

#define AFX_EXDIBVIEW_H__21237A6B_20B7_11D2_8B56_006097AD9DDC__INCLUDED_

#if _MSC_VER > 1000

#pragma once

#endif // _MSC_VER > 1000

class CExDibView : public CView

{

protected: // create from serialization only

CExDibView();

DECLARE_DYNCREATE(CExDibView)

// Attributes

public:

CExDibDoc* GetDocument();

// Operations

public:

// Overrides

// ClassWizard generated virtual function overrides

// {{AFX_VIRTUAL(CExDibView)

public:

virtual void OnDraw(CDC* pDC);  // overridden to draw this view

virtual BOOL PreCreateWindow(CREATESTRUCT& cs);

virtual void OnInitialUpdate();

protected:

virtual BOOL OnPreparePrinting(CPrintInfo* pInfo);

virtual void OnBeginPrinting(CDC* pDC, CPrintInfo* pInfo);

virtual void OnEndPrinting(CDC* pDC, CPrintInfo* pInfo);

// }}AFX_VIRTUAL

// Implementation

public:

BITMAPINFO szBitmapInfo;// 헤더 정보 설정

LPSTR m_pDib;// DIB 데이터 설정 변수

virtual ~CExDibView();

#ifdef _DEBUG

virtual void AssertValid() const;

virtual void Dump(CDumpContext& dc) const;

#endif

protected:

// Generated message map functions

protected:

// {{AFX_MSG(CExDibView)

// NOTE - the ClassWizard will add and remove member functions here.

//    DO NOT EDIT what you see in these blocks of generated code !

// }}AFX_MSG

DECLARE_MESSAGE_MAP()

};

#ifndef _DEBUG  // debug version in ExDibView.cpp

inline CExDibDoc* CExDibView::GetDocument()

{ return (CExDibDoc*)m_pDocument; }

#endif

//////////////////////////////////////////////////////////////////////////

// {{AFX_INSERT_LOCATION}}

// Microsoft Visual C++ will insert additional declarations immediately before the previous line.

#endif // !defined(AFX_EXDIBVIEW_H__21237A6B_20B7_11D2_8B56_006097AD9DDC__INCLUDED_)

// ExDibView.cpp : implementation of the CExDibView class

//

#include “stdafx.h”

#include “ExDib.h”

#include “ExDibDoc.h”

#include “ExDibView.h”

#include <io.h>

#include <fcntl.h>

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

//////////////////////////////////////////////////////////////////////////// CExDibView

IMPLEMENT_DYNCREATE(CExDibView, CView)

BEGIN_MESSAGE_MAP(CExDibView, CView)

// {{AFX_MSG_MAP(CExDibView)

// NOTE - the ClassWizard will add and remove mapping macros here.

//    DO NOT EDIT what you see in these blocks of generated code!

// }}AFX_MSG_MAP

// Standard printing commands

ON_COMMAND(ID_FILE_PRINT, CView::OnFilePrint)

ON_COMMAND(ID_FILE_PRINT_DIRECT, CView::OnFilePrint)

ON_COMMAND(ID_FILE_PRINT_PREVIEW, CView::OnFilePrintPreview)

END_MESSAGE_MAP()

//////////////////////////////////////////////////////////////////////////// CExDibView construction/destruction

CExDibView::CExDibView()

{

m_pDib=NULL;

}

CExDibView::~CExDibView()

{

}

BOOL CExDibView::PreCreateWindow(CREATESTRUCT& cs)

{

// TODO: Modify the Window class or styles here by modifying

//  the CREATESTRUCT cs

return CView::PreCreateWindow(cs);

}

//////////////////////////////////////////////////////////////////////////// CExDibView drawing

void CExDibView::OnDraw(CDC* pDC)

{

CExDibDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

// 비트맵을 출력하는 모드를 색상 형태로

::SetStretchBltMode(pDC->m_hDC, COLORONCOLOR);

// DIB를 DC에 전송한다.

::SetDIBitsToDevice(pDC->m_hDC,

0,             // DestX

0,              // DestY

380,        // nDestWidth

380,       // nDestHeight

0,            // SrcX

0,   // SrcY

0,                 // nStartScan

(WORD)380,  // nNumScans

m_pDib,                  // lpBit

&szBitmapInfo,     // lpBitsInfo

DIB_RGB_COLORS);            // wUsage

}

//////////////////////////////////////////////////////////////////////////// CExDibView printing

BOOL CExDibView::OnPreparePrinting(CPrintInfo* pInfo)

{

// default preparation

return DoPreparePrinting(pInfo);

}

void CExDibView::OnBeginPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)

{

// TODO: add extra initialization before printing

}

void CExDibView::OnEndPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)

{

// TODO: add cleanup after printing

}

//////////////////////////////////////////////////////////////////////////// CExDibView diagnostics

#ifdef _DEBUG

void CExDibView::AssertValid() const

{

CView::AssertValid();

}

void CExDibView::Dump(CDumpContext& dc) const

{

CView::Dump(dc);

}

CExDibDoc* CExDibView::GetDocument() // non-debug version is inline

{

ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CExDibDoc)));

return (CExDibDoc*)m_pDocument;

}

#endif // _DEBUG

//////////////////////////////////////////////////////////////////////////// CExDibView message handlers

void CExDibView::OnInitialUpdate()

{

CView::OnInitialUpdate();

HFILE hfi;

int size;

// 파일을 열고

hfi=_open(“kim.raw”,_O_BINARY | _O_RDONLY);

if(hfi<0)

return;

// 파일 크기를 얻는다.

size=_lseek(hfi,0,SEEK_END);

// 파일 처음으로 가서

_lseek(hfi,0,SEEK_SET);

// 파일 데이터를 로드할 메모리 설정

m_pDib= new char[size];

// 파일에서 데이터를 읽는다.

_read(hfi,m_pDib,size);

// 파일을 닫는다.

_close(hfi);

// bmiHeader의 구조체 크기 설정

szBitmapInfo.bmiHeader.biSize=sizeof(BITMAPINFOHEADER);

// 이미지 가로 크기

szBitmapInfo.bmiHeader.biWidth=380;

// 세로 크기

szBitmapInfo.bmiHeader.biHeight=380;

// 비트플렌은 1

szBitmapInfo.bmiHeader.biPlanes=1;

// 비트카운트, 즉 픽셀당 비트수. 트루컬러이므로 24

szBitmapInfo.bmiHeader.biBitCount=24;

// 압축이 없으므로 BI_RGB

szBitmapInfo.bmiHeader.biCompression=BI_RGB;

// 그림 크기는 가로 * 세로 * 3

szBitmapInfo.bmiHeader.biSizeImage=380*380*3;

// 나머지는 모두 0으로

szBitmapInfo.bmiHeader.biXPelsPerMeter=0;

szBitmapInfo.bmiHeader.biYPelsPerMeter=0;

szBitmapInfo.bmiHeader.biClrUsed =0;

szBitmapInfo.bmiHeader.biClrImportant =0;

}

패  스

패스란 미리 선을 그려놓고 출력을 결정하여 후에 출력하는 기법을 이야기합니다. 복잡한 라인들을 그릴 경우 화면에 하나하나 계속적으로 그려가면 속도가 떨어져 답답한 느낌을 줄 수 있습니다. 예를 들어서 포인터가 3000개인 라인을 그리거나 이 다각형을 출력한다고 가정할 경우 한 번 화면을 출력할 때마다 그리는 시간 때문에 사용자는 지루하게 느껴집니다. 패스를 사용하면 다소 출력 시간을 줄일 수 있으며, 또한 그린 형태의 출력 형태를 결정할 수 있습니다. 패스를 사용하여 그래픽을 출력하는 방법은 다음과 같습니다.

pDC->BeginPath(); // 패스 시작

: 선을 그리거나 다각형을 그린다.

pDC->EndPath()// 패스 종료

: // 그려놓은 형태의 출력을 결정한다.

BeginPath와 EndPath만 사용하면 이 사이에 출력한 선들은 실제 화면에 출력되지 않습니다. 내부에 좌표로만 설정되기 때문입니다. 이후에 설정한 패스를 출력하는 것을 결정할 때 이때도형들이 그려집니다. 설정한 패스를 결정하는 함수는 StrokePath, FillPath, StrokeAndFillPath입니다. StrockePath는 외곽선만 그리는 패스 함수이며, FillPath는 설정한 점 사이에 색을 칠하는 함수입니다. 외곽선과 색을 함께 사용할 경우에는 StrockeAndFillPath입니다.

위의 예에서 외곽선만 출력하고자 한다면 EndPath 다음에 StrockePath 함수를 다음과 같이 해줍니다.

pDC->StrokePath();// 외곽선만 출력

ExPath는 패스를 사용한 예제입니다. 이 예제는 SDI 형태로 제작되었습니다.

ExPath의 전체 구성도는 그림 1-3-28과 같습니다.

그림 1-3-28에서 보듯이 ExPath는 별모양의 그림을 Path 함수를 이용하여 그린 다음 메뉴에서 선택한 항목에 따라 내용을 출력합니다. 메뉴 항목을 클릭했을 때 수행되는 함수들은 그림 1-3-28의 ⑥, ⑦, ⑧과 같습니다. 이렇게 되면 멤버 변수 m_nPathStyle에 따라서 그림 1-3-28의 ⑨ 형태로 OnDraw를 수정합니다. ⑨ 항목 형태로 수정한 OnDraw 함수는 다음과 같습니다.

void CExPathView::OnDraw(CDC* pDC)

{

CExPathDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

int pointx[10]={96,275,268,88,96,68,166,299,172,68};

int pointy[10]={68,75,242,233,68,148,28,142,290,148};

// 패스 시작

pDC->BeginPath();

// 별모양을 그린다.

pDC->MoveTo(pointx[0],pointy[0]);

for(int i=1;i<5;i++)

{

pDC->LineTo(pointx[i],pointy[i]);

}

pDC->MoveTo(pointx[5],pointy[5]);

for(i=6;i<10;i++)

{

pDC->LineTo(pointx[i],pointy[i]);

}

pDC->EndPath();

// 팬과 브러시 설정

CBrush Brush,*oldBrush;

CPen Pen,*oldPen;

Brush.CreateSolidBrush(RGB(0,0,0));

oldBrush=pDC->SelectObject(&Brush);

Pen.CreatePen(PS_SOLID,2,RGB(0,0,255));

oldPen=pDC->SelectObject(&Pen);

// 패스 스타일에 따라 출력

switch(m_nPathStyle)

{

case 0:

pDC->StrokePath();

break;

case 1:

pDC->FillPath();

break;

case 2:

pDC->StrokeAndFillPath();

break;

}

// 객체 지우기

pDC->SelectObject(oldBrush);

pDC->SelectObject(oldPen);

Pen.DeleteObject();

Brush.DeleteObject();

}

ExPath의 프로그램 소스는 생략합니다. 본 내용만 이해해도 Path에 대한 개념을 이해할 수 있을 것입니다. 소스를 보고자 한다면 제공되는 CD를 참조하기 바랍니다.

확장펜

확장펜이란 그림 1-3-29처럼 펜의 끝이 둥그렇고 교차점이 부드럽게 이어지는 등 다양하게 만들어진 펜을 말합니다. 일반 펜으로는 딱딱한 느낌을 주지만 확장펜을 사용하면 매우 부드러운 느낌을 낼 수 있습니다.

확장펜을 사용하기 위해서는 펜을 만들 때 CreatePen 함수를 이용하여야 합니다.

CPen::CreatePen

BOOL CreatePen(

DWORD dwPenStyle, // 펜 스타일

DWORD dwWidth, // 펜 가로 굵기

CONST LOGBRUSH *lplb, // 브러시의 속성이 들어 있는 포인터

DWORD dwStyleCount, // 사용자 스타일 비트가 설정되어 있는 비트수

CONST DWORD *lpStyle // 사용자 스타일 비트 포인터

);

dwPenStyle은 펜의 스타일로 다음과 같은 값을 가질 수 있습니다.

PS_GEOMETRIC : 기하학적인 형태

PS_COSMETIC : 장식하는 스타일

PS_ENDCAP_ROUND : 선 끝이 둥근 형태

PS_ENDCAP_SQUARE : 선 끝이 각진 형태

PS_ENDCAP_FLAT : 일반적인 각진 형태

PS_JOIN_ROUND: 연결 부위가 둥근 형태

위에서 설정한 PS 계열 외에 CreatePen 함수를 사용할 때 설정하는 플러그를 같이 사용할 수 있습니다. PS_GEOMETRIC은 기하학 형태이며, 이 옵션을 설정하면 dwWidth에 설정한 굵기대로 선이 그어지고, PS_COSMETIC일 경우에는 사용자 스타일로 설정한 lpStyle에 의거하여 출력됩니다. CreatePen 함수와 다른 것은 펜을 설정할 때 브러시를 설정해주는 것입니다.

ExtCreatePen 함수를 사용하여 확장펜을 만드는 방법에 대해서 설명하겠습니다. 먼저 확장펜에 설정할 브러시를 만듭니다.

LOGBRUSH lbrush;

lbrush.lbStyle=BS_SOLID;

lbrush.lbColor=RGB(0,0,255);

설정된 브러시는 BS_SOLID, 즉 직선 형태의 브러시입니다. 이 브러시가 등록되었을 때 ExtCreatePen의 style도 이 브러시와 맞게 해주어야 합니다. 직선 형태의 브러시로 끝을 둥글게 그리고 연결 부위가 둥근 확장펜을 만든다면 다음과 같습니다.

pen.CreatePen(PS_SOLID|PS_ENDCAP_ROUND|PS_GEOMETRIC|PS_JOIN_R   OUND,20,&lbrush,0,NULL);

펜을 설정하는 것은 일반펜을 설정하는 방법과 같이 SelectObject를 이용하며, 펜을 다 쓰고 해제할 경우에는 DeleteObject 함수를 이용합니다.

ExExtentPen 예제는 확장펜을 이용하여 그림을 그리는 예제입니다. 이 예제의 전 구성도는 그림 1-3-30과 같습니다.

ExExtentPen 예제는 마우스를 누르고 그림을 그리면 현재 그려진 그림이 확장펜 형태로 그려지는 형태입니다. 이렇게 그려진 그림을 그림 1-3-30의 ⓑ∼ⓕ까지의 항목을 선택하면 해당 항목으로 그림이 그려집니다. ⓑ∼ⓕ까지의 항목을 선택하면 이때 선택된 값이 저장되는 멤버 변수는 그림 1-3-30의 ⑥과 같습니다. 이 예제를 SDI 형태로 다음과 같은 순서로 제작합니다.

(1) 멤버 변수를 설정한다.

멤버 변수는 View 헤더에 그림 1-3-30의 ⑥과 같이 설정합니다. m_nTotalPoly는 마우스 좌측 버튼을 클릭한 상태에서 이동한 좌표의 수를 저장하는 변수이며, 이때 이동한 좌표들을 저장하는 배열이 m_pPoly입니다. 이 배열은 최대 500까지 저장할 수 있도록 설정하였습니다.

(2) 마우스가 움직일 때 그림을 그려준다.

지금까지는 XOR 스타일로 펜을 만들고 영역을 설정하는 형태였으나, ExExtentPen은 마우스가 눌려진 상태에서 움직일 때 이전 좌표와 현재 좌표를 라인으로 그려주는 방식을 택했습니다. 이 기능을 만들 때 사용하는 함수는 좌측 마우스 버튼을 눌렀을 때, 마우스가 이동될 때, 마우스를 떼었을 때입니다. 이 세 개의 함수를 만들고 코딩한 형태는 다음과 같습니다.

void CExExtentPenView::OnLButtonDown(UINT nFlags, CPoint point)

{

// 마우스 버튼 눌림 설정

m_bDown=TRUE;

// m_point와 m_oldpoint를 현재 마우스 위치로 설정

m_point=point;

m_oldpoint=point;

// 전체 좌표 포인터 수는 0으로 초기화

m_nTotalPoly=0;

CView::OnLButtonDown(nFlags, point);

}

void CExExtentPenView::OnMouseMove(UINT nFlags, CPoint point)

{

if(m_bDown)

{

// 현재 마우스 위치 저장

m_point=point;

CDC *pDC=GetDC();

// 확장펜을 만든다.

LOGBRUSH logbr;

CPen pen,*oldpen;

logbr.lbStyle=m_lbStyle;

logbr.lbHatch=m_lbHatch;

logbr.lbColor=lbColor;

pen.CreatePen(m_dPenStyle,m_dWidth,&logbr);

// 확장펜 설정

oldpen=pDC->SelectObject(&pen);

// 이전 위치로 이동하고

pDC->MoveTo(m_oldpoint);

// 현재 마우스 위치로 라인을 그린다.

pDC->LineTo(m_point);

// 이전 객체 설정

pDC->SelectObject(oldpen);

pen.DeleteObject();

ReleaseDC(pDC);

// 현재 위치를 m_pPoly 구조체에 넣고

m_pPoly[m_nTotalPoly]=m_point;

// m_nTotalPoly를 증가시킨다.

m_nTotalPoly++;

// m_pPoly 배열을 초과하면 더 이상 값을 넣지 않는다.

if(m_nTotalPoly>=500)

{

m_bDown=FALSE;

Invalidate();

}

// 현재 위치를 다시 이전 위치 포인터에 저장

m_oldpoint=point;

}

CView::OnMouseMove(nFlags, point);

}

void CExExtentPenView::OnLButtonUp(UINT nFlags, CPoint point)

{

// 마우스 버튼 눌림 설정

m_bDown=FALSE;

// OnDraw 함수 호출

Invalidate();

CView::OnLButtonUp(nFlags, point);

}

(3) 메뉴 항목을 선택하였을 때의 함수 만들기

그림 1-3-30의 ⓑ∼ⓕ까지의 항목을 선택했을 때의 함수를 만들고, 각각에 해당하는 멤버 변수(⑥ 항목을 참조하세요.)에 해당 항목을 눌렀을 때의 값을 설정합니다.

다음은 확장펜 메뉴에서 PS_ENDCAP_ROUND 항목을 선택했을 때의 함수입니다.

void CExExtentPenView::OnPsEndcapRound()

{

m_dPenStyle=PS_GEOMETRIC|PS_ENDCAP_ROUND;

Invalidate();

}

나머지 부분은 소스를 참조하기 바랍니다.

(4) OnDraw 함수 수정

OnDraw 함수에서는 메뉴에서 선택한 옵션에 의해서 확장펜을 만들고 m_pPoly에 설정된 좌표대로 라인을 그려주는 내용을 기록합니다. 이 내용은 다음과 같습니다.

void CExExtentPenView::OnDraw(CDC* pDC)

{

CExExtentPenDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

// 확장펜을 만든다.

LOGBRUSH logbr;

CPen pen,*oldpen;

logbr.lbStyle=m_lbStyle;

logbr.lbHatch=m_lbHatch;

logbr.lbColor=lbColor;

pen.CreatePen(m_dPenStyle,m_dWidth,&logbr);

// 펜을 설정하고

oldpen=pDC->SelectObject(&pen);

// m_nTotalPoly가 0보다 크면

if(m_nTotalPoly)

pDC->MoveTo(m_pPoly[0]);// 첫 번째 점으로 이동하고

// 계속적으로 라인을 그린다.

for(int i=1;i<m_nTotalPoly;i++)

pDC->LineTo(m_pPoly[i]);

// 이전 펜 복귀

pDC->SelectObject(oldpen);

pen.DeleteObject();

}

확장펜 예제 ExExtentPen 소스

다음은 ExExtentPen의 View 클래스 소스입니다.

// ExExtentPenView.h : interface of the CExExtentPenView class

//

//////////////////////////////////////////////////////////////////////////

#if

!defined(AFX_EXEXTENTPENVIEW_H__21237A06_20B7_11D2_8B56_006097AD9DDC__INCLUDED_)

#define AFX_EXEXTENTPENVIEW_H__21237A06_20B7_11D2_8B56_006097AD9DDC__INCLUDED_

#if _MSC_VER > 1000

#pragma once

#endif // _MSC_VER > 1000

class CExExtentPenView : public CView

{

protected: // create from serialization only

CExExtentPenView();

DECLARE_DYNCREATE(CExExtentPenView)

// Attributes

public:

CExExtentPenDoc* GetDocument();

// Operations

public:

// Overrides

// ClassWizard generated virtual function overrides

// {{AFX_VIRTUAL(CExExtentPenView)

public:

virtual void OnDraw(CDC* pDC);  // overridden to draw this view

virtual BOOL PreCreateWindow(CREATESTRUCT& cs);

protected:

virtual BOOL OnPreparePrinting(CPrintInfo* pInfo);

virtual void OnBeginPrinting(CDC* pDC, CPrintInfo* pInfo);

virtual void OnEndPrinting(CDC* pDC, CPrintInfo* pInfo);

// }}AFX_VIRTUAL

// Implementation

public:

int m_nTotalPoly;

CPoint m_pPoly[500];

BOOL m_bDown;

LONG m_lbHatch;

int m_dWidth;

COLORREF lbColor;

UINT m_lbStyle;

int m_dPenStyle;

CPoint m_oldpoint;

CPoint m_point;

virtual ~CExExtentPenView();

#ifdef _DEBUG

virtual void AssertValid() const;

virtual void Dump(CDumpContext& dc) const;

#endif

protected:

// Generated message map functions

protected:

// {{AFX_MSG(CExExtentPenView)

afx_msg void OnLButtonDown(UINT nFlags, CPoint point);

afx_msg void OnMouseMove(UINT nFlags, CPoint point);

afx_msg void OnLButtonUp(UINT nFlags, CPoint point);

afx_msg void OnPsEndcapFlat();

afx_msg void OnPsEndcapRound();

afx_msg void OnPsEndcapSquare();

afx_msg void OnBsHatched();

afx_msg void OnBsHollow();

afx_msg void OnBsPattern();

afx_msg void OnBsSolid();

afx_msg void OnHsBdiagonla();

afx_msg void OnHsCross();

afx_msg void OnHsDiagcross();

afx_msg void OnHsHorizontal();

afx_msg void OnHsVertical();

afx_msg void OnColorBlack();

afx_msg void OnColorBlue();

afx_msg void OnColorGreen();

afx_msg void OnColorRed();

afx_msg void OnWidth5();

afx_msg void OnWidth10();

afx_msg void OnWidth15();

afx_msg void OnWidth20();

afx_msg void OnFdiagonal();

// }}AFX_MSG

DECLARE_MESSAGE_MAP()

};

#ifndef _DEBUG  // debug version in ExExtentPenView.cpp

inline CExExtentPenDoc* CExExtentPenView::GetDocument()

{ return (CExExtentPenDoc*)m_pDocument; }

#endif

//////////////////////////////////////////////////////////////////////////

// {{AFX_INSERT_LOCATION}}

// Microsoft Visual C++ will insert additional declarations immediately before the previous line.

#endif // !defined(AFX_EXEXTENTPENVIEW_H__21237A06_20B7_11D2_8B56_006097AD9DDC__INCLUDED_)

// ExExtentPenView.cpp : implementation of the CExExtentPenView class

//

#include “stdafx.h”

#include “ExExtentPen.h”

#include “ExExtentPenDoc.h”

#include “ExExtentPenView.h”

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

//////////////////////////////////////////////////////////////////////////// CExExtentPenView

IMPLEMENT_DYNCREATE(CExExtentPenView, CView)

BEGIN_MESSAGE_MAP(CExExtentPenView, CView)

// {{AFX_MSG_MAP(CExExtentPenView)

ON_WM_LBUTTONDOWN()

ON_WM_MOUSEMOVE()

ON_WM_LBUTTONUP()

ON_COMMAND(ID_PS_ENDCAP_FLAT, OnPsEndcapFlat)

ON_COMMAND(ID_PS_ENDCAP_ROUND, OnPsEndcapRound)

ON_COMMAND(ID_PS_ENDCAP_SQUARE, OnPsEndcapSquare)

ON_COMMAND(ID_BS_HATCHED, OnBsHatched)

ON_COMMAND(ID_BS_HOLLOW, OnBsHollow)

ON_COMMAND(ID_BS_PATTERN, OnBsPattern)

ON_COMMAND(ID_BS_SOLID, OnBsSolid)

ON_COMMAND(ID_HS_BDIAGONLA, OnHsBdiagonla)

ON_COMMAND(ID_HS_CROSS, OnHsCross)

ON_COMMAND(ID_HS_DIAGCROSS, OnHsDiagcross)

ON_COMMAND(ID_HS_HORIZONTAL, OnHsHorizontal)

ON_COMMAND(ID_HS_VERTICAL, OnHsVertical)

ON_COMMAND(ID_COLOR_BLACK, OnColorBlack)

ON_COMMAND(ID_COLOR_BLUE, OnColorBlue)

ON_COMMAND(ID_COLOR_GREEN, OnColorGreen)

ON_COMMAND(ID_COLOR_RED, OnColorRed)

ON_COMMAND(ID_WIDTH5, OnWidth5)

ON_COMMAND(ID_WIDTH10, OnWidth10)

ON_COMMAND(ID_WIDTH15, OnWidth15)

ON_COMMAND(ID_WIDTH20, OnWidth20)

ON_COMMAND(ID_FDIAGONAL, OnFdiagonal)

// }}AFX_MSG_MAP

// Standard printing commands

ON_COMMAND(ID_FILE_PRINT, CView::OnFilePrint)

ON_COMMAND(ID_FILE_PRINT_DIRECT, CView::OnFilePrint)

ON_COMMAND(ID_FILE_PRINT_PREVIEW, CView::OnFilePrintPreview)

END_MESSAGE_MAP()

//////////////////////////////////////////////////////////////////////////// CExExtentPenView construction/destruction

CExExtentPenView::CExExtentPenView()

{

m_lbHatch=0;

m_dWidth=4;

lbColor=RGB(0,0,0);

m_lbStyle=BS_SOLID;

m_dPenStyle=PS_GEOMETRIC|PS_ENDCAP_SQUARE;

m_oldpoint=CPoint(0,0);

m_point=CPoint(0,0);

m_bDown=FALSE;

}

CExExtentPenView::~CExExtentPenView()

{

}

BOOL CExExtentPenView::PreCreateWindow(CREATESTRUCT& cs)

{

// TODO: Modify the Window class or styles here by modifying

//  the CREATESTRUCT cs

return CView::PreCreateWindow(cs);

}

//////////////////////////////////////////////////////////////////////////// CExExtentPenView drawing

void CExExtentPenView::OnDraw(CDC* pDC)

{

CExExtentPenDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

// 확장펜을 만든다.

LOGBRUSH logbr;

CPen pen,*oldpen;

logbr.lbStyle=m_lbStyle;

logbr.lbHatch=m_lbHatch;

logbr.lbColor=lbColor;

pen.CreatePen(m_dPenStyle,m_dWidth,&logbr);

// 펜을 설정하고

oldpen=pDC->SelectObject(&pen);

// m_nTotalPoly가 0보다 크면

if(m_nTotalPoly)

pDC->MoveTo(m_pPoly[0]);// 첫 번째 점으로 이동하고

// 계속적으로 라인을 그린다.

for(int i=1;i<m_nTotalPoly;i++)

pDC->LineTo(m_pPoly[i]);

// 이전 펜 복귀

pDC->SelectObject(oldpen);

pen.DeleteObject();

}

//////////////////////////////////////////////////////////////////////////// CExExtentPenView printing

BOOL CExExtentPenView::OnPreparePrinting(CPrintInfo* pInfo)

{

// default preparation

return DoPreparePrinting(pInfo);

}

void CExExtentPenView::OnBeginPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)

{

// TODO: add extra initialization before printing

}

void CExExtentPenView::OnEndPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)

{

// TODO: add cleanup after printing

}

//////////////////////////////////////////////////////////////////////////// CExExtentPenView diagnostics

#ifdef _DEBUG

void CExExtentPenView::AssertValid() const

{

CView::AssertValid();

}

void CExExtentPenView::Dump(CDumpContext& dc) const

{

CView::Dump(dc);

}

CExExtentPenDoc* CExExtentPenView::GetDocument() // non-debug version is inline

{

ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CExExtentPenDoc)));

return (CExExtentPenDoc*)m_pDocument;

}

#endif // _DEBUG

//////////////////////////////////////////////////////////////////////////// CExExtentPenView message handlers

void CExExtentPenView::OnLButtonDown(UINT nFlags, CPoint point)

{

// 마우스 버튼 눌림 설정

m_bDown=TRUE;

// m_point와 m_oldpoint를 현재 마우스 위치로 설정

m_point=point;

m_oldpoint=point;

// 전체 좌표 포인터 수는 0으로 초기화

m_nTotalPoly=0;

CView::OnLButtonDown(nFlags, point);

}

void CExExtentPenView::OnMouseMove(UINT nFlags, CPoint point)

{

if(m_bDown)

{

// 현재 마우스 위치 저장

m_point=point;

CDC *pDC=GetDC();

// 확장펜을 만든다.

LOGBRUSH logbr;

CPen pen,*oldpen;

logbr.lbStyle=m_lbStyle;

logbr.lbHatch=m_lbHatch;

logbr.lbColor=lbColor;

pen.CreatePen(m_dPenStyle,m_dWidth,&logbr);

// 확장펜 설정

oldpen=pDC->SelectObject(&pen);

// 이전 위치로 이동하고

pDC->MoveTo(m_oldpoint);

// 현재 마우스 위치로 라인을 그린다.

pDC->LineTo(m_point);

// 이전 객체 설정

pDC->SelectObject(oldpen);

pen.DeleteObject();

ReleaseDC(pDC);

// 현재 위치를 m_pPoly 구조체에 넣고

m_pPoly[m_nTotalPoly]=m_point;

// m_nTotalPoly를 증가시킨다.

m_nTotalPoly++;

// m_pPoly 배열을 초과하면 더 이상 값을 넣지 않는다.

if(m_nTotalPoly>=500)

{

m_bDown=FALSE;

Invalidate();

}

// 현재 위치를 다시 이전 위치 포인터에 저장

m_oldpoint=point;

}

CView::OnMouseMove(nFlags, point);

}

void CExExtentPenView::OnLButtonUp(UINT nFlags, CPoint point)

{

// 마우스 버튼 눌림 설정

m_bDown=FALSE;

// OnDraw 함수 호출

Invalidate();

CView::OnLButtonUp(nFlags, point);

// 확장펜 메뉴 항목

void CExExtentPenView::OnPsEndcapFlat()

{

m_dPenStyle=PS_GEOMETRIC|PS_ENDCAP_FLAT;

Invalidate();

}

void CExExtentPenView::OnPsEndcapRound()

{

m_dPenStyle=PS_GEOMETRIC|PS_ENDCAP_ROUND;

Invalidate();

}

void CExExtentPenView::OnPsEndcapSquare()

{

m_dPenStyle=PS_GEOMETRIC|PS_ENDCAP_SQUARE;

Invalidate();

// 브러시 메뉴 항목

void CExExtentPenView::OnBsHatched()

{

m_lbStyle=BS_HATCHED;

Invalidate();

}

void CExExtentPenView::OnBsHollow()

{

m_lbStyle=BS_HOLLOW|BS_SOLID;

Invalidate();

}

void CExExtentPenView::OnBsPattern()

{

m_lbStyle=BS_PATTERN;

Invalidate();

}

void CExExtentPenView::OnBsSolid()

{

m_lbStyle=BS_SOLID;

Invalidate();

}

// 빗금 메뉴 항목

void CExExtentPenView::OnHsBdiagonla()

{

m_lbHatch=HS_BDIAGONAL;

Invalidate();

}

void CExExtentPenView::OnHsCross()

{

m_lbHatch=HS_CROSS;

Invalidate();

}

void CExExtentPenView::OnHsDiagcross()

{

m_lbHatch=HS_DIAGCROSS;

Invalidate();

}

void CExExtentPenView::OnFdiagonal()

{

m_lbHatch=HS_FDIAGONAL;

Invalidate();

}

void CExExtentPenView::OnHsHorizontal()

{

m_lbHatch=HS_HORIZONTAL;

Invalidate();

}

void CExExtentPenView::OnHsVertical()

{

m_lbHatch=HS_VERTICAL;

Invalidate();

}

// 색상 메뉴 항목

void CExExtentPenView::OnColorBlack()

{

lbColor=RGB(0,0,0);

Invalidate();

}

void CExExtentPenView::OnColorBlue()

{

lbColor=RGB(0,0,255);

Invalidate();

}

void CExExtentPenView::OnColorGreen()

{

lbColor=RGB(0,255,0);

Invalidate();

}

void CExExtentPenView::OnColorRed()

{

lbColor=RGB(255,0,0);

Invalidate();

}

// 굵기 메뉴 항목

void CExExtentPenView::OnWidth5()

{

m_dWidth=5;

Invalidate();

}

void CExExtentPenView::OnWidth10()

{

m_dWidth=10;

Invalidate();

}

void CExExtentPenView::OnWidth15()

{

m_dWidth=15;

Invalidate();

}

void CExExtentPenView::OnWidth20()

{

m_dWidth=20;

Invalidate();

}

매핑 모드

윈도에서 지원하는 매핑 모드는 표11과 같습니다.

MM_TEXT는 모니터에 출력하는 기본 모드를 의미합니다. 실제로는 디바이스 기본 모드라고 생각하면 됩니다. 우리가 지금까지 사용한 모드는 MM_TEXT입니다. MM_LOMETRIC은 기본 단위가 0.1mm이며 이 좌표는 위로 올라갈수록 증가됩니다.

MM_HIMETRIC, MM_LOENGLIST, MM_HIENGLISH, MM_TWIPS 등은 일반적인 미터법 매핑 모드로 사용됩니다. 이런 매핑 모드를 사용할 때 수행되는 함수가 CDC의 멤버 함수인 SetMapMode입니다.

int SetMapMode(

int fnMapMode // 매핑 모드

);

표11    매핑 모드

매핑 모드	기본 단위	X축 증가값	Y축 증가값

MM_TEXT	픽셀	우측으로	아래로

MM_LOMETRIC	0.1mm	우측으로	위로

MM_HIMETRIC	0.01 mm	우측으로	위로

MM_LOENGLISH	0.01 in	우측으로	위로

MM_HIENGLISH	0.001 in	우측으로	위로

MM_TWIPS	1/1440 in	우측으로	위로

MM_ISOTROPIC	(X,Y)값 설정	선택적	선택적

MM_ANISOTROPIC	(X,Y)값 설정	선택적	선택적

(1) 로직 좌표와 디바이스 좌표

MetricMap에서 보면 실제 출력되는 상자의 픽셀 단위 좌표는 (4, 4, 38, 38)의 영역에 그려지나 이것을 MM_LOMETRIC으로 설정했을 때는 (10, -10, 100, -100)이라는 형태로 인식됩니다. 즉 실제 디바이스에 출력되는 좌표를 디바이스 좌표라 하며, SetMapMode 함수 등을 이용하여 설정한 좌표를 로직 좌표라고 합니다.

모니터나 프린터로 출력할 경우 시스템에서는 언제나 디바이스 좌표를 사용하여 출력합니다. 만일 SetMapMode 함수를 이용하여 매핑 모드를 바꾸었다고 한다면 시스템에서는 자신이 출력하는 좌표와 사용자가 생각하는 좌표 두 개를 만들어야 합니다. 그리고 사용자가 설정한 그래픽 좌표(로직 좌표)를 디바이스 좌표로 전환시킵니다. 이런 로직 좌표와 디바이스 좌표를 사용하는 이유는 출력 디바이스가 모니터가 아니라는 데 매우 중요한 의미가 있습니다. 어떤 내용을 프린터로 출력한다고 가정합시다. 이때 프린터의 성능에 따라 프린터의 점 단위가 매우 다릅니다. 600dpi 프린터와 1200dpi 프린터에서 한 점의 크기는 같을 수가 없습니다.

만약 일반적인 MM_TEXT 모드로 라인을 출력한다고 가정했을 경우 50도트의 라인을 프린터에 출력하면 600dpi에서 출력되는 형태가 1200dpi에서 출력되는 형태보다 크게 나타납니다. 그 이유는 600dpi 프린터가 출력하는 점의 크기가 1200dpi 프린터의 점보다 2배가 크기 때문입니다. 어떤 내용을 프린터로 출력했을 때 1200dpi에서 출력된 것이 600dpi에서 출력된 것보다 작게 나온다면, 즉 프린터에 따라 출력물이 변한다면 이것은 문제가 있습니다. 따라서 어떤 프린터든지 같은 크기의 내용이 출력될 수 있게 하기 위해서는 디바이스 좌표 형태로 하지 않고 로직 좌표를 하나 만들어 이 좌표 형태로 그려 놓으면 시스템에서는 로직 좌표 내용을 디바이스 좌표로 전환하여 출력하기 때문에 균일하게 출력할 수 있습니다.

프린터로 가로 1cm, 세로 1cm 위치에 대각선 라인을 그리고자 한다면 매핑 모드를 MM_LOMETRIC으로 설정하고

pDC->SetMapMode(MM_LOMETRIC);

pDC->MoveTo(0,0);

pDC->LineTo(0,-1000);

하면 될 것입니다.  600dpi 프린터에서 1cm 안에 점이 50개가 들어가고 1200dpi에서는 1cm 안에 점이 100개가 들어간다고 가정합시다.

600dpi 프린터에서 위의 좌표를 디바이스 좌표로 전환하면 (0, 0)에서 (0, 50)으로 점이 찍히게 되고, 1200dpi에서는 (0, 0)에서 (0, 100)으로 점이 찍히게 되는 것입니다.

프로그래밍할 때 로직 좌표를 MM_TEXT로 사용하지 않는 경우 때로는 디바이스 좌표값을 알고자 할 때가 있고, 디바이스 좌표를 로직 좌표로 변환시키고자 할 경우가 있습니다. 이때 사용하는 함수가 DPtoLP LPtoDP입니다.

BOOL LPtoDP(

LPPOINT lpPoints, // 전환될 좌표 배열

int nCount // 좌표수

);

BOOL DPtoLP(

LPPOINT lpPoints, // 전환될 좌표 배열

int nCount // 좌표수

);

(2) Viewport와 Window

디바이스 좌표를 가지고 있는 어떤 좌표 평면의 영역을 Viewport라 하고 로직 좌표를 가지고 있는 어떤 좌표 평면을 Window라고 합니다. 이런 개념을 쓰는 이유는 좌표 평면의 정보를 수정하고자 할 경우가 있기 때문입니다.

그림 1-3-31의 (a)처럼  MM_TEXT에서는 좌측 상단이 (0, 0)의 초기점입니다. 이때때로는 화면 영역의 중앙을 (0, 0)으로 하고자 할 경우가 있습니다 그림 1-3-31의 (b)처럼 말입니다. 이럴 경우 SetWindowOrg라는 함수를 사용하여 이동시킬 수 있습니다.

BOOL SetWindowOrg(

int X, // 이동 X좌표

int Y, // 이동 Y좌표

);

예를 들어 화면의 현재 전체 영역이 (400, 400)이라고 가정하고 (b) 같은 좌표로 전환하고자 할 경우 다음과 같이 할 수 있습니다.

pDC->SetWindowOrg(200,200);

SetWindowOrg라는 함수는 로직 좌표계에서 초기 좌표를 이동하는 함수입니다. 전  항목에서 좌표계는 디바이스 좌표계와 로직 좌표계를 사용한다고 하였습니다. SetWindowOrg 함수에 의해서 로직 좌표계의 좌표 원점이 이동하였다 하여도 디바이스 좌표계의 원점이 이동되는 것은 아닙니다. 다바이스 좌표계의 원점을 이동하고자 한다면 SetViewportOrg라는 함수를 사용합니다.

BOOL SetViewportOrg(

int X, // 이동 X좌표

int Y, // 이동 Y좌표

);

MM_LOMETRIC으로 매핑 모드를 설정하고 로직 좌표와 디바이스 좌표 중앙에 원점을 설정하고자 한다면 다음과 같은 형식이 될 것입니다.

쥱 클라이언트 전체 영역을 얻습니다.

GetClientRect(&rect)

쥲 rect 좌표는 dc와 상관되는 좌표가 아닙니다. 이 좌표는 디바이스 좌표입니다.

디바이스 좌표를 이용하여 원점을 이동하는 함수는 SetViewportOrg입니다.

pDC->SetViewportOrg(rect.right/2,rect.bottom/2);

쥳 이제 rect를 로직 좌표로 전환하고 로직 좌표계의 원점을 이동합니다.

pDC->DPtoLP((PPOINT)&rect,2);

pDC->SetWindowOrg(rec,right/2,rect.bottom/2);

좌표를 리턴하는 함수들 중에 디바이스 좌표만 리턴하는 함수들이 있습니다. 위의 예에서 보듯이 GetClientRect 함수는 dc에서 얻는 함수가 아니기 때문에 디바이스 좌표를 리턴합니다. 이 외에 GetDeviceCaps라는 함수는 DC를 이용하지만 로직 좌표계가 아닌 디바이스 좌표를 리턴합니다. 프로그래밍할 경우 특정 매핑 모드를 선언하면 함수가 디바이스 좌표를 리턴하는가, 또는 로직 좌표를 리턴하는가에 대해서 주의해야 합니다.

좌표 평면 정보를 수정할 경우 어떤 좌표 평면을 수정할 것인가를 알려주어야 하기 때문에 디바이스 좌표 평면을 보통 Viewport라고 하며 로직 좌표 평면을 Window라고 합니다.

이번 항목부터는 GDI를 이용하여 하나의 툴을 만드는 중·상급 항목입니다. 초급자들은 본 항목을 그냥 넘어가도 됩니다. 본 항목에서 제작하고자 하는 것은 추적 상자입니다. MFC에서 제공하는 CRectTracker와 같은 클래스를 만들고 이것을 이용하는 간단한 프로그램을 제작하고자 하는 것입니다. 추적 상자는 다용도에 필요합니다. 그래픽 에디터나 저작 도구 등을 만들 때, 또는 OLE에서 객체 삽입 등을 할 때 등등 다양한 용도로 사용됩니다. MFC에서 제공하는 CRectTracker도 쓸모는 있으나 자신만의 추적 상자 클래스를 만드는 것도 매우 유용하다고 봅니다.

필자는 많은 프로그램을 제작하여 왔습니다. 그 중에서 “노가다급”에 해당되는 프로그램이 CD-ROM 타이틀이었습니다. 이 프로그램은 어떤 기술적인 난이도보다는 시간을 많이 투자해야 하는 그런 프로그램 중 하나입니다. 이런 프로그램을 제작할 때 가장 고통스러웠던 점은 제작이 거의 완료되어 갈 때쯤에서야 제시되는 “아! 이게 아닌 것 같은데!” 하는 나폴레옹식의 제안들이었습니다. 처음 스팩을 잡은 대로 프로그램을 만들어 놓고 완성품이 나오기 전에 시연회를 하게 되는데(보통 하청을 받아서 제작하였을 때), 높은 분들은 제작 도중에는 한 번도 보지 않다가 시연회에 와서야 “거, 버튼 여기다 놓으면 안 되나?”, “거, 색이 좀 맘에 안 들어!”,  “그거, 이런 방법보다는 뭐 ‘뿅!’ 하면서 나오면 안 돼?” 하고 말합니다. 또는 높은 분들이 많이 있으니까 좀 잘난 체 한다고 “어…, 내가 보기에는 말입니다. 이 기능이 조금 문제가 있네요…. 이것은 말이에요….” 등등으로 옆에서 고춧가루를 치는 사람들까지 있습니다. 이럴 때는 몇날 몇일을 밤새워서 만든 프로그램을 다시 만들어야 합니다.(이 책을 보는 독자들 중에서도 아마 이런 슬픔을 아직도 당하고 있는 분들이 있을 겁니다. 마음으로나마 위로를 표합니다.)

이럴 때 가장 힘든 프로그램이 CD-ROM 타이틀입니다. 규격화하여 만들기 때문에 하나를 수정하더라도 참으로 난감해 집니다. 그래서 필자는 CD-ROM 타이틀을 만들 때면 아예 저작 도구 비슷한 것을 만들어 놓습니다. 이렇게 저작 도구를 만들어 놓으면 수정할 때 프로그래머가 실행 파일을 재컴파일할 필요가 없기 때문에 데이터 구성자에게 노가다급 작업이 넘어가게 되는 것이지요. 이때저작 도구에서 필수적으로 필요한 게 바로 추적 상자입니다. 처음에는 MFC에서 제공하는 CRectTracker를 사용하다 보니 내 기능에 맞지 않는 문제점이 있어서 나만의 추적 상자를 만들어서 사용하는 경우가 많았습니다. 추적 상자 펜을 이용하여 상자를 그리기 때문에 본 장의 내용과 결합되는 것이 대부분입니다. 이 책을 읽는 여러분들도 추적 상자를 만들 필요가 있을지 몰라 추적 상자를 만드는 방법을 본 항목에서 소개합니다.

본 항목에서 만드는 추적 상자

본 항목에서 만드는 추적 상자 클래스는 CMyTracker입니다. 이 추적 상자의 기능은 그림 1-3-32와 같습니다.

CMyTracker 추적 상자의 기능은 CRectTracker의 기능과 거의 비슷합니다. 여덟 개 방향의 위치에는 작은 상자의 점이 설정되고, 해당 위치에 마우스가 있으면 커서가 변하게 되며, 이때 마우스를 누른 상태에서 드래깅하면 영역이 변경됩니다. CMyTracker의 특징을 살펴보면 그림 1-3-32의 ⓑ 항목에 나타나 있습니다. 즉, 추적 상자에 현재 어떤 프로세서를 설정하였다고 알려주면 현재의 추적 상자에 상관없이 좌측 마우스 버튼을 누르면서 드래깅하면 새로운 영역이 만들어집니다. 예를 들어서 도형 그리기 프로그램이 있다고 가정합니다. 도형 그리기 중 상자 그리기를 설정하여 상자를 그리고 나서 이 상자 위에 추적 상자를 설정하였다고 합시다. 이때 다시 원 그리기를 설정하여 새로운 그림을 그리고자 할 경우 현재 상자 영역에 설정되어 있는 추적 상자와 상관없이 새로운 영역을 만들어주어야 합니다. 본 장 예제인 ExDraw에서 그리기 항목을 선택하고 영역을 선택했을 때 XOR 형태로 그려지는 영역이 현재 추적 상자와는 상관없이 이루어져야 한다는 의미입니다. 이 부분이 필자에게는 매우 필요한 항목이었습니다. CRectTracker를 사용하면 새로운 영역을 설정할 때 추적 상자를 비활성화하고 마우스 버튼 누름, 드래깅, 띄움을 할 때마다 다시 XOR펜을 써서 일일이 그려야 하기 때문입니다. 그래서 이 부분을 CMyTracker에 내장시켜 놓고 현재 새로운 프로세서를 시작한다고 설정하면 추적 상자에 상관없이 새로운 영역을 설정할 수 있도록 하였습니다.

추적 상자 클래스 CMyTracker의 멤버 함수

본 항목에서 만드는 CMyTracker 클래스의 멤버 함수는 다음과 같습니다.

쥱 CRect m_rect;

추적 상자의 현재 영역을 저장하는 CRect 구조체입니다.

쥲 CRect m_rectTrack;

추적 상자의 변경되는 영역을 저장하는 CRect 구조체입니다. 예를 들어 이동 상태에서 마우스 버튼을 누른 상태에서 드래깅하면 상자 영역이 이동되는 것을 볼 수 있습니다. 이때이동되는 좌표는 m_rectTrack에 저장되고 이전에 이미 설정된 값은 m_rect에 있게 됩니다. 마우스 버튼을 놓으면 m_rectTrack에 저장된 값이 다시 m_rect로 넘어갑니다.

쥳 CMyTracker();

기본 생성자입니다.

쥵 CMyTracker(CWnd *pWnd, int dot=2, int tick=3);

부모 윈도 포인터 pWnd와 추적 상자의 라인 두께값 tick, 그리고 여덟 개 방향의 상자 점의 크기를 현재 라인보다 얼마나 크게 할 것인가의 dot값을 설정하여 생성하는 생성자입니다.

쥶 void SetRect(CRect rect);

추적 상자에 rect 영역을 설정하는 함수입니다.

쥷 void Draw(CDC *pDC);

이 함수는 추적 상자를 그려줍니다. 보통 OnDraw 함수에 함께 설정하면 됩니다.

쥸 void OnMouseMove(UINT nFlags, CPoint point);

마우스가 움직일 때 수행되는 함수

쥹 void OnLButtonDown(UINT nFlags, CPoint point);

좌측 마우스 버튼을 눌렀을 때 수행되는 함수

쥺 void OnLButtonUp(UINT nFlags, CPoint point);

좌측 마우스 버튼을 놓았을 때 수행되는 함수

쥻 void SetProcess(int num);

새로운 프로세서가 시작될 때, 즉 추적 상자에 상관없이 새로운 영역을 설정하고자 할 때는 num에 0이 아닌 값을 설정하고 그렇지 않을 경우에는 0값을 설정합니다.

쥽 int  GetProcess();

SetProcess에 설정한 값을 얻습니다.

쥾 int PtInRect(CPoint point);

여덟 개 방향과 선상에 마우스 위치가 있는가를 확인하는 함수가 있으면 어느 위치에 있는가의 번호, 그렇지 않으면 음수값을 리턴합니다. 좌측 상단부터 여덟 개의 꼭지점 값은 0∼7이며, 선상에 있을 경우에는 8∼11의 값을 리턴합니다.

쥿 void SetMouseCursor();

PtInRect에 의해서 마우스 위치가 방향점이나 선 위치에 있는가를 확인하고 해당 마우스 커서로 전환합니다.

즀 int GetChoNum(){return m_nChoNum;}

m_nChoNum은 protected 변수로서 PtInRect를 호출하여 설정된 값을 저장합니다. 즉, 현재 마우스 여덟 개 방향과 이동 위치를 선택하고 있는가 아닌가를 확인합니다. 음수일 경우에는 어느 영역에도 있지 않다는 의미이며, 0∼11 사이의 값은 현재 마우스가 추적 상자의 특정 위치에 있다는 의미입니다.

CMyTracker 만들기

이제 CMyTracker의 함수를 만들어 보겠습니다.

멤버 변수 만들기

먼저 이 클래스에서 필요한 멤버 변수들을 설정합니다. 이 멤버 변수는 다음과 같습니다.

protected:

CWnd *m_pWnd;// 부모 윈도 핸들러

BOOL m_bDown;// 마우스 버튼 플러그

CRect m_rectOld;// 이전 영역 좌표 저장 변수

// 마우스가 추적 상자에서 여덟 개 방향과 이동 위치의 어느 점에 있는가

// 의 값을 저장하고 있는 변수

int m_nChoNum;

// 현재 프로세스를 설정하고 있는 변수

int m_nProcess;

// 여덟 개 방향의 좌표와 네 개 방향의 이동 좌표 저장 변수

CRect m_rectDirect[12];

// 이전 마우스 포인터 변수

CPoint m_OldPoint;

// 여덟 개 영역 상자의 점이 선보다 얼마나 두꺼운가를 저장하는 변수

int m_nLineDot;

// 추적 상자 라인의 두께

int m_nLindTick;

// m_rectDirect 영역이 설정되어 있으면 TRUE, 그렇지 않으면 FALSE

int m_bSet;

public:

// 이동되는 영역 좌표 저장 변수

CRect m_rectTrack;

// 현재 추적 상자의 영역 좌표

CRect m_rect;

위의 변수들을 CMyTracker 클래스로 만들고 설정합니다. 이 변수들을 사용하는 부분은 뒤에서 계속 설명하겠습니다.

생성자 만들기

추적 상자의 생성자는 다음과 같습니다.

CMyTracker::CMyTracker()

{

m_rect=CRect(0,0,0,0);

m_bDown=FALSE;

m_pWnd=NULL;

m_nProcess=0;

m_nLineDot=2;

m_nLindTick=3;

m_nChoNum=-1;

m_bSet=FALSE;

m_OldPoint=CPoint(0,0);

}

CMyTracker::CMyTracker(CWnd *pWnd,int dot,int tick)

{

m_rect=CRect(0,0,0,0);

m_pWnd=pWnd;

m_bDown=FALSE;

m_nProcess=0;

m_nLineDot=dot;

m_nLindTick=tick;

m_nChoNum=-1;

m_bSet=FALSE;

m_OldPoint=CPoint(0,0);

}

CMyTracker() 함수와 CMyTracker(CWnd *pWnd, int dot, int tick)의 차이는 부모 윈도 핸들을 설정하는가 안 하는가이며, 또한 라인 두께와 여덟 개 방향의 상자 크기를 설정할 수 있는가 아닌가의 차이입니다. 이 외는 (1)에서 만든 멤버들을 초기화하는 내용이 들어갑니다.

좌측 마우스 버튼 눌림시

좌측 마우스 버튼을 눌렸을 때 수행되는 내용은 그림 1-3-33과 같습니다.

그림 1-3-33처럼 좌측 마우스 버튼을 클릭했을 때는 두 가지 형태에 따라 방향이 다릅니다. 첫 번째는 프로세서가 설정되어 있지 않은 상태에서 추적 상자 영역이 아닌 새로운 곳을 클릭했을 때입니다. 이때는 새로운 객체이거나 새로운 액션이 있어야 하기 때문에 m_rectTrack과 m_rectOld를 모두 한 점으로 일치시킵니다. 그 밖의 것은 m_OldPoint에 마우스 현재 위치를 저장하하면 됩니다.

이 내용은 다음과 같습니다.

void CMyTracker::OnLButtonDown(UINT nFlags, CPoint point)

{

// 만약에 현재 아무것도 설정되어 있지 않고

// 마우스 버튼 누름은 그림 1-3-33의 ① 항목

if(m_nChoNum<0)

{

// 화살표 커서 설정

SetClassLong(m_pWnd->m_hWnd,

GCL_HCURSOR,(long)LoadCursor(NULL,IDC_ARROW));

// m_rectTrack과 m_rectOld를 현재 point의 한 점으로 설정

m_rectTrack.left=point.x;

m_rectTrack.right=point.x;

m_rectTrack.top=point.y;

m_rectTrack.bottom=point.y;

m_rectOld=m_rectTrack;

}

// m_OldPoint에 point값 설정

m_OldPoint=point;

m_bDown=TRUE;

}

(2) 마우스 이동시

마우스 이동시의 내용은 그림 1-3-34와 같습니다.

그림 1-3-34의 ① 항목처럼 프로세서가 설정되어 있지 않은 상태에서 추적 상자 영역을 좌측 마우스 버튼이 눌린 상태에서 이동하면 이동된 위치로 XOR 형태의 새로운 상자가 그려집니다. 예를 들어 추적 상자가 이동 위치에서 마우스 버튼을 클릭하고 움직였다면 이동 상태이므로, 이동 상태로 새로운 상자를 XOR 형태로 그려준다는 의미입니다. 이 내용은 다음과 같습니다.

// 추적 상자 영역이 아니면 리턴

if(m_nChoNum<0)

return;

// 추적 상자 영역일 경우

switch(m_nChoNum)

{

case 0:// 좌측 상단 선택시

m_rectTrack.left=point.x;

m_rectTrack.top=point.y;

break;

case 1:// 우측 상단 선택시

m_rectTrack.right=point.x;

m_rectTrack.top=point.y;

break;

case 2:// 우측 하단 선택시

m_rectTrack.right=point.x;

m_rectTrack.bottom=point.y;

break;

case 3:// 좌측 하단 선택시

m_rectTrack.left=point.x;

m_rectTrack.bottom=point.y;

break;

case 4:// 위쪽으로

m_rectTrack.top=point.y;

break;

case 5:// 아래로

m_rectTrack.bottom=point.y;

break;

case 6:// 좌측으로

m_rectTrack.left=point.x;

break;

case 7:// 우측으로

m_rectTrack.right=point.x;

break;

// 이동될 경우

case 8:case 9:case 10:case 11:

{

CPoint def;

def=point-m_OldPoint;

m_rectTrack.top+=def.y;

m_rectTrack.bottom+=def.y;

m_rectTrack.left+=def.x;

m_rectTrack.right+=def.x;

m_OldPoint=point;

}

break;

}

CDC *pDC=m_pWnd->GetDC();

CPen pen,*oldPen;

pDC->SetROP2(R2_XORPEN);

// 이전에 그려진 영역을 지우고

pen.CreatePen(PS_DOT,2,RGB(125,125,125));

oldPen=pDC->SelectObject(&pen);

pDC->MoveTo(m_rectOld.left,m_rectOld.top);

pDC->LineTo(m_rectOld.right,m_rectOld.top);

pDC->LineTo(m_rectOld.right,m_rectOld.bottom);

pDC->LineTo(m_rectOld.left,m_rectOld.bottom);

pDC->LineTo(m_rectOld.left,m_rectOld.top);

// 새로운 영역을 그린다.

pDC->MoveTo(m_rectTrack.left,m_rectTrack.top);

pDC->LineTo(m_rectTrack.right,m_rectTrack.top);

pDC->LineTo(m_rectTrack.right,m_rectTrack.bottom);

pDC->LineTo(m_rectTrack.left,m_rectTrack.bottom);

pDC->LineTo(m_rectTrack.left,m_rectTrack.top);

pDC->SelectObject(oldPen);

pen.DeleteObject();

m_rectOld=m_rectTrack;

m_pWnd->ReleaseDC(pDC);

그림 1-3-34의 ② 항목처럼 마우스 버튼이 눌려져 있지 않은 상태에서 마우스를 이동한다면 현재 마우스가 추적 상자에 있는가를 확인한 후 커서만 바꾸어줍니다. 이 내용은 다음과 같습니다.

m_nChoNum=PtInRect(point);

SetMouseCursor();

PiInRect 함수에 현재 마우스 위치를 넣으면 추적 상자 영역일 경우에는 0∼11의 값이, 그렇지 않을 경우에는 -1을 리턴합니다. 이 값을 m_nChoNum에 저장하고 SetMouseCursor 함수를 호출하면 m_nChoNum에 따라서 해당 마우스 커서를 로드합니다.

그림 1-3-34의 ③ 항목처럼 마우스 버튼이 눌려지고 현재 프로세스가 설정되어 있다면 추적 상자와 상관없이, 즉 ExDraw에서 사용한 방법처럼 XOR 형태로 영역을 그려주면 됩니다. 이 내용은 다음과 같습니다.

m_rectTrack.right=point.x;

m_rectTrack.bottom=point.y;

CDC *pDC=m_pWnd->GetDC();

CPen pen,*oldPen;

pDC->SetROP2(R2_XORPEN);

pen.CreatePen(PS_DOT,2,RGB(125,125,125));

oldPen=pDC->SelectObject(&pen);

pDC->MoveTo(m_rectOld.left,m_rectOld.top);

pDC->LineTo(m_rectOld.right,m_rectOld.top);

pDC->LineTo(m_rectOld.right,m_rectOld.bottom);

pDC->LineTo(m_rectOld.left,m_rectOld.bottom);

pDC->LineTo(m_rectOld.left,m_rectOld.top);

pDC->MoveTo(m_rectTrack.left,m_rectTrack.top);

pDC->LineTo(m_rectTrack.right,m_rectTrack.top);

pDC->LineTo(m_rectTrack.right,m_rectTrack.bottom);

pDC->LineTo(m_rectTrack.left,m_rectTrack.bottom);

pDC->LineTo(m_rectTrack.left,m_rectTrack.top);

pDC->SelectObject(oldPen);

pen.DeleteObject();

m_rectOld=m_rectTrack;

m_pWnd->ReleaseDC(pDC);

마우스 이동시 수행하는 위의 세 개의 내용을 합친 OnMouseMove 함수는 다음과 같습니다.

void CMyTracker::OnMouseMove(UINT nFlags, CPoint point)

{

// 그림 1-3-34의 ③ 항목

if(m_bDown && m_nProcess!=0)

{

m_rectTrack.right=point.x;

m_rectTrack.bottom=point.y;

CDC *pDC=m_pWnd->GetDC();

CPen pen,*oldPen;

pDC->SetROP2(R2_XORPEN);

pen.CreatePen(PS_DOT,2,RGB(125,125,125));

oldPen=pDC->SelectObject(&pen);

pDC->MoveTo(m_rectOld.left,m_rectOld.top);

pDC->LineTo(m_rectOld.right,m_rectOld.top);

pDC->LineTo(m_rectOld.right,m_rectOld.bottom);

pDC->LineTo(m_rectOld.left,m_rectOld.bottom);

pDC->LineTo(m_rectOld.left,m_rectOld.top);

pDC->MoveTo(m_rectTrack.left,m_rectTrack.top);

pDC->LineTo(m_rectTrack.right,m_rectTrack.top);

pDC->LineTo(m_rectTrack.right,m_rectTrack.bottom);

pDC->LineTo(m_rectTrack.left,m_rectTrack.bottom);

pDC->LineTo(m_rectTrack.left,m_rectTrack.top);

pDC->SelectObject(oldPen);

pen.DeleteObject();

m_rectOld=m_rectTrack;

m_pWnd->ReleaseDC(pDC);

}

// 그림 1-3-34의 ② 항목

if(m_nProcess==0 && !m_bDown && m_bSet)

{

m_nChoNum=PtInRect(point);

SetMouseCursor();

}

// 그림 1-3-34의 ① 항목

else if(m_nProcess==0 && m_bSet)

{

// 추적 상자 영역이 아니면 리턴

if(m_nChoNum<0)

return;

// 추적 상자 영역일 경우

switch(m_nChoNum)

{

case 0:// 좌측 상단 선택시

m_rectTrack.left=point.x;

m_rectTrack.top=point.y;

break;

case 1:// 우측 상단 선택시

m_rectTrack.right=point.x;

m_rectTrack.top=point.y;

break;

case 2:// 우측 하단 선택시

m_rectTrack.right=point.x;

m_rectTrack.bottom=point.y;

break;

case 3:// 좌측 하단 선택시

m_rectTrack.left=point.x;

m_rectTrack.bottom=point.y;

break;

case 4:// 위쪽으로

m_rectTrack.top=point.y;

break;

case 5:// 아래로

m_rectTrack.bottom=point.y;

break;

case 6:// 좌측으로

m_rectTrack.left=point.x;

break;

case 7:// 우측으로

m_rectTrack.right=point.x;

break;

// 이동될 경우

case 8:case 9:case 10:case 11:

{

CPoint def;

def=point-m_OldPoint;

m_rectTrack.top+=def.y;

m_rectTrack.bottom+=def.y;

m_rectTrack.left+=def.x;

m_rectTrack.right+=def.x;

m_OldPoint=point;

}

break;

}

CDC *pDC=m_pWnd->GetDC();

CPen pen,*oldPen;

pDC->SetROP2(R2_XORPEN);

// 이전에 그려진 영역 지우고

pen.CreatePen(PS_DOT,2,RGB(125,125,125));

oldPen=pDC->SelectObject(&pen);

pDC->MoveTo(m_rectOld.left,m_rectOld.top);

pDC->LineTo(m_rectOld.right,m_rectOld.top);

pDC->LineTo(m_rectOld.right,m_rectOld.bottom);

pDC->LineTo(m_rectOld.left,m_rectOld.bottom);

pDC->LineTo(m_rectOld.left,m_rectOld.top);

// 새로운 영역을 그린다.

pDC->MoveTo(m_rectTrack.left,m_rectTrack.top);

pDC->LineTo(m_rectTrack.right,m_rectTrack.top);

pDC->LineTo(m_rectTrack.right,m_rectTrack.bottom);

pDC->LineTo(m_rectTrack.left,m_rectTrack.bottom);

pDC->LineTo(m_rectTrack.left,m_rectTrack.top);

pDC->SelectObject(oldPen);

pen.DeleteObject();

m_rectOld=m_rectTrack;

m_pWnd->ReleaseDC(pDC);

}

}

좌측 마우스를 떼었을 때

마우스를 떼었을 때는 간단하게 이전에 그려주었던 영역을 지워주는 것으로 끝납니다. 이것은 ExDraw의 좌측 마우스 버튼을 떼었을 때와 같은 루틴입니다.

void CMyTracker::OnLButtonUp(UINT nFlags, CPoint point)

{

CDC *pDC=m_pWnd->GetDC();

CPen pen,*oldPen;

pDC->SetROP2(R2_XORPEN);

pen.CreatePen(PS_DOT,2,RGB(125,125,125));

oldPen=pDC->SelectObject(&pen);

pDC->MoveTo(m_rectOld.left,m_rectOld.top);

pDC->LineTo(m_rectOld.right,m_rectOld.top);

pDC->LineTo(m_rectOld.right,m_rectOld.bottom);

pDC->LineTo(m_rectOld.left,m_rectOld.bottom);

pDC->LineTo(m_rectOld.left,m_rectOld.top);

pDC->SelectObject(oldPen);

pen.DeleteObject();

m_pWnd->ReleaseDC(pDC);

m_rectOld=CRect(0,0,0,0);

m_bDown=FALSE;

}

Draw 함수

Draw 함수는 추적 상자를 그려주는 함수입니다. 이 함수는 먼저 여덟 방향의 영역에 상자 영역을 만들고 추적 상자 영역을 그린 다음 작은 상자를 그려줍니다.

이 내용은 다음과 같습니다.

void CMyTracker::Draw(CDC *pDC)

{

if(m_rect.left==0 && m_rect.top==0 && m_rect.bottom==0 && m_rect.right==0)

return;

// 여덟 개 방향의 영역을 설정한다.

MakeDirect();

// 여덟 개 방향의 영역이 설정되면 m_bSet은 TRUE로

m_bSet=TRUE;

// 추적 상자의 메인 상자를 그린다.

CPen pen,*oldPen;

CBrush brush,*oldBrush;

pen.CreatePen(PS_DOT,m_nLindTick,RGB(125,125,125));

oldPen=pDC->SelectObject(&pen);

pDC->MoveTo(m_rect.left,m_rect.top);

pDC->LineTo(m_rect.right,m_rect.top);

pDC->LineTo(m_rect.right,m_rect.bottom);

pDC->LineTo(m_rect.left,m_rect.bottom);

pDC->LineTo(m_rect.left,m_rect.top);

pDC->SelectObject(oldPen);

pen.DeleteObject();

brush.CreateSolidBrush(RGB(0,0,0));

oldBrush=pDC->SelectObject(&brush);

// 여덟 개 방향의 상자를 그린다.

for(int i=0;i<8;i++)

pDC->Rectangle(m_rectDirect[i]);

pDC->SelectObject(oldBrush);

brush.DeleteObject();

}

CMyTracker의 전체 소스

지금까지 설명한 부분 외의 나머지 부분은 클래스의 전체 소스를 보면서 이해하기 바랍니다. 어려운 내용이 없으므로 쉽게 이해할 수 있을 것입니다.

// MyTracker.h

class CMyTracker{

protected:

CWnd *m_pWnd;// 부모 윈도 핸들러

BOOL m_bDown;// 마우스 버튼 플러그

CRect m_rectOld;// 이전 영역 좌표 저장 변수

// 마우스가 추적 상자에서 여덟 개 방향과 이동 위치의 어느 점에 있는가의

// 값을 저장하고 있는 변수

int m_nChoNum;

// 현재 프로세스를 설정하고 있는 변수

int m_nProcess;

// 여덟 개 방향의 좌표와 네 개 방향의 이동 좌표 저장 변수

CRect m_rectDirect[12];

// 이전 마우스 포인터 변수

CPoint m_OldPoint;

// m_rect 영역이 설정되면 이 영역을 토대로 m_rectDirect의 영역을 만들어주는

// 함수

void MakeDirect();

// 여덟 개 영역 상자 점이 선보다 얼마나 두꺼운가를 저장하는 변수

int m_nLineDot;

// 추적 상자 라인의 두께

int m_nLindTick;

// m_rectDirect 영역이 설정되어 있으면 TRUE, 그렇지 않으면 FALSE

int m_bSet;

public:

// 이동되는 영역 좌표 저장 변수

CRect m_rectTrack;

// 현재 추적 상자의 좌표

CRect m_rect;

CMyTracker();

CMyTracker(CWnd *pWnd,int dot=2,int tick=3);

void SetRect(CRect rect);

void Draw(CDC *pDC);

void OnMouseMove(UINT nFlags, CPoint point);

void OnLButtonDown(UINT nFlags, CPoint point);

void OnLButtonUp(UINT nFlags, CPoint point);

void SetProcess(int num);

int  GetProcess();

int PtInRect(CPoint point);

void SetMouseCursor();

int GetChoNum(){return m_nChoNum;}

};

// MyTracker.cpp

#include “stdafx.h”

#include “ExTrackRect.h”

#include “MyTracker.h”

// 생성자

CMyTracker::CMyTracker()

{

m_rect=CRect(0,0,0,0);

m_bDown=FALSE;

m_pWnd=NULL;

m_nProcess=0;

m_nLineDot=2;

m_nLindTick=3;

m_nChoNum=-1;

m_bSet=FALSE;

m_OldPoint=CPoint(0,0);

}

CMyTracker::CMyTracker(CWnd *pWnd,int dot,int tick)

{

m_rect=CRect(0,0,0,0);

m_pWnd=pWnd;

m_bDown=FALSE;

m_nProcess=0;

m_nLineDot=dot;

m_nLindTick=tick;

m_nChoNum=-1;

m_bSet=FALSE;

m_OldPoint=CPoint(0,0);

}

// 추적 상자 그리기

void CMyTracker::Draw(CDC *pDC)

{

if(m_rect.left==0 && m_rect.top==0 && m_rect.bottom==0 && m_rect.right==0)

return;

// 여덟 개 방향의 영역을 설정한다.

MakeDirect();

// 여덟 개 방향의 영역이 설정되면 m_bSet은 TRUE로

m_bSet=TRUE;

// 추적 상자의 메인 상자를 그린다.

CPen pen,*oldPen;

CBrush brush,*oldBrush;

pen.CreatePen(PS_DOT,m_nLindTick,RGB(125,125,125));

oldPen=pDC->SelectObject(&pen);

pDC->MoveTo(m_rect.left,m_rect.top);

pDC->LineTo(m_rect.right,m_rect.top);

pDC->LineTo(m_rect.right,m_rect.bottom);

pDC->LineTo(m_rect.left,m_rect.bottom);

pDC->LineTo(m_rect.left,m_rect.top);

pDC->SelectObject(oldPen);

pen.DeleteObject();

brush.CreateSolidBrush(RGB(0,0,0));

oldBrush=pDC->SelectObject(&brush);

// 여덟 개 방향의 상자를 그린다.

for(int i=0;i<8;i++)

pDC->Rectangle(m_rectDirect[i]);

pDC->SelectObject(oldBrush);

brush.DeleteObject();

}

// 추적 상자의 여덟 개 방향 영역과 이동 영역에

// 마우스 포인터가 있는가를 찾는 함수

int CMyTracker::PtInRect(CPoint point)

{

// 12개의 방향을 m_rect를 참조하여 만든다.

MakeDirect();

// 이 12개의 방향 위에 있으면 해당 번호를

// 리턴한다.

for(int i=0;i<12;i++)

if(m_rectDirect[i].PtInRect(point))

{

return i;

}

// 추적 상자 영역 위에 없으면 -1을 리턴

return -1;

}

// 해당 방향에 따라 마우스 커서를 설정한다.

void CMyTracker::SetMouseCursor()

{

switch(m_nChoNum)

{

case 0:

case 2:

SetClassLong(m_pWnd->m_hWnd,

GCL_HCURSOR,(long)AfxGetApp()->LoadCursor

(AFX_IDC_TRACKNWSE));

break;

case 1:

case 3:

SetClassLong(m_pWnd->m_hWnd,GCL_HCURSOR,

(long)AfxGetApp()->LoadCursor(AFX_IDC_TRACKNESW));

break;

case 4:

case 5:

SetClassLong(m_pWnd->m_hWnd,GCL_HCURSOR,

(long)AfxGetApp()->LoadCursor(AFX_IDC_TRACKNS));

break;

case 6:

case 7:

SetClassLong(m_pWnd->m_hWnd,GCL_HCURSOR,

(long)AfxGetApp()->LoadCursor(AFX_IDC_TRACKWE));

break;

case 8:

case 9:

case 10:

case 11:

SetClassLong(m_pWnd->m_hWnd,GCL_HCURSOR,

(long)AfxGetApp()->LoadCursor(AFX_IDC_TRACK4WAY));

break;

default:

SetClassLong(m_pWnd->m_hWnd,GCL_HCURSOR,

(long)LoadCursor(NULL,IDC_ARROW));

break;

}

}

// 마우스 이동시

void CMyTracker::OnMouseMove(UINT nFlags, CPoint point)

{

if(m_bDown && m_nProcess!=0)

{

m_rectTrack.right=point.x;

m_rectTrack.bottom=point.y;

CDC *pDC=m_pWnd->GetDC();

CPen pen,*oldPen;

pDC->SetROP2(R2_XORPEN);

pen.CreatePen(PS_DOT,2,RGB(125,125,125));

oldPen=pDC->SelectObject(&pen);

pDC->MoveTo(m_rectOld.left,m_rectOld.top);

pDC->LineTo(m_rectOld.right,m_rectOld.top);

pDC->LineTo(m_rectOld.right,m_rectOld.bottom);

pDC->LineTo(m_rectOld.left,m_rectOld.bottom);

pDC->LineTo(m_rectOld.left,m_rectOld.top);

pDC->MoveTo(m_rectTrack.left,m_rectTrack.top);

pDC->LineTo(m_rectTrack.right,m_rectTrack.top);

pDC->LineTo(m_rectTrack.right,m_rectTrack.bottom);

pDC->LineTo(m_rectTrack.left,m_rectTrack.bottom);

pDC->LineTo(m_rectTrack.left,m_rectTrack.top);

pDC->SelectObject(oldPen);

pen.DeleteObject();

m_rectOld=m_rectTrack;

m_pWnd->ReleaseDC(pDC);

}

if(m_nProcess==0 && !m_bDown && m_bSet)

{

m_nChoNum=PtInRect(point);

SetMouseCursor();

}

else if(m_nProcess==0 && m_bSet)

{

// 추적 상자 영역이 아니면 리턴

if(m_nChoNum<0)

return;

// 추적 상자 영역일 경우

switch(m_nChoNum)

{

case 0:// 좌측 상단 선택시

m_rectTrack.left=point.x;

m_rectTrack.top=point.y;

break;

case 1:// 우측 상단 선택시

m_rectTrack.right=point.x;

m_rectTrack.top=point.y;

break;

case 2:// 우측 하단 선택시

m_rectTrack.right=point.x;

m_rectTrack.bottom=point.y;

break;

case 3:// 좌측 하단 선택시

m_rectTrack.left=point.x;

m_rectTrack.bottom=point.y;

break;

case 4:// 위쪽으로

m_rectTrack.top=point.y;

break;

case 5:// 아래로

m_rectTrack.bottom=point.y;

break;

case 6:// 좌측으로

m_rectTrack.left=point.x;

break;

case 7:// 우측으로

m_rectTrack.right=point.x;

break;

// 이동될 경우

case 8:case 9:case 10:case 11:

{

CPoint def;

def=point-m_OldPoint;

m_rectTrack.top+=def.y;

m_rectTrack.bottom+=def.y;

m_rectTrack.left+=def.x;

m_rectTrack.right+=def.x;

m_OldPoint=point;

}

break;

}

CDC *pDC=m_pWnd->GetDC();

CPen pen,*oldPen;

pDC->SetROP2(R2_XORPEN);

// 이전에 그려진 영역을 지우고

pen.CreatePen(PS_DOT,2,RGB(125,125,125));

oldPen=pDC->SelectObject(&pen);

pDC->MoveTo(m_rectOld.left,m_rectOld.top);

pDC->LineTo(m_rectOld.right,m_rectOld.top);

pDC->LineTo(m_rectOld.right,m_rectOld.bottom);

pDC->LineTo(m_rectOld.left,m_rectOld.bottom);

pDC->LineTo(m_rectOld.left,m_rectOld.top);

// 새로운 영역을 그린다.

pDC->MoveTo(m_rectTrack.left,m_rectTrack.top);

pDC->LineTo(m_rectTrack.right,m_rectTrack.top);

pDC->LineTo(m_rectTrack.right,m_rectTrack.bottom);

pDC->LineTo(m_rectTrack.left,m_rectTrack.bottom);

pDC->LineTo(m_rectTrack.left,m_rectTrack.top);

pDC->SelectObject(oldPen);

pen.DeleteObject();

m_rectOld=m_rectTrack;

m_pWnd->ReleaseDC(pDC);

}

}

// 마우스 버튼 눌렀을 때

void CMyTracker::OnLButtonDown(UINT nFlags, CPoint point)

{

// 만약에 현재 아무것도 설정되어 있지 않고

// 마우스 버튼을 누름

if(m_nChoNum<0)

{

// 화살표 커서 설정

SetClassLong(m_pWnd->m_hWnd,GCL_HCURSOR,(long)LoadCursor(NULL,IDC_ARROW));

// m_rectTrack과 m_rectOld를 현재 point의 한 점으로 설정

m_rectTrack.left=point.x;

m_rectTrack.right=point.x;

m_rectTrack.top=point.y;

m_rectTrack.bottom=point.y;

m_rectOld=m_rectTrack;

}

// m_OldPoint에 point값 설정

m_OldPoint=point;

m_bDown=TRUE;

}

// 마우스 버튼 놓음

void CMyTracker::OnLButtonUp(UINT nFlags, CPoint point)

{

CDC *pDC=m_pWnd->GetDC();

CPen pen,*oldPen;

pDC->SetROP2(R2_XORPEN);

pen.CreatePen(PS_DOT,2,RGB(125,125,125));

oldPen=pDC->SelectObject(&pen);

pDC->MoveTo(m_rectOld.left,m_rectOld.top);

pDC->LineTo(m_rectOld.right,m_rectOld.top);

pDC->LineTo(m_rectOld.right,m_rectOld.bottom);

pDC->LineTo(m_rectOld.left,m_rectOld.bottom);

pDC->LineTo(m_rectOld.left,m_rectOld.top);

pDC->SelectObject(oldPen);

pen.DeleteObject();

m_pWnd->ReleaseDC(pDC);

m_rectOld=CRect(0,0,0,0);

m_bDown=FALSE;

}

// 프로세서값 설정

void CMyTracker::SetProcess(int num)

{

m_nProcess=num;

}

// 현재 설정된 프로세서값 얻음

int CMyTracker::GetProcess()

{

return m_nProcess;

}

// 12개의 방향을 만들어 준다.

void CMyTracker::MakeDirect()

{

m_rect=CRect(min(m_rect.left,m_rect.right),

min(m_rect.top,m_rect.bottom),

max(m_rect.left,m_rect.right),

max(m_rect.top,m_rect.bottom));

m_rectTrack=m_rect;

int pos;

m_rectDirect[0]=CRect(m_rect.left-m_nLineDot,m_rect.top-m_nLineDot,

m_rect.left+m_nLineDot,m_rect.top+m_nLineDot);

m_rectDirect[1]=CRect(m_rect.right-m_nLineDot,m_rect.top-m_nLineDot,

m_rect.right+m_nLineDot,m_rect.top+m_nLineDot);

m_rectDirect[2]=CRect(m_rect.right-m_nLineDot,m_rect.bottom-m_nLineDot,

m_rect.right+m_nLineDot,m_rect.bottom+m_nLineDot);

m_rectDirect[3]=CRect(m_rect.left-m_nLineDot,m_rect.bottom-m_nLineDot,

m_rect.left+m_nLineDot,m_rect.bottom+m_nLineDot);

pos=(m_rect.Width()/2)+m_rect.TopLeft().x;

m_rectDirect[4]=CRect(pos-m_nLineDot,m_rect.top-m_nLineDot,

pos+m_nLineDot,m_rect.top+m_nLineDot);

pos=(m_rect.Width()/2)+m_rect.TopLeft().x;

m_rectDirect[5]=CRect(pos-m_nLineDot,m_rect.bottom-m_nLineDot,

pos+m_nLineDot,m_rect.bottom+m_nLineDot);

pos=(m_rect.Height()/2)+m_rect.TopLeft().y;

m_rectDirect[6]=CRect(m_rect.left-m_nLineDot,pos-m_nLineDot,

m_rect.left+m_nLineDot,pos+m_nLineDot);

m_rectDirect[7]=CRect(m_rect.right-m_nLineDot,pos-m_nLineDot,

m_rect.right+m_nLineDot,pos+m_nLineDot);

m_rectDirect[8]=CRect(m_rect.left,m_rect.top-m_nLineDot,

m_rect.right,m_rect.top+m_nLineDot);

m_rectDirect[9]=CRect(m_rect.left-m_nLineDot,m_rect.top,

m_rect.left+m_nLineDot,m_rect.bottom);

m_rectDirect[10]=CRect(m_rect.right-m_nLineDot,m_rect.top,

m_rect.right+m_nLineDot,m_rect.bottom);

m_rectDirect[11]=CRect(m_rect.left,m_rect.bottom-m_nLineDot,

m_rect.right,m_rect.bottom+m_nLineDot);

}

// 추적 상자 영역을 설정한다.

void CMyTracker::SetRect(CRect rect)

{

m_rect=rect;

m_rectTrack=rect;

}

CMyTracker 이용 예제 ExTrackRect

예제 ExTrackRect는 CMyTracker 클래스를 이용한 간단한 예제입니다. 본 항목은 중고급자를 위한 항목이므로 간단하게 프로그램을 제작하는 방법을 순서적으로 알려드리겠습니다. ExTrackRect 프로젝트는 SDI 형태입니다.

쥱 CMyTracker 형의 변수 및 프로세스 변수 설정

ExTrackRect에 필요한 멤버 변수를 View 클래스에 다음과 같이 설정합니다.

CMyTracker m_rectTrack;// 추적 상자

CRect m_rect;// 추적 상자에 의해서 설정된 영역 저장 변수

int m_nProcess;// 그리기 항목에서 항목이 설정되었을 때 값이 저장되는 변수

쥲 View 클래스의 OnInitialUpdate() 함수에 각 멤버 변수를 초기화

짾 항목에서 설정한 멤버 변수들을 OnInitialUpdate() 함수에서 초기화합니다.

void CExTrackRectView::OnInitialUpdate()

{

CView::OnInitialUpdate();

m_rectTrack = CMyTracker(this,2,5);

m_rect=CRect(50,50,200,200);

m_rectTrack.SetRect(m_rect);

}

쥳 OnDraw 함수에 추적 상자 Draw 함수 내장

OnDraw 함수에 추적 상자 멤버 함수 Draw를 설정합니다.

void CExTrackRectView::OnDraw(CDC* pDC)

{

CExTrackRectDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

m_rectTrack.Draw(pDC);// 추적 상자 Draw 함수

}

쥵 좌측 마우스 버튼 눌림과 마우스 이동시 추적 상자 함수 호출

좌측 마우스 버튼이 눌려졌을 때와 마우스 이동시에는 단순하게 추적 상자의 함수를 호출합니다.

void CExTrackRectView::OnLButtonDown(UINT nFlags, CPoint point)

{

m_rectTrack.OnLButtonDown(nFlags,point);

CView::OnLButtonDown(nFlags, point);

}

void CExTrackRectView::OnMouseMove(UINT nFlags, CPoint point)

{

m_rectTrack.OnMouseMove(nFlags,point);

CView::OnMouseMove(nFlags, point);

}

쥶  좌측 마우스 버튼을 놓았을 때

좌측 마우스 버튼을 놓았을 때는 다음과 같습니다.

void CExTrackRectView::OnLButtonUp(UINT nFlags, CPoint point)

{

// 추적 상자의 OnLButtonUP 호출

m_rectTrack.OnLButtonUp(nFlags,point);

// 프로세서 0으로 설정

m_nProcess=0;

// 추적 상자 영역을 m_rect에 저장

m_rect=m_rectTrack.m_rectTrack;

// 프로세서 설정

m_rectTrack.SetProcess(m_nProcess);

// 화면 재출력

Invalidate();

CView::OnLButtonUp(nFlags, point);

}

쥷 메뉴 항목에서 상자 그리기를 선택했을 때

본 예제는 메뉴 항목에 상자 그리기라는 항목을 두었습니다. 이 항목을 클릭하여 프로세스의 번호를 1번으로 설정하고 이 값을 추적 상자에 넣습니다.

void CExTrackRectView::OnDrawBox()

{

m_nProcess=1;

m_rectTrack.SetProcess(m_nProcess);

}

ExTrackRect의 예제 소스는 생략하겠습니다. 제공되는 CD를 참조하기 바랍니다.

본 항목은 본 장의 마지막 부분으로 화면을 캡처하는 방법에 대해 설명합니다. 보통 PaintShop Pro에서 Captrue 메뉴 항목의 Start 항목을 클릭하면 자신의 윈도는 아이콘화되고 좌측 마우스 버튼이나 특정키를 누르면 화면이 캡처되는 것을 보았을 것입니다. 본 항목에서는 이런 캡처를 어떻게 받는가를 설명합니다. 본 장 또한 고급자에 해당하는 항목입니다.

화면 캡처의 일반적인 방법

화면을 캡처하고자 할 때 가장 기본적인 방법은 해당 윈도의 DC를 얻고 메모리 DC를 만든 다음 이 DC에 비트맵을 설정하고, 이 메모리 DC에 캡처하고자 하는 윈도의 DC 화면을 BitBlt 함수를 이용하여 출력하면 됩니다.

다음은 스크린 DC를 얻어서 화면을 출력하는 예입니다.

CDC ScreenDC;

ScreenDC.CreateDC(“DISPLAY”, NULL, NULL, NULL);

CDC memDC;

// 스크린 DC와 호환되는 DC를 만든다.

memDC.CreateCompatibleDC(&ScreenDC);

// 스크린 DC와 호환되는 비트맵을 만든다.

m_pBitmap.CreateCompatibleBitmap(&ScreenDC,

m_rect.BottomRight().x-m_rect.TopLeft().x,

m_rect.BottomRight().y-m_rect.TopLeft().y);

// 비트맵을 메모리 DC에 설정한다.

memDC.SelectObject(&m_pBitmap);

// 스크린 DC의 화면을 메모리 DC에 설정한다.

memDC.BitBlt(0,0,

m_rect.BottomRight().x-m_rect.TopLeft().x,

m_rect.BottomRight().y-m_rect.TopLeft().y,

&ScreenDC,

m_rect.TopLeft().x,m_rect.TopLeft().y,SRCCOPY) ;

위와 같이 하면 m_pBitmap에 스크린 DC의 그림이 설정됩니다. 아주 간단합니다.

자신의 윈도를 아이콘화시키는 메시지 훅킹

메시지 훅킹의 방법은 전 장의 마지막 부분에 자세히 설명하였습니다. PaintShop Pro 같은 프로그램의 메시지 훅킹 방법은 시스템과 스레드를 모두 가로챌 수 있는 WH_MOSUE 같은 메시지 훅킹 플러그를 사용했습니다. 이때는 dll을 이용하여 메시지를 가로채야 합니다. 이 방법은 PART 2에서 다시 설명합니다. 본 항목에서는 메시지 훅킹을 WH_JOURNALRECORD 방법으로 하였습니다. 이 방법을 이용하면 애플리케이션 프로그램에서 메시지를 가로챌 수 있습니다. 캡처 루틴을 선택하면 WH_JOURNALRECORD 플러그를 이용하여 메시지 훅킹을 시작하고 자신의 프로그램이 아이콘화되면 됩니다. 이 내용은 다음과 같습니다.

// 자신의 윈도를 아이콘화한다.

AfxGetMainWnd()->ShowWindow(SW_SHOWMINIMIZED);

// 메시지 훅킹 시작

hHook = SetWindowsHookEx(WH_JOURNALRECORD,

HookMouseProc,GetModuleHandle(NULL), 0);

훅킹 프로시저 HookMouseProc 함수는 다음과 같습니다.

HHOOK hHook=NULL;

int mode;

POINT point;

LRESULT CALLBACK HookMouseProc(int nCode, WPARAM wParam, LPARAM lParam)

{

LRESULT lResult = 1;

MSG msg;

static POINT point;

if (nCode == HC_ACTION)

{

// 우측 마우스 버튼을 놓으면

if(((PEVENTMSG) lParam)->message == WM_RBUTTONUP )

{

// 마우스를 현재 위치로 이동

SetCursorPos(point.x,point.y);

return lResult;

}

// 캡처 시작일 때

if(mode==1)

{

// 우측 마우스 버튼을 클릭하면

if(((PEVENTMSG) lParam)->message ==

WM_RBUTTONDOWN )

{

// mode를 2로 설정하고

mode=2;

// 현재 마우스 위치를 얻은 다음

GetCursorPos(&point);

// 마우스를 화면 밖으로 이동시킨다.

SetCursorPos(-100,-100);

return lResult;

}

}

else if(mode==2)

{// 우측 마우스 버튼을 클릭한 상태에서 좌측 마우스 번튼을

// 클릭하면

if(((PEVENTMSG) lParam)->message ==

WM_LBUTTONDOWN  )

{

// view의 CaptureDo 함수를 호출한다.

pView->CaptureDo();

// 모드를 3으로 설정

mode=3;

return lResult;

}

}

else if(mode==3)

{

// mode가 3인 상태에서 좌측 마우스 버튼을 클릭하면

if(((PEVENTMSG) lParam)->message ==

WM_LBUTTONDOWN  )

{

// 화면 캡처를 종료한다.

pView->CaptureEnd();

// 메시지 훅킹을 종료한다.

UnhookWindowsHookEx(hHook);

hHook = NULL;

return lResult;

}

}

}

return CallNextHookEx(hHook, nCode, wParam, lParam);;

}

자신의 윈도가 아이콘화되고 마우스 이벤트를 훅킹하고 있다가(이때 mode를 1로 설정합니다.) 우측 마우스 버튼을 클릭하면 현재 마우스 위치를 저장하고 마우스를 화면 밖으로 이동시킵니다. 이렇게 하는 이유는 우측 마우스 버튼이 떼어졌을 때 액션이 발생되는 프로그램들이 많기 때문에 우측 마우스 버튼을 떼었을 때는 액션이 발생되지 않도록 하기 위해서입니다. 이렇게 한 후에 우측 마우스 버튼을 놓고 다시 마우스 위치를 우측 마우스 버튼을 누른 위치로 이동시키고 mode를 2로 설정합니다. mode가 2인 상태에서 다시 좌측 마우스 버튼을 클릭하면 현재부터 화면 영역이 설정되기 때문에 이때부터 스크린 영역에 XOR 모드로 영역 상자를 그려 영역을 설정합니다. 이때 mode를 3으로 설정합니다. mode가 3인 상태에서 마우즈 좌측 버튼을 클릭하면 영역 설정이 종료되는 것이므로 영역 설정이 종료되면 해당 영역이 캡처됩니다. 그리고 자신의 윈도는 다시 보통 형태로 되돌리면 됩니다.

화면 캡처 예제 ExCapture

ExCapture는 화면 캡처 항목을 클릭하면 자신의 윈도가 아이콘화되고 우측 마우스 버튼을 누르고 다시 좌측 마우스 버튼을 누른 상태에서 영역을 설정한 다음 다시 좌측 마우스 버튼을 클릭하면 해당 영역이 캡처되어 화면에 나타나는 예제입니다. 이 예제의 전체 구성도는 그림 1-3-35와 같습니다.

그림 1-3-35의 ① 항목과 같이 ExCapture 프로그램에서 화면 캡처를 선택하면 자신의 윈도는 아이콘화되고 우측 마우스 버튼을 클릭할 때까지 기다리는 메시지 훅킹 함수를 실행합니다. 이때 수행되는 함수가 OnCapture()입니다. 이런 상태에서 우측 마우스 버튼을 클릭하고 캡처할 영역 시작점에서 좌측 마우스 버튼을 클릭하고 마우스를 이동시키면 그림 1-3-35의 ③ 항목처럼 좌측 마우스 버튼이 눌려진 위치부터 마우스 이동 영역까지 상자가 설정됩니다. 이 상태에서 다시 좌측 마우스 버튼을 클릭하면 설정한 영역이 캡처되고 ExCapture 프로그램은 다시 활성화되어 View 화면에 현재 캡처한 그림이 나타나게 됩니다.

예제 ExCapture 소스

ExCaputrue는 View 클래스에서만 내용을 기록하였습니다. 본 항목은 고급자에게 한한 내용이기 때문에 자세한 프로그래밍 방법을 생략하였습니다. 프로그램 소스는

다음과 같습니다.

// ExCaptureView.h : interface of the CExCaptureView class

//

//////////////////////////////////////////////////////////////////////////

#if !defined(AFX_EXCAPTUREVIEW_H__70F5690F_225D_11D2_94E9_006097ADB880__INCLUDED_)

#define AFX_EXCAPTUREVIEW_H__70F5690F_225D_11D2_94E9_006097ADB880__INCLUDED_

#if _MSC_VER >= 1000

#pragma once

#endif // _MSC_VER >= 1000

class CExCaptureView : public CView

{

protected: // create from serialization only

CExCaptureView();

DECLARE_DYNCREATE(CExCaptureView)

// Attributes

public:

CExCaptureDoc* GetDocument();

// 영역 시작점

POINT start;

// 영역 끝점

POINT end;

// XOR 형태로 마우스 영역 그리는 변수

CRect m_oldrect;

CRect m_rect;

// 비트맵 클래스

CBitmap m_pBitmap;

// 캡처 플러그

int m_nCapture;

// 메모리 DC

CDC memDC;

// Operations

public:

// Overrides

// ClassWizard generated virtual function overrides

// {{AFX_VIRTUAL(CExCaptureView)

public:

virtual void OnDraw(CDC* pDC);  // overridden to draw this view

virtual BOOL PreCreateWindow(CREATESTRUCT& cs);

protected:

virtual BOOL OnPreparePrinting(CPrintInfo* pInfo);

virtual void OnBeginPrinting(CDC* pDC, CPrintInfo* pInfo);

virtual void OnEndPrinting(CDC* pDC, CPrintInfo* pInfo);

// }}AFX_VIRTUAL

// Implementation

public:

void CaptureEnd();

void CaptureDo();

virtual ~CExCaptureView();

#ifdef _DEBUG

virtual void AssertValid() const;

virtual void Dump(CDumpContext& dc) const;

#endif

protected:

// Generated message map functions

protected:

// {{AFX_MSG(CExCaptureView)

afx_msg void OnLButtonDown(UINT nFlags, CPoint point);

afx_msg void OnCapture();

afx_msg void OnTimer(UINT nIDEvent);

// }}AFX_MSG

DECLARE_MESSAGE_MAP()

};

#ifndef _DEBUG  // debug version in ExCaptureView.cpp

inline CExCaptureDoc* CExCaptureView::GetDocument()

{ return (CExCaptureDoc*)m_pDocument; }

#endif

//////////////////////////////////////////////////////////////////////////

// {{AFX_INSERT_LOCATION}}

// Microsoft Developer Studio will insert additional declarations

// immediately before the previous line.

#endif // !defined(AFX_EXCAPTUREVIEW_H__70F5690F_225D_11D2_94E9_006097ADB880__INCLUDED_)

// ExCaptureView.cpp : implementation of the CExCaptureView class

//

#include “stdafx.h”

#include “ExCapture.h”

#include <winuser.h>

#include “ExCaptureDoc.h”

#include “ExCaptureView.h”

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

//////////////////////////////////////////////////////////////////////////// CExCaptureView

IMPLEMENT_DYNCREATE(CExCaptureView, CView)

BEGIN_MESSAGE_MAP(CExCaptureView, CView)

// {{AFX_MSG_MAP(CExCaptureView)

ON_WM_LBUTTONDOWN()

ON_COMMAND(ID_CAPTURE, OnCapture)

ON_WM_TIMER()

// }}AFX_MSG_MAP

// Standard printing commands

ON_COMMAND(ID_FILE_PRINT, CView::OnFilePrint)

ON_COMMAND(ID_FILE_PRINT_DIRECT, CView::OnFilePrint)

ON_COMMAND(ID_FILE_PRINT_PREVIEW, CView::OnFilePrintPreview)

END_MESSAGE_MAP()

//////////////////////////////////////////////////////////////////////////// CExCaptureView construction/destruction

CExCaptureView::CExCaptureView()

{

// TODO: add construction code here

m_nCapture=0;

}

CExCaptureView::~CExCaptureView()

{

}

BOOL CExCaptureView::PreCreateWindow(CREATESTRUCT& cs)

{

// TODO: Modify the Window class or styles here by modifying

//  the CREATESTRUCT cs

return CView::PreCreateWindow(cs);

}

//////////////////////////////////////////////////////////////////////////// CExCaptureView drawing

void CExCaptureView::OnDraw(CDC* pDC)

{

CExCaptureDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

// 화면 캡처가 되면

if(m_nCapture)

{

CDC cdc;

// m_pBitmap을 화면에 출력한다.

cdc.CreateCompatibleDC(pDC);

cdc.SelectObject(&m_pBitmap);

pDC->BitBlt(0,0,

m_rect.BottomRight().x-m_rect.TopLeft().x,

m_rect.BottomRight().y-m_rect.TopLeft().y,

&cdc,0,0,SRCCOPY);

}

}

//////////////////////////////////////////////////////////////////////////// CExCaptureView printing

BOOL CExCaptureView::OnPreparePrinting(CPrintInfo* pInfo)

{

// default preparation

return DoPreparePrinting(pInfo);

}

void CExCaptureView::OnBeginPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)

{

// TODO: add extra initialization before printing

}

void CExCaptureView::OnEndPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)

{

// TODO: add cleanup after printing

}

//////////////////////////////////////////////////////////////////////////// CExCaptureView diagnostics

#ifdef _DEBUG

void CExCaptureView::AssertValid() const

{

CView::AssertValid();

}

void CExCaptureView::Dump(CDumpContext& dc) const

{

CView::Dump(dc);

}

CExCaptureDoc* CExCaptureView::GetDocument() // non-debug version is inline

{

ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CExCaptureDoc)));

return (CExCaptureDoc*)m_pDocument;

}

#endif // _DEBUG

//////////////////////////////////////////////////////////////////////////// CExCaptureView message handlers

void CExCaptureView::OnLButtonDown(UINT nFlags, CPoint point)

{

// TODO: Add your message handler code here and/or call default

CView::OnLButtonDown(nFlags, point);

}

HHOOK hHook=NULL;

int mode;

CExCaptureView *pView;

POINT point;

LRESULT CALLBACK HookMouseProc(int nCode, WPARAM wParam, LPARAM lParam)

{

LRESULT lResult = 1;

static POINT point;

if (nCode == HC_ACTION)

{

// 우측 마우스 버튼을 놓으면

if(((PEVENTMSG) lParam)->message == WM_RBUTTONUP )

{

// 마우스를 현재 위치로 이동

SetCursorPos(point.x,point.y);

return lResult;

}

// 캡처 시작일 때

if(mode==1)

{

// 우측 마우스 버튼을 클릭하면

if(((PEVENTMSG) lParam)->message == WM_RBUTTONDOWN	)

// mode를 2로 설정하고

mode=2;

// 현재 마우스 위치를 얻은 다음

GetCursorPos(&point);

// 마우스를 화면 밖으로 이동시킨다.

SetCursorPos(-100,-100);

return lResult;

}

}

else if(mode==2)

{// 우측 마우스 버튼을 클릭한 상태에서 좌측 마우스 버튼을

// 클릭하면

if(((PEVENTMSG) lParam)->message == WM_LBUTTONDOWN  )

{

// view의 CaptureDo 함수를 호출한다.

pView->CaptureDo();

// 모드를 3으로 설정

mode=3;

return lResult;

}

}

else if(mode==3)

{

// mode가 3인 상태에서 좌측 마우스 버튼을 클릭하면

if(((PEVENTMSG) lParam)->message == WM_LBUTTONDOWN  )

{

// 화면 캡처를 종료한다.

pView->CaptureEnd();

// 메시지 훅킹을 종료한다.

UnhookWindowsHookEx(hHook);

hHook = NULL;

return lResult;

}

}

}

return CallNextHookEx(hHook, nCode, wParam, lParam);;

}

// 캡처 시작 메뉴 선택

void CExCaptureView::OnCapture()

{

// View 클래스를 전역변수 포인터로 넘겨줌

pView=this;

// 자신의 윈도는 아이콘화된다.

AfxGetMainWnd()->ShowWindow(SW_SHOWMINIMIZED);

// 메시지 훅킹 시작

hHook = SetWindowsHookEx(WH_JOURNALRECORD,

HookMouseProc,GetModuleHandle(NULL), 0);

mode=1;

// 이미 캡처한 비트맵이 있으면 지운다.

if(m_nCapture)

m_pBitmap.DeleteObject();

// 캡처 시작

void CExCaptureView::CaptureDo()

{

// 현재 위치를 start에 저장

GetCursorPos(&start);

m_oldrect=CRect(start.x,start.y,start.x,start.y);

// 타이머 발동

SetTimer(1,50,NULL);

}

void CExCaptureView::OnTimer(UINT nIDEvent)

{

POINT point;

CDC ScreenDC;

CPen pen,*oldpen;

// 현재 마우스 위치를 얻고

GetCursorPos(&point);

// 이전 영역을 XOR 형태를 이용하여 지우고

// 현재 다시 영역 설정

m_rect=CRect(start.x,start.y,point.x,point.y);

// 스크린 DC를 얻는다.

ScreenDC.CreateDC(“DISPLAY”, NULL, NULL, NULL);

pen.CreatePen(PS_SOLID,3,RGB(127,127,127));

// 널 브러시를 사용하면 브러시가 없음

ScreenDC.SelectStockObject(NULL_BRUSH);

oldpen=ScreenDC.SelectObject(&pen);

ScreenDC.SetROP2(R2_XORPEN);

ScreenDC.Rectangle(m_oldrect);

ScreenDC.Rectangle(m_rect);

ScreenDC.SelectObject(oldpen);

m_oldrect=m_rect;

CView::OnTimer(nIDEvent);

// 캡처 종료

void CExCaptureView::CaptureEnd()

{

KillTimer(1);

POINT point;

CDC ScreenDC;

CPen pen,*oldpen;

GetCursorPos(&point);

m_rect=CRect(start.x,start.y,point.x,point.y);

// 화면 영역에서 이전에 그려진 상자 지우기

ScreenDC.CreateDC(“DISPLAY”, NULL, NULL, NULL);

pen.CreatePen(PS_SOLID,3,RGB(127,127,127));

ScreenDC.SelectStockObject(NULL_BRUSH);

oldpen=ScreenDC.SelectObject(&pen);

ScreenDC.SetROP2(R2_XORPEN);

ScreenDC.Rectangle(m_oldrect);

ScreenDC.SelectObject(oldpen);

m_oldrect=m_rect;

CDC memDC;

// 스크린 DC와 호환되는 DC를 만든다.

memDC.CreateCompatibleDC(&ScreenDC);

// 스크린 DC와 호환되는 비트맵을 만든다.

m_pBitmap.CreateCompatibleBitmap(&ScreenDC,

m_rect.BottomRight().x-m_rect.TopLeft().x,

m_rect.BottomRight().y-m_rect.TopLeft().y);

// 비트맵을 메모리 DC에 설정한다.

memDC.SelectObject(&m_pBitmap);

// 스크린 DC의 화면을 메모리 DC에 설정한다.

memDC.BitBlt(0,0,

m_rect.BottomRight().x-m_rect.TopLeft().x,

m_rect.BottomRight().y-m_rect.TopLeft().y,

&ScreenDC,

m_rect.TopLeft().x,m_rect.TopLeft().y,SRCCOPY) ;

m_nCapture=1;

// 자신의 윈도를 활성화한다.

AfxGetMainWnd()->ShowWindow(SW_SHOWNORMAL);