|
MFC 프로젝트를 통하여 기본적으로 생성된 App, ViewWnd, Document, MainWnd의 설명은 생략. DrawManager는 View에서 도형을 그리는 일을 위임 받아 Document에서 Diagarm의 정보를 받아서 그림을 그려주는 역할을 하고 있습니다. View에서 OnDraw가 호출될 때 Document에서 정보를 받아 DrawManager가 그림을 그려주게 됩니다. Document는 Diagram을 관리하며 이 Diagram은 그림의 정보와 직렬화, 역 직렬화가 가능한 메소드를 포함하고 있습니다. Pen, Line, Rect, Circle은 Diagram을 상속받아 데이터를 자기 자신의 형식에 맞게 보관합니다. |
2. 시퀀스 다이어그램 및 소스
2-1. 도형 선택
도형을 Toolbar나 Menu를 통하여 클릭하였을 때 View에서 Document로 도형의 타입을 결정
지어주는 SetType 메소드를 호출하여 줍니다.
이를 통하여 생성될 도형의 Type을 알아내서 각 알맞은 도형을 생성해줍니다.
종류는 PEN : 2000, LINE : 2001, RECT : 2002, CIRCLE : 2003 으로 각각의 Type을 지정해줍니다.
void C그림판실습Doc::SetType(int type) { mType = type; } |
2-2. 마우스 왼쪽 버튼 클릭(OnLButtonDown)
마우스 왼쪽을 눌렀을 때 도형의 타입이 존재한다면(미리 정의해둔 값) DownEvent()의 값을
true로 해줘 마우스 다운 이벤트가 발생했음을 Document에 통지하여 줍니다.
그리고 마우스의 좌표를 기억해주기 위하여 SetOldPoint와 SetNowPoint 메소드를 호출하여
현재 마우스의 위치인 point를 값으로 넘겨줍니다.
다른 도형들은 마우스를 눌렀을 때의 point와 마우스를 땠을 때의 point를 기억하면 되지만
펜의 경우에는 주기적으로 마우스가 움직일 때 마다 마우스의 위치를 기억해줘야 하기 때문에
Document의 AddPoint() 메소드를 호출해 줍니다.
void C그림판실습View::OnLButtonDown(UINT nFlags, CPoint point) { // TODO: 여기에메시지처리기코드를추가및/또는기본값을호출합니다. C그림판실습Doc *doc = GetDocument(); int tmpType = doc->GetType();
if(!tmpType) return;
if(tmpType >= MYPEN && tmpType <= MYCIRCLE){ doc->DownEvent(true); }
doc->SetOldPoint(point); doc->SetNowPoint(point);
if(tmpType == MYPEN){ doc->AddPoint(); //연필이라면CPoint를기록하기시작한다. }
CView::OnLButtonDown(nFlags, point); } |
AddPoint() 메소드는 CPoint를 동적으로 생성하여 Document의 vmPt라는 CPoint를 관리하는
vector에 push_back으로 생성된 CPoint를 저장해 줍니다.
void C그림판실습Doc::AddPoint() { CPoint *tmpPt = new CPoint(pNow); vmPt.push_back(tmpPt); } |
2-3. 마우스 이동([안내]태그제한으로등록되지않습니다-xxOnMouseMove)
마우스 이동 이벤트는 Document에서 현재 선택된 도형의 타입을 GetType()을 통하여
알아옵니다.
만약 이 반환된 값이 PEN에 해당한 값이라면 새롭게 갱신된 마우스의 위치를
기억하여 AddPoint를 통하여 CPoint를 Document에 있는 vmPt에 데이터를 넣어
주어야 합니다.
void C그림판실습View::[안내]태그제한으로등록되지않습니다-xxOnMouseMove(UINT nFlags, CPoint point) { // TODO: 여기에메시지처리기코드를추가및/또는기본값을호출합니다. C그림판실습Doc *doc = GetDocument();
if(!doc->IsDown()) return;
if(doc->GetType() ==MYPEN){ doc->SetOldPoint(doc->GetNowPoint()); }
doc->SetNowPoint(point);
if(doc->GetType() == MYPEN){ doc->AddPoint(); this->Invalidate(0); }
CView::[안내]태그제한으로등록되지않습니다-xxOnMouseMove(nFlags, point); } |
2-4. 마우스 왼쪽 버튼 업(OnLButtonUp)
마우스 왼쪽 버튼 업 이벤트가 발생하였을 경우에 현재 도형을 그리는 중인지 IsDown()
메소드를 통하여 확인을 합니다.
만약 반환된 값이 true일 경우에는 DownEvent를 false로 통지하여 그리기 작업이
끝났음을 알려주며, CreateDiagram() 메소드를 통하여 도형정보를 만들어 줍니다.
void C그림판실습View::OnLButtonUp(UINT nFlags, CPoint point) { // TODO: 여기에메시지처리기코드를추가및/또는기본값을호출합니다. C그림판실습Doc *doc = GetDocument(); if(doc->IsDown()){ doc->DownEvent(false); doc->CreateDiagram();//다이어그램을삽입한다. this->Invalidate(0); }
CView::OnLButtonUp(nFlags, point); } |
Document의 CreateDiagram() 메소드는 현재 임시적으로 저장되었던 정보를 기반으로
알맞은 도형을 실제 Diagarm을 보관하는 vector에 push_back을 통하여 등록을 해 줍니다.
만약에 도형의 type이 펜이라면 임시로 저장해 두었던 CPoint의 vector인 vmPt를 초기화
해주는 ClearPoint()를 호출하여 정상적으로 다음 펜을 그릴 수 있도록 해 줍니다.
void C그림판실습Doc::CreateDiagram() { RECT rt = {pOld.x,pOld.y,pNow.x,pNow.y}; switch(mType){ case MYRECT: base.push_back(new MyRect(rt)); break; case MYCIRCLE: base.push_back(new MyCircle(rt)); break; case MYLINE: base.push_back(new MyLine(pOld,pNow)); break; case MYPEN: base.push_back(new MyPen(vmPt)); break; }
if(mType == MYPEN){ ClearPoint(); } mPos++; //현재보여지는다이어그램의위치를증가시켜줘야한다. } |
2-4. 도형 그리기(OnDraw)
화면을 갱신하는 이벤트가 발생하면 View의 OnDraw() 메소드가 호출되게 되는데 이 때
DrawManager에게 도형을 전부 그려주는 DrawAll() 메소드를 통하여 도형정보를
그려주도록 명령합니다.
DrawManager는 Document에 저장된 정보인 현재 보여지는 인덱스 값을 GetPos() 를 통하여 알아내어 반환된 값만큼 Diagram의 정보를 꺼내어 그리기를 수행합니다.
void C그림판실습View::OnDraw(CDC* pDC) { C그림판실습Doc* pDoc = GetDocument(); ASSERT_VALID(pDoc); if (!pDoc) return;
CBrush *pOldBr = (CBrush*)pDC->SelectStockObject(NULL_BRUSH);
// TODO: 여기에원시데이터에대한그리기코드를추가합니다. if(pDoc->GetTotalSize() || pDoc->GetPenCnt()){ DrawManager *dm = DrawManager::GetInstance(); dm->DrawAll(pDC);
pDC->SelectObject(pOldBr); ReleaseDC(pDC); } } |
그릴 데이터가 있는지에 대한 확인은 Document의 데이터의 총 카운터 숫자가 있거나
임시로 저장된 Pen정보가 있다면 도형 그리기를 수행합니다.
이 때 view의 DC영역을 매개변수로 넘겨줍니다.
void DrawManager::DrawAll(CDC *pDC) { C그림판실습Doc *doc = GetDocument();
int nPos = doc->GetPos(); //현재위치를가져온다. Diagram *tmpData =0; int tmpType =0; for(int i=0;i<nPos;i++){ tmpData = doc->GetData(i); tmpType = tmpData->GetType();
switch(tmpType){ case MYRECT: DrawRect(dynamic_cast<MyRect*>(tmpData),pDC); break; case MYCIRCLE: DrawCircle(dynamic_cast<MyCircle*>(tmpData),pDC); break; case MYLINE: DrawLine(dynamic_cast<MyLine*>(tmpData),pDC); break; case MYPEN: DrawPen(dynamic_cast<MyPen*>(tmpData),pDC); break; } }
DrawTempPen(pDC);
} |
DrawManager는 DrawAll을 수행하여 모든 도형 정보를 Document에서 가져오게 됩니다.
Document에서 현재 보여지는 위치를 반환하는 GetPos를 통하여 총 몇 개의 도형을 그리는지 결정하게 됩니다.
GetData는 index를 매개변수로 넘겨주면 해당하는 Diagram의 정보를 반환합니다.
이를 통하여 해당 도형의 Type이 무엇인지 알아 낼 수 있는 GetType 메소드를 호출 합니다.
반환 받은 Type의 정보를 tmpType에 저장하게 되는데, swich문을 이용하여 각 타입에 맞게 dynamic_cast를 사용하여 형변환을 해주어 각각의 DrawRect, DrawCircle, DrawLine, DrawPen 메소드를 호출하면서 매개변수로 넘겨줍니다.
모든 그리기를 수행한 후 마지막으로 DrawTempPen을 해주게 되는데, 이는 아직 생성이 확정되지 않은 펜의 정보를 읽어와서 화면에 뿌려주기 위하여 호출합니다.
void DrawManager::DrawRect(MyRect *src, CDC *pDC) { pDC->Rectangle(&src->GetData()); } |
void DrawManager::DrawCircle(MyCircle *src, CDC *pDC) { pDC->Ellipse(&src->GetData()); } |
void DrawManager::DrawPen(MyPen *src, CDC *pDC) { int cnt = src->GetSize(); CPoint tmpOld; CPoint tmpNow;
for(int i=0;i<cnt-1;i++){ tmpOld = src->GetData(i); tmpNow = src->GetData(i+1);
pDC->MoveTo(tmpOld); pDC->LineTo(tmpNow); } } |
void DrawManager::DrawLine(MyLine *src, CDC *pDC) { MyPoint mp = src->GetData();
CPoint tmpOld = mp.pOld; CPoint tmpNow = mp.pNow;
pDC->MoveTo(tmpOld); pDC->LineTo(tmpNow); } |
2-5. 파일 저장, 직렬화(OnFileSave)
OnFileSave 이벤트가 발생하게 되면 사용자가 입력한 값을 path에 저장하여 Document의 SaveFile의 매개변수로 넣어 줍니다.
Document는 내부적으로 파일 저장을 하기 위하여 준비를 합니다. 파일 저장을 위하여 CFile의 정보를 셋팅해 주고, 가장 먼저 총 현재 도형의 개수와 현재 도형의 보여지는 index 값을 먼저 저장을 합니다.
그 후에 총 개수만큼 반복해서 해당 도형의 Serialization을 호출하여 직렬화를 수행합니다.
void C그림판실습View::OnFileSave() { C그림판실습Doc *doc = GetDocument();
CString szFilter = TEXT("nm (*.nm)|*.nm|"); CFileDialog dlg(FALSE, NULL, NULL, OFN_HIDEREADONLY, szFilter);
if(IDOK == dlg.DoModal()) { CString path = dlg.GetPathName(); CString _path(path+TEXT(".nm"));
doc->SaveFile(_path); } } |
void C그림판실습Doc::SaveFile(CString path) { CFile file; if(!file.Open(path,CFile::modeCreate | CFile::modeWrite)){ return; } int totalSize = base.size();
file.Write(&totalSize,sizeof(int)); file.Write(&mPos,sizeof(int));
for(int i=0;i<totalSize;i++){ base[i]->Serialization(&file); }
file.Close(); } |
SaveFile에서는 매개변수로 들어온 path를 기반으로 파일을 생성합니다.
총 보관된 도형의 개수를 구하여 그 개수를 제일먼저 저장한 후에 현재 보여지는 도형의 index도 따라 저장을 합니다.
이 이유는 되돌리기를 한 상태로 저장을 해도 보여지지 않는 도형에 대한 저장도 수행하여 나중에 파일을 불러왔을 때 저장했을 때의 모습을 유지하기 위해서 입니다.
총 개수만큼 반복적으로 해당 Diagram의 Serialization을 수행합니다.
void MyRect::Serialization(CFile *file) { int myType = MYRECT; file->Write(&myType,sizeof(int)); //일단타입을지정해줘야한다. file->Write(&rt,sizeof(RECT)); //정보를입력한다. } |
void MyCircle::Serialization(CFile *file) { int myType = MYCIRCLE; file->Write(&myType,sizeof(int)); //일단타입을지정해줘야한다. file->Write(&rt,sizeof(RECT)); //정보를입력한다. } |
사각형과 원형의 저장방법은 동일하며, 먼저 자기 자신의 타입을 Head로 저장하고, RECT의
정보를 저장합니다.
void MyLine::Serialization(CFile *file) { int myType = MYLINE; file->Write(&myType,sizeof(int)); //일단타입을지정해줘야한다. file->Write(&pt.pOld,sizeof(CPoint)); //시작점 file->Write(&pt.pNow,sizeof(CPoint)); //끝점 } |
선의 경우에는 시작점과 끝점을 저장해주면 됩니다.
void MyPen::Serialization(CFile *file) { int myType = MYPEN; file->Write(&myType,sizeof(int)); //일단타입을지정해줘야한다. file->Write(&m_Cnt,sizeof(int)); //총가지고있는CPoint 갯수
for(int i=0;i<m_Cnt;i++){ file->Write(m_Pt[i],sizeof(CPoint)); }
} |
펜의 경우 자기 자신이 가지고 있는 CPoint의 개수를 저장해야 하기 때문에 타입을 저장 후
CPoint의 개수인 m_Cnt 를 저장해준 후에 m_Cnt 만큼 반복적으로 CPoint의 정보를 저장 해 줘야 합니다.
2-5. 실행 취소, 다시 실행
실행 취소와 다시 실행을 하려면 보여지는 위치를 카운트만 해주면 되기 때문에 Document의 mPos의 값을 증가, 감소시켜주는 메소드인 PosPlus와 PosMinus를 호출하여
쉽게 실행 취소와 다시 실행을 구현할 수 있습니다.
현재 위치를 기반으로 그림을 그려주는 메소드는 DrawManager의 DrawAll 부분에 구현 되어있습니다.
|