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 -
5.2 SDI 응용프로그램예제 이절에서다룰예제응용프로그램은행단위의텍스트입력기능을갖춘 SDI 응용프로그램이다. 편집메뉴의 " 추가 (A) Ctrl+A" 메뉴항목을선택하면텍스트입력다이얼로그가나타나며, 입력된행은문서의마지막행으로추가된다. 뷰창에는텍스트행들앞에행번호를붙여표시하는데, 최대 5행까지만표시한다. 행추가기능은메뉴항목을통해서도사용할수있지만단축키 "Ctrl+A" 또는툴바의버튼을통해사용할수도있다. 이응용프로그램은문서의저장, 열기, 인쇄, 인쇄미리보기등의일반파일기능들도지원한다. 또상 / 하화살표키들을이용하여문서의앞쪽또는뒤쪽으로이동할수있다. 프로젝트생성단계 프로젝트이름으로는 "SDI" 를사용하기로한다. 응용프로그램유형은 < 단일문서 >, 프로젝트스타일은 <MFC 표준 > 을선택한다. 나머지대부분의옵션들은변경할필요는없다. 이예제에서는리소스언어로한국어를선택한다. [ 문서템플릿속성 ] 선택화면은문서를저장할때사용하는확장자와파일열기 / 저장다이얼로그나뷰창등에서문서유형을표시하는스트링들을지정하기위한것이다. 문서를취급하는응용프로그램들은흔히가장최근에사용된몇개의파일에대한목록을유지한다. [ 고급기능 ] 선택화면끝의스핀컨트롤은이응용프로그램에서유지할 - 140 -
목록의크기를지정하기위해사용되는데, 기본값은 4 이다. 생성된클래스들을보여주는화면은다음과같다. - 141 -
위의화면에따르면 AppWizard가생성하는클래스는 4개이며, 각기응용프로그램, 주실행창, 문서, 뷰등을나타내기위한것이다. 이들클래스는각각 CWinAppEx, CFrameWnd, CDocument, CView 등에서파생된클래스이며, 이름은각기 CxxxApp, CMainFrame, CxxxDoc, CxxxView와같이정해지는데, 여기서 xxx는프로젝트이름을나타낸다. 대화상자기반응용프로그램을선택하면 AppWizard는다이얼로그, 아이콘, 스트링테이블, 버전정보등의 4가지리소스를기본적으로생성한다. SDI/MDI 응용프로그램의경우에도이 4가지리소스가포함되며, 여기에메뉴, 단축키 (accelerator), 툴바등 3가지리소스가추가로생성된다. SDI/MDI 응용프로그램에서는메뉴제공이필수이며, 단축키와툴바도자주사용되는메뉴항목에대한접근을용이하게하기위한방법으로제공된다. CxxxDoc 클래스관련작업 도큐먼트클래스에서일반적으로코딩이필요한사항은문서데이터를메모리에서나타내기위해필요한자료구조의정의, 자료구조의초기화, 문서의저장 / 열기를처리하는 Serialize() 함수편집등이다. 도큐먼트클래스안의자료구조가 private 가시성을갖도록선언될경우에는뷰클래스에서문서클래스자료구조에접근할수있도록필요한인터페이스함수들을정의해주어야할것이지만, 이예제에서는자료구조를 public 가시성을갖도록선언하고있다. 이는물론객체지향프로그래밍의정보은닉원리에위배되며바람직하지않은것으로평가되지만, 편리성으로인해많이이용되고있다. 1. 여기에서는텍스트행들을저장하는방법으로크기가 100인 CString 배열을사용한다. 따라서최대 100 행까지입력받을수있다. 또실제입력된행의수를표시하기위한정수형변수가필요하다. 따라서다음과같은변수들을 public 가시성을갖도록선언한다. CString int m_strlines[100]; m_nlines; 2. CxxxDoc::OnNewDocument() 함수를편집하여초기화작업을표시한다. 이함수는문서초기화를담당하는데, 프로그램이시작될때와새파일메뉴항목이선택될때호출되어실행된다. 여기에서는현재입력된텍스트가없음을나타내기위해아래문장을포함시킨다. - 142 -
m_nlines = 0; 3. CxxxDoc::Serialize(CArchive &ar) 함수에파일저장및열기를위한코드를아래와같이만들어넣는다. 이함수는 CArchive 객체참조를파라미터로받는데, CArchive는파일에대한입출력을 << 연산자와 >> 연산자를사용하여쉽게표현할수있게해주기위한클래스이다. CArchive 객체는파일 (CFile) 객체에연계되어있으며, 현재저장용으로사용될것인지열기용으로사용될것인지에대한정보를갖고있다. 이객체에 IsStoring() 또는 IsLoading() 함수를적용시켜용도를확인할수있다. CArchive 객체가나타내는파일은파일에대한열기, 저장, 다른이름으로저장등의메뉴항목의처리과정에서정해진다. if (ar.isstoring()) // 저장작업코드 ar << m_nlines; for (int i = 0; i < m_nlines; i++) ar << m_strlines[i]; else // 열기작업코드 ar >> m_nlines; for (int i = 0; i < m_nlines; i++) ar >> m_strlines[i]; 참고사항 : 직렬화 (serialization) 직렬화란프로그램안의객체를디스크파일과같은외부저장매체에출력하거나혹은매체로부터입력받는프로세스를말한다. C++ 프로그램안의객체의데이터부분을나타내는구조체안에는포인터만몇개들어있고, 실제데이터는포인터가가리키는별도의주소에들어있을수도있다. 따라서구조체자체가차지하는메모리부분에들어있는값을출력하는것은무의미할수도있으며, 나중에다시읽었을때의미있는데이터가되기위한출력방법은단순하지않을수도있다. 프로그램안의객체가실제로나타내는데이터는포인터들에의해연결된 2차원적인구조이기쉬우며, 이데이터를의미있게정리하여출력한결과는일차원적인바이트열이다. 직렬화라는단어는객체출력의바로이러한점을잘표현하고있다. 직렬화에의해저장된데이터를다시읽어들여객체를구성하는것은직렬화의반대과정이며, 이를역직렬화 (de-serialization) 라고한다. CxxxView 클래스관련작업 뷰클래스에서도멤버변수의선언및초기화가필요할것이다. 초기화는생성자함수와 OnInitialUpdate() 함수등에서이루어질수있는데, OnInitialUpdate() 함수를이용할경우에는 - 143 -
뷰클래스에대해가상함수추가절차가필요하다. 뷰클래스에서가장본질적인기능이라면문서의내용을사용자에게보여주는것이며, 이를위해도큐먼트객체에접근할필요가있다. 뷰클래스안에는연계되어있는도큐먼트객체에대한포인터를리턴해주는 GetDocument() 라는함수가정의되어있으며, 이러한경우에이용된다. 문서내용을화면에표시하는코드는흔히뷰클래스의멤버함수 OnDraw() 안에들어간다. 창의생성시, 또는창의가려졌던부분이다시전면에나타날때, WM_PAINT 메시지가발생하며이메시지는 CWnd::OnPaint() 라는함수에의해처리된다. 이함수는많은경우스스로이작업을처리하기보다뷰클래스의 OnDraw() 함수를호출하여화면표시작업을맡긴다. OnPaint() 함수가 OnDraw() 함수를사용하는이유는인쇄나인쇄미리보기등의메뉴항목을처리하는 OnPrint() 함수가동일한작업을필요로하기때문에공통적인코드를 OnDraw() 함수로만들어두고 OnPaint() 함수와 OnPrint() 함수등에서호출하여사용할수있도록하기위함이다, OnDraw() 함수는 CDC* 타입의파라미터를취하는데, 이파라미터는페인트가필요한화면영역이나프린터를나타내는 DC 객체를가리키는포인터이다. 뷰클래스에서는또한사용자의문서편집작업이나문서안에서의위치이동등의상호작용을처리하기위한메시지처리기함수들을만들어주어야한다. 이상의작업들을하나씩처리해보자. 1. 문서의여러텍스트행중현재어느부분을화면에보여줄지를나타내기위해화면에나타나는첫번째행번호를표시하는정수형멤버변수 m_npos 를둔다. 또예제프로그램에서는화면에최대다섯행까지의텍스트만을보여주며, 이뷰의크기는변경되지는않는다. 그러나추후의확장성을위해뷰의크기를나타내는정수형변수 m_nvusz를둔다. 이변수들을 private 가시성을갖도록다음과같이선언한다. int m_npos; // 화면상첫행의인덱스, 0 <= m_npos < m_nlines ( 도큐먼트크기 ) int m_nvusz; // 뷰의높이 ( 값은 5로초기화한다 ) 2. 생성자 CxxxView() 안에서 m_nvusz 를 5 로초기화한다. m_nvusz = 5; 3. 클래스마법사를불러 [ 가상함수 ] 탭에서가상함수추가절차를사용하여뷰클래스에 CxxxView::OnInitialUpdate() 함수를추가하고, 그안에서 m_npos를 0으로초기화한다. m_npos = 0; 4. 뷰의내용을그려내기위해호출되는 CxxxView::OnDraw() 함수는아래와같은모양으로만들어져있다. 이함수는먼저 GetDocument() 함수를호출하여이뷰에연계되어있는문서객체를가리키는포인터를받아온다. 받아온포인터의유효성을점검하기위해 - 144 -
ASSERT_VALID 마크로를사용한다. void CSDIView::OnDraw(CDC* pdc) CSDIDoc* pdoc = GetDocument(); ASSERT_VALID(pDoc); if (!pdoc) return; // TODO: 여기에원시데이터에대한그리기코드를추가합니다. 출력대상텍스트행의인덱스는 m_npos에서시작하여 m_npos + (m_nvusz - 1) 까지이다. 그러나이인덱스범위가도큐먼트텍스트마지막행을지나칠경우도큐먼트텍스트마지막행까지만출력한다. 각행의앞에는 행인덱스 : 를덧붙여문서상에서의행위치를표시한다. CString::Format() 함수는 C 표준함수 sprintf() 와유사한방식으로동작하여출력결과가스트링객체의값이된다. CDC::TextOut() 함수에서각행출력위치는 (10, 10), (10, 30), (10, 50),... 등과같이정한다. OnDraw() 함수의 // TODO: 아래에들어가는코드는다음과같다. for (int i = m_npos; i < m_npos + m_nvusz && i < pdoc->m_nlines; i++) CString str; str.format(_t("%d : "), i); pdc->textout(10, 10 + 20*(i - m_npos), str + pdoc->m_strlines[i]); 텍스트입력다이얼로그의추가 [ 편집 ] 메뉴의 [ 리소스추가 ] 메뉴항목을선택한다음리소스형식으로는 Dialog를선택하고 < 새로만들기 > 버튼을누른다. 다이얼로그안에는 < 확인 > 버튼, < 취소 > 버튼, Static text, Edit Box (m_strline 변수 ) 등을둔다. 버튼들은다이얼로그안에기본으로포함되어있는것을지우지않고남겨두는방법으로포함시킨다. 이점은중요한데, 이는이버튼들에연결되어있는 CDialog::OnOK() 함수와 CDialog::OnCancel() 함수를이용할필요가있기때문이다. 만일이버튼들을새로추가하여캡션들을 " 확인 ", " 취소 " 등으로만들어주었다면여기에메시지처리기함수들을만들고그안에서 OnOK() 와 - 145 -
OnCancel() 함수를호출해주어야한다. 이러한복잡한일을피하기위해서는원래의버튼들을살려두어야한다. 설계된다이얼로그리소스들은대체로프로그램에서 modal 다이얼로그로사용된다. 이응용프로그램의경우에는 " 추가 (A) Ctrl+A" 메뉴항목을선택하게되면, 이메뉴항목의처리기함수안에서위의다이얼로그를생성하여화면에나타나게만들것이다. 이를위해서는다이얼로그객체를생성하여여기에 CDialog::DoModal() 함수를호출하여야하는데, 객체의생성을위해서는위의다이얼로그를나타내는클래스가필요하다. ( 참고 : modeless 다이얼로그의경우에는 DoModal() 함수대신 Create() 함수가사용되며, 추가로고려해야할사항들이많다.) 다이얼로그리소스가활성화되어있는상태에서, 즉, 다이얼로그편집화면상태에서팝업메뉴를불러 [ 클래스추가 ] 메뉴항목을선택하면아래그림과같은클래스추가마법사가나타난다. 이때클래스이름으로 CInputDialog를입력하면, 이클래스의정의및구현을위한파일이름으로 InputDialog.h, InputDialog.cpp 등이정해진다. 대화상자 ID는위에서만든다이얼로그 ID이다. 행추가메뉴항목 1. 메뉴리소스를편집하여행추가를위한메뉴항목을포함시킨다. SDI 응용프로그램에서는이미메뉴리소스가포함되어있어필요한메뉴나메뉴항목을 - 146 -
쉽게추가할수있다. 먼저프로젝트워크스페이스다이얼로그에서리소스뷰탭을클릭한다. 메뉴리소스왼쪽의 아이콘을클릭하여메뉴리소스를펼치면, 주실행창을위한메뉴리소스가나타난다. ( 레이블 : IDR_MAINFRAME) 위의 IDR_MAINFRAME 메뉴리소스를더블클릭하여메뉴를펼친다음 " 편집 (E)" 메뉴끝에분리선을추가한다음행추가를위한메뉴항목 " 추가 (A) Ctrl+A" 을추가한다. 이메뉴항목의 ID는다른메뉴항목 ID들과형식을맞추기위해 ID_EDIT_APPEND로정한다. 메뉴항목속성중 Prompt 속성은메뉴항목위에마우스커서를위치시켰을때주실행창의상태표시줄에나타날메뉴항목에대한간단한설명이다. Prompt 속성값끝의 n 이후부분은툴팁 (tooltip) 이다. 2. ID_EDIT_APPEND 메뉴항목을위한처리기함수 OnEditAppend() 를뷰클래스, 즉, CSDIView 클래스안에추가한다. 이를위해리소스뷰에서 [ 추가 ] 메뉴항목위에팝업메뉴를부른다음 [ 이벤트처리기 ] 메뉴항목을선택하면아래화면과같은 [ 이벤트처리기마법사 ] 화면이나타난다. ( 클래스마법사를통해이벤트처리기를생성할수도있다.) 아래화면에서 < 메시지형식 > 에는 COMMAND, < 클래스목록 > 에는뷰클래스를선택한다. 실제로메뉴항목처리기함수는아무클래스에나만들어넣을수있다, 그러나텍스트행추가와같은문서편집작업은그결과가뷰에도반영되어야하며또문서에도반영되어야한다. 뷰와문서데이터모두에가장손쉽게접근할수있는곳이뷰클래스이며, 따라서문서편집관련처리기함수는뷰클래스안에정의하는것이바람직하다. - 147 -
위의화면에서 < 추가및편집 > 버튼을누른후아래코드를만들어넣는다. #include "InputDialog.h" void CSDIView::OnEditAppend() CInputDialog idlg; if (idlg.domodal() == IDOK) CSDIDoc* pdoc = GetDocument(); ASSERT_VALID(pDoc); pdoc->m_strlines[pdoc->m_nlines] = idlg.m_strline; pdoc->m_nlines++; pdoc->setmodifiedflag(true); m_npos = pdoc->m_nlines - m_nvusz; if (m_npos < 0) m_npos = 0; Invalidate(); - 148 -
OnEditAppend() 함수안에서먼저할일은텍스트행을받아들일다이얼로그박스를띄우는것이며, 이를위해 CInputDialog 객체 idlg를생성한다음이객체에대해 DoModal() 함수를호출한다. 텍스트행의추가는다이얼로그박스에서 OK 버튼을클릭하여닫아줄경우에만이루어져야하는데, 이는 DoModal() 함수의리턴값이 IDOK임을확인하면된다. 입력된텍스트는문서의끝에추가되어야하며, 이를위해먼저문서객체를가리키는포인터를받아와서 pdoc에저장한다. 문서에텍스트행을추가한다음 CDocument::SetModifiedFlag() 함수를호출하는데, 이는문서에변경이있었음을표시하는플래그를설정하기위한것이다. 나중에파일을저장하지않고프로그램을종료하려고하면, 문서에변경이있었음을알리고현재문서내용을저장할것인지묻게된다. 추가된텍스트를뷰에반영하는방법은여러가지가있으나이미만들어져있는 OnDraw() 함수를이용하는것이가장간단한방법이다. CWnd::Invalidate() 함수는클라이언트영역을대상으로 OnDraw() 함수를호출하는결과를초래하게되며, 이과정에서 OnDraw() 함수에전달될 DC 객체도적절히준비된다. 여기에서는먼저텍스트상의출력위치를나타내는 m_npos를적절히설정한다음 Invalidate() 함수를호출한다. AppWizard는응용프로그램유형에따라필요한여러클래스들을만들고이들을사용하는곳에클래스를정의하는헤더파일에대한 #include 명령들을적절하게포함시켜준다. 그러나 CInputDialog는 AppWizard가만든클래스가아니므로이클래스를정의하는헤더파일에대한 #include 명령을넣는것은프로그래머의책임이다. 이예제의경우이명령은 SDIView.h의임의의위치, SDIView.cpp에서 OnEditAppend() 함수가정의되기전의임의의위치에들어가면된다. 행추가메뉴항목을위한단축키 (accelerator) 설정 메뉴항목을사용하기위해서는메뉴를먼저펼친다음원하는메뉴항목을클릭해야하는데, 때로 Ctrl+F와같이특별한키조합을메뉴항목에연계시켜두고이키조합을사용하는것이효율적일수도있다. 이러한키조합을단축키라고부르며, 자주사용되는메뉴항목들에는대개단축키들이설정되어있는경우가많다. 단축키는 Accelerator 리소스를통해설정된다. 아래그림과같이 Accelerator 리소스맨끝에는새로운단축키를설정할수있도록비어있는행이주어져있다. 이행을클릭한후 [ 속성 ] 창에서 ID 속성과동작속성들을설정한다. - 149 -
동작속성들은단축키로사용될키조합을표시하는데, 키조합표시방법을모를경우에는선택된단축키리소스행에대해팝업메뉴를부른후 [ 다음입력된키 ] 메뉴항목을선택한다. 옆의그림과같은화면이나타날텐데, 이때단축키로사용될키조합을누르면동작속성들에적절한값이들어간다. 단축키가메뉴항목과동일한작업을수행하도록만들기위해필요한것은단축키를위한 ID와메뉴항목의 ID를일치시켜주는것이며, 이경우단축키를위한별도의처리기함수를만들필요는없다. 앞에서메뉴항목의캡션을 " 추가 (A) Ctrl+A" 와같이지정하였었는데, 캡션에포함된 Ctrl+A는사용자에게단축키를알려주는설명일뿐기능적의미는전혀없다. Ctrl+A가실제로이메뉴항목을위한단축키로작용하기위해서는지금기술한바와같이단축키리소스에서필요한설정을거쳐야한다. 행추가메뉴항목을위한툴바 (Toolbar) 버튼설정 특히자주사용되는메뉴항목들은대개메뉴아래에위치한툴바 (Toolbar) 에버튼으로등록되어있어메뉴에서보다쉽게이용할수있도록만들어져있다. 툴바버튼은툴바리소스를통해설정된다. 아래그림과같이 Toolbar 리소스맨끝에는새로운버튼을설정할수있도록비어있는버튼이주어져있다. 이버튼을편집할수있도록이미지편집기가제공되며, 여기에서 Visual Studio 하단툴바의그리기도구들과화면오른쪽색상선택창을사용하여버튼이미지를그린다. 또속성창에서해당버튼의 ID를메뉴항목의 ID와일치시켜주는것만으로메뉴항목기능과의연계가이루어진다. - 150 -
화살표키 (UP) 와 (DOWN) 의기능설정 화살표키들은단축키로서기능한다. 따라서 Accelerator 리소스를통해설정할수있다. 이키들에대한키값은 VK_UP과 VK_DOWN이다. 이키들이갖는기능은메뉴항목에는없으며, 따라서새로운 ID를설정해야하는데, 여기에서는각기 ID_ARROW_UP, ID_ARROW_DOWN 등으로정한다. - 151 -
이키들은뷰에서보여주는문서상의텍스트위치를한행위로또는한행아래로이동시키기위해사용된다. 이는 ID_ARROW_UP, ID_ARROW_DOWN 등의 ID들에대해각기 COMMAND 메시지처리기를만들어줌으로써해결될수있다. 이함수들도뷰클래스안에두는것이바람직하다. 이함수들을만들기위해서도이벤트처리기마법사또는클래스마법사를사용할수있다. void CSDIView::OnArrowDown() CSDIDoc* pdoc = GetDocument(); ASSERT_VALID(pDoc); (m_npos < pdoc->m_nlines - 1)? m_npos++ : 0; Invalidate(); void CSDIView::OnArrowUp() (m_npos > 0)? m_npos-- : 0; Invalidate(); 연습문제 본문예제에관련된다음질문들에답하라. 1. CSDIDoc 클래스에서문서데이터를표현하기위해사용된 m_strlines 배열과 m_nlines 등의가시성을 private로선언하고이에따라 CSDIDoc 클래스와 CSDIView 클래스를고쳐라. ( 단, friend 메커니즘은사용하지않는다.) 2. 이장에서는텍스트행들의저장을위해고정크기의스트링배열을사용하고있다. 스트링배열대신 char 배열안에텍스트행들을저장하는방식으로프로그램을재작성하라. 이러한변경이원래의프로그램에대해이루어지는경우와위의 1번문제에서수정된프로그램에대해이루어지는경우를비교하여논하라. 3. CStringArray는스트링들을저장하는배열을나타내는 MFC 클래스이며, 저장될스트링들이많아지면자바의 Vector 클래스처럼크기가자동적으로늘어난다. char 배열대신 CStringArray 객체를사용하여앞의 2번문제에대해답하라. - 152 -
4. CSDIDoc 클래스에서 m_nlines의초기화를생성자에서수행하지않고 OnNewDocument() 함수안에서수행하고있다. 이초기화를생성자에서수행할경우어떤문제점이생길지조사하고그이유에대해논하라. 5. CSDIView 클래스의경우초기화문장들을생성자안에서처리하는경우와 OnInitialUpdate() 함수안에서처리하는경우사이에실행상가시적차이를관찰할수있는지에대해실험하고논하라. 6. 이프로그램의뷰는화면이가려졌다나타날때에도원래의내용을유지한다. 그이유는무엇인가? 7. 메뉴항목과 accelerator 가동일한기능을갖도록만드는방법을설명하라. 8. 다이얼로그기반의응용프로그램에는포함되지않지만 SDI/MDI 응용프로그램에는자동으로포함되는리소스 3 가지는무엇인가? 9. SDI 응용프로그램을작성할때, 프로그래머가도큐먼트클래스와뷰클래스에포함시키는요소들을기술하라. 프로그램소스 ( 일부 ) InputDialog.h InputDialog.cpp SDIDoc.h SDIDoc.cpp SDIView.h SDIView.cpp ================================================================================= // InputDialog.h : header file #pragma once // CInputDialog 대화상자입니다. class CInputDialog : public CDialogEx DECLARE_DYNAMIC(CInputDialog) public: - 153 -
CInputDialog(CWnd* pparent = NULL); // 표준생성자입니다. virtual ~CInputDialog(); // 대화상자데이터입니다. enum IDD = IDD_DIALOG1 ; protected: virtual void DoDataExchange(CDataExchange* pdx); // DDX/DDV 지원입니다. public: ; DECLARE_MESSAGE_MAP() CString m_strline; ================================================================================= // InputDialog.cpp : 구현파일입니다. // #include "stdafx.h" #include "SDI.h" #include "InputDialog.h" #include "afxdialogex.h" // CInputDialog 대화상자입니다. IMPLEMENT_DYNAMIC(CInputDialog, CDialogEx) CInputDialog::CInputDialog(CWnd* pparent /*=NULL*/) : CDialogEx(CInputDialog::IDD, pparent), m_strline(_t("")) CInputDialog::~CInputDialog() void CInputDialog::DoDataExchange(CDataExchange* pdx) CDialogEx::DoDataExchange(pDX); DDX_Text(pDX, IDC_EDIT1, m_strline); BEGIN_MESSAGE_MAP(CInputDialog, CDialogEx) END_MESSAGE_MAP() // CInputDialog 메시지처리기입니다. - 154 -
================================================================================= // SDIDoc.h : CSDIDoc 클래스의인터페이스 // #pragma once class CSDIDoc : public CDocument protected: // serialization에서만만들어집니다. CSDIDoc(); DECLARE_DYNCREATE(CSDIDoc) // 특성입니다. public: CString m_strlines[100]; int m_nlines; // 작업입니다. public: // 재정의입니다. public: virtual BOOL OnNewDocument(); virtual void Serialize(CArchive& ar); #ifdef SHARED_HANDLERS virtual void InitializeSearchContent(); virtual void OnDrawThumbnail(CDC& dc, LPRECT lprcbounds); #endif // SHARED_HANDLERS // 구현입니다. public: virtual ~CSDIDoc(); #ifdef _DEBUG virtual void AssertValid() const; virtual void Dump(CDumpContext& dc) const; #endif protected: // 생성된메시지맵함수 protected: DECLARE_MESSAGE_MAP() - 155 -
#ifdef SHARED_HANDLERS // 검색처리기에대한검색콘텐츠를설정하는도우미함수 void SetSearchContent(const CString& value); #endif // SHARED_HANDLERS ; ================================================================================= // SDIDoc.cpp : CSDIDoc 클래스의구현 // #include "stdafx.h" // SHARED_HANDLERS는미리보기, 축소판그림및검색필터처리기를구현하는 // ATL 프로젝트에서정의할수있으며해당프로젝트와문서코드를공유하도록해줍니다. #ifndef SHARED_HANDLERS #include "SDI.h" #endif #include "SDIDoc.h" #include <propkey.h> #ifdef _DEBUG #define new DEBUG_NEW #endif // CSDIDoc IMPLEMENT_DYNCREATE(CSDIDoc, CDocument) BEGIN_MESSAGE_MAP(CSDIDoc, CDocument) END_MESSAGE_MAP() // CSDIDoc 생성 / 소멸 CSDIDoc::CSDIDoc() // TODO: 여기에일회성생성코드를추가합니다. CSDIDoc::~CSDIDoc() - 156 -
BOOL CSDIDoc::OnNewDocument() if (!CDocument::OnNewDocument()) return FALSE; // TODO: 여기에재초기화코드를추가합니다. // SDI 문서는이문서를다시사용합니다. m_nlines = 0; return TRUE; // CSDIDoc serialization void CSDIDoc::Serialize(CArchive& ar) if (ar.isstoring()) // 저장작업코드 ar << m_nlines; for (int i = 0; i < m_nlines; i++) ar << m_strlines[i]; else // 열기작업코드 ar >> m_nlines; for (int i = 0; i < m_nlines; i++) ar >> m_strlines[i]; #ifdef SHARED_HANDLERS // 축소판그림을지원합니다. void CSDIDoc::OnDrawThumbnail(CDC& dc, LPRECT lprcbounds) // 문서의데이터를그리려면이코드를수정하십시오. dc.fillsolidrect(lprcbounds, RGB(255, 255, 255)); CString strtext = _T("TODO: implement thumbnail drawing here"); LOGFONT lf; CFont* pdefaultguifont = CFont::FromHandle((HFONT) GetStockObject(DEFAULT_GUI_FONT)); pdefaultguifont->getlogfont(&lf); lf.lfheight = 36; CFont fontdraw; - 157 -
fontdraw.createfontindirect(&lf); CFont* poldfont = dc.selectobject(&fontdraw); dc.drawtext(strtext, lprcbounds, DT_CENTER DT_WORDBREAK); dc.selectobject(poldfont); // 검색처리기를지원합니다. void CSDIDoc::InitializeSearchContent() CString strsearchcontent; // 문서의데이터에서검색콘텐츠를설정합니다. // 콘텐츠부분은 ";" 로구분되어야합니다. // 예 : strsearchcontent = _T("point;rectangle;circle;ole object;"); SetSearchContent(strSearchContent); void CSDIDoc::SetSearchContent(const CString& value) if (value.isempty()) RemoveChunk(PKEY_Search_Contents.fmtid, PKEY_Search_Contents.pid); else CMFCFilterChunkValueImpl *pchunk = NULL; ATLTRY(pChunk = new CMFCFilterChunkValueImpl); if (pchunk!= NULL) pchunk->settextvalue(pkey_search_contents, value, CHUNK_TEXT); SetChunkValue(pChunk); #endif // SHARED_HANDLERS // CSDIDoc 진단 #ifdef _DEBUG void CSDIDoc::AssertValid() const CDocument::AssertValid(); - 158 -
void CSDIDoc::Dump(CDumpContext& dc) const CDocument::Dump(dc); #endif //_DEBUG // CSDIDoc 명령 ================================================================================= // SDIView.h : CSDIView 클래스의인터페이스 // #pragma once class CSDIView : public CView protected: // serialization에서만만들어집니다. CSDIView(); DECLARE_DYNCREATE(CSDIView) // 특성입니다. public: CSDIDoc* GetDocument() const; // 작업입니다. public: // 재정의입니다. public: virtual void OnDraw(CDC* pdc); // 이뷰를그리기위해재정의되었습니다. 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); // 구현입니다. public: virtual ~CSDIView(); #ifdef _DEBUG - 159 -
#endif virtual void AssertValid() const; virtual void Dump(CDumpContext& dc) const; protected: // 생성된메시지맵함수 protected: afx_msg void OnFilePrintPreview(); afx_msg void OnRButtonUp(UINT nflags, CPoint point); afx_msg void OnContextMenu(CWnd* pwnd, CPoint point); DECLARE_MESSAGE_MAP() private: int m_npos; int m_nvusz; public: virtual void OnInitialUpdate(); afx_msg void OnEditAppend(); afx_msg void OnArrowUp(); afx_msg void OnArrowDown(); ; #ifndef _DEBUG // SDIView.cpp의디버그버전 inline CSDIDoc* CSDIView::GetDocument() const return reinterpret_cast<csdidoc*>(m_pdocument); #endif ================================================================================= // SDIView.cpp : CSDIView 클래스의구현 // #include "stdafx.h" // SHARED_HANDLERS는미리보기, 축소판그림및검색필터처리기를구현하는 // ATL 프로젝트에서정의할수있으며해당프로젝트와문서코드를공유하도록해줍니다. #ifndef SHARED_HANDLERS #include "SDI.h" #endif #include "SDIDoc.h" #include "SDIView.h" #ifdef _DEBUG #define new DEBUG_NEW - 160 -
#endif // CSDIView IMPLEMENT_DYNCREATE(CSDIView, CView) BEGIN_MESSAGE_MAP(CSDIView, CView) // 표준인쇄명령입니다. ON_COMMAND(ID_FILE_PRINT, &CView::OnFilePrint) ON_COMMAND(ID_FILE_PRINT_DIRECT, &CView::OnFilePrint) ON_COMMAND(ID_FILE_PRINT_PREVIEW, &CSDIView::OnFilePrintPreview) ON_WM_CONTEXTMENU() ON_WM_RBUTTONUP() ON_COMMAND(ID_EDIT_APPEND, &CSDIView::OnEditAppend) ON_COMMAND(ID_ARROW_UP, &CSDIView::OnArrowUp) ON_COMMAND(ID_ARROW_DOWN, &CSDIView::OnArrowDown) END_MESSAGE_MAP() // CSDIView 생성 / 소멸 CSDIView::CSDIView() : m_npos(0) // TODO: 여기에생성코드를추가합니다. m_nvusz = 5; CSDIView::~CSDIView() BOOL CSDIView::PreCreateWindow(CREATESTRUCT& cs) // TODO: CREATESTRUCT cs를수정하여여기에서 // Window 클래스또는스타일을수정합니다. return CView::PreCreateWindow(cs); // CSDIView 그리기 void CSDIView::OnDraw(CDC *pdc) CSDIDoc* pdoc = GetDocument(); - 161 -
ASSERT_VALID(pDoc); if (!pdoc) return; // TODO: 여기에원시데이터에대한그리기코드를추가합니다. for (int i = m_npos; i < m_npos + m_nvusz && i < pdoc->m_nlines; i++) CString str; str.format(_t("%d : "), i); pdc->textout(10, 10 + 20*(i - m_npos), str + pdoc->m_strlines[i]); // CSDIView 인쇄 void CSDIView::OnFilePrintPreview() #ifndef SHARED_HANDLERS AFXPrintPreview(this); #endif BOOL CSDIView::OnPreparePrinting(CPrintInfo* pinfo) // 기본적인준비 return DoPreparePrinting(pInfo); void CSDIView::OnBeginPrinting(CDC* /*pdc*/, CPrintInfo* /*pinfo*/) // TODO: 인쇄하기전에추가초기화작업을추가합니다. void CSDIView::OnEndPrinting(CDC* /*pdc*/, CPrintInfo* /*pinfo*/) // TODO: 인쇄후정리작업을추가합니다. void CSDIView::OnRButtonUp(UINT /* nflags */, CPoint point) ClientToScreen(&point); OnContextMenu(this, point); void CSDIView::OnContextMenu(CWnd* /* pwnd */, CPoint point) - 162 -
#ifndef SHARED_HANDLERS theapp.getcontextmenumanager()->showpopupmenu(idr_popup_edit, point.x, point.y, this, TRUE); #endif // CSDIView 진단 #ifdef _DEBUG void CSDIView::AssertValid() const CView::AssertValid(); void CSDIView::Dump(CDumpContext& dc) const CView::Dump(dc); CSDIDoc* CSDIView::GetDocument() const // 디버그되지않은버전은인라인으로지정됩니다. ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CSDIDoc))); return (CSDIDoc*)m_pDocument; #endif //_DEBUG // CSDIView 메시지처리기 void CSDIView::OnInitialUpdate() CView::OnInitialUpdate(); // TODO: 여기에특수화된코드를추가및 / 또는기본클래스를호출합니다. m_npos = 0; #include "InputDialog.h" void CSDIView::OnEditAppend() CInputDialog idlg; if (idlg.domodal() == IDOK) CSDIDoc* pdoc = GetDocument(); - 163 -
ASSERT_VALID(pDoc); pdoc->m_strlines[pdoc->m_nlines] = idlg.m_strline; pdoc->m_nlines++; pdoc->setmodifiedflag(true); m_npos = pdoc->m_nlines - m_nvusz; if (m_npos < 0) m_npos = 0; Invalidate(); void CSDIView::OnArrowUp() (m_npos > 0)? m_npos-- : 0; Invalidate(); void CSDIView::OnArrowDown() CSDIDoc* pdoc = GetDocument(); ASSERT_VALID(pDoc); (m_npos < pdoc->m_nlines - 1)? m_npos++ : 0; Invalidate(); - 164 -