MFC 에서 CListCtrl 의 컬럼헤더를 클릭하여 정렬순서를 변경하는 예
아래의 예제 코드에서 사용된 클래스는 MFC에서 제공하는 CListCtrl 클래스를 상속하여 파생된 클래스이다.
CListCtrl 클래스의 OnHdnItemclick() 메시지 핸들러 함수를 등록하면 이용자가 리스트 컬럼의 헤더를 클릭할 때마다 실행되는 로직을 작성할 수 있다.
함수호출 순서 정리
OnHdnItemclick() > CreateBody() > SortColumn() > SortFunc()
/**
리스트의 컬럼 헤더를 클릭할 때마다 이용자가 클릭한 컬럼의 인덱스를 받아내고
해당 컬럼에 대한 정렬순서를 오름차순/내림차순 으로 변경하고 CListCtrl이 포함된 뷰를 갱신한다
이 함수는 뷰의 Invalidate()를 호출하여 뷰를 갱신하는데, 이어서 호출되는 뷰의 OnDraw()함수에서
이 클래스의 CreateBody()를 호출하며, CreateBody()에서는 SoftColumn()를 호출하여 해당 컬럼의
정렬방법을 오름차순/내림차순으로 변경한다
*/
void DCMListCtrl::OnHdnItemclick(NMHDR *pNMHDR, LRESULT *pResult)
{
LPNMHEADER phdr = reinterpret_cast<LPNMHEADER>(pNMHDR);
*pResult = 0;
// TODO: Add your control notification handler code here
m_iColumnSort = phdr->iItem; // 클릭된 컬럼 인덱스
m_bAscending = !m_bAscending;; // 정렬방법 토글(오름차순/내림차순)
CMainFrame* mf = (CMainFrame*)AfxGetMainWnd();
CXPACSView* mainView = (CXPACSView*)mf->m_wndSplitter.GetPane(1,0);
mainView->m_bListDataUpdated = TRUE;
mainView->Invalidate(); // CListCtrl 이 포함된 뷰를 갱신한다(뷰의 OnDraw()함수 호출됨)
*pResult = 0;
}
/**
리스트의 각행을 구성하는 아이템을 출력한다
각 행의 첫 컬럼 값을 추가할 때는 InsertItem()을 사용하고
그 이후의 다른 컬럼 값을 추가할 때는 InsertItem()에서 리턴한 값을
SetItem()의 파라미터로 전달하여 사용해야 한다는 것을 주의해야 한다
또 리스트에 컬럼의 값을 설정할 때 SetItem()이나 SetItemData()를 사용하여 정렬기능이 가능하도록 한다
모든 행과 컬럼의 값을 설정한 후에는 이 클래스의 멤버함수인 SortColumn()을 호출하여 지정한 컬럼을 기준으로
내림차순/오름차순으로 정렬한다.
*/
void DCMListCtrl::CreateBody()
{
if(this->vecResult==NULL) return;
SetRedraw(FALSE);
DeleteAllItems();
int nItem = 0;
for(UINT i=0;i<this->vecResult->size();i++)
{
PatientTokenVO patient;
BOOL ok = FileNameTokenizer::toPatientTokenVO(CString((*vecResult)[i]), &patient);
if(!ok)//우리가 스캔한 파일이 아닌 경우, 파일이름만 리스트에 출력한다
{
int seq = i;
CString seq_string;
seq_string.Format(_T("%d"),seq+1);
CString dcmFullPath = patient.GetFileName();
int nItem = 0;
nItem = InsertItem(0,seq_string);
SetItem(nItem,5,LVIF_TEXT,dcmFullPath,0,0,0,NULL);
continue;
}
int seq = i;
CString seq_string;
seq_string.Format(_T("%d"),seq+1);
DateUtility::ParseStrDate(patient.GetStudyDate());
CString studyDate = DateUtility::Format();
CString patientID = patient.GetPatientID();
CString patientName = patient.GetPatientName();
CString patientSex = patient.GetPatientSex();
CString dcmFullPath = patient.GetFileName();
nItem = InsertItem(0,seq_string);
SetItem(nItem,1,LVIF_TEXT,patientName,0,0,0,NULL);
SetItem(nItem,2,LVIF_TEXT,patientID,0,0,0,NULL);
SetItem(nItem,3,LVIF_TEXT,patientSex,0,0,0,NULL);
SetItem(nItem,4,LVIF_TEXT,studyDate,0,0,0,NULL);
SetItem(nItem,5,LVIF_TEXT,dcmFullPath,0,0,0,NULL);
}
SortColumn(m_iColumnSort, m_bAscending);
SetRedraw(TRUE);
Invalidate();
}
/**
리스트의 바디(리스트 아이템)의 내용이 다시 설정되고 이어서 이 함수가 호출되면
ListView_SortItemsEx()함수를 호출하여 지정한 컬럼을 기준으로 오름차순/내림차순으로 정렬한다
이 함수는 CreateBody()에서 호출되고
이 함수에 이어서 Invalidate()를 호출하면 정렬된 리스트의 내용이 화면에 반영된다
이 함수 안에서 사용된 PARAMSORT는 전역범위에 선언한 구조체이며 이 구조체의 주소를 ListView_SortItems()의 파라미터로
전달해주면 정렬기능이 수행된다
ListView_SortItems()함수의 2번째 아규먼트는 실제 정렬을 위한 값을 비교하는 함수를 지정한다
*/
bool DCMListCtrl::SortColumn(int columnIndex, bool ascending)
{
::PARAMSORT paramsort(m_hWnd, columnIndex, ascending);
ListView_SortItemsEx(m_hWnd, SortFunc, (LPARAM)¶msort);
return true;
}
아래의 코드는 전역범위에 선언된 구조체와 정렬을 위한 비교함수(콜백함수)
namespace {
struct PARAMSORT
{
PARAMSORT(HWND hWnd, int columnIndex, bool ascending)
:m_hWnd(hWnd)
,m_ColumnIndex(columnIndex)
,m_Ascending(ascending)
{}
HWND m_hWnd;
int m_ColumnIndex;
bool m_Ascending;
};
/**
Comparison extracts values from the List-Control
지정한 컬럼을 기준으로 정렬할 목적으로 시스템함수인 ListView_SortItemsEx(m_hWnd, SortFunc, ¶mSort)를 호출하면,
시스템 내부에서는 두번째 아규먼트인 SortFunc 함수를 찾아 호출하면서
ListView_SortItemsEx()함수의 3번째 아규먼트를 SortFunc 함수의 3번째 파라미터에 전달한다
지정한 컬럼 내의 모든 값을 비교하기위해 반복 호출되며 2개의 값을 비교하고 그 결과를 리턴함
@param lParam1 - 정렬대상 컬럼내에서 컬럼의 값들을 서로 비교할 때 컬럼의 첫번째 행에 대한 내부표현
@param lParam2 - 비교할 두번째 행의 값에 대한 내부표현
@param lParamSort - ListView_SortItemsEx()함수를 호출할 때 전달한 3번째 아규먼트의 주소에 대한 내부표현
*/
int CALLBACK SortFunc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
{
PARAMSORT& ps = *(PARAMSORT*)lParamSort;
TCHAR left[256] = _T(""), right[256] = _T("");
ListView_GetItemText(ps.m_hWnd, lParam1, ps.m_ColumnIndex, left, sizeof(left));
ListView_GetItemText(ps.m_hWnd, lParam2, ps.m_ColumnIndex, right, sizeof(right));
if(ps.m_ColumnIndex == 0 || ps.m_ColumnIndex == 2)//숫자 컬럼인 경우
{
int num1 = _tstoi(left);
int num2 = _tstoi(right);
if(ps.m_Ascending) return num1==num2 ? 0 : num1 > num2;
else return num1==num2 ? 0 : num1 < num2;
}
// 문자열 컬럼인 경우
if (ps.m_Ascending)
return _tcscmp( left, right );
else
return _tcscmp( right, left );
}
};