[퍼옴]MFC03

WindowsPrograming : 2007.03.11 09:26

나올것입니다.


          (그림 12) 메뉴 속성 수정하는 대화상자.

이대화상자에서 Language가 US로 되어 있는 것을 Korean으로 바꿉니다. 이렇게 바꾸지 않으면 메뉴에 한글을 입력하면 글자가 깨져서 나오게 됩니다. 다시 그림 10과 같은 화면에서 IDR_MAINFRAME를 더블 클릭하면 메인 화면에 본 프로젝트에서 사용하는 메뉴가 보일것입니다. 그림 13는 메뉴가 출력된 형태입니다.


       (그림 13)메뉴 수정 화면

프로젝트를 만들 때 File로 설정되어 있던 첫 번째 팝업메뉴를 “그리기” 로바꾸고 이 “그리기”의 첫 번째 메뉴항목을 “박스 그리기” 로 설정합니다. 이렇게 설정하는 방법은 첫 번째 메뉴항목이 설정되었을 때 Alt+Enter키를 누르면 그림 12에서 보는것처럼 대화상자가 출력됩니다. 이대화상자의 Caption에 박스그리그를 하여주시고 ID에서는 ID_DRAWBOX라고 입력을 하는것입니다. 이대화상자에 입력하는 ID는 Caption은 작성자 마음대로 할수 있습니다. 박스그리기를 “네모 그리기”로 할수도 있고 ID_DARWBOX를 IDD_BOXDRAW등등으로 바꿀수 있습니다. 이렇게 바꾼후에 박스그리기에 그림 12처럼 빗금 박스가 그려진 상황에서 ClassWizard를 실행시킵니다.(Ctrl+W키나 View메뉴의 ClassWizard항목) 클래스위저드가 실행되면 그림 14과 같은 화면이 나타납니다.

                 (그림 14) 메뉴항목과 연결된 함수 만들기

클래스위저드의 Class name를 CMainFrame에서 CMExDrawView항목으로 전환한다음 설정된 ID(ID_DRAWBOX)항목을 선택하고 Messages의 COMMAND항목을 클릭하여 OnDrawBox함수를 만듭니다. 이렇게 만든후에 EditCode를 하면 다음과 같은 소스가 나옵니다.


void CMExDrawView::OnDrawbox()

{

       // TODO: Add your command handler code here

}


이곳에 박스를 그리는 내용을 기록하면 됩니다. OnDraw함수에서의 그리기 출력대신에 GetDC를 사용하여 DC를 얻고 이곳에 그리는 것을 해보겠습니다.

OnDrawBox함수안에 다음과 같이 코딩을 합니다.


void CMExDrawView::OnDrawbox()

{

       // TODO: Add your command handler code here

       CDC *pDC=GetDC();//DC를 얻고

       pDC->Rectangle(CRect(0,0,100,100));//박스를 그린다음

       ReleaseDC(pDC);//DC해제

}


GetDC를 이용하여 DC를 pDC로 얻은다음 Rectangle함수로 화면 상단 0,0,100,100의 위치에 박스를 그리고 DC를 해제 하는 것입니다.

위와 같이 OnDrawbox를 수정한후에 컴파일하고 실행을 시킵니다. 그리기 메뉴항목에서 박스그리기를 선택하면 그림 15과 같은 화면이 출력됩니다.



       (그림 15) 박스그리기 선택화면

위와 같은 상태에서 타이틀바 우측에 있는 축소 버튼을 클릭하여 아이콘화 하였다가 다시 일반화면으로 전환시키면 박스가 사라집니다. 그이유는 GetDC()를 하여 얻는 DC는 일시적으로 화면에 출력할뿐이지 영구적으로 남아 있지 않기 때문입니다.

CPen 설정

MExDraw에서 메뉴의 박스그리기를 선택하여 박스를 그릴때는 기본적으로 설정되어 있는 검정색 펜을 사용하였습니다. 이 펜을 바꾸어 보겠습니다. 펜을 하나 만들때는 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 : 펜의 스타일을 정의한 포인터


예를 들어 직선 형태로 펜을 만들고자 한다면 다음과 같이 할수 있습니다.

       

        CPen cPen,*oldPen;

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


위의 예는 파랑색으로 2도트의 굵기로 직선 펜을 만드는 방법입니다.

펜을 만들고 이것을 DC에 등록하고 선을 그리고 다시 이전 펜을 등록하는 방법은 CFont와 같습니다. 즉 SelectObject로 등록하고 그리기가 끝나면 이전펜으로 SelectObject한다음 DeleteObject하는것입니다.

이제 OnDrawbox함수를 박스를 파란색펜을 써서 그리는 것으로 수정해 보겠습니다. 수정된 내용은 다음과 같습니다.


void CMExDrawView::OnDrawbox()

{

       // TODO: Add your command handler code here

       CPen cPen,*oldPen;

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

       CDC *pDC=GetDC();//DC를 얻고

       oldPen=pDC->SelectObject(&cPen);

       pDC->Rectangle(CRect(0,0,100,100));//박스를 그린다음

       pDC->SelectObject(oldPen);

       cPen.DeleteObject();

       ReleaseDC(pDC);//DC해제

}

위와 같이 수정한후 컴파일 하고 실행하면 파란색 펜으로 설정된 박스가 나타납니다.


CBrush

CBrush 는 면을 칠하는 클래스입니다. 브러쉬를 만드는 함수중 대표적인 함수가 CBrush의 맴버함수인 CreateSolidBrush입니다.  CreateSolidBrush는 다음과 같습니다.

BOOL CreateSolidBrush( COLORREF crColor );


CreateSolidBrush는 단일색으로 그대로 칠하는 브러쉬를 말합니다. 이외에 패턴형태를 가지고 있는 CreateHatchBrush와 로그 브러쉬를 사용하는 CreateBrushIndirect가 있습니다.

예를 들어서 노랑색 브러쉬를 만들고자 하면 다음과 같이 할수 있습니다.


        CBrush cBrush;

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


MExDraw의 OnDrawbox 함수에 브러쉬를 사용하는 내용을 추가 해 보겠습니다. 브러쉬 또한 SelectObject를 이용하여 설정하고 사용후 이전 브러쉬를 SelectObject로 설정하고 DeleteObject로 브러쉬를 해제합니다. 수정된 OnDrawbox는 다음과 같습니다.


