태터데스크 관리자

도움말
닫기
적용하기   첫페이지 만들기

태터데스크 메시지

저장하였습니다.

[Vision] MFC Architect - MFC 구조

Posted on 2008.05.25 21:16
Filed Under 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 기반에서 수행되고 있는 점이나 , 클래스의 의도가 당초 의도와 많이 달라져서 의미가 좀 틀린 부분, 혹은 처음부터 네이밍이 개판인 문제도 있습니다.  위에서 언급했다 시피 프로젝트에 의존적이라 어쩔 수 없이 프로젝트의 일부 코드를 섞어서 적었습니다.

발표를 해야되는데 이번 주 진행한 내용이 전무해서 땜빵으로 만드는 자료라 정리가 안된 게 타격이 크네요.

다음에 기회가 되면 정리를 해서 매끈하게 업그레이드 해보겠습니다. (이 말은 안하겠다는 말)

댓글을 달아 주세요

About



모바일 페이지 QR 코드

Counter

· Total
: 509,495
· Today
: 1
· Yesterday
: 8


DNS server, DNS service