본문 바로가기
프로그래밍/MFC (C++)

MFC ] 통계 데이터 List Control (리스트 컨트롤) 에 로드하기

by eteo 2022. 7. 4.

 

 

 

ANSIData.csv
0.01MB

 

 

Data.h

#pragma once
#include <CString>
#include <string>

using namespace std;

#define COLUMN_NUMBER 13
#define LOCATION_NUMBER 11

class Data
{
private:
	CString siDo;
	CString siGunGoo;
	int accidentCount[LOCATION_NUMBER];

public:
	Data(string _siDo, string _siGunGoo, string* _accidentCount);

	CString& GetSiDo();
	CString& GetSiGunGoo();
	int GetAccidentCount(int idx);

};

 

 

 

 

Data.cpp

#include "pch.h"
#include "Data.h"

Data::Data(string _siDo, string _siGunGoo, string* _accidentCount)
	:siDo(_siDo.c_str()), siGunGoo(_siGunGoo.c_str())
{	
	for (int i = 0; i < LOCATION_NUMBER; i++)
	{
		if (*(_accidentCount + i) == "-")
		{
			accidentCount[i] = 0;
		}
		else
		{
			accidentCount[i] = stoi(*(_accidentCount + i));
		}
	}
}

CString& Data::GetSiDo()
{
	return siDo;
}

CString& Data::GetSiGunGoo()
{
	return siGunGoo;
}

int Data::GetAccidentCount(int idx)
{
	return accidentCount[idx];
}

 

setter는 굳이 안만들고 Data는 파일 불러오기시 생성자로 초기화하게끔 했다. 교통사고 수는 길이가 11인 배열로 만들었는데 인덱스를 매개변수로 받아서 int로 값을 반환하는 함수를 만들어두었다.

 

 

 

 

 

DataController.h

#pragma once

#include"Data.h"
#include <sstream>
#include <fstream>
#include <vector>

using namespace std;

class DataController
{

private:
	vector<Data*> list;

public:
	void loadListFromFile();
	vector<Data*>& getList();
	vector<int> searchList(CString keyword);
};

 

Data 포인터 타입의 벡터 list로 선언했다. 멤버함수로는

void loadListFromFile(); 파일 읽기와 

vector<Data*>& getList(); 멤버변수인 list를 참조형으로 반환하는 함수
vector<int> searchList(CString keyword); 검색 키워드를 받아서 검색결과의 인덱스번호들을 벡터에 담아 반환하는 함수

이렇게 만들었다.

 

 

 

 

DataController.cpp

#include "pch.h"
#include "DataController.h"


void DataController::loadListFromFile()
{
	ifstream data("ANSIData.csv");
	
	string line;

	list.clear();

	while (getline(data, line))
	{
		stringstream linestream(line);
		string cell[COLUMN_NUMBER];
		for (int j = 0; j < COLUMN_NUMBER; j++)
		{
			getline(linestream, cell[j], ',');
		}
		list.push_back(new Data(cell[0], cell[1], &cell[2]));
	}

}

vector<Data*>& DataController::getList()
{
	return this->list;
}


vector<int> DataController::searchList(CString keyword)
{
	
	int chk = 0;
	vector<int> searchedRow;

	for (int i = 0; i < list.size(); i++)
	{
		if (keyword.Compare(list.at(i)->GetSiDo()) == 0)
		{
			chk++;
			searchedRow.push_back(i);
		}
	}

	if (chk == 0)
	{
		for (int i = 0; i < list.size(); i++)
		{
			if (keyword.Compare(list.at(i)->GetSiGunGoo()) == 0)
			{
				searchedRow.push_back(i);
			}
		}
	}
	return searchedRow;
}

 

CString 클래스에도 문자열을 비교하는 함수가 있다. string은 그냥 compare 였는데 CString은 대문자로 시작하는 Compare로 비교한다.

 

그리고 loadListFromFile() 함수가 호출될 때마다 .clear()로 벡터를 초기화했다.

 

 

 

 

 

-Dlg.h

// CarAccidentListDlg.h: 헤더 파일
//

#pragma once
#include "DataController.h"
#include <string>
using namespace std;

// CCarAccidentListDlg 대화 상자
class CCarAccidentListDlg : public CDialogEx
{
// 생성입니다.
public:
	CCarAccidentListDlg(CWnd* pParent = nullptr);	// 표준 생성자입니다.

// 대화 상자 데이터입니다.
#ifdef AFX_DESIGN_TIME
	enum { IDD = IDD_CARACCIDENTLIST_DIALOG };
#endif

	protected:
	virtual void DoDataExchange(CDataExchange* pDX);	// DDX/DDV 지원입니다.


// 구현입니다.
protected:
	HICON m_hIcon;