void CMExDrawView::OnDrawbox()

{

       // TODO: Add your command handler code here

       CPen cPen,*oldPen;

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

       CBrush cBrush,*oldBrush;

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

       CDC *pDC=GetDC();//DC를 얻고

       oldPen=pDC->SelectObject(&cPen);

       oldBrush=pDC->SelectObject(&cBrush);

       pDC->Rectangle(CRect(0,0,100,100));//박스를 그린다음

       pDC->SelectObject(oldBrush);

       cBrush.DeleteObject();

       pDC->SelectObject(oldPen);

       cPen.DeleteObject();

       ReleaseDC(pDC);//DC해제

}


프로그램을 컴파일하고 실행하면 그림 16와 같이 외곽선이 청색이고 칠한색이 노랑색인 박스가 화면에 출력될것입니다.



           (그림 16) 브러쉬를 설정한 MExDraw


라인그리기

라인을 그릴때는 두가지 함수를 사용합니다. 이함수들 또한 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(0,0);



모가 둥근 박스 그리기

모가 둥근 박스는 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의 출력형태는  그림 17 같습니다.

         (그림 17) Arc,Chord,Pie 출력 형태


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 : 좌표의 수



베지어 곡선

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

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

        (그림 18)베지어 곡선의 형태


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


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

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

nCount: 배열의 수


다양한 형태의 도형 그리기 예제

지금까지 배운 라인,라운드박스,다각형,다각형칠하기,베지어 곡선을 출력하는 예제를 MExDraw를 이용하여 만들어 보겠습니다. MExDrawView.c에 있는 OnDraw함수에 다음과 같이 코딩합니다.


void CMExDrawView::OnDraw(CDC* pDC)

{

       CMExDrawDoc* pDoc = GetDocument();

       ASSERT_VALID(pDoc);

       // TODO: add draw code for native data here

       //Polyline와 PolyBezier에 사용할 POINT배열

        POINT pline[6]={50,133,146,99,246,133,247,212,58,216,50,133};

        //다각형 칠하기에 사용할 POINT배열

        POINT pgon[4]={308,120,440,118,380,212,308,120};

        RECT rect;

        CBrush cBrush,*oldBrush;

        //RoundRect에 사용할 모서리 둥근원의 x,y지름

        POINT round={50,50};

        //브러쉬를 노랑색 브러쉬로 만든다

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

        //클라이언트 영역을 얻는다.

        GetClientRect(&rect);

        //라운드 박스를 그린다.

        pDC->RoundRect(&rect,round);

        //윈을 그린다.

     pDC->Ellipse(rect.left+30,rect.top+30,rect.right-30,rect.bottom-30);

        //0,0좌표에서 화면 전체에 대각선을 그린다.

        pDC->MoveTo(0,0);

        pDC->LineTo(rect.right,rect.bottom);

        //다각형 외곽선을 그린다.

        pDC->Polyline(pline,6);

        //베지어 곡선을 그린다.

        pDC->PolyBezier(pline,4);

        //브러쉬를 설정하고

        oldBrush=pDC->SelectObject(&cBrush);

        //내부를 칠하는 다각형을 그린다.

        pDC->Polygon(pgon,4);

        //이전 브러쉬를 설정하고

        pDC->SelectObject(oldBrush);

        //브러쉬를 삭제한다.

        cBrush.DeleteObject();


}


위의 소스중에서 GetClientRect 함수는 현재 클라이언트 영역을 얻는 함수입니다. 인자로 넘겨준 rect에 클라이언트 영역이 left,top,right,bottom으로 설정됩니다. MM_TEXT모드 (즉 일반적인 화면모드)에서는 left,top는 0,0이됩니다.

화면 좌표를 얻어서 라운드 박스와 화면전체의 대각선 그리고 화면에서 좌,우측,상,하 에서 30씩 떨어진 위치에 원을 그립니다.대각선은 pline와 pgon두개의 배열은 다각형과 베지어 곡선을 그리고자 할때 사용하였습니다. 브러쉬는 polygon을 표시할때 색이 칠해지는 것을 보여주기 위해서 설정한것입니다. 이렇게 설정하지 않으면 칠하기는 흰색 그리고 외곽선은 검정색으로 설정되기 때문에 polyline와 polygon의 차이를 알수가 없기 때문입니다.

위와 같이 코딩을 한후 컴파일하고 실행하면 그림 19과 같은 화면이 출력됩니다.

               (그림 19) MExDraw 출력 결과



다양한 형태의 도형 그리기 예제


윈도우 시스템에서 화면 출력이라는 의미는 단순하게 모니터만을 의미하지 않습니다. 출력에 관련된 모든 디바이스에 화면을 출력할수 있습니다. 화면을 출력하고자 할 경우 일반적으로 출력 디바이스는 모니터와 프린터로 볼수가 있을것입니다. 앞에서도 설명하였듯이 프린터에 문자나 그래픽을 출력하고자 할경우에도 DC를 이용합니다. 보통 쉽게 말해서 프린터 DC를 받아서 그곳에 출력한다라고 합니다. 여기에서 출력 디바이스에 따라서 출력하는 내용의 맵핑에 문제가 됩니다. 모니터로 출력할 경우 모니터는 가로가 길고 세로가 가로보다 작습니다. 또한 모니터는 출력하는 형태에서 기본 단위가 점입니다. 그러나 프린터에서는 가로가 작고 세로가 깁니다. 또한 기본단위를 점으로 하기에는 문제가 있습니다.(실지로 프린터에 출력할경우에도 하부에서 보면 점이 기본입니다)

윈도우 프로그래밍에서 프린터에 그래픽을 출력할때는 cm단위로 출력하는 것이 매우 편리합니다. 모니터는 모니터의 크기에 따라서 보는 형태를 자유롭게 변화 시킬수 있으나 프린터로 출력을 하게 되면 용지로 나오기 변화가 불가능합니다. 또한 글자크기를 정할경우에도 cm나 mm또는 인치로 사용하는 것이 편리합니다.

윈도우 시스템은 출력을 할 때 어떤 형태로 출력할것인가의 맵핑모드를 설정할수 있는 함수를 제공합니다. 즉 점단위나 또는 mm나 인치형태 또는 사용자정의의 맵핑모드를 할수 있는 다양한 방법을 제공합니다.


지원되는 맵핑 모드

