이전에 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);
}
}