1. 개선 사항
- 코드의 구현부랑 GUI가 분리되어 있지 않아 코드를 분석하기 어렵고 처음 보는 사람은 이해가 되지 않았던 코드를
클래스를 사용하여 CODE랑 GUI를 분리하여 코드를 보는 사람에게 더 잘 알아볼수 있도록 변경.
- 변수 및 함수명이 무슨 함수인지 어디서 어떻게 쓰이는지 잘 몰랐던 것을 변수와 함수는 사용 직전에 선언하고 보면 바로 알아볼수 있도록 이름을 직관적으로 변경.
무엇을 추가한다기 보다는 코드를 이해하는것이 중요하다고 생각하여 코드 이해의 중점을 두었다.
2. 코드 상세 설명
이벤트 처리 클래스
const wchar_t* strMenu[] = { LISTCTRL_COLUMN_USERID, LISTCTRL_COLUMN_USERNAME,LISTCTRL_COLUMN_USERPHONENUMBER };
const int columnWidths[] = { LISTCTRL_COLIMN_WIDTHS0, LISTCTRL_COLIMN_WIDTHS1, LISTCTRL_COLIMN_WIDTHS2 };
안에 있는 내용을 변경할 수 없게 strMenu 배열을 만들어 그 안에 LISTCTRL_COLIMN의 값을 넣음.
const는 안에 있는 내용을, 데이터를 변경하지 못하게 하기 위해 사용.
수정하기 전에는 columnWidths이 constexpr로 선언되어 있었다. constexpr은 컴파일 때 값이 결정되어 가변적인 프로그램에서는 런타임에 값이 결정되어 변경 할 수 없다. 예를 들면 std::rand()는 난수를 생성하는 함수인데. 이는 런타임때 실행되므로 이 코드는 컴파일 때 변경 할 수 없다.
const는 안에 내용을 변경하지 못하게 하기 위해 사용되고, constexpr은 변경되지도 않고 컴파일때 값이 이미 결정되기 때문에 런타임때 값을 사용할 수 없다. 그렇기 때문에 그냥 다 const로 해도 된다. 하지만 궂이 최적화를 원한다면 constexpr을 써라.
LVCOLUMN lvColumn;
LVCOLUMN 구조체 변수에 lvColumn으로 선언
LVCOLUMN 은 열에 대한 정보를 저장 및 전달하는데 사용됨.
lvColumn.mask = LVCF_WIDTH | LVCF_SUBITEM | LVCF_TEXT;
lvColumn이 어떤 속성들을 사용할 것인지 지정하는 비트 마스크.
LVCF_WIDTH : 컬럼의 너비
LVCF_SUBITEM : iSubItem 멤버 변수를 사용할 것임을 나타냄. ( 열의 인덱스를 나타냄 )
LVCF_TEXT : pszText 멤버 변수를 사용할 것임을 나타냄. ( 텍스트 )
한마디로 리스트뷰 컬럼이 어떤 속성들을 사용할 것인지 설정하는 비트 마스크 이다.
LVITEM lvItem;
LVITEM 구조체 변수를 lvItem이란 이름으로 선언
LVITEM은 ListView 컨트롤의 항목을 설정하는데 사용되는 구조체이다.
ListView에 항목을 추가, 삭제, 수정하는 작업을 함.
한마디로 리스트뷰 아이템은 리스트뷰 컨트롤의 항목을 설정하는데 사용되는 구조체이다. 여기서 하는 역활은 그냥 lvItem으로 선언해주는것이다.
HWND hList = GetDlgItem(hDlg, IDC_LIST_BOX);
다이얼로그에서 IDC_LIST_BOX 컨트롤의 핸들을 가져옴.
lvItem.iItem = ListView_GetItemCount(hList);
ListView에 현제 행의 개수를 반환함.
현제 순번, 이름, 전화번호로 3개의 행이 있으므로 3을 반환함. 그렇게 하여 lvItem.iTem의 값은 3이다.
lvItem.iSubItem = 0;
ListView에 열을 의미함.
현제 아무런 데이터가 입력되지 않았기 때문에 처음으로 입력되는 열이므로 (첫번째열)0으로 설정함.
lvItem.mask = LVIF_TEXT;
lvItem.mask는 LVITEM의 구조체의 멤버변수. 어떤 속성을 설정할 것인지 나타내는 비트마스크 값.
LVIF_TEXT는 텍스트 속성으로 설정하겠다는 것을 나타냄. LVIT_TEXT외에 이미지 핸들을 나타낸다면 LVIF_IMAGE 같은 이미지에 핸들을 지정하는 것도 있다.
LVIF_TEXT를 사용하면 ListView_SetItemText, ListView_GetItemText 함수등을 사용하여 항목의 값을 바꾸거나 항목의 텍스트를 가져오는 역활을 한다.
wchar_t IndexStr[LISTCTRL_INDEX_MAX_LEN] = { 0, };
wchar_t nameStr[LISTCTRL_INDEX_MAX_LEN] = { 0, };
wchar_t phoneStr[LISTCTRL_INDEX_MAX_LEN] = { 0, };
100 크기만큼 유니코드 타입에 배열을 선언하고 배열을 0으로 초기화 함.
wchar_t는 Wide Character의 약자로 유니코드 문자를 사용하는데 사용된다. ASCII 코드 사용시에는 char를 사용함.
_snwprintf_s(IndexStr, LISTCTRL_INDEX_MAX_LEN, L"%d", lvItem.iItem);
IndexStr 버퍼에 100만큼의 크기로 정수형 스트링 포멧을 이용하여 lvItem.iTem 구조체(리스트뷰의 아이템의대한 정보를 저장하는 구조체) pszText(문자열 데이터를 저장하는 포인터) 멤버 변수가 IndexStr 버퍼를 가리키도록 설정함.
쉽게 이야기 하면 lvItem.iItem의 있는 인트형 데이터를 문자형 데이터로 변경한다는 것입니다.
_snwprintf_s는 세이프 함수이다. 정해 놓은 크기를 초과했을때 익셉션을 발생시켜 어디서 문제가 생겼는지 알려준다. 그래서 버그를 찾을때 유용하다. 하지만 sprintf_s와 swprintf_s함수를 사용하면 입력한대로 값을 다 받아줘서 값을 가져올때 문제가 생기고 익셉션을 발생시키지 않아 어디서 문제가 생겼는지 찾기 힘들다.
_snwprintf_s는 버퍼 오버플로어를 발생시킴. sprintf_s와 swprint_s 함수는 버퍼 오버플로어를 발생시키지 않음.
lvItem.pszText = IndexStr;
lvItem 구조체는 리스트뷰에 표시할 각 항목의 정보를 저장하는 구조체이다.
pszText 멤버 변수는 해당 항목의 문자열 데이터를 가리키는 포인터이다.
IndexStr 배열은 _snwprintf_s 함수를 통해 문자열 데이터가 저장된 배열이다. 이 배열의 주소를 pszText 멤버 변수에 대입한다. 따라서 lvItem.pszText = IndexStr은 IndexStr 배열의 주소를 lvItem 구조체의 pszText 멤버 변수에 대입하여 해당 항목의 문자열 데이터를 설정한다는 의미이다.
ListView_InsertItem(hList, &lvItem);
새로운 행을 추가하는 코드이다. 먼저, lvItem 구조체를 이용하여 새로운 행을 초기화하고, ListView_GetItemCount 함수를 사용하여 현재 리스트뷰에 몇 개의 행이 있는지 계산한다. 이를 통해 새로 추가할 행을 결정한다. 그 후, ListView_InsertItem 함수를 호출하여 새로운 행을 추가하고, 반환된 인덱스를 lvitem구조체의 iItem멤버 변수에 저장한다. 이렇게 하면 새로운 행이 추가되고, 추가된 행의 인덱스를 lvItem구조체에 저장하게 됩니다.
이해하기 쉽게 설명하면 hList는 ListView_GetItemCount(hList) 이걸 가르키고 있다. 그렇기 때문에 몇개의 행이 있는지 체크를 한 후 ListView_inserItem 함수를 호출하여 새로운 행을 추가한다. 그리고 반환된 값을 lvItem에 저장한다.
GetDlgItemText(hDlg, IDC_EDIT_NAME, nameStr, LISTCTRL_INDEX_MAX_LEN);
GetDlgItemText(hDlg, IDC_EDIT_PHONE, phoneStr, LISTCTRL_INDEX_MAX_LEN);
사용자가 IDC_EDIT_NAME, IDC_EDIT_PHONE에 입력한 텍스트를 가져와서 nameStr, phoneStr 버퍼에 저장하고 정상적으로 가져왔다면 가져온 버퍼의 크기를, 정상적이지 않다면 0을, 문자열이 더 길 경우 -1을 반환한다. 4번째 값은 함수 호출 시 버퍼의 크기를 나타내는 값이다.
GetDigItemText() 함수는 다이얼로그 안에 컨트롤에서 사용자가 입력한 텍스트를 가져오는 함수이다.
ListView_SetItemText(hList, lvItem.iItem, 1, nameStr);
ListView_SetItemText(hList, lvItem.iItem, 2, phoneStr);
hList 핸들의 ListView에서 lvItem.iItem(0) 번째 행의 1번째 열에 nameStr의 문자열을 입력함.
hList 핸들의 ListView에서 lvItem.iItem(0) 번째 행의 2번째 열에 phoneStr의 문자열을 입력함.
lvItem.iItem은 처음 프로그램을 시작하면 아무 행이 없기 때문에 0을 반환하고, 첫 번째 행을 입력하면 1개의 행이 있기때문에 1을 반환한다. 그맇기 때문에 행을 입력할 때 마다. 다음 행에 문자열이 입력된다.
lvItem.iItem = ListView_GetItemCount(hList);
SetDlgItemText(hDlg, IDC_EDIT_NAME, L"");
SetDlgItemText(hDlg, IDC_EDIT_PHONE, L"");
사용자가 IDC_EDIT_NAME, IDC_EDIT_PHONE 컨트롤에 문자열을 입력하여 리스트컨트롤의 행이 입력되면 두 에디트 컨트롤의 텍스트를 빈 문자열로 설정한다.
프로그램의 시작점
#include "framework.h"
#include "Project_AddressBook_Basic.h"
#include "CAddressBook.h"
#include <Commctrl.h>
#include <wchar.h>
#include <memory>
이 프로그램을 구현하기 위해 필요한 헤더 파일을 포함시킨다.
- freamework.h : 프로젝트에서 사용하는 공톡적 헤더로, Windows API를 사용하기 위해 필요함.
- Commctrl.h : Windows Common Controls 라이브러리를 사용하기 위해 필요한 헤더 파일로, ListView 컨트롤을 사용하기 위해 필요함.
- wchar.h : wide character 관련 함수를 사용하기 위한 헤더 파일, 문자열 처리 함수 등이 정의됨.
- memory : 스마트 포인터를 사용하기 위한 헤더 파일로 unique_ptr과 같은 스마트 포인터를 사용하기 위함.
(unique_ptr은 메모리 누수방지, 자동으로 할당된 메모리를 해제하여 코드의 안전성을 높이는 스마트 포인터이다.)
std::unique_ptr<CAddressBookUI> g_AddrBookUI = nullptr;
CAddressBookUI 클래스 타입의 unique pointer인 g_AddrBokkUI를 선언하고 nullptr로 초기화 하는 코드.
프로그램의 어디에서든 g_AddrBookUI에 접근 가능하도록 전역변수로 선언하고, 초기에는 가르킬 값이 없으므로 초기값으로 nullptr를 할당함. nullptr는 어떠한것도 가르키지 않는 상태를 의미함.
쉽게 말해 유니크 포인터인 g_AddrBookUI를 선언하고 CAddressBookUI클래스를 프로그램 어디에서든지 사용할 수 있게 전역변수로 선언후 현제는 가리키는 값이 없으니 널포인터로 초기화 하는것.
INT_PTR CALLBACK DlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
다이얼로그에서 발생하는 이벤트 처리를 담당하는 함수이다. 아직 HWND hDlg, UINT message, WPARAM wOaram, LPARAM lParam에 대해서는 잘 모르겠다.
3. 키워드
구조체
구조체란 여러 데이터 타입의 변수들을 묶어서 새로운 데이터 타입을 만들 수 있도록 하는 기능.
윈도우 핸들
윈도우 핸들이란 운영체제에서 윈도우를 식별하기 위한 고유의 값.
익셉션
익셉션(Exception)이란 프로그램의 예외가 발생하였을 때 예기치 않는 종료를 말함.
#define
#define이란 프로그램에서 사용할 상수를 정의하는 명령어 이다.
4. 소스코드
AddressBook_Basic.cpp
// 필요한 헤더 파일들을 포함시킵니다.
#include "framework.h"
#include "Project_AddressBook_Basic.h"
#include "CAddressBook.h"
#include <Commctrl.h>
#include <wchar.h>
#include <memory>
// unique_ptr이라는 스마트 포인터를 사용해서
// CAddressBookUI를 사용하기 위해 메모리를 할당하고 초기화 함
std::unique_ptr<CAddressBookUI> g_AddrBookUI = nullptr;
// DigProc 함수는 다이얼로그 이벤트에 대한 처리를 담당합니다.
INT_PTR CALLBACK DlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_INITDIALOG:
g_AddrBookUI = std::make_unique<CAddressBookUI>();
g_AddrBookUI->InitDlg(hDlg);
return (INT_PTR)TRUE;
case WM_CLOSE:
EndDialog(hDlg, 0);
return (INT_PTR)TRUE;
case WM_COMMAND:
if (LOWORD(wParam) == IDC_BUTTON_INSERT) {
g_AddrBookUI->InsertItem(hDlg);
return (INT_PTR)TRUE;
}
}
return (INT_PTR)FALSE;
}
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR lpCmdLine,
_In_ int nCmdShow)
{
DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG_MAIN), NULL, DlgProc);
return 0;
}
CAddressBook.cpp
#include <vector>
#include <string>
#include <windows.h>
#include <CommCtrl.h>
#include "Resource.h"
#include "CAddressBook.h"
#include "framework.h"
void CAddressBookUI::InitDlg(HWND hDlg)
{
const wchar_t* strMenu[] = { LISTCTRL_COLUMN_USERID, LISTCTRL_COLUMN_USERNAME,LISTCTRL_COLUMN_USERPHONENUMBER };
const int columnWidths[] = { LISTCTRL_COLIMN_WIDTHS0, LISTCTRL_COLIMN_WIDTHS1, LISTCTRL_COLIMN_WIDTHS2 };
LVCOLUMN lvColumn;
HWND hList = GetDlgItem(hDlg, IDC_LIST_BOX);
lvColumn.mask = LVCF_WIDTH | LVCF_SUBITEM | LVCF_TEXT;
int nIndex = 0;
for (int nData : columnWidths) {
lvColumn.cx = nData;
lvColumn.pszText = const_cast<LPWSTR>(strMenu[nIndex]);
ListView_InsertColumn(hList, nIndex, &lvColumn);
++nIndex;
}
}
void CAddressBookUI::InsertItem(HWND hDlg)
{
LVITEM lvItem;
HWND hList = GetDlgItem(hDlg, IDC_LIST_BOX);
lvItem.iItem = ListView_GetItemCount(hList);
lvItem.iSubItem = 0;
lvItem.mask = LVIF_TEXT;
wchar_t IndexStr[LISTCTRL_INDEX_MAX_LEN] = { 0, };
_snwprintf_s(IndexStr, LISTCTRL_INDEX_MAX_LEN, L"%d", lvItem.iItem);
lvItem.pszText = IndexStr;
ListView_InsertItem(hList, &lvItem);
wchar_t nameStr[LISTCTRL_INDEX_MAX_LEN] = { 0, }, phoneStr[LISTCTRL_INDEX_MAX_LEN] = { 0, };
GetDlgItemText(hDlg, IDC_EDIT_NAME, nameStr, LISTCTRL_INDEX_MAX_LEN);
GetDlgItemText(hDlg, IDC_EDIT_PHONE, phoneStr, LISTCTRL_INDEX_MAX_LEN);
ListView_SetItemText(hList, lvItem.iItem, 1, nameStr);
ListView_SetItemText(hList, lvItem.iItem, 2, phoneStr);
SetDlgItemText(hDlg, IDC_EDIT_NAME, L"");
SetDlgItemText(hDlg, IDC_EDIT_PHONE, L"");
}
framwork.h
// header.h: 표준 시스템 포함 파일
// 또는 프로젝트 특정 포함 파일이 들어 있는 포함 파일입니다.
//
#pragma once
#include "targetver.h"
// Windows 헤더 파일
#include <windows.h>
// C 런타임 헤더 파일입니다.
#include <stdlib.h>
#include <malloc.h>
#include <memory.h>
#include <tchar.h>
// 포함 헤더
#include <CommCtrl.h>
#include <windowsx.h>
#define LISTCTRL_INDEX_MAX_LEN 100
#define LISTCTRL_COLUMN_USERID L"순번"
#define LISTCTRL_COLUMN_USERNAME L"이름"
#define LISTCTRL_COLUMN_USERPHONENUMBER L"전화번호"
#define LISTCTRL_COLIMN_WIDTHS0 60
#define LISTCTRL_COLIMN_WIDTHS1 90
#define LISTCTRL_COLIMN_WIDTHS2 120
5. 팁
- 회사 일을 하고 나면 여러 사람이랑 일하다 보면 의사소통이 되게 중요함
- 코드리뷰를 하거나 코드를 설명할 때 자기만의 언어로 설명하면 의사소통이 안 됨
- 무한 반복을 하며 계속 훈련하는 수밖에 없음
- 목적 의식을 가지고 코드나 기능을 만들 때 어떤 걸 써야 되고 어떻게 해야 되겠가를 알아야 함.
- 모든 함수, 변수는 목적을 가지고 명확히 한 후에 써야 함. 또한 굳이 얘를 선택한 이유를 알아야함.
- 모든 함수는 에라 리포트가 있어야 된다고 생각함
'C++' 카테고리의 다른 글
[C++] Detours (0) | 2024.06.21 |
---|---|
[C++] Win32 API 메시지 박스 (0) | 2023.05.03 |
[C++] Win32API WebView2 (웹뷰) (0) | 2023.04.20 |
[C++] C++언어 (0) | 2023.04.14 |
[C++] Win32 API를 활용한 간단한 연락처 관리 프로그램 만들기(1일차) (0) | 2023.04.07 |
댓글