윈도우에서 지원하는 맵핑 모드는 표6와 같습니다.

맵핑 모드

  기본 단위

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)값 설정

  선택적

선택적



                       (표6) 맵핑모드


MM_TEXT는 모니터에 출력하는 기본 모드를 의미합니다. 실제로는 디바이스 기본 모드라고 생각하시면 됩니다. 우리가 지금까지 사용한 모드가 MM_TEXT입니다. MM_LOMETRIC는 기본단위가 0.1 mm이며  이좌표는 위로 올라갈수록 증가됩니다.

MM_HIMETRIC,MM_LOENGLIST,MM_HIENGLISH,MM_TWIPS 등은 일반적인 미터법 맵핑모드로 사용됩니다.  이런 맵핑모드를 사용할 때 수행되는 함수가 CDC의 맴버함수인 SetMapMode입니다.


int SetMapMode(

int fnMapMode // 맵핑모드

);


로직좌표와 디바이스 좌표

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 // 좌표수

);


Viewport와 Window

디바이스 좌표를 가지고 있는 어떤 좌표 평면의 영역이을 Viewport라고 하고 로직좌표를 가지고 있는 어떤 좌표 평면을 Window라고 합니다. 이런 개념을 쓰는 이유는 좌표 평면의 정보를 수정하고자 할경우가 있기 때문입니다.

                   (그림 20) (0,0)좌표 이동

그림 20의 (a)처럼  MM_TEXT에서는 좌측 상단 이 (0,0)의 초기점입니다. 이때 때로는 화면 영역의 중앙이 (0,0)으로 하고자 할경우가 있습니다 그림 20 의 (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 로 맵핑모드를 설정하고 로직좌표와 디바이스좌표 중앙에 원점을 설정하고자 한다면 다음과 같은 형식이 될것입니다.

1)클라이언트 전체 영역을 얻습니다.

  GetClientRect(&rect)

2)rect좌표는 dc와 상관되는 좌표가 아닙니다. 이좌표는 디바이스 좌표입니다.

  디바이스좌표를 이용하여 원점을 이동하는 함수가 SetViewportOrg이므로

  pDC->SetViewportOrg(rect.right/2,rect.bottom/2);

3)이제 rect를 로직좌표로 전환을 하고 로직좌표계의 원점을 이동합니다.

    pDC->DPtoLP((PPOINT)&rect,2);

    pDC->SetWindowOrg(rec,right/2,rect.bottom/2);

좌표를 리턴하는 함수들중에 디바이스 좌표만 리턴하는 경우의 함수들이 있습니다.

위의 예에서 보듯이 GetClientRect함수는 dc에서 얻는 함수가 아니기 때문에 디바이스 좌표를 리턴합니다. 이외에 GetDeviceCaps라는 함수는 DC를 이용하지만 로직좌표계가 아닌 디바이스좌표를 리턴합니다. 프로그래밍 할 경우 특정 맵핑모드를 선언하였을 경우 함수가 디바이스좌표를 리턴하는가? 또는 로직좌표를 리턴하는가에 대해서 주의를 해주어야 합니다.

좌표평면 정보를 수정할 경우 어떤 좌표평면을 수정할것인가를 알려주어야 하기 때문에 디바이스 좌표평면을 보통 Viewport라고하며 로직좌표 평면을 Window라고 합니다.


다양한 그래픽 기능

본편의 설명은 2편의 2장과 중복이 됩니다. 2편2장에서는 HDC함수를 이용한 다양한 그래픽 기능에 대해서 설명하였습니다. 윈도우에서 사용하는 다양한 그래픽기능은 2편 2장을 참조하시기 바랍니다. 보는 방법은 다음과 같습니다. 예를 들어서 Path기능의 함수가 BeginPath(hdc); 형식이라면 이것은 바로 pDC->BeginPath함수가 됩니다. 즉 HDC가 인자로 들어간 함수는 그인자만 뺀 함수이면서 CDC맴버 함수가 되는 것입니다.  이런식으로 2편 2장을 다시 복습하는 생각으로 각함수의 긴응을 MFC에서 CDC함수로 전환하여 제작해 보시기 바랍니다.

본장에서 그래도 꼭알아야 하는 부분은 중복하여 설명하였습니다. 나머지 부분은  2장을 공부하셔서 확장하는 방식으로 해나가시기 바랍니다.


다양한 형태의 그리기 예제 MExDraw2

메뉴 만들기

지금까지는 도형을 그리는 방법에 대해서 설명하였습니다. 이제 메뉴에 설정된 형태대로 그림을 그리는 예제를 만들어 보겠습니다. 이예제는 MExDraw2로 합니다. 프로젝트를 만들때 SDI형태입니다.

프로젝트를 만든다음 메뉴를 수정합니다. 메뉴를 수정하는 것은 MExDraw에 자세히 설명하였습니다. 그리고 한글이 출력이 안될까봐 IDR_MAINFRAME를 English로 설정하라고 분명히 하였습니다. (한글 안된다고 지킴이 웹사이트로 50회 이상은 질문이 왔었습니다) 본예제 만들면서 이제는 한글 안된다는 질문 절대 하지 말아주시기 바랍니다. 메뉴 형태는 그림 21과 같습니다.

               (그림 21) 메뉴형태

각메뉴에 대한 ID는 표 7와 같습니다.

              (표 7) 메뉴와  ID리스트


       ID

메뉴명

ID_DRAWBOX

박스그리기

ID_DRAWLINE

라인 그리기

ID_DRAWARC

원그리기

ID_COLORWHILE

흰색

ID_COLORBLACK

검정색

ID_COLORRED

빨간색

ID_COLORBLUE

청색

ID_COLORGREEN

녹색

ID_COLORYELLOW

노랑색

도큐먼트 멤버  만들기

위와 같이 메뉴를 만든다음 이제 도큐먼트에 색을 저장하는 맴버변수와 그리기 스타일을 저장하는 맴버 변수를 설정합니다.

Project Workspace윈도우에서 ClassView를 선택합니다. 그림 22와 같은 윈도우가 나왔을때 CMExDraw2Doc를 선택합니다.


       (그림 22)ClassView에서 CMExDraw2Doc를 선택한 화면

그림 22처럼 CMExDraw2Doc가 반전되고 마우스커서가 CMExDraw2Doc에 위치된 상황에서 마우스 우측버튼을 클릭합니다. 이때 팝업메뉴가 나오는데 이 팝업메뉴가그림 23입니다.


