4.3 그래픽프로그래밍 이절에서다룰예제응용프로그램은사각형이나타원을그리거나마우스이동위치를추적하여연결함으로써자유곡선을그리는등의그래픽작업을다루는프로그램이다. 어떤도형을그릴것인지는라디오버튼으로정한다. 타원을그릴때는매번타원모양, 테두리선의굵기와색상, 채우기패턴등을변경하는데, 이렇게변경된그래픽속성이다른도형을그릴때에도적용된다. 즉, 타원을그릴때는그래픽속성이매번변경되지만, 다른도형을그릴때는마지막타원을그렸을때의속성이계속적용된다. 사각형이나타원의위치는마우스클릭에의해정해진다. 4.3.1 그래픽관련 MFC 클래스 그래픽프로그래밍을하기위해서는두부류의 MFC 클래스들을이용하게되는데, 바로디바이스콘텍스트관련클래스들과그리기도구관련클래스들이다. 디바이스콘텍스트및관련클래스 디바이스콘텍스트 (device context) 란모니터화면, 프린터, 플로터, 통신용모뎀등의그래픽출력장치에관한정보, 그리기속성관련정보등을갖고있는윈도우자료구조체 ( 객체 ) 를말한다. MFC가제공하는디바이스콘텍스트를사용함으로써윈도우응용프로그램에서는실제출력장치에상관없이동일한코드를사용할수있다. 그리기속성이란선의모양, 굵기, - 128 -
색상, 채우기색상이나패턴, 폰트등등의다양한요소가포함된다. 그리기속성을포함하는 DC의옵션들은기본값들이정해져있으며, 이들은변경될수있다. 디바이스콘텍스트를그리기기능함수 (CDC 클래스의멤버함수 ) 나타내는기본클래스는선 MoveTo, LineTo CDC이다. CDC 클래스는사각형 Rectangle, FillRect, FrameRect, Draw3dRect 또한선분그리기, 사각형타원 Ellipse 파이 Pie 그리기, 타원그리기등의호 Arc 다양한도형을그릴수있다각형 Polygon 는멤버함수들을갖고있베지어곡선 PolyDraw, PolyBezier, PolyBezierTo 영역의경계선 FrameRgn 으며, 이러한함수들은디바이스콘텍스트에저장된그리기속성정보를참고한다. 그래픽작업은디바이스콘텍스트객체를생성한후, 이에대해그림그리기관련함수를호출함으로써이루어진다. CDC는 200개정도의멤버함수를갖는방대한클래스인데, 그래픽출력영역에따라특화된 CClientDC, CWindowDC, CPaintDC, CMetaFileDC 등 4개의자식클래스들이정의되어있다. 이들중 CClientDC, CWindowDC, CPaintDC 등은그리기대상영역이창과연관되어있으며, 따라서모니터화면의경우에만적용될수있다. CMetaFileDC는그림을지금그리는대신그래픽명령을저장해두었다가나중에재생하기위한메타파일을나타낸다. 창이외의영역을대상으로하거나프린터등비-화면출력장치를사용할경우에는 CDC 객체가사용된다. 물론 CDC 객체는창을출력대상으로하는경우에도적용될수있다. CClientDC 클래스 : 지정된윈도우의클라이언트영역에출력한다. this가현재윈도우를가리키는포인터라고하면, 아래코드는현재윈도우의클라이언트영역안에사각형을그리게된다. CClientDC dc(this); dc.rectangle(10,20,80,90); // (10,20) 은좌측상단 (80,90) 은우측하단 CWindowDC 클래스 : 타이틀바, 메뉴바, 스크롤바등과같이클라이언트영역이아닌곳도출력영역에포함시키고자할때사용한다. CPaintDC 클래스 : 창의일부또는전체가가려졌다가다시나타나는등의이유로윈도우의클라이언트의내용이다시그려질필요가있을때, WM_PAINT라는메시지가전달된다. 이메시지를처리하는함수가 CWnd::OnPaint() 인데, 이함수에전달되는디바이스콘텍스트가바로 CPaintDC 타입의객체를가리키는포인터이다. CPaintDC 타입의객체가가리키는부분은클라이언트영역중가려졌다가다시나타나는부분으로국한된다. CMetaFileDC 클래스 : 원하는그림의생성을위해재생될수있는그래픽출력명령 sequence를담고있는메타파일을나타낸다. - 129 -
그리기도구관련클래스 DC 옵션들은여러개의카테고리로그룹화되어있으며, 이러한 DC 옵션카테고리를나타내는클래스들이있다. 이러한클래스들의인스턴스를 GDI(Graphics Device Interface) 객체들이라고하며, 이들을나타내기위한클래스에는다음과같은것들이있다. CPen CBrush CFont CPalette CBitmap CRgn 선이나영역경계선의색상, 두께, 패턴폐곡선영역내부를채울때사용되는픽셀의색, 패턴문자출력시글꼴모양, 크기등색상수가제한되는상황에서실제로출력될색상세트비트맵그림그림그릴영역을나타내는타원또는다각형설정 이러한클래스들은 DC 상에그림을그리는도구들을표현하고있으며, DC 옵션설정에사용된다. DC 옵션들을설정하기위해서는해당옵션을위한 GDI 객체를생성하고관련옵션값들을설정한후특정 DC에연결하는절차가필요하다. 아래의코드는 CPen 객체를이용하여선그리기옵션변경을해보이고있다. CPen pen; pen.createpen(ps_solid, 3, RGB(255, 0, 0)); CClientDC dc(this); CPen *poldpen = (CPen *) dc.selectobject(&pen); dc.rectangle(10, 10, 100, 100); dc.selectobject(poldpen); CPen::CreatePen() 함수의첫번째파라미터는 PS_SOLID 선모양을나타내는데, 이를위해오른쪽그림과 PS_DASH 같은상수들이정의되어있다. 두번째파라미터 PS_DOT 는선의굵기를나타내는데, 선의굵기가 2 이상 PS_DASHDOT 이면첫번째파라미터값에상관없이선모양은 PS_DASHDOTDOT PS_SOLID가된다. 세번째파라미터는선의색상을나타내는데, RGB(r, g, b) 마크로를사용하 PS_NULL 여각기 0-255 사이의 red, green, blue 색상 PS_INSIDEFRAME 값을조합하여표현한다. CDC::SelectObject() 함수는 GDI 객체에대한포인터를파라미터로취하며, GDI 객체가설정하는그리기옵션을 DC에반영한다. 이함수는변경전옵션을나타내는 GDI 객체에대한포인터를리턴하는데, 이를이용하여원래옵션으로복원시킬수있다. - 130 -
그리기함수사용예 CPaintDC dc(this); CBrush bluebrush; bluebrush.createsolidbrush(rgb(0, 0, 255)); CBrush redbrush; redbrush.createsolidbrush(rgb(255, 0, 0)); dc.moveto(0, 10); dc.lineto(30, 30); dc.rectangle(40, 10, 80, 30); dc.fillrect(crect(90, 10, 130, 30), &bluebrush); dc.framerect(crect(140, 10, 180, 30), &bluebrush); dc.draw3drect(190, 10, 40, 20, RGB(255, 0, 0), RGB(0, 255, 0)); dc.ellipse(240, 10, 280, 30); dc.pie(290, 10, 330, 30, 300, 20, 320, 25); dc.arc(290, 10, 330, 30, 320, 15, 300, 15); CPoint pts[7] = { CPoint(20, 80), CPoint(60, 160), CPoint(140, 200), CPoint(160, 160), CPoint(160, 120), CPoint(140, 100), CPoint(60, 80)}; dc.polygon(pts, 7); CPen redpen; redpen.createpen(ps_solid, 3, RGB(255, 0, 0)); dc.selectobject(&redpen); dc.polybezier(pts, 7); CRgn rgn1, rgn2; CPoint pts2[6] = { CPoint(320, 60), CPoint(320, 100), CPoint(350, 120), CPoint(380, 100), CPoint(380, 60), CPoint(350, 50)}; rgn1.createellipticrgn(200, 40, 300, 100); rgn2.createpolygonrgn(pts2, 6, ALTERNATE); dc.framergn(&rgn1, &bluebrush, 10, 2); dc.framergn(&rgn2, &bluebrush, 10, 2); dc.selectcliprgn(&rgn1, RGN_AND); dc.fillrect(crect(100, 50, 400, 80), &redbrush); - 131 -
글꼴과텍스트출력 MFC에서는텍스트출력도그래픽작업으로취급된다. 즉, 관련 GDI 객체로 CFont를사용하여 CDC 객체에대해텍스트출력함수 TextOut() 이나 DrawText() 등을사용한다. LOGFONT 는논리적글꼴의표현을위한구조체이며, 다음과같이정의되어있다. typedef struct taglogfontw { 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; 글꼴의세부사항을설정하기위해사용되는 CFont::CreateFont() 함수의파라미터들은 LOGFONT 구조체의각필드에대응된다. BOOL CreateFont(int nheight, int nwidth, int nescapement, int norientation, int nweight, BYTE bitalic, BYTE bunderline, BYTE cstrikeout, BYTE ncharset, BYTE noutprecision, BYTE nclipprecision, BYTE nquality, BYTE npitchandfamily, LPCTSTR lpszfacename); 폰트의생성과설정을위한코드예는다음과같다. CFont newfont, *poldfont; newfont.createfont(100, 0, 0, 0, 400, FW_NORMAL, FALSE, FALSE, DEFAULT_CHARSET, OUT_CHARACTER_PRECIS, CLIP_CHARACTER_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH FF_DONTCARE, _T( Times New Romans )); poldfont = (CFont *)pdc->selectobject(&newfont); - 132 -
텍스트출력함수들의프로토타입은다음과같다. BOOL TextOut(int x, int y, const CString& str); int DrawText(const CString& str, LPRECT lprect, UINT nformat); 간단한텍스트출력코드예는다음과같다. dc.textout() 함수는 DC 상에서좌표 (40, 60) 에서시작하여스트링 "Hello" 를출력한다. dc.drawtext() 함수는현재윈도우의클라이언트영역의정중앙에한줄로스트링 "Hello" 를출력한다. dc.textout(40, 60, Hello ); CRect rect; GetClientRect(&rect); // 클라이언트영역의크기를얻어냄 dc.drawtext("hello", &rect, DT_SINGLELINE DT_CENTER DT_VCENTER); 텍스트색상및배경색상변경은다음과같은코드에의해이루어질수있다. dc.settextcolor(rgb(255, 0, 0)); dc.setbkcolor(rgb(0, 0, 255)); 텍스트배경모드변경은다음과같은코드에의해이루어질수있다. dc.setbkmode(transparent); dc.setbkmode(opaque); // 투명 // 불투명 : 배경색상적용 4.3.2 예제프로그램의작성 이절의예제프로그램을위한프로젝트생성단계는이전의예제들과같다. 시각적설계단계도다이얼로그박스안에하나의그룹을형성하는 3개의라디오버튼을만들고이들의캡션은각기 "Square", "Ellipse", "Free Drawing" 등으로정한다. 다이얼로그클래스멤버변수는다음과같다. m_nshape: 라디오버튼그룹에연계된변수 m_pointold: Free Drawing의경우마우스의이전위치를나타내는 CPoint 타입의 private 변수 m_bmousedown: 마우스왼쪽버튼이상태를나타내는 bool 타입의 private 변수 - 133 -
이프로그램에서처리기함수를요하는이벤트는마우스왼쪽버튼누르기, 마우스왼쪽버튼풀어주기, 마우스이동등이며, 이들은각기다이얼로그창에대한 WM_LBUTTONDOWN, WM_LBUTTONUP, WM_MOUSEMOVE 등의메시지에해당된다. 클래스마법사를부르고 [ 메시지 ] 탭을선택한다음상단의 < 클래스이름 > 아래의이름이다이얼로그클래스 CGraphicsDlg임을확인한다. 클래스마법사화면이나타내는것처럼 < 메시지 > 아래의리스트박스에서 WM_LBUTTONDOWN, WM_LBUTTONUP, WM_MOUSEMOVE 등의메시지들에대해처리기함수를추가한다. CGraphicsDlg::OnInitDialog() 안에다음문장을추가하여처음에는마우스왼쪽버튼이눌려져있지않음을나타낸다. m_bmousedown = FALSE; - 134 -
CGraphicsDlg 클래스의 OnLButtonDown(), OnLButtonUp(), OnMouseMove() 함수들은아래와같이작성한다. 이들에앞서전역변수 pdc를다음과같이선언하는데, 이변수는 OnLButtonDown() 함수에서설정한 DC를 OnMouseMove() 함수에서도공유할수있도록하기위해선언된다. OnLButtonDown() 함수는두개의파라미터를취한다. 첫번째파라미터는마우스버튼이나키보드 (ctrl, shift) 상태를나타내며이예제에서는사용되지않고있다. 두번째파라미터는마우스의현재위치를나타내는좌표이다. 이함수에서는먼저현재다이얼로그창의클라이언트영역을출력대상으로하는 DC 객체 dc를 static으로생성하여이객체의생성을불필요하게반복하지않도록한다. 이객체에대한포인터를전역변수 pdc에저장해둔다. UpdateData() 함수는라디오버튼선택을 m_nshape 변수에반영한다. 그런다음 m_nshape의값에따라필요한처리를한다. m_nshape가 0일경우 CDC::Rectangle() 함수를사용하여마우스가가리키는위치를좌측상단으로하고한변의길이가 20인정사각형을그린다. m_nshape가 1일경우에는 CDC::Ellipse() 함수를사용하여타원을그린다. 타원의크기는난수를사용하여매번변경되는데, 타원외접사각형의한변의길이는 20-99 사이이다. 외곽선과채우기속성은각기 CPen 객체와 CBrush 객체를사용하여무작위로변경하여설정한다. 설정된속성들은변경될때까지이함수안에서사각형을그릴때와 OnMouseMove() 에서선분을그릴때적용된다. m_nshape가 2일경우에는마우스이동위치들을연결하게되는데, 이함수에서는시작위치가되는현재의좌표를 m_pointold에저장만하며, 그리기출력은하지않는다. 이함수의끝에서는 m_bmousedown의값을 TRUE로지정하여마우스왼쪽버튼이눌려져있음을나타낸다. OnLButtonUp() 함수는그리기동작의완료를위한작업을수행하는데, m_nshape의값을 -1 로하고 UpdateData(FALSE) 를수행함으로써라디오버튼선택을지우고, m_bmousedown의값을 FALSE로지정하여마우스왼쪽버튼이더이상눌려져있지않음을나타낸다. OnMouseMove() 함수는 Free Drawing의경우에만의미가있는데, 이전의마우스위치와현재마우스위치를연결하는선분을그린다. 선분을그리기위한조건은 m_nshape == 2(Free Drawing 선택 ) 와 m_bmousedown == TRUE( 마우스왼쪽버튼이눌려져있음 ) 두가지모두충족되어야한다. 선분을그리기위해사용하는함수는시작점으로이동하기위한 MoveTo() 와실제선분을그리는 LineTo() 이다. 선분을그린후에는현재의위치가 m_pointold에저장된다. - 135 -
CDC *pdc = 0; void CGraphicsDlg::OnLButtonDown(UINT nflags, CPoint point) { // TODO: Add your message handler code here and/or call default static CClientDC dc(this); pdc = &dc; UpdateData(); if (m_nshape == 0) { dc.rectangle(point.x, point.y, point.x + 20, point.y + 20); } else if (m_nshape == 1) { CPen pen; pen.createpen(ps_dot, 1 + rand()%5, RGB(rand()%256, rand()%256, rand()%256)); dc.selectobject(&pen); CBrush brush; int r = rand() % 7; int hatch_style =!(r - 1) * HS_BDIAGONAL +!(r - 2) * HS_CROSS +!(r - 3) * HS_DIAGCROSS +!(r - 4) * HS_FDIAGONAL +!(r - 5) * HS_HORIZONTAL +!(r - 6) * HS_VERTICAL; if (r == 0) brush.createsolidbrush(rgb(rand()%256, rand()%256, rand()%256)); else brush.createhatchbrush(hatch_style, RGB(rand()%256, rand()%256, rand()%256)); dc.selectobject (&brush); dc.ellipse(point.x, point.y, point.x + 20 + rand()%80, point.y + 20 + rand()%80); } else if (m_nshape == 2) { m_pointold = point; } m_bmousedown = TRUE; } CDialogEx::OnLButtonDown(nFlags, point); - 136 -
void CGraphicsDlg::OnLButtonUp(UINT nflags, CPoint point) { // TODO: Add your message handler code here and/or call default m_nshape = -1; m_bmousedown = FALSE; UpdateData(FALSE); } CDialogEx::OnLButtonUp(nFlags, point); void CGraphicsDlg::OnMouseMove(UINT nflags, CPoint point) { // TODO: Add your message handler code here and/or call default if (m_nshape == 2 && m_bmousedown) { pdc->moveto(m_pointold); pdc->lineto(point); m_pointold = point; } } CDialogEx::OnMouseMove(nFlags, point); 연습문제 1. 창 (window) 안의클라이언트영역에굵은적색원을그리기위해필요한문장들을나열하라. 원의크기와테두리선의굵기는임의로정하고, 원을그릴창객체를가리키는포인터는 p_win 이라고하자. 2. (1) void CGraphicsDlg::OnMouseMove(UINT nflags, CPoint point) 등의함수에서파라미터 nflags에대해조사하라. (2) 이절의예제프로그램에서멤버변수 m_bmousedown을사용하는대신 nflags를이용하는형태로프로그램을변경하라. - 137 -
5 장 SDI 응용프로그램 5.1 문서 / 뷰구조와 SDI 응용프로그램 문서 / 뷰구조 SDI 응용프로그램유형또는 MDI 응용프로그램유형이선택될경우응용프로그램마법사 (AppWizard) 가생성하는프로그램구조는문서 / 뷰 (Document/View) 구조를중심으로이루어진다. 이러한응용프로그램들은문서의전체또는일부를메모리에읽어들인후그내용을어떤형태로든사용자에게보여준다. 이렇게문서의내용을화면을통해사용자에게제시하는것을뷰라고한다. 응용프로그램들중에는하나의문서에대해한가지뷰만제공하는경우도있고여러가지뷰를제공하는경우도있다. 문서를취급하는응용프로그램들중에는한번에한개의문서만을다루는경우도있고동시에여러개의문서를열어둔상태에서작업을수행하는경우도있다. 여러개의문서를동시에다루는경우문서유형은한가지인경우도있고서로다른여러유형의문서들을취급하는경우도있다. 메모장의경우한번에한개의문서만취급하며다루는문서유형도텍스트파일로한정된경우이다. 또뷰의형태도한가지로정해져있다. 웹브라우저의경우일반뷰에서는 HTML 문서를해석한다음출력형식을조정하여사용자가보기좋게보여준다. 또대부분의웹브라우저들은 HTML 문서의소스를텍스트파일로보여주는뷰도제공한다. 또 MS Excel의경우테이블형태로작성된문서에대해히스토그램이나파이차트등여러가지뷰를제공하기도한다. MS 워드의경우입력파일이나문서저장형식은여러종류이다. 그러나 MS 워드프로그램에서작업중인문서형식은 MS 워드문서라는한가지유형이다. 반면에 Visual C++ 의경우, 프로그램소스를나타내는텍스트파일, 아이콘들을나타내기위한이미지파일, 클래스마법사를위한.clw 파일등다양한유형의파일들을동시에열어두고사용한다. 응용프로그램에서문서가나타내는측면은주로파일에저장하거나파일로부터읽어들이는등의외부저장매체와연관된측면인데비해, 뷰의경우는문서의내용을사용자에보여주는측면을담당하고있다. 이러한기능을분리하고있는가장중요한이유는하나의문서에대응되는뷰가둘이상인경우도있기때문이다. 이와같이문서와뷰로이루어지는프로그램구조를지원하기위한클래스로 MFC는 CDocument와 CView를제공하고있다. CDocument 클래스는문서를메모리상에서나타내기위한자료구조의표현과문서의저장및읽어들이기등을주로담당한다. ( 도큐먼트클래스안의자료구조가문서에대응되는파일의데이터전부를메모리안에갖고있을필요는없으며, 특히대용량파일의경우이는불가능하기도하다. 현재뷰가필요로하는문서부분만메모리안에갖고있을수도있고, 또는파일에접근하기위해필요한정보만갖고있을수도있다.) 반면 CView 클래스는문서의내용을화면상에서사용자에게보여주는일과문서에대한 - 138 -
사용자의편집행위를문서에반영하는일등을담당한다. 즉, 문서와사용자사이의인터페이스를담당하는것이뷰라고할수있으며, 이러한인터페이스는양방향으로이루어진다. SDI/MDI 응용프로그램유형을선택하면 AppWizard는 CDocument에서파생된클래스와 CView에서파생된클래스를만들어주며, 이러한파생클래스들에필요한멤버들을추가하거나편집하여응용프로그램을만들어나간다. 오른쪽그림은앞에서설명된문서클래스와뷰클래스의관계와이들클래스의기능을요약해보이고있다. 문서클래스는디스크에서읽어들이거나디스크에저장하는기능이필요할때는대체로 Serialize() 함수를사용하도록되어있다. 뷰클래스에서문서클래스의데이터에접근할경우에는잘정의된문서클래스의멤버함수를통해서접근하는것이바람직한방법이다. 뷰클래스는문서클래스의데이터를받아와서화면에보여주고사용자이벤트및시스템이벤트등을처리하기위한코드를포함한다. SDI 응용프로그램의형태 AppWizard가생성하는 SDI 응용프로그램의주실행창을나타내는객체는 CFrameWnd에서파생된클래스인스턴스이며, 기본외형은오른쪽그림과같이메뉴와툴바를포함한다. 문서내용을보여주는데사용되는부분은이창의클라이언트영역이며, 이는 CView에서파생된클래스의인스턴스이다. SDI 응용프로그램의경우에는뷰와문서가 1:1 대응관계에있으며, 문서는대체로디스크에저장되는파일에대응된다. - 139 -