MFC로 프로젝트를 만들 때는 다음과 같이 프로젝트 템플릿에서 MFC 앱을 클릭해야한다.

그리고 프로젝트 이름, 경로 설정 후에 다음을 누르면 애플리케이션의 종류가 보일 것이다.

 

애플리케이션의 종류는 다음과 같다.

단일 문서 : 뷰 클래스를 기반으로 응용 프로그램에 대 한 단일 문서 인터페이스 (SDI) 아키텍처를 만듦

여러 문서 : 뷰 클래스를 기반으로 응용 프로그램에 대 한 여러 문서 MDI (인터페이스) 아키텍처를 만듦

대화 상자 기반 : 대화 상자 클래스를 기반으로 하는 응용 프로그램에 대 한 대화 상자 기반 아키텍처를 만듦

여러 최상위 문서 : 뷰 클래스를 기반으로 응용 프로그램에 대 한 여러 최상위 아키텍처를 만듦

 

그 다음 프로젝트 스타일을 설정해야한다.

일반적으로는 MFC 기본 스타일로 만든다.

 

현재 자신이 만들 프로젝트가 어떤 것인지 확인하고,

그것에 맞게 애플리케이션 종류와 프로젝트 스타일을 선택하고 만들면된다.

이전에 UnitTool Part2를 이어서 ListBox에 추가된 정보를 저장 및 불러오기를 진행하려고한다.

 

먼저 다음과 같이 버튼을 추가하고 이벤트 처리 함수를 추가해준다.

 

void CUnitTool::OnBnClickedSave()
{
	// TODO: 여기에 컨트롤 알림 처리기 코드를 추가합니다.
}

void CUnitTool::OnBnClickedLoad()
{
	// TODO: 여기에 컨트롤 알림 처리기 코드를 추가합니다.
}

 

#1. 저장

 

MFC에서는 정보를 저장할 때 CFileDialog()라는 클래스를 사용한다.

docs.microsoft.com/ko-kr/cpp/mfc/reference/cfiledialog-class?f1url=%3FappId%3DDev14IDEF1%26l%3DKO-KR%26k%3Dk(AFXDLGS%252FCFileDialog);k(CFileDialog);k(DevLang-C%252B%252B);k(TargetOS-Windows)%26rd%3Dtrue&view=msvc-160

 

CFile Dialog 클래스

CFile Dialog 클래스CFileDialog Class 이 문서의 내용 --> 파일 열기 또는 파일 저장 작업에 사용되는 공통 대화 상자를 캡슐화합니다.Encapsulates the common dialog box that is used for file open or file save operations. 구

docs.microsoft.com

  CFileDialog Dlg(FALSE,       
	L"dat",
	L"*.dat",
	OFN_OVERWRITEPROMPT,
	L"Data File(*.dat) | *.dat||",
	this);

해당 클래스를 사용하면 기본 확장자를 따로 지정해줄 수 있다.

 

첫 번째 인자는 FALSE이면 저장 TRUE이면 불러온다는 의미이다.

 

두 번째 인자(dat)는 기본 확장자 설명 문자열이다.

 

세 번째 인자(*.dat)는 Save 버튼을 눌렀을 때 뜨는 저장 화면에서 파일 이름에 기본적으로 *.dat 파일로 지정해준다.

 

OFN_OVERWRITEPROMPT는 저장하는 대화상자에서 선택한 파일이 이미 있을 경우 파일을 덮어씌울 것인지 물어본다.

 

L"Data File(*.dat) | *.dat||"는 파일 형식 부분에 나올 텍스트와 실제로 사용될 필터이다.

여기서 | 과 ||이 있는데, 이것은 |는 caption, ||는 사용될 확장자를 의미한다.

만약 마지막에 ||가 아닌 |가 붙을 경우 화면에는 *.dat이라고 써져있지만 실제로는 default 값으로 지정되어

다른 파일 형식으로 저장 될 수가 있다. 그렇기 때문에 필터의 경우 가장 마지막에는 ||로 표시를 해줘야한다.

 