그림 23)CMExDraw2Doc에서 우측버튼 클릭시 출력되는 팝업메뉴

위 메뉴에서 Add Member variable를 선택합니다. 이것을 선택하는 의미는 CMExDraw2Doc클래스에 맴버 변수를 설정한다는 의미입니다. Add Member variable를 선택한 화면은 그림 24와 같습니다.

         (그림 24) Add Member Variable화면

Variable Type는 형을 의미하며 Variable Declaration는 변수명을 이야기 합니다. Access에서 Public Protected Private는 클래스에 설정할때 어느 부분에 설정할것인가를 선택하는 것입니다.  그림 24와 같은 경우는 MExDraw2Doc클래스에 public맴버로 int m_nDrawStyle를 설정하라는 의미입니다.

위와 같이 설정하고 OK버튼을 클릭하면 MExDraw2Doc.h에는 다음과 같이 public에 설정되었을것입니다.


// Implementation

public:

       int m_nDrawStyle;

       :

메뉴

박스그리기

1

라인그리기

2

원그리기

3

이변수를 설정하는 이유는 메뉴에서 그리기 스타일을 설정하였을때 값을 넣기 위해서입니다. 예를 들어서 메뉴에서 박스그리기를 선택하면 1 등으로 값을 설정하기 위해서입니다. 표8은 m_nDrawStyle에 설정될 값 리스트입니다.

       (표8) 메뉴 선택시 m_nDrawStyle에 설정되는 값


같은 방법으로 MExDraw2에 다음과 같은 형을 설정합니다.

COLORREF m_nColor;

그림 25는 도큐먼트에 위와 같은 멤버를 설정하는 화면입니다.





       (그림 25) 도큐먼트에 m_nColor멤버 설정화면

m_nColor과 m_nDrawStyle맴버가 설정되었다면 이제 이맴버에 데이터를 넣는 맴버 함수를 설정하도록 하겠습니다. C++은 맴버변수는 보호하고 맴버변수에 접근하고자 한다면 그맴버변수에 접근하는 함수를 만드는것이 기본입니다. 따라서 도큐먼트에 설정된 두개의 맴버에 데이터를 얻는 Get계열과 데이터를 저장하는 Set계열함수를 만드는것이 좋습니다. 이함수는 다음과 같이 만듭니다.


void SetColor(COLORREF color); //칼라를 설정하는 함수

COLORREF GetColor(); //칼라값을 얻는 함수

void SetDrawStyle(int style);//그리기 스타일을 설정하는 함수

int GetDrawStyle();//그리기 스타일을 얻는 함수


위4개의 함수를 만드는 방법또한 ClassView에서 합니다. 그림 22처럼 MExDraw2Doc 에 마우스를 위치한후에 우측버튼을 클릭하면 그림 23과 같은 화면이 나옵니다. 이때 Add Member Function 항목을 클릭합니다. 이때 그림 26과 같은 화면이 출력됩니다.

          (그림 26)Add Member Function 화면

먼저 SetColor함수를 설정합니다. SetColor 를 그림 26처럼 설정하고 OK를 클릭하면 MExDraw2Doc.cpp안에 다음과 함수가 만들어지면서 커서가 위치하게 됩니다.


void CMExDraw2Doc::SetColor(COLORREF color)

{


}

물론 이때 이함수의 형은 MExDraw2Doc.h에 다음과 같이 정의되어 있습니다.

       

void SetColor(COLORREF color);


Add Member Function이나 Add Member variable 항목은 해당클래스에 함수와 변수를 자동적으로 설정해주는 항목입니다. 이 항목을 이용하지 않고 헤더와 소스에 기록하여도 됩니다.

이제 SetColor함수를 수정하겠습니다. 다음과 같이 수정을 합니다.


void CMExDraw2Doc::SetColor(COLORREF color)

{

       m_nColor=color;

}

위의 의미는 SetColor함수에 의해서 넘어온 칼라값인자는 전에 만든 맴버변수 m_nColor에 등록하자는 의미입니다. 이렇게 하여 도큐먼트에 있는 데이터에 값을 지정할때는 도큐먼트의 SetColor함수를 호출하는 방식을 이용하는 것입니다.

같은방법으로 GetColor,SetDrawStyle,GetDrawStyle를 설정합니다. 설정된 후에 MExDraw2.cpp 에 새로 설정된 함수는 다음과 같습니다.

COLORREF CMExDraw2Doc::GetColor()

{

       return m_nColor;

}


void CMExDraw2Doc::SetDrawStyle(int style)

{

       m_nDrawStyle=style;

}


int CMExDraw2Doc::GetDrawStyle()

{

       return m_nDrawStyle;

}


내용을 보시면 알수 있듯이 Get는 맴버 변수의 값을 리턴하는 것이고 Set은 맴버변수에 값을 대입하는 것입니다.

보통 맴버는 생성자에 초기화 하는것이 기본입니다. 그이유는 변수를 초기화 하지 않으면 쓰레기값으로 설정되기 때문에 자칫 잘못하여 에러를 발생할수 있기 때문입니다. MExDraw2에는 다음과 같은 생성자가 있을것입니다.


CMExDraw2Doc::CMExDraw2Doc()

{

       // TODO: add one-time construction code here

}

위와 같은 생성자에 m_nDrawStyle와 m_nColor값을 초기화 합니다. m_nDrawStyle는 0을 그리고 m_nColor는 검정색을 설정합니다.


CMExDraw2Doc::CMExDraw2Doc()

{

       // TODO: add one-time construction code here

       m_nColor=RGB(0,0,0);//검정색 설정

       m_nDrawStyle=0;//아무스타일도 아님

}


OnDraw함수에 현재 색선택 출력

이제 View의 OnDraw함수를 수정하겠습니다. MExDraw2View.cpp 파일안에 OnDraw함수로 이동합니다. 이동하는 방법은 클래스위저드를 이용하거나 또는 파일을 오픈하시면 됩니다. OnDraw함수는 다음과 같을 것입니다.


void CMExDraw2View::OnDraw(CDC* pDC)

{

       CMExDraw2Doc* pDoc = GetDocument();

       ASSERT_VALID(pDoc);


       // TODO: add draw code for native data here

}


