본문 바로가기

Application Programming Interface/Microsoft Foundation Class

MFC 기본 컨트롤 - ListBox의 사용법

336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

MFC 기본 컨트롤

ListBox의 사용법

MFC에서 제공하는 기본 컨트롤에는,
1. CButton 계열의 버튼 컨트롤(BS_PUSHBUTTON: 일반적인 버튼, BS_CHECKBOX: 체크 박스, BS_RADIOBUTTON: 라디오 버튼, BS_GROUPBOX: 그룹 박스)과
2. CStatic 계열의 정적 컨트롤(SS_SIMPLE: 일반적인 텍스트 표시, SS_BITMAP: 비트맵을 표시)
3. CEdit계열의 입력 컨트롤(ES_MULTILINE: 여러 줄 편집 가능, ES_PASSWORD: 비밀번호 입력, ES_NUMBER: 숫자만 입력)
4. CListBox 계열의 리스트 박스 컨트롤
5. CTreeCtrl 계열의 트리 뷰 컨트롤
6. CComboBox 계열의 콤보 박스 컨트롤
7. CScrollBar 계열의 스크롤 바 컨트롤 등이 있습니다.

이번 포스팅에서는 MFC에서 ListBox를 다루는 방법에 대해 설명하겠습니다. 본 포스트를 읽기에 앞서 ComboBox 컨트롤을 다루는 방법과 그 예제(http://tapito.tistory.com/568)를 읽어주시면 더욱 빠르게 이해하실 수 있습니다.

ListBox란?

ListBox는 미리 구성된 몇 개의 선택지 중 하나 이상을 선택하는 동작을 지원하는 컨트롤입니다.

1. ListBox의 선택지 관리하기

int AddString(LPCTSTR lpszItem);
int InsertString(int nIndex, LPCTSTR lpszItem);
int DeleteString(UINT nIndex);

AddStringInsertString은 ListBox에 선택지를 추가하는 함수입니다. CComboBox와 마찬가지로 AddString은 항상 맨 끝에 새 선택지를 추가하고, InsertStringnIndex 매개변수로 지정한 위치에 선택지를 삽입합니다. 기존에 있던 선택지들은 하나씩 뒤로 밀려납니다. 이 작업이 성공하면 새로 추가된 선택지의 index가 반환되지만, 내부적인 공간이 부족할 경우 LB_ERRSPACE 매크로 상수를 반환하고, 그 외의 오류에 대해서는 LB_ERR 매크로 상수가 반환됩니다.

DeleteStringnIndex로 지정한 위치의 선택지를 제거합니다. 성공하면 선택지 제거 후 남은 항목들의 개수를 반환합니다. 만일 nIndex가 가리키는 index의 값이 현재 ListBox가 포함하는 선택지 개수를 벗어나는 경우 LB_ERR 매크로 상수를 반환합니다.

 

2. ListBox의 선택지에 데이터 부여하기

int SetItemData(int nIndex, DWORD_PTR dwItemData);
int SetItemDataPtr(int nIndex, void* pData);
DWORD_PTR GetItemData(int nIndex) const;
void* GetItemDataPtr(int nIndex) const;

CComboBox와 동일합니다. 선택지에 부여하고자 하는 데이터가 간단한 정수형일 경우 SetItemDataGetItemData로 부여하거나 가져올 수 있습니다. 만일 선택지의 데이터 타입이 배열, 구조체 및 기타 포인터 형일 경우 SetItemDataPtrGetItemDataPtr로 부여하거나 가져올 수 있습니다.

SetItemData는 성공 시 임의의 값을 반환합니다. 통상 1이지만 의미는 없습니다. 작동 실패하면 LB_ERR 매크로 상수를 반환합니다. GetItemData는 해당 선택지에 부여된 정수 데이터를 반환하지만 내부 오류 발생 시 LB_ERR 매크로 상수를 반환합니다. 이 상수의 실제 값은 DWORD_PTR로 캐스팅된 -1, 즉 0xFFFFFFFF이므로 선택지에 값을 부여할 때는 이러한 오류 매크로 상수 값들을 피해서 지정하는 것이 권장됩니다.

SetItemDataPtr는 성공 시 임의의 값(통상 1이지만 의미는 없음)을 반환합니다. 내부 오류 발생 시 LB_ERR 매크로 상수를 반환합니다. GetItemDataPtr는 선택지에 부여된 포인터를 반환하지만 오류 발생 시 -1을 반환합니다.

 

3. 다중 선택의 지원 여부 설정

MFC ListBox는 선택지의 다중 선택이 가능합니다. 아래의 함수 호출로써 다중 선택 허용 여부를 설정할 수 있습니다.

BOOL ModifyStyle(DWORD dwRemove, DWORD dwAdd, UINT nFlags = 0);
BOOL ModifyStyleEx(DWORD dwRemove, DWORD dwAdd, UINT nFlags = 0);

MSDN을 참고하면 ModifyStyleModifyStyleEx는 차이가 없습니다. 다만 nFlags 매개변수를 사용하여 SetWindowPos 함수를 내부적으로 호출할 때 확장 윈도우 스타일에 대한 조정의 가능 여부에서 차이가 있습니다.

dwRemove는 제거할 스타일의 비트 조합이고 dwAdd는 추가할 스타일의 비트 조합입니다. 예를 들어 ListBox가 다중 선택을 지원하게 하려면 다음과 같습니다.

CListBox m_listBox;
/* ... 중략 ... */
this->m_listBox.ModifyStyleEx(0, LBS_MULTIPLESEL);
/* ... 중략 ... */

그러나 이미 윈도우에 컨트롤이 생성된 후에 위와 같이 스타일을 변경하려 해도 그 변경이 반영되지는 않을 것입니다. 이것은 MFC의 (낡은) 특성 때문입니다. 이러한 스타일 변경은 컨트롤이 생성되는 최초 1회에 한해 유효하기 때문입니다. ListBox가 다중 선택을 게 하려면,
1. 리소스 대화상자 기반의 UI일 경우 아래 그림과 같이 UI 편집 화면에서 "Select" 속성을 Multiple로 변경합니다.
2. 그 외의 경우에는 (극히 번거롭지만) CListBox 클래스를 상속하여 OnCreate 또는 OnInitUpdate 등의 함수를 재정의하여 위의 코드를 삽입합니다.

 

4. 선택된 선택지가 무엇인지 알아보기/특정 선택지를 코드로써 선택하기

int SetCurSel(int nSelect);
int SetSel(int nIndex, BOOL bSelect = TRUE);
int GetCurSel() const;
int GetSel(int nIndex) const;

int GetSelCount( ) const;
int GetSelItems(int nMaxItems, LPINT rgIndex) const;

GetCurSel은 현재 선택된 선택지의 index 번호를 가져옵니다. 오류 발생 시 LB_ERR 매크로 상수를 반환합니다. 다중 선택이 가능한 상태에서는 이 함수를 사용하면 안됩니다. 다중 선택 모드일 경우, 선택된 선택지들의 index를 모두 가져오기 위해서는 GetSelItems 함수를 사용해야 합니다. GetSel 함수는 nIndex 매개변수에 해당하는 선택지가 클릭하여 선택되었는지 여부를 가져옵니다. 매크로 상수 TRUE 또는 FALSE가 반환되며 오류가 있을 경우 LB_ERR 매크로 상수가 반환됩니다.

SetCurSel은 선택지의 선택을 변경할 때 사용합니다. 마찬가지로, 다중 선택이 가능한 상태에서는 이 함수를 사용하면 안됩니다. 다중 선택 모드일 경우 SetSel 함수를 사용합니다.

GetSelItemsGetSelCount 함수는 다중 선택이 가능한 ListBox에 대해 선택된 선택지들과 그 개수를 가져오는 함수입니다. GetSelItems 함수는 rgIndex 매개변수로 index들을 담아올 버퍼를 받고 nMaxItems 매개변수로 버퍼가 수용 가능한 정수의 최대 개수를 받습니다. 반환 값으로는, 버퍼에 실제로 복사된 index의 개수를 반환합니다. 예를 들면 다음과 같은 방식으로 쓰입니다.

INT nSelected = 0;
HLOCAL hSelected = NULL;
LPINT rgSelected = NULL;
CListBox m_listBox;

/* ... 중략 ... */

/* 선택된 선택지의 개수 가져오기 */
nSelected = m_listBox.GetSelCount();

/* 선택지 개수만큼 버퍼 확보 */
hSelected = LocalAlloc(LMEM_MOVEABLE | LMEM_ZEROINIT, nSelected * sizeof(INT));
rgSelected = (LPINT) LocalLock(hSelected);

/* 선택된 각 선택지의 index 가져오기 */
VERIFY(m_listBox.GetSelItems(nSelected, rgSelected) == nSelected);

/* ... 중략 ... */

LocalUnlock(hSelected);
LocalFree(hSelected);

 

5. 사용자의 선택지 선택에 반응하기

다음은 사용자의 선택지 선택/선택해제에 반응할 수 있는 이벤트 핸들러의 선언과 정의입니다. 이벤트 핸들러의 반환형은 void이고 매개변수는 없습니다. 이름은 아무 이름이나 가능합니다.

void 함수()
{
    /* TODO: 선택 변경에 따른 수행 작업 */
}

예를 들면 아래와 같이 함수를 선언 및 정의하고 이벤트로 등록합니다. Visual Studio의 버전과 MFC 프로젝트 생성 옵션에 따라 소스 코드에 차이가 있을 수 있습니다.

5-1. 헤더 파일에 함수 선언하기

/* CMFCApplication1Dlg 대화 상자 */
class CMFCApplication1Dlg : public CDialogEx
{
    /* ... 중략 ... */
private:
    CListBox m_listBox;
private:
    void OnSelectChanged();
    /* ... 중략 ... */

}

5-2. 소스 파일에 함수 구현하기

void CMFCApplication1Dlg::OnSelectChanged()
{
    /* ... 중략 ... */
}

5-3. 함수를 이벤트 핸들러로 등록하기

BEGIN_MESSAGE_MAP(CMFCApplication1Dlg, CDialogEx)
    /* ... 중략 ... */
    ON_LBN_SELCHANGE(IDC_LIST1, &CMFCApplication1Dlg::OnSelectChanged)
    /* ... 중략 ...*/
END_MESSAGE_MAP()

 

6. 예제

다음은 MFC의 ListBox를 다루는 간단한 예입니다. January, February, March, April, May의 5가지 옵션에 각각 1, 2, 3, 4, 5의 정수 값을 배정합니다. ListBox는 다중 선택을 지원하며, 사용자의 선택에 따라 각 정수의 합을 보여주겠습니다.

struct { TCHAR szLabel[32]; DWORD dwValue; } DUMMY_DATA[DUMMY_LEN] =
{
    { TEXT("January"), 1 },
    { TEXT("February"), 2 },
    { TEXT("March"), 3 },
    { TEXT("April"), 4 },
    { TEXT("May"), 5 }
};

/* ... 중략 ... */

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

    /* ... 중략 ... */

    int index;

    for (int i = 0; i < DUMMY_LEN; i++)
    {
        index = this->m_listBox.AddString(DUMMY_DATA[i].szLabel);
        this->m_listBox.SetItemData(index, DUMMY_DATA[i].dwValue);
    }

    /* ... 중략 ... */

}

