개발/VC++

[스크랩] VC 2012 MFC 분할윈도우 만들기

99iberty 2015. 3. 16. 15:45

 

http://micropilot.tistory.com/2161

 

 

CSplitterWnd 클래스는 Visual C++ 에서 MFC 기반 프로그램을 작성할 때 화면에 다수개의 뷰를 보여줄 수 있도록 화면을 분할하는 기능을 가진 클래스이다

화면을 분할하여 보여주기 위해서는 먼저 프로젝트 생성시부터 염두에 두고 설정해주어야 하는 항목이 있다.


분할 윈도우를 사용하기 위한 프로젝트 설정 사항 (VC++ 2012 의 설정)


1. Document/View 구조를 필수적으로 선택



2. Split window 항목 체크


위와같이 설정한 후에 생성된 프로젝트는 MainFrm.h 파일과 MainFrm.cpp 파일에 약간 특별한 코드가 더 추가되어 있다.


MainFrm.h

#pragma once


class CMainFrame : public CFrameWnd

{

protected: // serialization에서만 만들어집니다.

CMainFrame();

DECLARE_DYNCREATE(CMainFrame)


// 특성입니다.

protected:

CSplitterWnd m_wndSplitter;



MainFrm.cpp

BOOL CMainFrame::OnCreateClient(LPCREATESTRUCT /*lpcs*/,

CCreateContext* pContext)

{

return m_wndSplitter.Create(this,

2, 2, // TODO: 행 및 열의 개수를 조정합니다.

CSize(10, 10), // TODO: 최소 창 크기를 조정합니다.

pContext);

}


위와 같이 생성된 코드를 그대로 빌드하면 화면이 분할되어 있는지 잘 확인이 되지 않지만 화면의 우상단에 분할선이 붙어 있기 때문에 분할되지 않은 것처럼 보인다. 마우스를 분할선에 위치하면 마우스 포인터가 분할선을 상하로 이동할 수 있도록 변경된다.

좌우분할도 마찬가지로 화면의 좌하단 모서리에 분할선 조정이 가능하도록 되어 있으므로 마우스로 드래그하여 분할선을 다른 곳으로 위치할 수 있다.


위의 함수를 아래와 같이 변경하면 상하로 화면이 분할되어 처음 나타날 때부터 분할된 화면을 볼 수 있다.

상하로 분할된 윈도우의 각 Pane에는 CFormViewCListView의 파생클래스를 배정할 예정이므로 MyFormView, MyListView 처럼 파생클래스를 생성해야 한다.

MainFrm.h

#pragma once


#include "MyFormView.h"

#include "MyListView.h"


class CMainFrame : public CFrameWnd

{

protected: // serialization에서만 만들어집니다.

CMainFrame();

DECLARE_DYNCREATE(CMainFrame)


// 특성입니다.

protected:

CSplitterWnd m_wndSplitter;


MainFrm.cpp

BOOL CMainFrame::OnCreateClient(LPCREATESTRUCT /*lpcs*/,

CCreateContext* pContext)

{

// Create a splitter with 2 rows and 1 column

if(!m_wndSplitter.CreateStatic(this, 2, 1))

{

AfxMessageBox(_T("Failed to create static splitter"));

return FALSE;

}


if(!m_wndSplitter.CreateView(0,0, RUNTIME_CLASS(MyFormView), CSize(0,100), pContext))

{

AfxMessageBox(_T("Failed to create first pane of nested splitter"));

return FALSE;

}


if(!m_wndSplitter.CreateView(1,0, RUNTIME_CLASS(MyListView), CSize(0,0), pContext))

{

AfxMessageBox(_T("Failed to create second pane of splitter"));

return FALSE;

}


return TRUE;

}


위의 코드에서 사용된 MyFormView 클래스는 CFormView 를 기반으로 파생된 클래스이기 때문에 클래스를 생성하면 Resources 에서 Dialog 항목에 등록이 되므로 화면 우측에 있는 툴박스에서 콘트롤을 원하는대로 드래그하여 폼 내용을 마음대로 편집할 수가 있다.

또한 분할된 화면의 아래 쪽 Pane에 사용된 MyListView는 CListView를 기반으로 생성한 클래스이다. CWnd 클래스를 상속한 모든 하위 클래스들은 분할된 윈도우의 각 Pane에 사용될 수 있다.


프로젝트를 빌드하고 실행하면 다음과 같이 분할된 윈도우를 볼 수 있다 (윈도우 상부는 CFormView의 파생클래스를 설정하였기 때문에 리스스의 Dialog항목에서 편집하였고 하부는 CListView의 파생클래스를 설정한 것이다)



 


화면의 좌우분할은 위의 코드에서 아래처럼 약간만 변경해주면 된다.

BOOL CMainFrame::OnCreateClient(LPCREATESTRUCT /*lpcs*/,

CCreateContext* pContext)

{

if(!m_wndSplitter.CreateStatic(this, 1, 2))

{

AfxMessageBox(_T("Failed to create static splitter"));

return FALSE;

}


if(!m_wndSplitter.CreateView(0,0, RUNTIME_CLASS(MyFormView), CSize(300,0), pContext))

{

AfxMessageBox(_T("Failed to create first pane of nested splitter"));

return FALSE;

}


if(!m_wndSplitter.CreateView(0,1, RUNTIME_CLASS(MyListView), CSize(0,0), pContext))

{

AfxMessageBox(_T("Failed to create second pane of splitter"));

return FALSE;

}


return TRUE;

 

}

 



위의 코드를 사용하여 빌드하고 실행하면 아래처럼 좌우분할된 윈도우를 확인할 수 있다




개인적으로 계획하고 있던 프로젝트를 위해서 다음과 같이 분할된 화면이 필요하므로 프로젝트 설정부터 순서대로 알아보고자 한다

왼쪽에는 Navigation 영역이 있고 오른쪽에는 상하로 분할된 영역의 상부에는 CFormView 파생클래스를 배치하고 아래쪽 영역에는 CListView 파생클래스를 배치하려고 한다



프로젝트 설정












프로젝트 설정의 마지막 창에서는 사용할 뷰 클래스의 베이스 클래스를 지정한다 여기서는 CListView를 지정하기로 한다. 그러나 꼭 CListView를 기반클래스로 해야 하는 것은 아니고, 여러 자료를 참조한 결과 오히려 CView 클래스를 기반으로 하여 CListCtrl 을 멤버로 선언하고 사용하는 것이 더 용이하는 의견이 많았다. CView를 기반클래스로 설정하고 CListCtrl 을 멤버로 선언하여 사용하는 방법에 대해서는 이 글의 하단에 소개한다.



위와 같이 설정한 프로젝트를 빌드하고 실행하면 다음과 같은 화면을 볼 수가 있다




위와 같이 분할된 화면을 가능하게 한 코드는 다음과 같다

BOOL CMainFrame::OnCreateClient(LPCREATESTRUCT /*lpcs*/,

CCreateContext* pContext)

{

return m_wndSplitter.Create(this,

2, 2, // TODO: 행 및 열의 개수를 조정합니다.

CSize(10, 10), // TODO: 최소 창 크기를 조정합니다.

pContext);

}



위의 코드를 아래처럼 변경하고 CFormView를 기반으로 MyFormView를 생성하고 MyFormView를 리소스의 Dialog 항목에서 편집하여 원하는 콘트롤을 배치하고 프로젝트를 다시 빌드/실행하면 된다, 디폴트로 생성된 CNavSplitView 클래스는 CListView 클래스를 기반으로 생성되도록 프로젝트를 설정했으며 이 클래스를 아래쪽 Pane에 배치하려고 한다

BOOL CMainFrame::OnCreateClient(LPCREATESTRUCT /*lpcs*/,

CCreateContext* pContext)

{

/*

return m_wndSplitter.Create(this,

2, 2, // TODO: 행 및 열의 개수를 조정합니다.

CSize(10, 10), // TODO: 최소 창 크기를 조정합니다.

pContext);

*/

if(!m_wndSplitter.CreateStatic(this, 2, 1))

{

AfxMessageBox(_T("Failed to create static splitter"));

return FALSE;

}


if(!m_wndSplitter.CreateView(0,0, RUNTIME_CLASS(MyFormView), CSize(0,100), pContext))

{

AfxMessageBox(_T("Failed to create first pane of nested splitter"));

return FALSE;

}


if(!m_wndSplitter.CreateView(1,0, RUNTIME_CLASS(CNavSplitView), CSize(0,0), pContext))

{

AfxMessageBox(_T("Failed to create second pane of splitter"));

return FALSE;

}


return TRUE;

}



리소스 > Dialog 에서 MyFormView 를 편집한다




프로젝트를 저장하고 빌드/실행하면 다음과 같이 원하는 화면분할을 확인할 수 있다



위의 그림에서 아래쪽 Pane 의 뷰로 사용된 CListView는 CSplitterWnd 클래스에 의해 내부적으로 인스턴스가 생성되므로 개발자가 직접 인스턴스를 생성하여 사용할 수는 없다. CSplitterWnd 클래스의 내부에 포함된 뷰 클래스의 인스턴스는 다음과 같은 방법으로 그 포인터를 구할 수가 있다

int row = 1, col = 0;

CNavSplitView* pListView = (CNavSplitView*)m_wndSplitter.GetActivePane(&row, &col);

CListCtrl& refListCtrl = pListView->GetListCtrl();

또한 CListView클래스는 내부에 CListCtrl을 포함하고 있으므로 위와 같은 방법으로 CListCtrl을 구하고 아이템을 제어할 수 있다.


위의 코드에서 몇가지 주의할 점은 m_wndSplitter.GetActivePane() 함수는 파라미터 타입이 정수포인터라는 점이다. MSDN참조

또 한가지 주의할 점은 CListView 클래스의 GetListCtrl() 함수는 리턴타입이 참조형(CListCtrl&)이라는 점이다.


CListView 안에서는 다음과 같이 간편하게 CListCtrl& 를 구할 수 있다

CListCtrl& listCtrl = GetListCtrl();



CView 기반클래스에서 CListCtrl 사용하기


화면에 데이터를 정렬할 때 리스트 기능이 필요하다고 해서 반드시 CListView 클래스를 기반 클래스로 설정할 필요는 없고, 프로젝트를 설정할 때 마지막 설정창에서 CView 클래스를 기반클래스로 설정하고 프로젝트가 생성된 후에 CView 기반으로 생성된 클래스 안에 CListCtrl 멤버변수를 선언하여 OnInitialUpdate() 함수를 오버라이드하면 된다


void CNavSplitView::OnInitialUpdate()

{

CView::OnInitialUpdate();

// TODO: Add your specialized code here and/or call the base class


m_ListCtrl.Create(

WS_CHILD|WS_VISIBLE|WS_BORDER|LVS_REPORT|LVS_EDITLABELS, CRect(0,0,800,500), this, NULL);

m_ListCtrl.SetExtendedStyle( LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES );

m_ListCtrl.DeleteAllItems();

m_ListCtrl.InsertColumn(0, _T("순번"), LVCFMT_LEFT, 40, -1);

m_ListCtrl.InsertColumn(1, _T("환자이름"), LVCFMT_LEFT, 70, -1);

m_ListCtrl.InsertColumn(2, _T("환자번호"), LVCFMT_LEFT, 70, -1);

m_ListCtrl.InsertColumn(3, _T("환자성별"), LVCFMT_LEFT, 70, -1);

m_ListCtrl.InsertColumn(4, _T("진료날짜"), LVCFMT_LEFT, 70, -1);

m_ListCtrl.InsertColumn(5, _T("사진파일"), LVCFMT_LEFT, 600, -1);

//칼럼 추가 인덱스, 칼람명, 정렬방향, 칼럼길이, 서브아이템 갯수

int seq = 0;

CString seq_string;

seq_string.Format(_T("%d"),seq);

CString patientName =_T("홍길동");

CString patientID =_T("365412");

CString patientSex =_T("M");

CString studyDate =_T("20120321");

CString dcmFullPath =_T("D:\\DCM ROOT\\365412\\20123021\\홍길동.dcm");


m_ListCtrl.InsertItem(seq,seq_string);

m_ListCtrl.SetItem(seq,1,LVIF_TEXT,patientName,0,0,0,NULL);

m_ListCtrl.SetItem(seq,2,LVIF_TEXT,patientID,0,0,0,NULL);

m_ListCtrl.SetItem(seq,3,LVIF_TEXT,patientSex,0,0,0,NULL);

m_ListCtrl.SetItem(seq,4,LVIF_TEXT,studyDate,0,0,0,NULL);

m_ListCtrl.SetItem(seq,5,LVIF_TEXT,dcmFullPath,0,0,0,NULL);


seq = 1;

seq_string.Format(_T("%d"),seq);

patientName =_T("박환자");

patientID =_T("145632");

patientSex =_T("F");

studyDate =_T("20130215");

dcmFullPath =_T("D:\\DCM ROOT\\145632\\20130215\\박환자.dcm");


m_ListCtrl.InsertItem(seq,seq_string);

m_ListCtrl.SetItem(seq,1,LVIF_TEXT,patientName,0,0,0,NULL);

m_ListCtrl.SetItem(seq,2,LVIF_TEXT,patientID,0,0,0,NULL);

m_ListCtrl.SetItem(seq,3,LVIF_TEXT,patientSex,0,0,0,NULL);

m_ListCtrl.SetItem(seq,4,LVIF_TEXT,studyDate,0,0,0,NULL);

m_ListCtrl.SetItem(seq,5,LVIF_TEXT,dcmFullPath,0,0,0,NULL);

}


위의 코드를 빌드하고 실행하면 다음과 같은 결과화면을 볼 수 있다.