[Vision] MFC Architect - MFC 구조
Posted on 2008/05/25 21:16
Filed Under Complete project
이 글은 Furyheimdall 에 의해 furyheimdall.springnote.com 에서 작성되었습니다.
퍼가실 때는 furyheimdall.springnote.com 혹은 furyheimdall.tistory.com 을 표기해주세요.
1. 요 약#
MFC MDI 구조에서는 차일드를 관리하거나 다중(뷰or문서) 에 관한 인터페이스에 대한 지원이 거의 없음.Multi View - Single Document 구조를 이용하여 단일 데이터에서 여러가지 분석 결과를 보여주는 이미지 처리를 위한 인터페이스를 설계.
2. 구 현#
2.1 Document Template#
[code]BOOL C<ProjectName>App::InitInstance(){
...
//노멀뷰 템플릿 등록
CMultiDocTemplate* pViewTemplate = new CMultiDocTemplate(IDR_<ProjectName>TYPE,
RUNTIME_CLASS(C<ProjectName>Doc),
RUNTIME_CLASS(CChildFrame),
RUNTIME_CLASS(C<ProjectName>View));
if (!pViewTemplate)
return FALSE;
AddDocTemplate(pViewTemplate);
//스크롤뷰 템플릿 등록
CMultiDocTemplate* pScrollViewTemplate = new CMultiDocTemplate(IDR_<ProjectName>TYPE,
RUNTIME_CLASS(C<ProjectName>Doc),
RUNTIME_CLASS(CChildFrame),
RUNTIME_CLASS(C<ProjectName>ScrollView));
if (!pScrollViewTemplate)
return FALSE;
AddDocTemplate(pScrollViewTemplate);
...
}[/code]
기본적으로 MDI 환경으로 프로젝트를 생성하면 <ProjectName>.cpp 에 있는 CTeamCPP 클래스의 멤버 InitInstance()에 하나의 템플릿을 등록하는 코드가 생성되어 있습니다. 이 템플릿은 Document, Frame, View같은 MFC 기본구조에 해당하는 동적클래스의 정보를 가짐으로서 템플릿으로 부터 객체를 생성할 수 있게 됩니다.
위 코드에서는 View 가 다른 두가지 템플릿을 등록하여 Multi-View , Single-Document 의 구조의 기반을 작성합니다.
- C<ProjectName>ScrollView 클래스는 CScrollView 에서 상속받아 새로 작성한 클래스 입니다.
2.2 CMainFrame#
[CODE]class CMainFrame : public CMDIFrameWndEx{
...
/* Common Document */
CDocument* CommonDoc;
CChildFrame* CreateChildWindow(int ChildType, int ViewType, CString WndName, CRect MoveVal);
...
};[/CODE]
MainFrame 클래스의 추가된 코드입니다.
윈도우를 생성시 동일한 Document 를 가지도록 CommonDoc 라는 CDocument 의 포인터를 생성하였습니다.
그리고 새로운 윈도우를 생성하기 위한 CreateChildWindow 메소드를 정의하고 있습니다.
[code]CChildFrame* CMainFrame::CreateChildWindow(int ChildType, int ViewType, CString WndName, CRect MoveVal)
{
POSITION pos = ((CTeamCPPApp*)AfxGetApp())->GetFirstDocTemplatePosition( );
CDocTemplate* pDocTemplate = dynamic_cast<CTeamCPPApp*>(AfxGetApp())->GetNextDocTemplate(pos);
for(int i = 0 ; i< ChildType ; i++)
pDocTemplate = dynamic_cast<CTeamCPPApp*>(AfxGetApp())->GetNextDocTemplate(pos);
CFrameWnd* pFrame;
if(ChildType == CT_SCROLL){ //CT_SCROLL 은 메인 뷰 타입용 (즉, 처음 생성 윈도우)
CommonDoc = pDocTemplate->CreateNewDocument( ); //새로운 도큐멘츠를 생성
pFrame = pDocTemplate->CreateNewFrame( CommonDoc, NULL ); //생성한 도큐멘트를 이용하여 차일드 프레임을 생성
pDocTemplate->InitialUpdateFrame( pFrame, CommonDoc );
}
else //일반 차일드 윈도우 생성시점, 공용 도큐멘트와 연결한다.
pFrame= dynamic_cast<CTeamCPPDoc*>(CommonDoc)->CreateNewWindow(pDocTemplate,CommonDoc);
dynamic_cast<CChildFrame*>(pFrame)->VIEWTYPE = ViewType; //뷰타입 설정
dynamic_cast<CChildFrame*>(pFrame)->SetTitle(WndName); //윈도우 이름을 변경
dynamic_cast<CChildFrame*>(pFrame)->MoveWindow(&MoveVal); //윈도우 사이즈 변경
return (CChildFrame*)pFrame;
}[/code]
CreateWindow 는 위 코드처럼 새로운 ChildWindow 를 생성하는 코드이며 몇가지 추가적으로 윈도우 정보를 설정하고 있습니다.
//차일드 윈도우 스타일 (Child Style)
#define CT_NORMAL 0
#define CT_SCROLL 1
ChildStyle 는 위와 같이 정의되어 있는데 이 값은 ChildWindow 를 구분하기위한 식별자와 동시에 Document template 에 등록된 인덱스이기도 합니다.
즉 이 ChildStyle 로 Template 를 찾아서 해당하는 Template 에 대해서 Frame과 Document 를 생성시키게 됩니다.
중요한 부분은 현재 위 코드상에서는 CT_SCROLL일때와 아닐때를 기준으로 Document 를 생성시키느냐, 기존 Document 를 연결해서 쓰느냐를 구분하고 있습니다.
진행중인 프로젝트 상에서는 CT_SCROLL은 원 이미지를 가져올 때 가장 처음 열리는 윈도우기 때문에 저렇게 사용하고 있지만,
일반적으로 가장 처음 윈도우를 생성시킬때 만들어지게 하는것이 좋으므로 CommonDoc 의 NULL 체크를 통해서 생성하는게 좋지 않을까 합니다.
그리고 CDocument* 로 선언한 이유는 Document 타입이 여러가지 인 경우(Multi-View , Multi-Document 인 경우)도 있기 때문입니다.
참고로 C<ProjectName>Doc 를 직접 생성하는 것도 불가능 합니다. (동적클래스 타입이기 때문에 생성자가 protected 로 선언되어 있습니다)
2.3 CChildFrame#
[code]class CChildFrame : public CMDIChildWndEx
{
...
int VIEWTYPE; //차일드의 식별자 (차일드에 소속된 View 도 같은 식별자를 지닌다)
...
}
[/code]
CChildFrame는 자신을 누가 식별할 수 있도록 식별자를 두고 있습니다.
[code]//차일드 윈도우를 닫을때의 작업 (이 루틴은 메인 윈도우를 닫을 때는 수행되지 않는다)
void CChildFrame::OnClose()
{
CViewManager* Obj = WndManager.FindViewManager(this); //메세지를 받은 차일드창이 자신의 참조 포인터로 뷰메니저를 구함
if(VT_MAINWINDOW == WndManager.DeleteWindow(Obj)){ //그 윈도우를 리스트에서 삭제하고 삭제한 윈도우가 카메라 창이라면
if( COMPARE_FLAG(IS_TYPE) == IT_CAMERA){ //카메라창의 타입이 IT_CAMERA (카메라 연결상태) 일 경우
m_CamControl.Close();
m_CamControl.Init();
CLEAR_FLAG(IS_SELECT_INTERFACE); //인터페이스 선택 플래그를 없앰
CLEAR_FLAG(IS_START_GRAB); //그랩을 중지상태로 플래그 셋팅
}
else { //카메라창의 타입이 IT_IMAGE (이미지 연결상태) 일 경우
CLEAR_FLAG(IS_SELECT_INTERFACE); //인터페이스 선택 플래그를 없앰
}
}
else //프로세스 항목의 플래그를 돌려주는 코드 삽입
;
//CMDIChildWndEx::OnClose();
}[/code]
그리고 차일드를 닫을때의 작업입니다.
현재 별도로 제작한 Window Manager 클래스와 View Manager 클래스가 등장하고 있습니다.
Window Manager 클래스와 View Manager 클래스는 다음 링크를 참조하세요.
2.4 C<ProjectName>Doc#
[code]class C<ProjectName>Doc : public CDocument{
...
void AutomationProcess(int ChildType, int ViewType, CString WindowName,IplImage* Iplimg); //화면출력을 전담
...
}[/code]
C<ProjectName>Doc 클래스는 AutomationProcess 라는 메소드를 두고 있습니다.
이 메소드는 새로운 창 생성을 당담하게 되는데 기본적인 윈도우 생성과 함께 생성된 윈도우를 관리하는 View Manager을 생성하고 설정을 하게 됩니다.
[code]void C<ProjectName>Doc::AutomationProcess(int ChildType, int ViewType, CString WindowName, IplImage* Iplimg)
{
CChildFrame* Object = (CChildFrame*)WndManager.FindChildFrameFromViewType(ViewType);
CTeamCPPView *pView;
if(Object == NULL){
CChildFrame* NewObj =
((CMainFrame*)AfxGetMainWnd())->CreateChildWindow(ChildType,ViewType,WindowName
,CRect(0,0,CT_SIZE(Iplimg->width,Iplimg->height)));
//Window Create
pView = (CTeamCPPView*)NewObj->GetActiveView();
pView->ActiveView = WndManager.AddWindow(NewObj);
pView->VIEWTYPE = ViewType; //View type set of the New Window's View
View->ActiveView->IplRegister(Iplimg);
}
UpdateAllViews(NULL);
}[/code]
실제 코드를 보면 Window Manager 로 부터 해당 ViewType 의 ChildFrame 포인터를 얻어냅니다.
//차일드의 아이덴티티를 판별하는 뷰타입(ViewType) 정의
#define VT_MAINWINDOW 10
#define VT_GHISTOGRAM 11
#define VT_CHISTOGRAM 12
#define VT_THRESHOLD 13
ViewType 는 위와 같이 정의 되어있습니다. ChildFrame 를 식별하기 위해 위에서 VIEWTYPE 란 멤버를 추가했었지요.
이 ViewType 으로 현재 ChildWindow가 열려있는지 열리지 않았는지 체크를 하게 됩니다.
만약 열려있지 않은 ChildWindow 라면 저 위 페이지의 MainFrame 의 멤버인 CreateChildWindow를 호출하여 새 윈도우를 생성하는 작업을 하고,
열려있는 윈도우라면 단순히 현재 화면을 업데이트만 하게 됩니다.
ViewType 은 고유하며 같은 ViewType을 가진 녀석들이 AutomationProcess 메소드를 호출하게 되면 처음 녀석만 윈도우를 생성하고 그 이후부터는 생성된 윈도우에 업데이트만 하게 됩니다.
이런 방식은 현재 프로젝트때문에 이런 방식으로 구조를 잡았고, 만약 같은 타입의 ChildWindow 가 여러개 열리도록 하고 싶다면 위 처럼 단순하게 User Define 상수에 의존하는게 아닌 고유한 ID 를 주고 받고 하는 설계가 필요하겠습니다.
2.5 C<ProjectName>View#
[code]class C<ProjectName>View : public CView{
...
CViewManager *ActiveView; //현재 뷰가 소속되어있는 뷰매니저
int VIEWTYPE; //뷰의 식별자
...
}[/code]
MFC 구조에서 Default 로 생성되는 View 클래스입니다.
View 클래스도 ChildFrame 와 같은 식별자 VIEWTYPE 를 가집니다.
그리고 ActiveView 라는 CViewManager 를 가지게 됩니다.
[code]void C<ProjectName>View::OnDraw(CDC* /*pDC*/)
{
if(ActiveView != NULL)
ActiveView->ShowImage();
}[/code]
View 클래스에서 ActiveView 를 가지는 이유는 위와 같이 OnDraw 구문때문입니다.
현재 WindowManager 에서 View 객체의 포인터로 윈도우를 찾는 메소드를 넣었을 때 런타임 에러가 간혹 나는 경우가 있어서 위와 같은 방법으로 사용하고 있습니다.
많이 보기 좋지 않는 코드지만 차 후 실시간으로 그려야 한는 문제라던지 속도에 민감할 경우 위와 List 에서 찾는 것보다 직접 그 객체포인터를 들고 있는 방법이 좋다고 생각되는 이유도 있었습니다.
2.6 C<ProjectName>ScrollView#
[code]class C<ProjectName>ScrollView : public CScrollView{
...
void Display(unsigned char*);
...
}[/code]
ScrollView 는 Major Window 전용으로 하나의 작업에서 단 하나만 생성되는 윈도우 타입입니다.
ScrollView 클래스에서는 입력받는 카메라 영상에 대해 처리하기 위한 Display 메소드가 존재합니다.
현재 Camera 클래스에서는 자체적으로 가지는 Display 메소드를 내부가 아닌 외부에서 구현하도록 설계되어 있습니다.
[code]//카메라 디스플레이는 스크롤뷰에서만 이뤄짐
C<ProjectName>ScrollView* RefClass;
void CCamera::Display(unsigned char* Buffer)
{
RefClass->Display(Buffer);
}[/code]
ScrollView 의 자체적인 포인터를 전역으로 RefClass 라는 식별자로 선언하였습니다.
이것은 Camera 의 Display 메소드를 구현하기 위함인데 Camera 클래스의 Display 메소드는 전역 포인터를 참조로 C<ProjectName>ScrollView 클래스의 멤버인 Display메소드로 이미지버퍼를 전달하는 역할을 합니다.
[code]void C<ProjectName>ScrollView::Display(unsigned char* imagebuf)
{
/* Resize Buffer */
vector<unsigned char> pImageresizeOrgBuffer(m_CamControl.reSizeWidth*m_CamControl.m_iHeight*3); // 4byte배수 이미지
for(int y=0; y<m_CamControl.m_iHeight*3; y++) //width가 4의 배수가 아닌경우 ex 659 x 494 -> 660 x 494로 표현
memcpy(&pImageresizeOrgBuffer[y*m_CamControl.reSizeWidth],&imagebuf[y*m_CamControl.m_iWidth],m_CamControl.m_iWidth);
CSize Size;
Size.cx = m_CamControl.reSizeWidth;
Size.cy = m_CamControl.m_iHeight;
/* YUV 4:2:2 이미지를 RGB로 변환하여 도큐멘트에 저장한다 */
if(RefClass != NULL) CCamera::ConvertYUV422ToRGB((PBYTE)(GetDocument()->OrgImage->imageData), &pImageresizeOrgBuffer[0], Size);
/* 입력받은 영상을 표시하도록 갱신 메세지 보냄 */
Invalidate(FALSE);
}
[/code]
그리고 그 이미지 버퍼를 전달받는 C<ProjectName>ScrollView 내의 Display 메소드는 카메라의 영상을 가공하여 화면에 표시하도록 합니다.
3. 문제점#
MFC 지식이 거진 없는 상태에서 헤딩... 그것도 MDI 에 자료도 잘 없는 Multi-View, Single-Document 구조를 만든거라 설계가 개판이고 범용적으로 쓸려면 상당한 수정이 가해져야 되는 거의 제가 진행하는 프로젝트 자체에 디펜던시를 가지고 있습니다.
구조 자체가 WindowManager 과 ViewManager 기반에서 수행되고 있는 점이나 , 클래스의 의도가 당초 의도와 많이 달라져서 의미가 좀 틀린 부분, 혹은 처음부터 네이밍이 개판인 문제도 있습니다. 위에서 언급했다 시피 프로젝트에 의존적이라 어쩔 수 없이 프로젝트의 일부 코드를 섞어서 적었습니다.
발표를 해야되는데 이번 주 진행한 내용이 전무해서 땜빵으로 만드는 자료라 정리가 안된 게 타격이 크네요.
다음에 기회가 되면 정리를 해서 매끈하게 업그레이드 해보겠습니다. (이 말은 안하겠다는 말)
'Complete project' 카테고리의 다른 글
| [Vision] Window Manager Class - 윈도우 관리 클래스 (0) | 2008/05/25 |
|---|---|
| [Vision] View Manager Class - 뷰 관리자 클래스 (0) | 2008/05/25 |
| [Vision] MFC Architect - MFC 구조 (0) | 2008/05/25 |
| [Vision] Camera management class - 카메라 관리 클래스 (0) | 2008/04/08 |
| [Vision] Display management class - 화면 출력 관리 클래스 (0) | 2008/04/08 |
| [Vision] Log management class - 로그 관리 클래스 (0) | 2008/03/28 |




댓글을 달아 주세요