void CMFCApplication1Dlg::OnSelectChanged()
{
    /* 사용자가 선택한 선택지들의 index 개수와 각 값 */
    DWORD dwSelection = LB_ERR;
    HANDLE hSelection = NULL;
    LPINT rgSelection = NULL;

    /* 선택지에 부여된 값들의 합 */
    int sum = 0;

    /* 사용자에게 보여줄 문자열 */
    TCHAR szBuffer[128];

    /* 문자열 버퍼 리셋 */
    ZeroMemory(szBuffer, sizeof(szBuffer));

    /* 사용자가 선택지 몇 개를 선택했는지 확인 */
    dwSelection = this->m_listBox.GetSelCount();

    if (dwSelection == LB_ERR) return;
    else if (dwSelection == 0)
    {
        /* 아무것도 선택하지 않았을 경우 */
        _tcscpy(szBuffer, TEXT("선택지에서 하나 이상 선택하세요."));
    }
    else
    {
        /* 하나 이상 선택했을 경우, 그 개수만큼 동적 할당 */
        VERIFY((hSelection = LocalAlloc(LMEM_MOVEABLE | LMEM_ZEROINIT, dwSelection * sizeof(INT))) != NULL);

        if ((rgSelection = (LPINT)LocalLock(hSelection)) != NULL)
        {
            /* 구체적으로 몇 번 선택지들을 선택했는지 가져오기 */
            VERIFY(this->m_listBox.GetSelItems(dwSelection, rgSelection) == dwSelection);

            /* 사용자가 선택한 각 선택지에 부여된 정수 값을 가져와 더하기 */
            for (DWORD dwIndex = 0; dwIndex < dwSelection; dwIndex++)
                sum += (int) this->m_listBox.GetItemData(rgSelection[dwIndex]);

            _stprintf(szBuffer, TEXT("선택된 정수들의 합은 %d입니다."), sum);
        }
    }

    LocalUnlock(hSelection);
    LocalFree(hSelection);

    this->m_static.SetWindowText(szBuffer);
}