위의 OnDraw함수에서 CMExDraw2Doc* pDoc = GetDocument(); 의미는 도큐먼트이 포인터를 받자는 의미입니다. 도규먼트와 View는 한쌍이라고 했습니다. 그래서 도큐먼트에 있는 데이터를 View에서 출력한다고 했습니다. 출력하는 부분의 핵심 위치가 OnDraw이므로 이곳에는 도큐먼트 클래스를 받는 함수를 미리 써준것입니다. 첫줄의 내용은 현재 View와 연결된 Document 클래스를 받자는 의미입니다. 이렇게 받으면 바로 현재 View와 연결된 CMExDraw2Doc클래스를 받는 것입니다.

우리가 도큐먼트 에 설정한 함수 SetColor를 호출하고자 한다면 pDoc->SetColor를 호출하면 되고 GetColor를 호출하고자 한다면 pDoc->GetColor를 호출하면 되는 것입니다. 이제 도큐먼트의 GetColor를 호출하여 현재어떤 색이 설정되어 있는 가를 출력해 보겠습니다.

소스 내용은 다음과 같습니다.


void CMExDraw2View::OnDraw(CDC* pDC)

{

       CMExDraw2Doc* pDoc = GetDocument();

       ASSERT_VALID(pDoc);

       // TODO: add draw code for native data here

       CString sColor,sText;

       if(pDoc->GetColor()==RGB(0,0,0))

               sColor="검정색";

       else if(pDoc->GetColor()==RGB(255,255,255))

               sColor="흰색";

       else if(pDoc->GetColor()==RGB(255,0,0))

               sColor="빨강색";

       else if(pDoc->GetColor()==RGB(0,255,0))

               sColor="녹색";

       else if(pDoc->GetColor()==RGB(0,0,255))

               sColor="청색";

       else

               sColor="노란색";

       sText.Format("현재 설정된색:%s",sColor);

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

       

}

우리가 설정한 도큐먼트의 GetColor함수를 받아서 즉 pDoc->GetColor()함수를 이용하여 이값에 따라서 각색의 정보를 sColor 에 넣고 sText 에 “현재 설정된색:[색명]”이 출력되도록 하였습니다.

위와 같이 하고 컴파일한후 실행시키면 그림 27과 같은 화면이 출력됩니다.


          (그림 27) OnDraw수정후 MExDraw2 출력 결과

도큐먼트 생성자에서 m_nColor를 검정색으로 설정하였기 때문에 현재 출력되는 값이 검정색이 되는 것입니다.

이제 색상 메뉴에서 각항목을 클릭할때 마다 색을 변화시켜 보겠습니다.

먼저 희색을 설정할때 수행되는 함수를 만들어 보겠습니다. 메뉴에 의해서 함수를 만드는 방법은 전장에서 설명을 하였습니다. 흰색 메뉴를 선택하였을때 View화면에서 실행하는 OnColorwhite() 를 만들기 바랍니다.(분명이 View입니다. CMExDraw2View입니다! 강의할때보면 CMainFrame에 만드시는 분들이 있습니다. ) 다음과 같은 함수가 생성될것입니다.


void CMExDraw2View::OnColorwhite()

{

       // TODO: Add your command handler code here 

}

다시 한번 설명드리지만 void CMExDraw2View인지 CMainFrame인지 확인해 보시기 바랍니다. 메뉴에서 클래스 위저드를 실행시키면 기본적인 클래스가 CMainFrame로 설정되어 있기 때문에 이것을 CMExDraw2View로 바꾸어 주어야 합니다.

위와 같이 설정된 함수에서 다음과 같이 수정을 합니다.

void CMExDraw2View::OnColorwhite()

{

       // TODO: Add your command handler code here 

       CMExDraw2Doc* pDoc = GetDocument();

       pDoc->SetColor(RGB(255,255,255));//흰색설정

                InvalidateRect(CRect(0,0,500,20),TRUE);

}

이 의미는 도큐먼트를 pDoc로 받아서 우리가 만든 도큐먼트 맴버함수이 SetColor함수를 에 색값을 넣어주는 것입니다. 이렇게 하면 도큐먼트 멤버의 m_nColor에 색값이 설정됩니다.InvalidateRect는 화면의 특정 영역을 다시 칠하라는 함수입니다.

이함수의 형은 다음과 같습니다.


void CWnd::InvalidateRect( LPCRECT lpRect, BOOL bErase = TRUE );

lpRect: 하면을 다시 출력할 좌표영역 CRect 클래스

bErase : TRUE이면 배경색부터 칠하고 FALSE이면 출력할 내용만 다시 칠함


InvalidateRect는 OnDraw함수를 다시 호출하는 함수이니다. 이함수는 CWnd의 멤버함수입니다. CView는 CWnd를 상속받았기 때문에 InvalidateRect를 사용할수 있는 것이지요. lpRect는 다시 출력하고자 하는 좌표가 설정됩니다. OnDraw함수를 호출하면서 특정 위치만 재출력하게 됩니다. 전체 영역이 다시 출력되는 것이 아닙니다. bErase는 배경색부터 칠할것인가를 설정하는 플러그립니다. TRUE를 설정하면 배경색부터 다시칠하게 됩니다. 위의 소스 내용은 화면의 (0,0,500,20) 영역을 다시 칠하라는 의미입니다. 쉽게 보면 글자가 출력되는 부분만 다시 칠하라는 뜻이 됩니다.

같은방법으로 녹색,검정색,청색,적색,노랑색을 설정합니다.

설정된 소스는 다음과 같습니다.


void CMExDraw2View::OnColorblack()

{

       // TODO: Add your command handler code here

       CMExDraw2Doc* pDoc = GetDocument();

       pDoc->SetColor(RGB(0,0,0));//검정색

       InvalidateRect(CRect(0,0,500,20),TRUE);

       

}


void CMExDraw2View::OnColorblue()

{

       // TODO: Add your command handler code here

       CMExDraw2Doc* pDoc = GetDocument();

       pDoc->SetColor(RGB(0,0,255));//청색

       InvalidateRect(CRect(0,0,500,20),TRUE);

}


void CMExDraw2View::OnColorgreen()

{

       // TODO: Add your command handler code here

       CMExDraw2Doc* pDoc = GetDocument();

       pDoc->SetColor(RGB(0,255,0));//녹색

       InvalidateRect(CRect(0,0,500,20),TRUE);

}



void CMExDraw2View::OnColoryellow()

