[ MFC ] list control header 클릭 시 정렬 기능 이벤트 구현
🌈 프로그래밍/MFC

[ MFC ] list control header 클릭 시 정렬 기능 이벤트 구현

반응형

 

안녕하세요? 수구리입니다.

이번 포스팅은 MFC에서 자주 쓰이는 list control에 대한 내용입니다.

list control은 테이블 모양을 하고 있으며, header에는 클릭 시 이벤트를 추가할 수 있습니다.

 

 

위의 사진이 MFC에서 list control을 사용한 부분입니다.

지금 상태는 Usage rate라는 list control header 부분에 마우스를 올려놓은 상태입니다.

이벤트를 아직 설정하지 않았으므로 클릭 시 아무런 동작을 하지 않고 있는데,

하나씩 추가해 주도록 하겠습니다.

우선 하려는 것은 각 header(PID, Name, Usage rate)를 클릭 시 오름차순 정렬을 하고

한번 더 클릭 시 반대로 내림차순으로 정렬을 하려고 하는 것입니다.

우선 우리가 제어하려고 하는 list control의 멤버 변수가 있는 곳의. h 파일로 이동합니다.

 

[ ResourceMonitorView.h ]

// ResourceMonitorView.h : CResourceMonitorView 클래스의 인터페이스
//

#pragma once
// inclue something

class CResourceMonitorView : public CScrollView
{
	// 특성입니다.
public:
	// 생략
    CListCtrl m_tableList;
    // 생략
    
    // 1. 사용할 변수 및 정렬 정보를 담은 구조체 선언
    // 정렬 방식에 대한 BOOL 변수
    BOOL m_bAscending; 
    // 구조체에 정보를 담아서 파라메터로 넘기기 위해서 사용
	struct SORTPARAM   
	{
		int iSortColumn;
		bool bSortDirect;
		CListCtrl *pList;
		int flag = -1;  // 클릭한 header에 따라서 정렬할 값이 다르기 때문에 구분해주기위한 변수
	};
    
    // 작업입니다.
public:
	// 생략
    
    // 2. 클릭시 이벤트 추가 및 정렬에 사용할 함수 선언
	afx_msg void OnHdnItemclickList1(NMHDR *pNMHDR, LRESULT *pResult);
	static int CALLBACK CompareItem(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort);
}

 

이렇게. h 파일에 선언을 해주고 다음으로는 구현 파일 (. cpp)로 이동하여

구체적으로 어떤 동작을 할 건지 구현해주도록 하겠습니다.

 

[ ResourceMonitorView.cpp ]

1. MessageMap에 ON_NOTIFY 추가

BEGIN_MESSAGE_MAP(CResourceMonitorView, CScrollView)
	// 표준 인쇄 명령입니다.
	ON_COMMAND(ID_FILE_PRINT, &CScrollView::OnFilePrint)
	ON_COMMAND(ID_FILE_PRINT_DIRECT, &CScrollView::OnFilePrint)
	ON_COMMAND(ID_FILE_PRINT_PREVIEW, &CScrollView::OnFilePrintPreview)
	ON_WM_SIZE()
	// 3. 헤더에서 만들어준 이벤트 함수를 등록
	ON_NOTIFY(HDN_ITEMCLICK, 0, &CResourceMonitorView::OnHdnItemclickList1)
END_MESSAGE_MAP()

&CResourceMonitorView 부분은 자신의 클래스 이름에 맞게 바꿔주어야 합니다.

 

2. 아래 부분에 다음 내용 추가

// 리스트컨트롤 컬럼 클릭시 데이터 정렬
void CResourceMonitorView::OnHdnItemclickList1(NMHDR * pNMHDR, LRESULT * pResult)
{
	LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);

	// 클릭한 컬럼의 인덱스
	int nColumn = pNMLV->iItem;

	// 현 리스트 컨트롤에 있는 데이터 총 자료 개수만큼 저장
	for (int i = 0; i < (m_tableList.GetItemCount()); i++) {
		m_tableList.SetItemData(i, i);
	}

	// 정렬 방식 저장
	m_bAscending = !m_bAscending;

	// 정렬 관련 구조체 변수 생성 및 데이터 초기화
	SORTPARAM sortparams;
	sortparams.pList = &m_tableList;
	sortparams.iSortColumn = nColumn;
	sortparams.bSortDirect = m_bAscending;

	// | 0	|  1   |     2		 |
	// | PID| NAME | USAGE(rate) |

	// PID 정렬
	if (nColumn == 0)
		sortparams.flag = 0;

	// NAME은 알파벳 정렬
	if (nColumn == 1)
		sortparams.flag = 1;

	// Usage(rate) 정렬
	if (nColumn == 2 || nColumn == 3)
		sortparams.flag = 2;

	// 정렬 함수 호출
	m_tableList.SortItems(&CompareItem, (LPARAM)&sortparams);
	*pResult = 0;
}

