[Develog] 메모장프로그래밍 6 - 열기/저장 기능 구현
이전 포스팅에서 Edit윈도우를 구현하였고, 이제 윈도우에 열기/저장 기능을 구현해보자.
먼저 윈도우에 쓰는글씨는 유니코드문자열이고, 이것을 데이터화시켜서 저장하기위해서는 멀티바이트문자열로 변환시켜야한다.
각각 문자열길이또한 변수로 담아야하고, 이 변환과정에서
OPENFILENAME 이라는 구조체가, 변환시
WideCharToMultiByte, MultiByteToWideChar함수를 사용한다.
OPENFILENAME Save; //파일 열기/저장 대화상자에 사용될 구조체
//name : 파일명 최대길이 255
//title : 최대길이 255 + " - 낙서장"의 6자 더함
TCHAR name[256] = L"*.txt", title[261];
//file : 새로운파일(경로명 포함)을 만들기위한 핸들
//filename : 위의 파일에서 경로명을 제외한 파일명만을 가지는 핸들
HANDLE file, filename;
//메모장의 edit윈도우에 작성된 내용의 길이
//LRESULT : 이를 얻기위해 쓰인 SendMessage의 반환값타입
LRESULT write_len;
LPTSTR unicode_str; //edit윈도우에 작성된 유니코드를 저장하는 변수
char* multibyte_str; //유니코드문자열을 멀티바이트로 변환후 저장하는 변수
WIN32_FIND_DATA WFD; //WIN32_FIND_DATA 구조체..
int multibyte_len; //유니코드 문자열을 멀티바이트로 변환시 필요한 멀티바이트문자열 길이
//구조체를 초기화안할시 쓰레기값이 들어가므로, 초기화
memset(&Save, 0, sizeof(Save));
주석을 많이달아놓긴했는데, 포인트는
- 구조체를 memset으로 초기화시켜야한다.(처음엔 쓰레기값이 들어가있다.)
- 파일명과 제목명을 담는 TCHAR변수를 선언했고 그 크기가 중요하다.
- WFD구조체는 이후 file, filename핸들에 쓰인다.
그다음으로 처음선언한 Save구조체를 설정한다.
Save.lStructSize = sizeof(Save); //구조체의 크기지정
Save.hwndOwner = hWnd; //낙서장의 핸들값을 지정하여 공통대화상자를 소유하게댐
//공통대화상자에 쓰일 필터들. 각각 \0으로 구분
Save.lpstrFilter = L"텍스트 문서(*.txt)\0*.txt\0모든 파일(*.*)\0*.*";
Save.lpstrFile = name; //파일이름과 경로를 저장할 배열을 전달
Save.nMaxFile = sizeof(name); //해당 배열의 크기
Save.Flags = OFN_PATHMUSTEXIST | OFN_OVERWRITEPROMPT; //사용할 옵션지정
Save.lpstrDefExt = L"*.txt"; //기본확장자 지정
가장 애먹었던 부분은 WideCharToMultiByte, MultiByteToWideChar함수인데, 각각첫번째인자로는 인코딩방식, 두번째는 변환방식인데,
WideCharToMultiByte(CP_UTF8, WC_NO_BEST_FIT_CHARS, unicode_str,
lstrlen(unicode_str), multibyte_str, multibyte_len, NULL, NULL);
MultiByteToWideChar(CP_UTF8, MB_PRECOMPOSED, multibyte_str, strlen(multibyte_str), unicode_str, unicode_len);
인코딩방식을 UTF8해주고 두번째인자를 위와같이 해주어야, 기존 메모장등에서 작업한 한글내용이 깨지지않고 불러와진다.
기존 메모장은 저장할때 인코딩방식을 지정할 수 있도록 공용대화상자에 인코딩란이 추가되있는데, 한글이적힌 문서는 보통 UTF8을 추천해주기때문.. (당연한것)
이제 작성한내용을 저장하지않고 끝내기를 눌렀을때 나올 saveDlgBox함수에서는 TaskDialogIndirect함수를 사용하여 저장하겠냐는 메세지 박스를 보여주는데, 이때 COMCTL32.dll을 추가적으로 로드해야하는데,
#include <commctrl.h>
#include<commdlg.h>
#pragma comment(lib,"comctl32.lib")
작성한 여러 함수에대한 설명은 주석으로 달아놨고,
현재까지 작성된 전체코드는 아래와 같다.
#include "pch.h"
#include "framework.h"
#include "MEMO.h"
#include <windows.h>
#include <commctrl.h>
#include<commdlg.h>
#if defined _M_IX86
#pragma comment(linker, "/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='x86' publicKeyToken='6595b64144ccf1df' language='*'\"")
#elif defined _M_IA64
#pragma comment(linker, "/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='ia64' publicKeyToken='6595b64144ccf1df' language='*'\"")
#elif defined _M_X64
#pragma comment(linker, "/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='amd64' publicKeyToken='6595b64144ccf1df' language='*'\"")
#else
#pragma comment(linker, "/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
#endif
#pragma comment(lib,"comctl32.lib")
#define BUFSIZE 256
//식별자들
LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
void save(HWND hWnd);
void save_as(HWND hWnd);
void open(HWND hWnd);
void saveDlgBox(HWND hWnd);
HWND Edit;
HINSTANCE Global_hInstance;
TCHAR FILEPATH[500] = L"제목 없음 - 낙서장";
int str_CHANGE = 0;
int CANCEL = 0;
//윈도우에서 발생한 메시지를 처리하는 함수
LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
//처리하고싶은 내용들을 작성하는 부분
RECT rt;
switch (uMsg) {
case WM_CREATE: //윈도우가 처음 생성될때 한번만 전달되는 메시지.
GetClientRect(hWnd, &rt);
//텍스트를 입력할 Edit윈도우 생성부분
//메인 윈도우와 같은크기로 생성한다.
Edit = CreateWindow(L"edit", NULL, WS_CHILD | WS_HSCROLL |
WS_VSCROLL | WS_VISIBLE | ES_MULTILINE,
rt.left, rt.top, rt.right, rt.bottom,
hWnd, NULL, Global_hInstance, NULL);
break;
case WM_DESTROY:
PostQuitMessage(0);
//종료메시지를 받지않고 윈도우자체가 파괴된경우 (X 눌렀을때)
//자신의 응용프로그램에 VM_QUIT메시지를 보내 종료시킴
case WM_SIZE:
GetClientRect(hWnd, &rt); //메인윈도우크기가 변경될경우 그값을 얻어옴
//Edit윈도우크기를 메인윈도우크기와 동일하도록
MoveWindow(Edit, rt.left, rt.top, rt.right, rt.bottom, TRUE);
break;
case WM_COMMAND:
switch (LOWORD(wParam)) {
case ID_FILE_MENU1: //새로만들기
saveDlgBox(hWnd);
if (CANCEL != 1) {
DestroyWindow(Edit);
SendMessage(hWnd, WM_CREATE, 0, 0);
SetWindowText(hWnd, L"제목 없음 - 낙서장");
}
else { //취소버튼을 누른경우
CANCEL = 0;
}
break;
case ID_FILE_MENU2: //새창
break;
case ID_FILE_MENU3: //열기
saveDlgBox(hWnd);
if (CANCEL != 1) {
open(hWnd);
}
else { //취소버튼을 누른경우
CANCEL = 0;
}
break;
case ID_FILE_MENU4: //저장
save(hWnd);
break;
case ID_FILE_MENU5: //다른이름으로 저장
save_as(hWnd);
break;
case ID_FILE_MENU6: //끝내기
saveDlgBox(hWnd);
if (CANCEL != 1) {
PostQuitMessage(0);
}
else { //취소버튼을 누른경우
CANCEL = 0;
}
break;
}
switch (HIWORD(wParam)) {
case EN_CHANGE: //편집컨트롤의 텍스트변경시 발생하는 메세지.
str_CHANGE = 1; //문자열 변경이이루어짐을 기록
break;
}
break;
case WM_CLOSE:
saveDlgBox(hWnd);
if (CANCEL != 1) {
PostQuitMessage(0);
}
else {
CANCEL = 0;
}
return 0;
}
//이외에 처리하지지않은 기본 메세지들을 대신 처리해주는 함수
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
void save_as(HWND hWnd) {
OPENFILENAME Save; //파일 열기/저장 대화상자에 사용될 구조체
//name : 파일명 최대길이 255
//title : 최대길이 255 + " - 낙서장"의 6자 더함
TCHAR name[256] = L"*.txt", title[261];
//file : 새로운파일(경로명 포함)을 만들기위한 핸들
//filename : 위의 파일에서 경로명을 제외한 파일명만을 가지는 핸들
HANDLE file, filename;
//메모장의 edit윈도우에 작성된 내용의 길이
//LRESULT : 이를 얻기위해 쓰인 SendMessage의 반환값타입
LRESULT write_len;
LPTSTR unicode_str; //edit윈도우에 작성된 유니코드를 저장하는 변수
char* multibyte_str; //유니코드문자열을 멀티바이트로 변환후 저장하는 변수
WIN32_FIND_DATA WFD; //WIN32_FIND_DATA 구조체..
int multibyte_len; //유니코드 문자열을 멀티바이트로 변환시 필요한 멀티바이트문자열 길이
//구조체를 초기화안할시 쓰레기값이 들어가므로, 초기화
memset(&Save, 0, sizeof(Save));
Save.lStructSize = sizeof(Save); //구조체의 크기지정
Save.hwndOwner = hWnd; //낙서장의 핸들값을 지정하여 공통대화상자를 소유하게댐
//공통대화상자에 쓰일 필터들. 각각 \0으로 구분
Save.lpstrFilter = L"텍스트 문서(*.txt)\0*.txt\0모든 파일(*.*)\0*.*";
Save.lpstrFile = name; //파일이름과 경로를 저장할 배열을 전달
Save.nMaxFile = sizeof(name); //해당 배열의 크기
Save.Flags = OFN_PATHMUSTEXIST | OFN_OVERWRITEPROMPT; //사용할 옵션지정
Save.lpstrDefExt = L"*.txt"; //기본확장자 지정
if (GetSaveFileName(&Save) != 0) { //정상인지 체크
wsprintf(FILEPATH, L"%s", Save.lpstrFile); //파일경로변수에 값넣기
//파일을 만들고, 해당파일의 핸들을 file로 지정.
file = CreateFile(Save.lpstrFile,
GENERIC_READ | GENERIC_WRITE, NULL, NULL,
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
//Edit윈도우에 WM_GETTEXTLENGTH메시지를 보내고 결괏값을 변수에저장
write_len = SendMessage(Edit, WM_GETTEXTLENGTH, 0, 0);
//unicode_str배열을 write_len에 맞게 동적할당 그후 포인터를받음
unicode_str = (TCHAR*)malloc(sizeof(TCHAR) * (write_len + 1));
multibyte_str = (CHAR*)malloc(sizeof(TCHAR) * (write_len + 1));
//배열들을 할당후 0으로 초기화
memset(unicode_str, 0, sizeof(TCHAR) * (write_len + 1));
memset(multibyte_str, 0, sizeof(TCHAR) * (write_len + 1));
//Edit윈도우의 모든 문자열을 가져옴
GetWindowText(Edit, unicode_str, write_len + 1);
//유니코드문자열을 멀티바이트 문자열로 바꿀시 길이를 받음.
multibyte_len = WideCharToMultiByte(CP_UTF8, WC_NO_BEST_FIT_CHARS, unicode_str,
lstrlen(unicode_str), NULL, NULL, NULL, NULL);
//해당 길이를 이용하여 변환
WideCharToMultiByte(CP_UTF8, WC_NO_BEST_FIT_CHARS, unicode_str,
lstrlen(unicode_str), multibyte_str, multibyte_len, NULL, NULL);
//생성됬던파일(핸들이용)에 멀티바이트문자열을 쓰기
WriteFile(file, multibyte_str, strlen(multibyte_str), (LPDWORD)&multibyte_len, NULL);
//핸들을 초기화 작업(파일명관련핸들)
filename = FindFirstFile(Save.lpstrFile, &WFD);
//낙서장 타이틀지정전에 파일명길이에맞게초기화작업
memset(title, 0, sizeof(TCHAR) * (lstrlen(WFD.cFileName) + 1));
//낙서장 타이틀 변수에 값넣기
wsprintf(title, L"%s - 낙서장", WFD.cFileName);
//낙서장 타이틀 변경
SetWindowText(hWnd, title);
//동적할당된 배열들 메모리 해제
free(unicode_str);
free(multibyte_str);
//핸들 전부 닫기
FindClose(filename);
CloseHandle(file);
//성공적으로 저장이끝났음을 알림. 이부분을통해 내용이변경되었음을
//사용자에게 다시알려 저장할것인가를 물을 수 있음
str_CHANGE = 0;
}
else {
//사용자가 공용대화상자에서 저장버튼이아닌 취소버튼을 눌렀을때 처리.
CANCEL = 1;
}
}
void save(HWND hWnd) {
TCHAR title[261]; //최대파일명은 255, 거기에 " - 낙서장" 6자합친길이
HANDLE file;
LRESULT write_len;
LPTSTR unicode_str;
char* multibyte_str;
int multibyte_len;
GetWindowText(hWnd, title, 261);
if (lstrcmp(title, L"제목 없음 - 낙서장") == 0) { //처음 저장할시
save_as(hWnd);
}
else {
file = CreateFile(FILEPATH, GENERIC_READ | GENERIC_WRITE,
NULL, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
write_len = SendMessage(Edit, WM_GETTEXTLENGTH, 0, 0);
multibyte_len = sizeof(TCHAR) * (write_len + 1);
unicode_str = (TCHAR*)malloc(sizeof(TCHAR) * (write_len + 1));
multibyte_str = (CHAR*)malloc(sizeof(TCHAR) * (write_len + 1));
memset(unicode_str, 0, sizeof(TCHAR) * (write_len + 1));
memset(multibyte_str, 0, sizeof(TCHAR) * (write_len + 1));
GetWindowText(Edit, unicode_str, write_len + 1);
multibyte_len = WideCharToMultiByte(CP_UTF8, WC_NO_BEST_FIT_CHARS, unicode_str,
lstrlen(unicode_str), NULL, NULL, NULL, NULL);
WideCharToMultiByte(CP_UTF8, WC_NO_BEST_FIT_CHARS, unicode_str, lstrlen(unicode_str), multibyte_str, multibyte_len, NULL, NULL);
WriteFile(file, multibyte_str, strlen(multibyte_str), (LPDWORD)&multibyte_len, NULL);
free(unicode_str);
free(multibyte_str);
CloseHandle(file);
str_CHANGE = 0;
}
}
void open(HWND hWnd) {
OPENFILENAME Open;
TCHAR name[256] = L"", title[261];
HANDLE file, filename;
LPTSTR unicode_str;
char* multibyte_str;
WIN32_FIND_DATA WFD;
int filesize, unicode_len;
DWORD size; //ReadFile함수에 쓰일 크기변수.
memset(&Open, 0, sizeof(Open));
Open.lStructSize = sizeof(Open);
Open.hwndOwner = hWnd;
Open.lpstrFilter = L"텍스트 문서(*.txt)\0*.txt\0모든 파일(*.*)\0*.*";
Open.lpstrFile = name;
Open.nMaxFile = sizeof(name);
Open.Flags = OFN_PATHMUSTEXIST | OFN_OVERWRITEPROMPT;
Open.lpstrDefExt = L"*.txt";
if (GetOpenFileName(&Open) != 0) {
wsprintf(FILEPATH, L"%s", Open.lpstrFile);
file = CreateFile(Open.lpstrFile, GENERIC_READ | GENERIC_WRITE,
NULL, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
filesize = GetFileSize(file, NULL);
multibyte_str = (CHAR*)malloc(sizeof(TCHAR) * (filesize + 1));
unicode_str = (TCHAR*)malloc(sizeof(TCHAR) * (filesize + 1));
memset(multibyte_str, 0, sizeof(TCHAR) * (filesize + 1));
memset(unicode_str, 0, sizeof(TCHAR) * (filesize + 1));
ReadFile(file, multibyte_str, filesize, &size, NULL); //파일을 읽음
//읽은 멀티바이트 문자열을 유니코드문자열로 바꾸기위함
unicode_len = MultiByteToWideChar(CP_UTF8, MB_PRECOMPOSED, multibyte_str, strlen(multibyte_str), NULL, NULL);
MultiByteToWideChar(CP_UTF8, MB_PRECOMPOSED, multibyte_str, strlen(multibyte_str), unicode_str, unicode_len);
SetWindowText(Edit, unicode_str);
filename = FindFirstFile(Open.lpstrFile, &WFD);
memset(title, 0, sizeof(TCHAR) * (lstrlen(WFD.cFileName) + 1));
wsprintf(title, L"%s - 낙서장", WFD.cFileName);
SetWindowText(hWnd, title);
free(multibyte_str);
free(unicode_str);
FindClose(filename);
CloseHandle(file);
str_CHANGE = 0;
}
}
void saveDlgBox(HWND hWnd) {
TASKDIALOGCONFIG TDBOX; //이번엔 새로운구조체를 사용
//두 버튼을 배열로 선언
const TASKDIALOG_BUTTON TDBUTTON[] = { {IDOK, L"저장"}, {IDNO, L"저장 안함"} };
LRESULT editstr_len; //문자열의 길이
LPTSTR MessageStr; //메세지 박스에 표시할 문자열을 저장할 변수
int result = 0, multibyte_len = 0;
memset(&TDBOX, 0, sizeof(TDBOX)); //구조체 정상작동을위한 초기화
TDBOX.cbSize = sizeof(TDBOX);
TDBOX.hwndParent = hWnd; //부모핸들을 메인윈도우로..
TDBOX.hInstance = Global_hInstance; //WINAPI인자인 hInstance로 지정
TDBOX.dwCommonButtons = TDCBF_CANCEL_BUTTON; //취소버튼을 추가하기위함
TDBOX.pszWindowTitle = L"낙서장"; //메세지박스의 타이틀
TDBOX.pButtons = TDBUTTON; //선언한 버튼들을 지정
TDBOX.cButtons = ARRAYSIZE(TDBUTTON); //2 라는 값과 같음
if (lstrcmp(FILEPATH, L"제목 없음 - 낙서장") == 0) {
//Edit윈도우로부터 문자열의 길이를 받아옴
editstr_len = SendMessage(Edit, WM_GETTEXTLENGTH, 0, 0);
if (editstr_len > 0) { //문자가 하나로있으면 아래코드 실행
//메세지 박스 내용 지정
TDBOX.pszMainInstruction = L"작업 내용을 제목 없음으로 저장 하시겠습니까?";
//메세지 박스 호출
TaskDialogIndirect(&TDBOX, &result, NULL, NULL);
//메세지 박스에서 눌린 버튼처리
switch (result) {
case IDOK:
save(hWnd);
break;
case IDNO:
CANCEL = 0;
break;
case IDCANCEL:
CANCEL = 1;
break;
}
}
else {
CANCEL = 0; //Edit윈도우에 아무것도 적히지않은 경우
}
}
else { //현재 낙서장의 제목타이틀이 제목 없음이 아닐경우
if (str_CHANGE == 1) { //문자열이 변경된 경우
multibyte_len = lstrlen(FILEPATH);
MessageStr = (TCHAR*)malloc(sizeof(multibyte_len + 19));
memset(MessageStr, 0, sizeof(TCHAR) * (multibyte_len + 19));
wsprintf(MessageStr, L"변경 내용을 %s에 저장 하시겠습니까?", FILEPATH);
TDBOX.pszMainInstruction = MessageStr;
TaskDialogIndirect(&TDBOX, &result, NULL, NULL);
switch (result) {
case IDOK:
save(hWnd);
break;
case IDNO:
CANCEL = 0;
break;
case IDCANCEL:
CANCEL = 1;
break;
}
free(MessageStr);
}
}
}
//메인윈도우 클래스 등록
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow) {
Global_hInstance = hInstance; //핸들을 전역변수로 설정
WNDCLASS wc;
wchar_t my_class_name[] = L"GraffitiPad";
wc.cbClsExtra = NULL; //추가메모리 사용X
wc.cbWndExtra = NULL; //추가메모리 사용,X
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); //배경을 흰색으로
wc.hCursor = LoadCursor(NULL, IDC_ARROW); //커서의 핸들값 저장
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); //아이콘의 핸들값 저장
wc.hInstance = hInstance;
wc.lpfnWndProc = WndProc;
wc.lpszClassName = my_class_name;
wc.lpszMenuName = MAKEINTRESOURCE(IDR_MENU1);
wc.style = CS_HREDRAW | CS_VREDRAW;
RegisterClass(&wc);
//윈도우를 생성하는부분
//hWnd : 윈도우 핸들
HWND hWnd = CreateWindow(my_class_name, L"제목 없음 - 낙서장",
//NULL NULL hInstance NULL 의 의미는
//부모윈도우핸들, 윈도우에서 사용할 메뉴의핸들, 현재윈도우핸들, 각윈도우의고유의파라미터
WS_OVERLAPPEDWINDOW, 100, 100, 400, 400, NULL, NULL,
hInstance, NULL);
//2번째인자 : 어떤형태로 띄울것인가(처음호출시 생략가능)
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
//프로그램에 전달된 메시지를 번역, 실행하는부분
//Win32 프로그램은 운영체제나 다른 프로그램상에
//메시지를 주고받는 방식. 그래서 항상 메시지를 받도록
//메시지 큐를 가지고있고 전달받은 메시지로 프로그램을 실행
MSG msg; //메시지 구조체
//평상시는 메시지에 0이아닌값을 받으나
//VM_QUIT메시지를 받으면 0값으로, 반복문이 종료됨
HACCEL hAccel;
hAccel = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDR_ACCELERATOR1));
while (GetMessage(&msg, NULL, 0, 0)) {
//엑셀레이터테이블에 등록된 값이 받아지면은 자동으로
//WndProc로 넘어가기때문에 if문을 추가해준 모습
if (!TranslateAccelerator(hWnd, hAccel, &msg)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
//반복문의 종료는 프로그램의 종료이다.
return msg.wParam;
}
Author And Source
이 문제에 관하여([Develog] 메모장프로그래밍 6 - 열기/저장 기능 구현), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@cldhfleks2/Develog-메모장프로그래밍-6-열기저장-기능-구현저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)