{

       // TODO: Add your command handler code here

       CMExDraw2Doc* pDoc = GetDocument();

       pDoc->SetColor(RGB(255,255,0));//노랑색

       InvalidateRect(CRect(0,0,500,20),TRUE);

}


void CMExDraw2View::OnColorred()

{

       // TODO: Add your command handler code here

       CMExDraw2Doc* pDoc = GetDocument();

       pDoc->SetColor(RGB(255,0,0));//빨강색

       InvalidateRect(CRect(0,0,500,20),TRUE);   

}


이와 같이한후에 컴파일하고 실행하여 보십시오. 메뉴에 따라서 화면에 현재 설정된 색이 무엇인가가 출력이 될것입니다. 그림 28은 메뉴에서 녹색을 설정하였을때 출력되는 결과입니다.




           (그림 28)메뉴에서 녹색을 선택하였을때


OnDraw함수에 현재 그리기 스타일 출력

이제 OnDraw함수에 현재 설정되어 있는 그리기 스타일을 출력해 보겠습니다. OnDraw함수를 다음과 같이 고칩니다.


void CMExDraw2View::OnDraw(CDC* pDC)

{

       CMExDraw2Doc* pDoc = GetDocument();

       ASSERT_VALID(pDoc);

       // TODO: add draw code for native data here

       CString sColor,sText,sDraw;

       if(pDoc->GetColor()==RGB(0,0,0))

               sColor="검정색";

       else if(pDoc->GetColor()==RGB(255,255,255))

               sColor="흰색";

       else if(pDoc->GetColor()==RGB(255,0,0))

               sColor="빨강색";

       else if(pDoc->GetColor()==RGB(0,255,0))

               sColor="녹색";

       else if(pDoc->GetColor()==RGB(0,0,255))

               sColor="청색";

       else

               sColor="노란색";

       if(pDoc->GetDrawStyle()==1)

               sDraw="박스그리기";

       else if(pDoc->GetDrawStyle()==2)

               sDraw="라인그리기";

       else if(pDoc->GetDrawStyle()==3)

               sDraw="원그리기";

       else

               sDraw="없음";

       sText.Format("그리기 스타일: %s 현재 설정된색:%s",sDraw,sColor);

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

       

}


수정된 내용은 도큐먼트에 설정한 함수 GetDrawStyle를 호출하여 현재 설정값을 알고 이설정된 값을 화면에 색상과 같이 출력하는 것입니다.

이제 메뉴에서 그리기 스타일을 선택할때마다 변경되는 함수를 만들어 보겠습니다. 만드는 방법은 메뉴에서 클래스 위저드를 이용합니다. 지금까지 한방법과 같습니다. 이렇게 만든 3개의 함수에 다음과 같이 수정을 합니다.


void CMExDraw2View::OnDrawbox()

{

       // TODO: Add your command handler code here

       CMExDraw2Doc* pDoc = GetDocument();

       pDoc->SetDrawStyle(1);//박스그리기

       InvalidateRect(CRect(0,0,500,20),TRUE);          

}


void CMExDraw2View::OnDrawarc()

{

       // TODO: Add your command handler code here

       CMExDraw2Doc* pDoc = GetDocument();

       pDoc->SetDrawStyle(3);//원그리기

       InvalidateRect(CRect(0,0,500,20),TRUE);                  

}


void CMExDraw2View::OnDrawline()

{

       // TODO: Add your command handler code here

       CMExDraw2Doc* pDoc = GetDocument();

       pDoc->SetDrawStyle(2);//라인그리기

       InvalidateRect(CRect(0,0,500,20),TRUE);                          

}


메뉴를 클릭하면 도큐먼트를 받고 해당되는 그리기 스타일 값을 SetDrawStyle함수를 이용하여 m_nDrawStyle에 설정하는 것입니다.

이제 컴파일하고 실행시키고 메뉴를 선택하면 선택된 항목대로 글자가 화면에 출력될것입니다. 그림 29는 색상은 청색으로 그리기 스타일은 라인으로 설정되었을때 출력되는 화면입니다.


          (그림 29) 청색, 라인그리기 선택시 출력화면


버튼 누르면서 이동하면 박스 그리기

이제 실제 도형을 그리는 작업으로 넘어가겠습니다. 좌측 마우스 버튼을 누른채로 마우스를 드래깅하면 화면에 이동하는 영역에 박스가 나오도록 해보겠습니다. 이방법은 다음과 같은 루틴을 따릅니다.


1.마우스 버튼을 누르면 누른 좌표(x,y)값을 박스영역에 (x,y,x,y)로 넣는다.

2.현재 눌려져있다는 표시로 검색 변수에 1를 넣는다.

3.마우스가 움직일때 검색변수가 1이면 이전 박스를 XOR로 지우고 다시 현재 박    스를 그린다.

4.마우스 버튼을 띄면 검색변수를 0으로 한다. 그리고 화면을 다시 출력한다.


좌측 마우스 버튼이 눌렸을때 현재 눌렸다는 것을 알려주면서 이때 마우스를 움직이면 움직인 영역에 박스를 그립니다. 박스를 그릴때 SetRop2함수를 이용하여 XOR 로 그리면 라인을 한번 그리고 그위에 다시그리면 라인이 지워집니다. SetRop2함수의 기능은 2편 2장을 참조하시기 바랍니다. 이렇게 하여 박스를 그리다가 버튼을 놓으면 화면을 다시출력하여 설정된 위치에 그림이 그려지는 것입니다.

이제 실제적인 프로그램을 작성해 보겠습니다.

Project Workspace윈도우에서 ClassView를 선택하고 마우스로 CMExDraw2View클래슬 선택합니다. 그리고 우측 버튼을 클릭하고 Add Member Variable를 선택하여 두개의 변수를 설정합니다.

       (그림 30) CMExDraw2View에서 Add Member Variable 선택화면

설정한 두개의 변수는 다음과 같습니다.


        CRect m_rect;

        BOOL m_bButFlag;


m_rect는 박스위치를 설정하는 변수이며 m_bButFlag는 버튼이 눌려졌을경우 TRUE 그리고 해제되었을경우 FALSE로 설정하는 항목입니다.

이제 MExDraw2View.cpp에서 CMExDraw2View의 생성자에 이 두변수의 값을 초기화 합니다. 초기화 한 내용은 다음과 같습니다.