	// 생성된 메시지 맵 함수
	virtual BOOL OnInitDialog();
	afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
	afx_msg void OnPaint();
	afx_msg HCURSOR OnQueryDragIcon();
	DECLARE_MESSAGE_MAP()
	DataController DC;
public:
	CListCtrl m_list;
	afx_msg void OnBnClickedButtonload();
	CString m_keyword;
	afx_msg void OnBnClickedButtonsearch();
	int selectedIDX;
	afx_msg void OnLvnItemchangedList2(NMHDR* pNMHDR, LRESULT* pResult);
};

 

이중에 int selectedIDX; 는 현재 선택된 row를 알기위해 둔 변수이다.

 

 

 

 

리스트 컨트롤의 속성 - 보기 - Report

 

-Dlg.cpp

// CarAccidentListDlg.cpp: 구현 파일
//

#include "pch.h"
#include "framework.h"
#include "CarAccidentList.h"
#include "CarAccidentListDlg.h"
#include "afxdialogex.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#endif


// 응용 프로그램 정보에 사용되는 CAboutDlg 대화 상자입니다.

class CAboutDlg : public CDialogEx
{
public:
	CAboutDlg();

// 대화 상자 데이터입니다.
#ifdef AFX_DESIGN_TIME
	enum { IDD = IDD_ABOUTBOX };
#endif

	protected:
	virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV 지원입니다.

// 구현입니다.
protected:
	DECLARE_MESSAGE_MAP()
};

CAboutDlg::CAboutDlg() : CDialogEx(IDD_ABOUTBOX)
{
}

void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialogEx::DoDataExchange(pDX);
}

BEGIN_MESSAGE_MAP(CAboutDlg, CDialogEx)
END_MESSAGE_MAP()


// CCarAccidentListDlg 대화 상자



CCarAccidentListDlg::CCarAccidentListDlg(CWnd* pParent /*=nullptr*/)
	: CDialogEx(IDD_CARACCIDENTLIST_DIALOG, pParent)
	, m_keyword(_T(""))
	, selectedIDX(-1)
{
	m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}

void CCarAccidentListDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialogEx::DoDataExchange(pDX);
	DDX_Control(pDX, IDC_LIST2, m_list);
	DDX_Text(pDX, IDC_EDITKEYWORD, m_keyword);
}

BEGIN_MESSAGE_MAP(CCarAccidentListDlg, CDialogEx)
	ON_WM_SYSCOMMAND()
	ON_WM_PAINT()
	ON_WM_QUERYDRAGICON()
	ON_BN_CLICKED(IDC_BUTTONLOAD, &CCarAccidentListDlg::OnBnClickedButtonload)
	ON_BN_CLICKED(IDC_BUTTONSEARCH, &CCarAccidentListDlg::OnBnClickedButtonsearch)
	ON_NOTIFY(LVN_ITEMCHANGED, IDC_LIST2, &CCarAccidentListDlg::OnLvnItemchangedList2)
END_MESSAGE_MAP()


// CCarAccidentListDlg 메시지 처리기

BOOL CCarAccidentListDlg::OnInitDialog()
{
	CDialogEx::OnInitDialog();

	// 시스템 메뉴에 "정보..." 메뉴 항목을 추가합니다.

	// IDM_ABOUTBOX는 시스템 명령 범위에 있어야 합니다.
	ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
	ASSERT(IDM_ABOUTBOX < 0xF000);

	CMenu* pSysMenu = GetSystemMenu(FALSE);
	if (pSysMenu != nullptr)
	{
		BOOL bNameValid;
		CString strAboutMenu;
		bNameValid = strAboutMenu.LoadString(IDS_ABOUTBOX);
		ASSERT(bNameValid);
		if (!strAboutMenu.IsEmpty())
		{
			pSysMenu->AppendMenu(MF_SEPARATOR);
			pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
		}
	}

	// 이 대화 상자의 아이콘을 설정합니다.  응용 프로그램의 주 창이 대화 상자가 아닐 경우에는
	//  프레임워크가 이 작업을 자동으로 수행합니다.
	SetIcon(m_hIcon, TRUE);			// 큰 아이콘을 설정합니다.
	SetIcon(m_hIcon, FALSE);		// 작은 아이콘을 설정합니다.

	// TODO: 여기에 추가 초기화 작업을 추가합니다.
	m_list.SetExtendedStyle(
		LVS_EX_GRIDLINES | LVS_EX_FULLROWSELECT);

	m_list.InsertColumn(0, _T("순서"), LVCFMT_CENTER, 40);
	m_list.InsertColumn(1, _T("시도"), LVCFMT_CENTER, 80);
	m_list.InsertColumn(2, _T("시군구"), LVCFMT_CENTER, 80);
	m_list.InsertColumn(3, _T("터널안"), LVCFMT_CENTER, 70);
	m_list.InsertColumn(4, _T("교량위"), LVCFMT_CENTER, 70);
	m_list.InsertColumn(5, _T("고가도로위"), LVCFMT_CENTER, 70);
	m_list.InsertColumn(6, _T("지하차도(도로)내"), LVCFMT_CENTER, 70);
	m_list.InsertColumn(7, _T("기타단일로"), LVCFMT_CENTER, 70);
	m_list.InsertColumn(8, _T("교차로내"), LVCFMT_CENTER, 70);
	m_list.InsertColumn(9, _T("교차로횡단보도내"), LVCFMT_CENTER, 70);
	m_list.InsertColumn(10, _T("교차로부근"), LVCFMT_CENTER, 70);
	m_list.InsertColumn(11, _T("철길건널목"), LVCFMT_CENTER, 70);
	m_list.InsertColumn(12, _T("기타"), LVCFMT_CENTER, 70);
	m_list.InsertColumn(13, _T("불명"), LVCFMT_CENTER, 70);

	return TRUE;  // 포커스를 컨트롤에 설정하지 않으면 TRUE를 반환합니다.
}