마지막 this는 부모 윈도우를 정해주는 부분이다.

 

위와 같이 코드를 정의해주면 결과는 다음과 같이 나오게된다.

이제 현재 작업을 하고 있는 경로를 가져와서

TCHAR szPath[MAX_PATH] = L"";
GetCurrentDirectory(MAX_PATH, szPath);
PathRemoveFileSpec(szPath);
lstrcat(szPath, L"\\Data");
Dlg.m_ofn.lpstrInitialDir = szPath;

GetCurrentDirectory를 통해 MAX_PATH의 사이즈만큼, szPath라는 변수에 현재 작업중인 경로를 저장한다.

그리고 PathRemoveFileSpec을 통해서 파일명이 포함된 경로에서 파일명을 제외한 경로만 남겨준다.

여기서 저장을 할 폴더가 따로 있을 경우 lstrcat을 통해서 해당 폴더 경로를 넣어주면 다음과 같이 나오게된다.

마지막 Dlg.m_ofn.lpstrInitialDir는 Save 버튼을 눌렀을 때 처음에 나오는 시작 경로를 변경해줄 때 사용한다.

기본적으로는 문서 폴더로 되어있지만 lpstrInitialDir을 통해서 다음과 같이 시작 경로를 변경 해줄 수 있다.

여기까지 진행이 완료되었으면, 이제 Tool에서 저장한 정보들을 따로 파일로 저장을 해야한다.