int CResourceMonitorView::CompareItem(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
{
	CListCtrl *pList = ((SORTPARAM*)lParamSort)->pList;
	int iSortColumn = ((SORTPARAM*)lParamSort)->iSortColumn;
	bool bSortDirect = ((SORTPARAM*)lParamSort)->bSortDirect;
	int flag = ((SORTPARAM*)lParamSort)->flag;

	LVFINDINFO info1, info2;
	info1.flags = LVFI_PARAM;
	info1.lParam = lParam1;

	info2.flags = LVFI_PARAM;
	info2.lParam = lParam2;

	int irow1 = pList->FindItem(&info1, -1);
	int irow2 = pList->FindItem(&info2, -1);

	CString strItem1 = pList->GetItemText(irow1, iSortColumn);
	CString strItem2 = pList->GetItemText(irow2, iSortColumn);

	// PID 정렬
	if (flag == 0)
	{
		int iItem1 = _tstoi(strItem1);
		int iItem2 = _tstoi(strItem2);

		if (bSortDirect) {
			return iItem1 == iItem2 ? 0 : iItem1 > iItem2;
		}
		else {
			return iItem1 == iItem2 ? 0 : iItem1 < iItem2;
		}
	}
	// NAME 정렬
	else if (flag == 1)
	{
		return	bSortDirect ? strcmp(LPSTR(LPCTSTR(strItem1)), LPSTR(LPCTSTR(strItem2))) : -strcmp(LPSTR(LPCTSTR(strItem1)), LPSTR(LPCTSTR(strItem2)));
	}
	// RATE 정렬
	else if (flag == 2)
	{
		double dItem1 = _wtof(strItem1);
		double dItem2 = _wtof(strItem2);

		if (bSortDirect) {
			return dItem1 == dItem2 ? 0 : dItem1 > dItem2;
		}
		else {
			return dItem1 == dItem2 ? 0 : dItem1 < dItem2;
		}
	}
	else {
		return AfxMessageBox(L"정렬할 수 없습니다!");
	}


}

 

기본적으로 list control의 값을 가져와서 정렬하기 때문에, 모든 타입이 CString입니다.

따라서, 문자로 정렬하기 때문에, Name 같은 경우는 문자열로 즉, 알파벳순으로 정렬해주면 잘 동작하지만,

PID나 Usage 같은 경우, int 또는 double형태로 값을 바꿔서 비교를 해주고 정렬을 해야 정상적으로 정렬이 가능합니다.

필자의 경우는 PID와 Usage는 클릭 시 클릭한 칼럼의 index값에 따라서 구분해주어 정렬을 수행하도록 하였습니다.

마지막으로는 실행 결과 화면을 차례로 보여드리겠습니다.

 

[ 실행 결과 ]

1. 우선 Usage rate를 첫 번째 클릭한 뒤의 모습입니다.

0.00 값으로 쭉 정렬된 모습입니다.

 

아래의 결과는 CPU 사용률이 많은 순으로 정렬된 모습입니다.

정렬하자마자 CPU값이 실시간으로 바뀌다 보니.. 2번째에 값이 바뀌어서 찍혔나 봅니다..

원래는 정상적으로 작동합니다!

 

다음으로는 클릭한 header를 PID 쪽을 첫 번째 클릭한 결과입니다.

0번 PID부터 쭉 정렬된 모습이 참 깔끔합니다.

 

아래는 한번 더 클릭한 경우겠죠?

내림차순으로 정렬된 모습입니다.

 

기본적으로 String으로 정렬하기 때문에 PID와 Usage rate는 한번 더 형 변환을 통해서 값을 비교해주어야 한다는 점을 알아가셨으면 좋겠습니다.

 

이상으로 MFC에서 List Control을 다루면서 header를 클릭했을 때 정렬하는 예제를 살펴보았습니다.

감사합니다.

반응형