void CCarAccidentListDlg::OnSysCommand(UINT nID, LPARAM lParam)
{
	if ((nID & 0xFFF0) == IDM_ABOUTBOX)
	{
		CAboutDlg dlgAbout;
		dlgAbout.DoModal();
	}
	else
	{
		CDialogEx::OnSysCommand(nID, lParam);
	}
}

// 대화 상자에 최소화 단추를 추가할 경우 아이콘을 그리려면
//  아래 코드가 필요합니다.  문서/뷰 모델을 사용하는 MFC 애플리케이션의 경우에는
//  프레임워크에서 이 작업을 자동으로 수행합니다.

void CCarAccidentListDlg::OnPaint()
{
	if (IsIconic())
	{
		CPaintDC dc(this); // 그리기를 위한 디바이스 컨텍스트입니다.

		SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);

		// 클라이언트 사각형에서 아이콘을 가운데에 맞춥니다.
		int cxIcon = GetSystemMetrics(SM_CXICON);
		int cyIcon = GetSystemMetrics(SM_CYICON);
		CRect rect;
		GetClientRect(&rect);
		int x = (rect.Width() - cxIcon + 1) / 2;
		int y = (rect.Height() - cyIcon + 1) / 2;

		// 아이콘을 그립니다.
		dc.DrawIcon(x, y, m_hIcon);
	}
	else
	{
		CDialogEx::OnPaint();
	}
}

// 사용자가 최소화된 창을 끄는 동안에 커서가 표시되도록 시스템에서
//  이 함수를 호출합니다.
HCURSOR CCarAccidentListDlg::OnQueryDragIcon()
{
	return static_cast<HCURSOR>(m_hIcon);
}

 

모든 리소스가 다 생성되고 배치된 이후에 실행되는 OnInitDialog() 함수 내에서 리스트 컨트롤의 초기 모양을 잡는 코드를 추가한다.

 

리스트 컨트롤의 멤버변수를 컨트롤 타입으로 만들어 사용한다.

m_list.SetExtendedStyle(
	LVS_EX_GRIDLINES | LVS_EX_FULLROWSELECT);

첫번째 매개변수는 선 그리기, 두번째 매개변수는 한 셀 선택했을 때 해당 행이 다 선택하되끔 하는 것이다.

 

m_list.InsertColumn(0, _T("순서"), LVCFMT_CENTER, 40);

위와 같이 열을 추가한다. 첫번째 열은 LVCFMT_CENTER 라고 해서 가운데 정렬이 되지 않는다. 마지막 매개변수는 열의 너비이다.

 

행을 추가할때는 아래처럼 처음엔 .InsertItem 으로 해당 행의 첫 열을 추가해주어야 하고 그 뒤로는 .SetItem 으로 필드를 입력하거나 수정할 수 있다.

m_list.InsertItem(i, str);
m_list.SetItem(i, 1, LVIF_TEXT, DC.getList().at(i)->GetSiDo(), NULL, NULL, NULL, NULL);
m_list.SetItem(i, 2, LVIF_TEXT, DC.getList().at(i)->GetSiGunGoo(), NULL, NULL, NULL, NULL);

매개변수의 의미는 아래와 같다. 첫번째로 행, 그다음에 열, 그다음에 Text 타입을 의미하는 Mask, CString 타입, 그 뒤로 NULL이 4개 온다.

 