CMExDraw2View::CMExDraw2View()

{

       // TODO: add construction code here

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

       m_bButFlag=FALSE;   

}


클래스위저드를 이용하여 CMExDraw2View에서 WM_LBUTTONDOWN 메시지가 설정되었을때 함수를 만듭니다. 이함수에 다음과 같이 코딩을 합니다.


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

{

       // TODO: Add your message handler code here and/or call default

       //좌표를 설정한다.

       m_rect.SetRect(point.x,point.y,point.x,point.y);

       //버튼이 눌려짐 설정

       m_bButFlag=TRUE;

       CView::OnLButtonDown(nFlags, point);

}


WM_LBUTTONDOWN에 의해서 만들어진 함수는 OnLButtonDown이며 point인자로 넘어오는 것이 현재 마우스가 눌려진 위치 좌표입니다. nFlags는 컨트롤 키나또는 Shift키가 눌려졌는가의 플러그입니다.MK_CONTROL 이면 Ctrl키가 눌려진것이고 MK_SHIFT 이면 Shift가 눌려진것입니다.

m_rect에 SetRect함수를 이용하여 마우스 좌표 (x,y)를 영역으로 지정하고 m_bButFlag=FALSE로 놓고 OnLButtonDown함수는 끝납니다.

이제 박스를 그릴 마우스 이동시의 함수를 만들어 보겠습니다. 마우스 이동시에는 WM_MOUSEMOVE메시지에 의한 함수입니다. 클래스 위저드를 이용하여 CMExDraw2View에서 WM_MOUSEMOVE 메시지가 발생될때의 함수를 만들고 다음과 같이 수정합니다.




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

{

       // TODO: Add your message handler code here and/or call default

       if(m_bButFlag)//마우스 버튼을 누르고 있는 상태이면

       {

               CDC *pDC=GetDC();

               CPen cPen,*oldPen;

               //펜을 만들고

               cPen.CreatePen(PS_SOLID,1,RGB(125,125,124));

               //XOR 모드로 화면 출력

               pDC->SetROP2(R2_XORPEN);

               //펜을 설정

               oldPen=pDC->SelectObject(&cPen);

               //박스 그리기

               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);

               m_rect.SetRect(m_rect.left,m_rect.top,point.x,point.y);

               //박스 그리기

               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);

               cPen.DeleteObject();

               ReleaseDC(pDC);


       }

       CView::OnMouseMove(nFlags, point);

}


화면에 그려줄 박스를 회색박스로 하기 위해서 cPen를 설정하여 주었습니다. 그리고 현재 설정된 m_rect영역에 라인으로 박스를 그려줍니다. 좌측 마우스 버튼을 클릭한 당시는 시작점과 끝점이 같기 때문에 아무것도 그려지지 않고 이동한만큼 right,bottom이 설정됨으로 이값에 의해 박스가 그려집니다. 그리고 다시 마우스가 이동되면 이전에 박스를 그린위치에 다시 박스를 또그리게 됩니다. 이때 출력연산이 XOR이기 때문에 이전에 그린 박스가 지워지게 됩니다. 그리고 다시 point로 받은 x,y를 ritht,bottom에 넣은 형태가 되는 것입니다.

이제 좌측 버튼을 놓았을때의 메시지 함수를 만듭니다. 이메시지는 WM_LBUTTONUP입니다. 다음은 WM_LUBTONUP에 의해서 만들어진 함수를 수정한 것입니다.


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

{

       // TODO: Add your message handler code here and/or call default

       m_bButFlag=FALSE;   

       Invalidate(TRUE);

       CView::OnLButtonUp(nFlags, point);

}

좌측 마우스 버튼을 놓으면 다시 m_bButFlag가 FALSE가 되고 이렇게 되면 마우스가 이동되어도 아무것도 그려지지 않습니다. 이상태에서 화면에 현재 설정한 스타일과 색으로 도형을 만들어 주기 위해서 Invalidate(TRUE);를 호출하였습니다.

Invalidate는 InvalidateRect와 같이 OnDraw함수를 호출합니다. 다만 이함수는 영역을 설정하는 것이 없이 전체 화면을 재출력하는 기능을 가지고 있습니다. 그릴 좌표를 설정하면 이전 그림은 삭제하고 현재 그림으로 대치시키기 위함입니다.

이와 같이 한후에 프로그램을 컴파일하고 화면에서 마우스를 클릭하고 드래깅하면 박스가 형성될것입니다. 그림 31은 버튼 드래깅 기능을 넣은 상태에서 출력결과입니다.

             (그림 31) 마우스 버튼 드래깅시 박스 출력 화면


OnDraw에 선택영역에 도형 그리기

이제 마지막 작업으로 선택된 영역에 도형을 그려 보겠습니다. OnDraw함수를 다음과 같이 수정합니다.

void CMExDraw2View::OnDraw(CDC* pDC)

{

       CMExDraw2Doc* pDoc = GetDocument();

       ASSERT_VALID(pDoc);

       // TODO: add draw code for native data here

       CString sColor,sText,sDraw;

       if(pDoc->GetColor()==RGB(0,0,0))

               sColor="검정색";

       else if(pDoc->GetColor()==RGB(255,255,255))

               sColor="흰색";

       else if(pDoc->GetColor()==RGB(255,0,0))

               sColor="빨강색";

       else if(pDoc->GetColor()==RGB(0,255,0))

               sColor="녹색";

       else if(pDoc->GetColor()==RGB(0,0,255))

               sColor="청색";

       else

               sColor="노란색";

       if(pDoc->GetDrawStyle()==1)

               sDraw="박스그리기";

       else if(pDoc->GetDrawStyle()==2)

               sDraw="라인그리기";

       else if(pDoc->GetDrawStyle()==3)

               sDraw="원그리기";

       else

               sDraw="없음";

       sText.Format("그리기 스타일: %s 현재 설정된색:%s",sDraw,sColor);

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

       CBrush cBrush,*oldBrush;

       CPen cPen,*oldPen;

       //설정된 색으로 브러쉬와 펜을 만든다.

       cBrush.CreateSolidBrush(pDoc->GetColor());

       cPen.CreatePen(PS_SOLID,1,pDoc->GetColor());

       //브러쉬와 펜을 설정

       oldBrush=pDC->SelectObject(&cBrush);

       oldPen=pDC->SelectObject(&cPen);

       //그리기 스타일 에 따라 그린다.

       switch(pDoc->GetDrawStyle())

       {

       case 1://박스

                       pDC->Rectangle(&m_rect);

                       break;

       case 2://라인

                       pDC->MoveTo(m_rect.left,m_rect.top);

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

                       break;

       case 3://원

                       pDC->Ellipse(&m_rect);

                       break;

       }

       pDC->SelectObject(oldBrush);

       pDC->SelectObject(oldPen);

       cPen.DeleteObject();

       cBrush.DeleteObject();

    }


