Visual C++/CListCtrl Head Click

MFC 에서 CListCtrl 의 컬럼헤더를 클릭하여 정렬순서를 변경하는 예

Soul-Learner 2013. 8. 6. 12:28

아래의 예제 코드에서 사용된 클래스는 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)&paramsort);


    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, &paramSort)를 호출하면,

시스템 내부에서는 두번째 아규먼트인 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 );            

    }

};