void CCarAccidentListDlg::OnBnClickedButtonload()
{
	DC.loadListFromFile();

	CString str;

	m_list.DeleteAllItems();

	for(int i = 0; i < (int)DC.getList().size(); i++)
	{
		str.Format(_T("%d"), i + 1);
		m_list.InsertItem(i, str);
		m_list.SetItem(i, 1, LVIF_TEXT, DC.getList().at(i)->GetSiDo(), NULL, NULL, NULL, NULL);
		m_list.SetItem(i, 2, LVIF_TEXT, DC.getList().at(i)->GetSiGunGoo(), NULL, NULL, NULL, NULL);


		for(int j=0; j < LOCATION_NUMBER; j++)
		{
			str.Format(_T("%d"), DC.getList().at(i)->GetAccidentCount(j));
			m_list.SetItem(i, j+3, LVIF_TEXT, str, NULL, NULL, NULL, NULL);
		}
	}
	
	UpdateData(false);
	
}


void CCarAccidentListDlg::OnBnClickedButtonsearch()
{
	UpdateData(true);
	vector<int> searchedRow = DC.searchList(m_keyword);

	if(searchedRow.size()==0)
	{
		AfxMessageBox(_T("일치하는 결과가 없습니다"));
		return;
	}

	CString str;

	m_list.DeleteAllItems();

	for (int i = 0; i < (int)searchedRow.size(); i++)
	{
		str.Format(_T("%d"), i + 1);
		m_list.InsertItem(i, str);
		m_list.SetItem(i, 1, LVIF_TEXT, DC.getList().at(searchedRow.at(i))->GetSiDo(), NULL, NULL, NULL, NULL);
		m_list.SetItem(i, 2, LVIF_TEXT, DC.getList().at(searchedRow.at(i))->GetSiGunGoo(), NULL, NULL, NULL, NULL);


		for (int j = 0; j < LOCATION_NUMBER; j++)
		{
			str.Format(_T("%d"), DC.getList().at(searchedRow.at(i))->GetAccidentCount(j));
			m_list.SetItem(i, j + 3, LVIF_TEXT, str, NULL, NULL, NULL, NULL);
		}
	}

	m_keyword = "";

	UpdateData(false);

}


void CCarAccidentListDlg::OnLvnItemchangedList2(NMHDR* pNMHDR, LRESULT* pResult)
{
	LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
	// TODO: 여기에 컨트롤 알림 처리기 코드를 추가합니다.
	*pResult = 0;

	selectedIDX = pNMLV->iItem;
}

 

불러오기 버튼이 눌려졌을 때 .DeleteAllItems() 함수로 싹다 지우고 다시 데이터를 대입하고 UpdateData(false); 한다.

 

그다음 검색하기 버튼이 눌러졌을 때 에디트 컨트롤의 멤버변수를 키워드로 넘기면서 핸들러의 searchList 함수를 호출하고 일치하는 행번호를 벡터로 받는다.

 

만약 반환된 벡터의 사이즈가 0이라면 AfxMessageBox(_T("일치하는 결과가 없습니다")); 를 출력하고 return 한다.

 

일치하는 검색결과가 있을 경우 해당 행번호만큼 출력하고 에디트 컨트롤 멤버변수는 ""로 만든다.

 

마지막 함수는 리스트 컨트롤에 LvnItemchanged 이벤트 처리기를 추가한 것으로 현재 선택된 행을 selectedIDX 변수에 담은 것이다.

 

나중에 삭제기능을 구현한다면 아래와 같이 구현 할 수 있는데 여기서는 사용하지 않았다. .DeleteItem 으로 해당 인덱스를 삭제하고 순번은 한칸씩 땡겨서 다시 그린다.

 

void ClistControltest01Dlg::OnBnClickedButton2()
{
	
	if(selectedIDX == -1)
	{
		return;
	}

	if(selectedIDX > m_list.GetItemCount())
	{
		return;
	}
	m_list.DeleteItem(selectedIDX);

	for (size_t i = 0; i < m_list.GetItemCount(); ++i)
	{
		CString str;
		str.Format(_T("%d"), i + 1);
		m_list.SetItem(i,0,LVIF_TEXT, str, NULL, NULL, NULL, NULL);

	}
	UpdateData(false);
}

 

 

 

그리고 디버그 모드로 실행 후 종료했을 때 메모리 누수가 났는데 확인해보니 strcore.cpp 파일에서 났고 CString 사용으로 인한 문제라고한다. 이 부분은 delete 부분을 추가하지 않아서 생긴 것으로 이후 해결했다.

 

2022.07.06 - [프로그래밍/MFC (C++)] - MFC ] new 로 동적할당 한 건 창이 닫히기 전에 OnDestroy() 에서 delete로 해제하기

 

MFC ] new 로 동적할당 한 건 창이 닫히기 전에 OnDestroy() 에서 delete로 해제하기

생성자에서 동적할당한건 소멸자에서 해제하면 될텐데 생성자가 아닌 곳에서 동적할당 한 것인데 할당해제할 시기가 마땅치 않은 경우 창이 닫히기 전에 해제하면 다음과 같이 해제하면 메모

eteo.tistory.com