수정한 코드의 의미는 도큐먼트에 설정된 색과 그리기 스타일을 이용하여 현재 설정된 내용을 출력하는 것입니다. 이출력은 좌측 버튼을 놓앗을때  Invalidate함수에 의해서 실행됩니다. 처음에는 m_rect가 (0,0,0,0)이며 그리기 스타일이 없기 때문에 아무것도 그리지 않습니다. 그러나 마우스를 이용하여 영역을 설정하고 그리기 스타일을 정의하면 정의 한 형태대로 그려집니다. 그림 32는 완결된 MExDraw2입니다.

               (그림 32)완결된 MExDraw2 출력 결과


데이터는 도큐먼트에... 왠만하면 코딩으로..

필자가 처음쓴책 Visual Bible 4.x와 5.x에서는 본책처럼 툴을 이용항 방법을 제시하지 않았습니다. 툴을 이용하는 방법이란 Project Workspace에서 ClassView를 선택하고 우측 마우스 버튼을 클릭하여 함수와 변수를 설정하는 방법들을 말합니다. 사실 본책도 그렇게 하고 싶었지만.. 많은 분들의 의견을 수렴하여 자동화 방법을 이용했습니다. 그러나 부탁드립니다. 왜만하면 파일을 열고 직접 변수와 함수를 만들어 주시기 바랍니다. 처음에는 자동적으로 처리되는게 편리하겠지만 가면갈수록 한계성을 느끼게 될것입니다. Bible 5.x와 4.x에는 소스가 많지만 본책에서는 소스가 매우 적을 것입니다. Bible 5.x에서의 의도는 소스보고 직접 코딩을 해보라는 의미가 컸었습니다. 손으로 직접 코딩을 해보면서 느끼기를 바랬기 때문입니다. 본책은 초보자를 위한 부분이 크기 때문에 방법을 바꾸었으나 그래도  자동화보다는 직접 코딩을 하는것이 분명히 실력이 많이 늘것이라는 확신은 변함이 없습니다.

또하나는 MFC에서 제공하는 필수적인 클래스 즉 CWnd를 상속받은 클래스나 메뉴 그리고 도큐먼트를 제외한  CRect나 CString 또는 CList같은 구조체를 왠만하면 안썻으면 합니다. 이구조체를 쓰지 않아도 프로그램은 가능합니다. 다만 프로그램 코딩 자체가 조금 지저분합니다.(필자는 지저분하다고 생각하지 않습니다).

왜만하면 라이브러리 쓰지 말고 직접 만들어 써달라는 것입니다. 5.x에서 이런 마음으로 많은 MFC라이브러리를 배제했었습니다.

필자는 도스시절에서 부터 프로그램을 해왔습니다. 도스시절에 처음에는 힘들었으나 한글라이브러리를 직접 만들어 써왔으며 데이터 베이스 또한 만들어서 썻습니다. 그때 당시 한글라이브러리를 이용하고 데이터베이스 라이브러리를 이용하여 도스에서 프로그램을 그래도 잘작성했다는 사람들이 윈도우에 들어서서 많이 탈락을 했습니다. 조금 다르게 비유를 들겠습니다. 공룡은 쥬라기에 지구를 지배했던 동물이었습니다. 그러나 갑자기 빙하기가 닥치자 공룡은 지구상에서 자취를 감추었습니다. 허나 포유류는 아직까지 살아남아서 이지구상에 존재합니다. MFC가 언제 까지 존재 할는지 아무도 모릅니다. 또한 윈도우가 언제까지 OS로 남아 있을지는 아무도 모릅니다. OS가 사라진다고 라이브러리가 사라진다고 프로그래머가 사라진다면.. 그리고 그런 프로그래머가 여러분이라면...

직접 만들어 쓰는 버릇은 이런 문제를 해결하는 지름길이 될것입니다.

데이터는 도큐먼트에 해달라는 부탁을 드립니다. 데이터처리와 화면 출력을 분리해야 한다는것은 당연합니다. 예를 들어 보겠습니다. 3D 그래픽을 출력하는 멋진 프로그램을 만들었습니다. 구매자가 이런말을 한다면요. “참좋은데 OS를 X-Window로 하지 않으면 구매할수가 없네요” 만일 화면 출력과 데이터 처리가 분리되어 있지 않다면 프로그램을 다시 다 작성해야 할지도 모릅니다. 그러나 데이터 처리만 따로 설정했다면요..출력부분만 갱신하면 됩니다. 바로 이런 의미때문에 화면 출력과 데이터 처리가 분리되어야 한다는 것입니다.

별것도 아닌 이야기 일는지 몰라도 10년간 프로그램을 한사람으로써 진짜 눈물젓은 커피를 마시며 느낀이야기 이기에 이렇게 말씀드립니다.


숙제 있습니다!

본장을 넘어가기전에 다음과 같은 프로그램을 만들어 보시기 바랍니다.

1.현재 한개의 그림만 출력하는것을 최대 20개의 그림을 출력하는 형태롤 구축

2.메뉴에 이전요소 다음요소 라는 항목이 있어서 이전 항목으로 넘어가면 이전것이 수정되고 다음항목으로 넘어가면 다음항목 요소가 수정되도록

3.배열 자체게 Linked List로 연결되어 next와 prev기능이 되도록

4.문자키를 입력받는 WM_CHAR메시지에 의한 함수를 만들고 문자입력도 출력시킬것.

숙제를 하는것은 여러분의 결정입니다.!

순간의 선택이 평생을 좌우합니다!

'WindowsPrograming' 카테고리의 다른 글

MFC Consol 간단한 계산. @_@;;;; 너무 쉬운거.  (0) 2007.03.28
[퍼옴]MFC03  (0) 2007.03.11
[퍼옴]MFC04  (0) 2007.03.11
[퍼옴]MFC05  (0) 2007.03.11
Posted by Real_G