본문 바로가기

Library/Windows Programming

CView를 어떻게 생성해야 하는가?

SDI 형태든, MDI 형태든 CView를 생성해서 CDocument와 연결하려고 하면 가장 먼저 다음과 같은 의문이 떠오른다. 어느 위치에서, 언제 CView를 생성해야 하는가?


먼저, 이론적으로 가장 적절한 위치는 ProcessShellCommand를 호출한 다음, 그 다음 필요한 View를 생성해서 CDocument와 연결하는 것이 가장 적절해 보인다. 따라서, CWinApp::InitInstance()가 적절한 위치가 될 것이다. 그렇지만, 가장 중요한 것은, 어떻게 CView를 생성하느냐이다. 물론, CView 역시 CWnd에서 파생받은 개체이므로 CWnd::Create()를 사용할 수 있을 것 같지만, CWnd를 생성하는 것처럼 Create()를 생성하고 CDocument::AddView()를 사용하면 AddView()는 실패한다. 그러면 어떻게 해야하는가?

CWnd::Create를 호출할 때의 인자를 생각해보면, 보통 마지막 CCreateContext* pContext를 NULL로 설정했을 것이다. CView를 생성할 때는, 이 CCreateContext를 설정해주어야 한다. MFC 프레임워크에서는, Document Template에 Document, View, FrameWindow를 묶는 과정에서 CreateNewDocument(), CreateNewView()를 호출하는데, CreateNewView()의 동작 도중 CCreateContext 정보를 생성해서 CView를 생성하는데 정보를 넘겨준다. CCreateContext는 현재의 FrameWindow, Document, 생성하려는 View 따위의 정보를 저장하여 CView를 생성하는데 필요한 정보다. 따라서, 우리가 새로운 CView 개체를 생성하기 위해서는 이 CCreateContext 구조체의 정보를 수동으로 채워서 CView를 생성해야 한다. CCreateContext::m_pCurrentDoc를 현재의 CDocument 개체로 설정하고, 나머지는 NULL로 설정한다. (필요에 따라 적절하게 설정할 수도 있다) 즉, 다음과 같다.


CCreateContext newContext;
...
newContext.m_pCurrentDoc = (CDocument*)((FrameWnd*)AfxGetMainWnd())->GetActiveDocument();
..

CView* view = new CView;
view->Create(..., &newContext);
..
view->SendMessage(WM_INITIALUPDATE);


여기서, WM_INITIALUPDATE는 afxpriv.h를 포함하지 않으면 컴파일이 되지 않는다. 일반적으로, 새로운 View가 Document에 추가될 때 WM_INITIALUPDATE를 보내게 되어 있지만, 명시적으로 한번 더 보내주는게 좋다. 이 메세지가 보내지면 각 CView는 OnInitialUpdate()를 호출하여 필요한 초기화 작업을 하게 된다. 이것은 CScrollView와 같은 경우에 특히 더 중요한데, CScrollView는 SetScrollSizes()를 설정하지 않으면 런타임 에러가 발생하기 때문이다.


CFrameWnd::CreateView() 메서드를 사용하더라도 View를 생성할 수 있다. 위의 방법대로 View를 사용한다면, CView를 파생받은 개체의 생성자는 public로 선언되어야 한다. AppWizard로 클래스를 생성하였다면, 생성자는 protected로 지정되어 있을 것이다. 위의 방법을 사용하려면, 생성자의 액세스 지정자를 public으로 선언해주어야 한다. CFrameWnd::CreateView()를 사용한다면, 이런 번거로운 작업이 필요없다. 방법은 다음과 같다. CView를 파생받은 개체를 CDerivedView라고 하자. CView에는 순수 가상함수인 OnDraw()가 있기 때문에, 이것을 필수적으로 재지정해야 한다. 그리고, CCreateContext 구조체를 채워줘야 하는 것은 변함이 없다. 코드는 다음과 같을 것이다.


CCreateContext newContext;
...
newContext.m_pNewViewClass = RUNTIME_CLASS(CDerivedView);
newContext.m_pCurrentFrame = (CFrameWnd*)AfxGetMainWnd();
newContext.m_pCurrentDoc = (CDocument*)((FrameWnd*)AfxGetMainWnd())->GetActiveDocument();
...

CDerivedView* pNewView = ((CFrameWnd*)AfxGetMainWnd)->CreatView(&newContext);
pNewView->SendMessage(WM_INITIALUPDATE);


그렇지만, SDI 환경에서 새로운 CView 클래스를 추가했다고 해서 동시에 한 화면에 여러개의 View를 띄울 수 없다는 점을 주의하라. 만약, SDI 환경에서 동시에 View를 보여준다면, 반드시 특정 View는 작동이 되지 않는, 먹통 현상이 발생할 것이다. SDI는 애초에 이런 목적으로 설계된 것이 아니며, SDI 환경에서 동시에 여러 작업 상황을 보여주고 싶다면 스플릿뷰를 사용하는게 좋다. 다만, 여러 개의 뷰를 등록하고, 전체 화면에서 이들 뷰를 전환하는 것은 가능하다. 이 경우, 뷰를 전환하면서 메세지를 받을 뷰를 선택해주는 코드를 추가로 작성해주어야 한다.

한 화면에서 여러개의 뷰를 보여주고 싶다면 MDI 환경을 사용하는게 좋다.