if (Dlg.DoModal())
	{
		CString wstrFilePath = Dlg.GetPathName(); 
		HANDLE hFile = CreateFile(wstrFilePath.GetString(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,nullptr);
		if (INVALID_HANDLE_VALUE == hFile)
		{
			ERR_MSG(L"파일 개방 실패"); 
			return; 
		}
		DWORD dwByte = 0; 
		DWORD dwStringSize = 0; 
		for (auto& rPair : m_mapUnitInfo)
		{
			dwStringSize = sizeof(wchar_t) * (rPair.second->strName.GetLength() + 1); 
			WriteFile(hFile, &dwStringSize, sizeof(DWORD), &dwByte, nullptr);
			WriteFile(hFile, rPair.second->strName.GetString(), dwStringSize, &dwByte, nullptr);
			WriteFile(hFile, &rPair.second->iAtt, sizeof(int), &dwByte, nullptr);
			WriteFile(hFile, &rPair.second->iDef, sizeof(int), &dwByte, nullptr);
			WriteFile(hFile, &rPair.second->byItem, sizeof(BYTE), &dwByte, nullptr);
			WriteFile(hFile, &rPair.second->byJop, sizeof(BYTE), &dwByte, nullptr);
		}
                
		CloseHandle(hFile); 
	}

Dlg.Domodal() 함수는 대화 상자를 열어주는 함수, 즉 우리가 Save 버튼을 눌렀을 때 나오는 창을 열어준다.

 

대화 상자를 닫아줄 때는 EndDialog()라는 함수가 있지만 저장 or 취소 버튼을 누르면 자동으로 닫히기 때문에

특별한 경우를 제외하고는 사용하지 않는다.

 

해당 함수 내에서 파일의 통로를 개방하여 저장하고 싶은 정보들을 저장하고,

대화 상자에서 저장하기를 누르면 정보가 저장이 되면서 파일의 통로가 닫히고 해당 함수가 종료된다.

 

정리된 코드는 다음과 같다.

void CUnitTool::OnBnClickedSave()
{
    // TODO: 여기에 컨트롤 알림 처리기 코드를 추가합니다.
    CFileDialog Dlg(FALSE,       
	L"dat",
	L"*.dat",
	OFN_OVERWRITEPROMPT,
	L"Data File(*.dat) | *.dat||",
	this);

	TCHAR szPath[MAX_PATH] = L"";
	GetCurrentDirectory(MAX_PATH, szPath);
	PathRemoveFileSpec(szPath);
	lstrcat(szPath, L"\\Data");
	Dlg.m_ofn.lpstrInitialDir = szPath;
	if (Dlg.DoModal())
	{
		CString wstrFilePath = Dlg.GetPathName(); 
		HANDLE hFile = CreateFile(wstrFilePath.GetString(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,nullptr);
		if (INVALID_HANDLE_VALUE == hFile)
		{
			ERR_MSG(L"파일 개방 실패."); 
			return; 
		}
		DWORD dwByte = 0; 
		DWORD dwStringSize = 0; 
		for (auto& rPair : m_mapUnitInfo)
		{
			dwStringSize = sizeof(wchar_t) * (rPair.second->strName.GetLength() + 1); 
			WriteFile(hFile, &dwStringSize, sizeof(DWORD), &dwByte, nullptr);
			WriteFile(hFile, rPair.second->strName.GetString(), dwStringSize, &dwByte, nullptr);
			WriteFile(hFile, &rPair.second->iAtt, sizeof(int), &dwByte, nullptr);
			WriteFile(hFile, &rPair.second->iDef, sizeof(int), &dwByte, nullptr);
			WriteFile(hFile, &rPair.second->byItem, sizeof(BYTE), &dwByte, nullptr);
			WriteFile(hFile, &rPair.second->byJop, sizeof(BYTE), &dwByte, nullptr);
		}
		CloseHandle(hFile); 
	}
}

 

#2. 불러오기

 

저장한 파일을 불러오기 전에 먼저 map 컨테이너를 비워주고,

ResetContent()함수를 통해ListBox를 초기화 시켜준다.

for (auto&  rPair: m_mapUnitInfo)
	Safe_Delete(rPair.second); 
m_mapUnitInfo.clear(); 
m_ListBox.ResetContent(); 

저장할 때와 같이 Load 버튼을 눌렀을 때 불러올 폴더 경로를 지정해준다.

불러오기 이므로 CFileDialog의 첫 번째 인자는 TRUE로 해준다.

CFileDialog Dlg(TRUE,
	L"dat",
	L"*.dat",
	OFN_OVERWRITEPROMPT,
	L"Data File(*.dat) | *.dat||",
	this);
    
TCHAR szPath[MAX_PATH] = L"";
GetCurrentDirectory(MAX_PATH, szPath);
PathRemoveFileSpec(szPath);
lstrcat(szPath, L"\\Data");
Dlg.m_ofn.lpstrInitialDir = szPath;

Dlg.Domodal() 함수를 통해 불러오기 창을 열어주고 해당 .dat 파일을 통해 얻어오는 정보를 빈 공간에 담아준다.

여기서 중요한 부분은 무조건 정보를 저장 했을 때와 똑같은 순서대로 불러와야한다. 

 

예를 들어

이름 -> 공격력 -> 방어력 -> 아이템 소지 여부 -> 직업

이 순서대로 저장을 하였으면 그 순서와 똑같이 불러와야 한다.

이유는 만약 저장한 순서대로 불러오지 않으면 저장한 값이 서로 바뀌기 때문이다.

 

if (Dlg.DoModal())
	{
		CString wstrFilePath = Dlg.GetPathName();
		HANDLE hFile = CreateFile(wstrFilePath.GetString(), GENERIC_READ, 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
		if (INVALID_HANDLE_VALUE == hFile)
		{
			ERR_MSG(L"파일 개방 실패.");
			return;
		}
		DWORD dwByte = 0;
		DWORD dwStringSize = 0;
		UNITINFO* pUnitInfo = nullptr; 
		while (true)
		{
			pUnitInfo = new UNITINFO;
			ReadFile(hFile, &dwStringSize, sizeof(DWORD), &dwByte, nullptr);
			TCHAR* pTemp = new TCHAR[dwStringSize]; 
			ReadFile(hFile, pTemp, dwStringSize, &dwByte, nullptr);
			ReadFile(hFile, &pUnitInfo->iAtt, sizeof(int), &dwByte, nullptr);
			ReadFile(hFile, &pUnitInfo->iDef, sizeof(int), &dwByte, nullptr);
			ReadFile(hFile, &pUnitInfo->byItem, sizeof(BYTE), &dwByte, nullptr);
			ReadFile(hFile, &pUnitInfo->byJop, sizeof(BYTE), &dwByte, nullptr);

			if (0 == dwByte)
			{
				Safe_Delete(pUnitInfo);
				if (pTemp)
				{
					delete[] pTemp; 
					pTemp = nullptr; 
				}
				break; 
			}
			pUnitInfo->strName = pTemp;
			m_mapUnitInfo.emplace(pUnitInfo->strName, pUnitInfo); 
			m_ListBox.AddString(pUnitInfo->strName);
		}
		CloseHandle(hFile);
	}

 

정리된 코드는 다음과 같다.

void CUnitTool::OnBnClickedLoad()
{
	for (auto&  rPair: m_mapUnitInfo)
		Safe_Delete(rPair.second); 
	m_mapUnitInfo.clear(); 
	m_ListBox.ResetContent(); 

	
	CFileDialog Dlg(TRUE,
		L"dat",
		L"*.dat",
		OFN_OVERWRITEPROMPT,
		L"Data File(*.dat) | *.dat||",
		this);
	
	TCHAR szPath[MAX_PATH] = L"";
	GetCurrentDirectory(MAX_PATH, szPath);
	PathRemoveFileSpec(szPath);
	lstrcat(szPath, L"\\Data");
	Dlg.m_ofn.lpstrInitialDir = szPath;
	if (Dlg.DoModal())
	{
		CString wstrFilePath = Dlg.GetPathName();
		HANDLE hFile = CreateFile(wstrFilePath.GetString(), GENERIC_READ, 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
		if (INVALID_HANDLE_VALUE == hFile)
		{
			ERR_MSG(L"파일 개방 실패.");
			return;
		}
		DWORD dwByte = 0;
		DWORD dwStringSize = 0;
		UNITINFO* pUnitInfo = nullptr; 
		while (true)
		{
			pUnitInfo = new UNITINFO;
			ReadFile(hFile, &dwStringSize, sizeof(DWORD), &dwByte, nullptr);
			TCHAR* pTemp = new TCHAR[dwStringSize]; 
			ReadFile(hFile, pTemp, dwStringSize, &dwByte, nullptr);
			ReadFile(hFile, &pUnitInfo->iAtt, sizeof(int), &dwByte, nullptr);
			ReadFile(hFile, &pUnitInfo->iDef, sizeof(int), &dwByte, nullptr);
			ReadFile(hFile, &pUnitInfo->byItem, sizeof(BYTE), &dwByte, nullptr);
			ReadFile(hFile, &pUnitInfo->byJop, sizeof(BYTE), &dwByte, nullptr);

			if (0 == dwByte)
			{
				Safe_Delete(pUnitInfo);
				if (pTemp)
				{
					delete[] pTemp; 
					pTemp = nullptr; 
				}
				break; 
			}
			pUnitInfo->strName = pTemp;
			m_mapUnitInfo.emplace(pUnitInfo->strName, pUnitInfo); 
			m_ListBox.AddString(pUnitInfo->strName);
		}
		CloseHandle(hFile);
	}
}

지난번 UnitTool Part1에서 이어서 진행을 하려고한다.

 

오브젝트한테 필요한 정보는 어떤 것이 있을까?

우선 오브젝트의 이름이 필요하다.

그리고 공격력 방어력 등의 수치와 현재 어떤 아이템을 가지고 있는지 등이 필요할 수도 있다.

 

그래서 다음과 같이 리소스 뷰를 설정 해놓는다.(리소스 뷰를 꾸미는 것은 자유!!)

변수 추가를 통해서 이름, 공격력, 방어력 등의 변수를 추가해준다.

Radio Button과 CheckBox는 따로 배열로 만들어 주었다.

 

여기까지 진행이 완료되었으면, 이제 위의 정보를 담을 구조체가 하나 필요하므로,

추가한 변수를 넣을 구조체를 하나 만들어준다.

typedef struct tagUnitInfo
{
	CString strName;
	int	iAttack; 
	int	iDef;
	BYTE	byJop;
	BYTE	byItem; 
}UNITINFO;

구조체까지 정의하였으면, 이제 UnitInfo의 정보를 담을 공간이 필요하다.

UnitInfo를 확인해보면 오브젝트 각각의 이름과 공격력 방어력 등이 있다.

여기서 오브젝트 각각의 이름은 Key 값 그 외 정보들(UnitInfo)는 Value라고 생각하면된다.

여기까지 생각해보았을 때 Key와 Value를 담을 공간, 즉 map 컨테이너를 사용하는 것이다.

 

다음과 같이 map 컨테이너의 변수를 하나 만들어준다.

private:
	map<CString, UNITINFO*> m_mapUnitInfo;

그리고 Add 버튼에 클릭 이벤트를 추가해준다.

추가를 하고나서 해당 이벤트 함수에 다음과 같이 코드를 작성한다.

UpdateData(TRUE);
auto& iter_find = m_mapUnitInfo.find(m_strName);

if (iter_find != m_mapUnitInfo.end())
	return;

UNITINFO* pUnit = new UNITINFO;
pUnit->strName = m_strName;
pUnit->iAttack = m_iAttack;
pUnit->iDef = m_iDef;
pUnit->byJop = 0;
pUnit->byItem = 0;

for (int i = 0; i < 3; ++i)
{
	if (m_Jop[i].GetCheck())
	{
		pUnit->byJop = i;
		break;
	}
}


if (m_CheckBox[0].GetCheck())
	pUnit->byItem |= SWORD;	
if (m_CheckBox[1].GetCheck())
	pUnit->byItem |= BOW;
if (m_CheckBox[2].GetCheck())
	pUnit->byItem |= SPEAR;


m_mapUnitInfo.emplace(pUnit->strName, pUnit);
m_ListBox.AddString(m_strName);
UpdateData(FALSE);

간단하게 설명하자면,

Add 버튼을 클릭하였을 때 리스트 박스에 있는 오브젝트 이름과 같은 이름이 있을 경우

새로운 정보 추가가 되지않도록 예외 처리를 해주었다.

 

만약 같은 이름이 추가가 되면 나중에 저장을 하였을 때 혼동이 찾아올 수도 있기 때문이다.

아래 GetCheck()라는 함수는 UnitTool에서 해당 체크박스를 체크하였는지 안했는지 여부를 가져오는 함수이다.

 

여기까지 하였으면 정보를 입력하고 Add 버튼을 눌렀을 때 다음과 같이 ListBox에 추가가 된 것을 볼 수 있다.

하지만 리스트에서 추가된 오브젝트를 클릭하였을 때 저장된 정보가 확인되지 않는다.

그래서 이제 리스트에 추가된 오브젝트를 클릭하면 이전에 만들었을때의 정보를 불러오게 하려고 한다.

 

우선 리소스뷰에서 리스트 박스를 클릭하고 다음과 같이 이벤트 처리기를 추가해준다.

 

추가한 이벤트 함수에 다음과 같이 정의한다.

UpdateData(TRUE);

int iIndex = m_ListBox.GetCurSel();
if (LB_ERR == iIndex)
	return;

CString strfindName;
m_ListBox.GetText(iIndex, strfindName);

auto& iter_Find = m_mapUnitInfo.find(strfindName);
if (iter_Find == m_mapUnitInfo.end())
	return;

for (int i = 0; i < 3; ++i)
{
	m_Jop[i].SetCheck(FALSE);
	m_CheckBox[i].SetCheck(FALSE);
}

m_strName = iter_Find->second->strName;
m_iAttack = iter_Find->second->iAttack;
m_iDef = iter_Find->second->iDef;
m_Jop[iter_Find->second->byJop].SetCheck(TRUE);

if (iter_Find->second->byItem & SWORD)
	m_CheckBox[0].SetCheck(TRUE);

if (iter_Find->second->byItem & BOW)
	m_CheckBox[1].SetCheck(TRUE);

if (iter_Find->second->byItem & SPEAR)
	m_CheckBox[2].SetCheck(TRUE);

UpdateData(FALSE);

위와 같이 정의한 후 실행을 시켜보면 다음과 같이 추가된 정보를 확인할 수 있게된다.

여기서 오브젝트를 추가할 때 아무런 직업도 선택을 안할경우,

제일 첫번째에 있는 직업으로 자동으로 저장이 되도록 하였다.

 

이제 오브젝트 추가에 대한 것을 끝났으니 리스트에 있는 오브젝트를 삭제하는 것을 추가해보자.

이전과 같이 버튼을 추가하고 클릭 이벤트 처리기 함수를 하나 만들어준다.

그 다음 해당 이벤트 함수에 다음과 같이 정의한다.

UpdateData(TRUE); 
int iIndex = m_ListBox.GetCurSel(); 
if (LB_ERR == iIndex)
	return; 
CString wstrName;
m_ListBox.GetText(iIndex, wstrName); 

auto& iter_find = m_mapUnitInfo.find(wstrName); 
if (m_mapUnitInfo.end() == iter_find)
	return; 
Safe_Delete(iter_find->second); 
m_mapUnitInfo.erase(wstrName); 
m_ListBox.DeleteString(iIndex); 

UpdateData(FALSE); 

 

위와같이 정의하였으면 다음과 같이 리스트에 있는 오브젝트의 정보를 Delete버튼을 클릭하여 삭제가 가능하게된다.

 

마지막으로 오브젝트를 추가한 상태로 프로그램을 종료시켰을 때 메모리 누수가 생기게될 것이다.

이유는 map 컨테이너에 원소를 추가만 한 상태로 종료를 해서 그렇다.

그러므로 소멸자 부분에 다음과 같이 메모리 누수가 생기지 않도록 map 컨테이너를 비워줘야한다.

for (auto& rPair : m_mapUnitInfo)
{
    if (rPair)
    {
   	delete rPair; 
	rpair = nullptr; 
    }
}
m_mapUnitInfo.clear();

여기까지 오브젝트의 정보 추가와 삭제를 해보았고,

다음에는 ListBox에 추가한 오브젝트를 저장 및 불러오기를 해보려고 한다.

'Programming > MFC' 카테고리의 다른 글

MFC - 프로젝트 세팅  (0) 2020.12.09
MFC - UnitTool Part3(저장 및 불러오기)  (0) 2020.12.05
MFC - 미니맵  (1) 2020.11.29
MFC - UnitTool Part1(기본 구성), 이벤트 처리기  (0) 2020.11.29
MFC - 기본 이론 , 계층 구조  (0) 2020.11.29

미니맵은 다음과 같이 여러 게임에서도 흔히 확인해 볼 수가 있다.

이전에 MFC에서 타일을 배치해보았다.

이번에는 위에 사진처럼 배치한 타일들을 띄워줄 미니맵을 만들어보려고 한다.

 

우선 미니맵을 띄워주기 위해 View클래스를 하나 만들어 준다.

기본 클래스는 CView로 해준다.

클래스를 생성한 다음, 생성한 클래스를 클릭하고 클래스 뷰를 누른다음 재정의를 클릭한다.

그리고 스크롤을 쭈욱 내리다 보면 OnDraw라는 함수가 보일 것이다.

클릭하여 재정의 해준다.

재정의를 하고나서 헤더파일을 가게되면 다음과 같이 나올 것이다.

OnDraw 함수까지 재정의를 완료하였으면 이제 다음과 같이 창을 분리해야 한다.

창을 분리하기 위해서 MFC에서 제공해주는 CSplitterWnd 클래스를 사용한다.

 

CSplitterWnd

-> 여러 개의 창이 포함된 창인 분할자 창 기능을 제공

docs.microsoft.com/ko-kr/cpp/mfc/reference/csplitterwnd-class?f1url=%3FappId%3DDev14IDEF1%26l%3DKO-KR%26k%3Dk(AFXEXT%252FCSplitterWnd);k(CSplitterWnd);k(DevLang-C%252B%252B);k(TargetOS-Windows)%26rd%3Dtrue&view=msvc-160

 

CSplitterWnd 클래스

CSplitterWnd 클래스CSplitterWnd Class 이 문서의 내용 --> 여러 개의 창이 포함된 창인 분할자 창 기능을 제공합니다.Provides the functionality of a splitter window, which is a window that contains multiple panes. 구문Syntax cl

docs.microsoft.com

CSplitterWnd는 메인 프레임 클래스에 정의하면 된다.

창을 분리하더라도 기존의 ToolView가 사용할 창이 필요하므로,

Main Splitter와 미니맵으로 사용할 SecondSplitter 두 개를 선언한다.

 

그 다음 MainFrame 클래스에서 OnCreateClient라는 함수를 재정의 해준다.

함수 재정의 후에 다음과 같이 MainSplitter와 SecondSplitter를 통해서 창을 분할해주면 된다.

BOOL CMainFrame::OnCreateClient(LPCREATESTRUCT lpcs, CCreateContext* pContext)
{
	// TODO: 여기에 특수화된 코드를 추가 및/또는 기본 클래스를 호출합니다.

	/*
	0,0		0,1
	1,0		1,1
	*/
    
 	m_MainSplitter.CreateStatic(this, 1, 2);
 	m_MainSplitter.CreateView(0, 1, RUNTIME_CLASS(CMFCToolView), CSize(800 , 600 ), pContext);
	
	m_SecondSplitter.CreateStatic(&m_MainSplitter, 2, 1, WS_VISIBLE | WS_CHILD, m_MainSplitter.IdFromRowCol(0, 0));
	m_SecondSplitter.CreateView(0, 0, RUNTIME_CLASS(CMiniView), CSize(300, 300), pContext);
	m_SecondSplitter.CreateView(1, 0, RUNTIME_CLASS(CMyFormView), CSize(300, 300), pContext); 
	m_MainSplitter.SetColumnInfo(0, 300, 10);
	return TRUE;
}

분할된 창을 만들려면 CreateStatic 함수를 먼저 선언하여야 한다.

그래야지 이후에 CreateView 함수를 통해 창 분할을 시도한다.

 

CSplitterWnd 내부 함수는 MSDN에서 확인 해볼 수 있다.

docs.microsoft.com/ko-kr/cpp/mfc/reference/csplitterwnd-class?f1url=%3FappId%3DDev14IDEF1%26l%3DKO-KR%26k%3Dk(AFXEXT%252FCSplitterWnd%253A%253ACreateView);k(CSplitterWnd%253A%253ACreateView);k(CreateView);k(DevLang-C%252B%252B);k(TargetOS-Windows)%26rd%3Dtrue&view=msvc-160

 

CSplitterWnd 클래스

CSplitterWnd 클래스CSplitterWnd Class 이 문서의 내용 --> 여러 개의 창이 포함된 창인 분할자 창 기능을 제공합니다.Provides the functionality of a splitter window, which is a window that contains multiple panes. 구문Syntax cl

docs.microsoft.com

위와 같이 정의하였으면 결과는 다음과 같다.

이제 미니맵 클래스에서 OnDraw 함수 부분으로 이동하여 다음과 같이 정의하면 된다.

void CMiniView::OnDraw(CDC* pDC)
{
	CDocument* pDoc = GetDocument();	
	CGraphic_Device::Get_Instance()->Render_Begin(); 
    
	// winApp -> MainFrame 얻어옴. 
	CMainFrame* pMain = dynamic_cast<CMainFrame*>(::AfxGetApp()->GetMainWnd());
    
	//MainFrame -> MainSplitter -> MFCToolView를 얻어옴. 
	CMFCToolView* pView = dynamic_cast<CMFCToolView*>(pMain->m_MainSplitter.GetPane(0, 1)); 
    
	// MFCToolView -> CTerrain 얻어옴. 
	CTerrain* pTerrain = pView->m_pTerrain;
    
	pTerrain->MiniRender_Terrain();
	CGraphic_Device::Get_Instance()->Render_End(m_hWnd); 
}

결과

+ Recent posts