본문 바로가기

Application Programming Interface/Microsoft Foundation Class

MFC 기본 컨트롤 - CComboBox 사용법

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

MFC 기본 컨트롤

CComboBox의 사용법

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에서 ComboBox를 다루는 방법에 대해 설명하겠습니다.

ComboBox란?

ComboBox는 미리 구성된 몇 개의 선택지 중 하나를 선택할 수 있는 Drop-Down ListBox와 사용자가 임의로 1줄 분량으로 입력할 수 있는 TextBox를 합친 컨트롤입니다.

1. ComboBox에 텍스트 설정하기/가져오기

void SetWindowText(LPCTSTR lpszString);
int GetWindowText(LPTSTR lpszStringBuf, int nMaxCount) const;
void GetWindowText(CString& rString) const;

SetWindowText는 ComboBox의 텍스트 영역에 표시할 텍스트를 설정하는 함수이고, GetWindowText는 ComboBox의 텍스트 영역에 표시된 텍스트를 가져오는 함수입니다. GetWindowText 함수는 2가지의 형태가 오버로드되어 있는데, 문자열을 포인터 배열에 받을 경우, 포인터 배열의 주소와 최대 크기를 매개변수로 전달해야 합니다. 이 때, 포인터 배열로 복사된 문자의 수(NULL 문자 제외한 개수)가 반환됩니다. 이러한 문자열의 문자 수 처리가 번거로운 경우 CString 형의 객체 참조를 전달하면 자동으로 복사됩니다.

다음은 ComboBox의 텍스트를 가져오거나 설정하는 예입니다.

TCHAR szMessage[] = TEXT("Hello, World!");
CComboBox m_comboBox;
/* ... 중략 ... */
m_comboBox.SetWindowText(szMessage);
/* ... 중략 ... */

 

 

2. ComboBox의 목록에 선택지 추가/제거하기

CComboBox의 ListBox에 추가되는 선택지의 기본 형식은 문자열(LPCTSTR)입니다.

int AddString(LPCTSTR lpszString);
int DeleteString(UINT nIndex);

AddString 함수의 lpszString 매개변수를 통해 ComboBox에 부속된 ListBox의 선택지에 추가될 문자열을 전달합니다. 이 때 반환되는 값은 0 이상의 정수인데 이 정수는 ListBox에서 몇 번째 선택지로 추가되었는지를 알려주는 값입니다. 만일 매크로 상수 CB_ERRSPACE가 반환되면 ComboBox의 내부에 문자열을 저장할만한 충분한 용량이 없다는 뜻입니다. 즉, 문자열의 길이가 너무 길다는 뜻입니다. 그 외의 오류가 발생한 경우 CB_ERR 매크로 상수가 반환됩니다.

DeleteString 함수는 선택지에서 특정 index에 해당하는 항목을 제거하는 함수입니다. nIndex 매개변수를 통해 제거하고자 하는 선택지의 index를 전달합니다. 이 때 반환되는 값은 0 이상의 정수인데 이 정수는 특정 선택지 선택 후 ListBox에 포함된 총 선택지의 개수입니다. 만일 nIndex가 삭제 전 현재 선택지의 개수보다 많은 개수일 때의 index를 나타낼 경우 매크로 상수 CB_ERR를 반환합니다. 참고로 삭제된 선택지 이후의 선택지들은 index 번호가 하나씩 끌어 당겨집니다. 삭제된 선택지 뒤에 있던 선택지들은 본래의 index값이 아니라 새로 조정된 index 번호를 갖는다는 뜻입니다.

다음은 ComboBox의 선택지를 추가하거나 제거하는 예입니다.

CComboBox m_comboBox;
/* ... 중략 ... */
m_comboBox.AddString(TEXT("가지"));
m_comboBox.AddString(TEXT("나비"));
m_comboBox.AddString(TEXT("다람쥐"));
m_comboBox.AddString(TEXT("라디오"));
m_comboBox.AddString(TEXT("마요네즈"));
m_comboBox.AddString(TEXT("바다"));
/* ... 중략 ... */
CComboBox m_comboBox;
/* ... 중략 ... */
m_comboBox.AddString(TEXT("가지"));
m_comboBox.AddString(TEXT("나비"));
m_comboBox.AddString(TEXT("다람쥐"));
m_comboBox.AddString(TEXT("라디오"));
m_comboBox.AddString(TEXT("마요네즈"));
m_comboBox.AddString(TEXT("바다"));
/* ... 중략 ... */
m_comboBox.DeleteString(4);
/* ... 중략 ... */

2-1. ComboBox의 목록으로부터 문자열 가져오기, 또는 문자열로부터 index 가져오기

index 번호는 아는데 해당 index의 문자열을 알고 싶다면 GetLBTextGetLBTextLen를 사용합니다.

int GetLBTextLen(int nIndex) const;
int GetLBText(int nIndex, LPTSTR lpszText) const;
void GetLBText(int nIndex, CString& rString) const;

먼저 GetLBTextLen으로 해당 선택지의 문자열이 몇 글자인지를 확인합니다. 이 때 NULL문자는 글자수에 가산되지 않았으므로 버퍼 확보시 주의 바라며, 오류 발생 시 CB_ERR이 반환됩니다. 정상적으로 실행 되었고, 이 글자수만큼 버퍼를 확보했으면 GetLBText로 문자열을 복사해옵니다. 이러한 과정이 번거롭다면 CString형 객체를 사용하면 됩니다.

찾아본 바로는, GetLBText는 있어도 후보자의 명칭을 수정하는 SetLBText의 함수는 없는듯 합니다. 그렇지만 후술할 편법을 쓰면 불가능한 것도 아니니 참고 바랍니다.

다음은 후보자의 문자열은 아는데 이 후보의 index값이 궁금할 경우입니다. 이를 위해 준비된 함수가 SelectString, FindString, FindStringExact입니다.

int SelectString(int nStartAfter, LPCTSTR lpszString);
int FindString(int nStartAfter, LPCTSTR lpszString) const;
int FindStringExact(int nIndexStart, LPCTSTR lpszFind) const;

lpszString 매개변수를 통해 index를 찾고자 하는 후보의 문자열을 지정합니다. nStartAfter는 검색 범위를 지정하는데, 후보자가 0부터 시작하는 각각의 index를 가졌으므로 몇 번째 후보 이후부터 검색하라는 조건을 줄 때 사용됩니다. 그냥 전체 범위에서 검색하려면 -1을 입력합니다. FindString은 해당 문자열로 시작되는 후보의 index를 가져오고, FindStringExact는 해당 문자열에 정확히 일치하는 후보의 index를 가져옵니다. SelectString도 해당 문자열로 시작하는 후보의 index를 가져오지만, ComboBox에 그 후보를 실제로 선택시키는 기능을 포함합니다.

2-2. ComboBox의 목록의 중간에 새 후보자 삽입하기.

DeleteString의 반대되는 개념으로 InsertString이 있습니다. 이미 만들어진 후보자 목록의 중간에 새 후보자를 끼워 넣는 함수입니다.

int InsertString(int nIndex, LPCTSTR lpszString);

이 함수를 사용하면 새 후보자가 nIndex의 위치에 있고, 원래 있던 기존 후보들은 한 자리씩 뒤로 밀려나서 새로운 index를 갖습니다. 만일 nIndex가 -1이면 맨 끝에 추가됩니다. 성공하면 nIndex가 반환되고 용량이 부족하면 CB_ERRSPACE 매크로 상수가 반환됩니다. 그 외의 오류는 CB_ERR 매크로 상수가 반환됩니다.

 

3. ComboBox의 선택지에 각종 데이터 연결시키기

AddStringDeleteString 함수를 사용하여 문자열 형식으로 선택지를 추가 및 제거하였습니다. 각 선택지에 대해 별도의 데이터를 부여하는 방법에 대해 설명합니다.

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

가져오거나 설정하고자 하는 데이터의 종류에 따라,
DWORD형 정수 값을 선택지에 부여하고자 할 때는 SetItemData를 사용합니다. 이 때 오류가 발생할 경우 CB_ERR 매크로 상수를 반환합니다. 정상일 경우 1을 반환하지만 이 1의 값 자체는 의미있게 쓰이지는 않습니다.

SetItemData로 설정한 DWORD값은 GetItemData 함수로 가져올 수 있습니다. 이 함수의 반환 값은 SetItemDatadwItemData 매개변수를 통해 설정한 정수 값이지만 오류가 발생할 경우 CB_ERR 매크로 상수가 반환됩니다.

CB_ERR의 실제 값은 DWORD로 캐스팅된 -1의 값, 즉 0xFFFFFFFF의 값입니다. 그러므로 정수 값을 선택지에 부여할 때는 이 값을 피해야 할 것입니다.

DWORD로 표현할 수 없는 형식(구조체, 포인터, 배열 등)은 SetItemDataPtrGetItemDataPtr로 설정하거나 가져올 수 있습니다. 이 때, SetItemDataPtrpData 매개변수를 통해 포인터를 전달하여 선택지에 데이터를 부여할 수 있고 오류가 발생하면 CB_ERR 매크로 상수가 반환됩니다. 정상일 경우 1이 반환되지만 이 1의 값 자체는 의미있게 쓰이지는 않습니다.

SetItemDataPtr로 부여한 선택지 데이터의 포인터는 GetItemDataPtr로 가져올 수 있습니다. 오류가 발생하면 포인터 대신 상수 -1을 반환합니다.

다음은 ComboBox에 부속된 ListBox 항목에 데이터를 부여하고 읽는 예입니다.

DWORD_PTR dwData1 = 99;
DWORD_PTR dwData2 = -1;
/* ... 중략 ... */
TCHAR szButterfly1[] = TEXT("A butterfly.");
LPCTSTR lpszButterfly2 = NULL;
CComboBox m_comboBox;
/* ... 중략 ... */
m_comboBox.AddString(TEXT("가지"));
m_comboBox.AddString(TEXT("나비"));

m_comboBox.SetItemData(0, dwData1);
m_comboBox.SetItemDataPtr(1, szButterfly1);

dwData2 = m_comboBox.GetItemData(0);
lpszButterfly2 = (LPCTSTR) m_comboBox.GetItemDataPtr(1);
/* ... 중략 ... */

 

4. ComboBox에서 선택된 후보를 확인하기 / 임의로 선택하기

위의 2, 3단계를 통해 선택지를 설정하였다면, 사용자에 의해 어떤 항목이 선택되었는지를 확인할 수 있어야 합니다. 또는 프로그램에 의해 미리 소정의 항목으로 선택해 둔 상태에서 사용자의 수정을 기다릴 수도 있습니다. 이와 같은 기능을 하는 함수가 GetCurSelSetCurSel입니다.

int SetCurSel(int nSelect);
int GetCurSel() const;

GetCurSel는 ComboBox가 갖는 후보들 중 현재 선택된 항목이 무엇인지를 알려주는 함수입니다. 이 함수가 반환하는 값은 0 이상의 양수인데 이는 후보의 index 번호입니다. 만일 후보들 중에서는 선택된 항목이 없다면 CB_ERR 매크로 상수가 반환됩니다. 반대로, 코드로서 후보를 직접 선택할 때는 SetCurSel 함수를 사용합니다. nSelect는 선택하고자 하는 항목의 index 번호이며, 정상적으로 선택될 경우 nSelect의 값이 그대로 반환되지만, 후보의 개수를 초과한 범위의 index가 전달될 경우 CB_ERR 매크로 상수가 반환됩니다.

다음은 ComboBox에 부속된 ListBox 항목 중 선택된 항목의 index를 가져오거나, 반대로 소프트웨어적으로 설정하는 예입니다.

int nSelected = CB_ERR;
CComboBox m_comboBox;
/* ... 중략 ... */
m_comboBox.AddString(TEXT("가지"));
m_comboBox.AddString(TEXT("나비"));
m_comboBox.AddString(TEXT("다람쥐"));
m_comboBox.AddString(TEXT("라디오"));
/* ... 중략 ... */
m_comboBox.SetCurSel(0);
nSelected = m_comboBox.GetCurSel();
/* ... 중략 ... */

 

5. ComboBox의 값 변경에 반응하기

ComboBox의 값 변경에 반응하기 위해서는 콜백(callback) 함수의 지정이 필요합니다. 다른 표현으로 이벤트 핸들러(event handler)입니다. 어떤 함수를 선언하고 그 함수를 ComboBox의 이벤트 핸들러로 등록하는 방법은 다음과 같습니다.

5-1. 이벤트 핸들러 정의하고 선언하기

ComboBox의 선택에 반응하여 호출될 함수를 선언 및 정의합니다. 반환형은 void이고 매개변수는 없습니다.

void 함수()
{
    /* TODO: 필요한 작업 */
}

이벤트 핸들러로 쓰일 함수의 위치는 전역 함수도 좋고 멤버 함수도 좋으나 객체지향적인 관점에서 보았을 때 ComboBox를 포함하고 있는 클래스의 멤버함수로 있는 것이 보기가 좋을 것 같습니다. 예를 들면 이렇게 합니다.

class CMFCApplication1Dlg : public CDialogEx
{
    /* ... 중략 ... */
    void OnComboBoxSelected(); /* 이벤트 핸들러의 이름은 아무 이름이나 지정해도 무방합니다. */
    /* ... 중략 ... */
}

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

void CMFCApplication1Dlg::OnComboBoxSelected()
{
    /* TODO: 필요한 작업 */
}

5-2. 이벤트 핸들러로 설정하기

위에서 선언한 함수를 ComboBox가 실제로 호출하기 위해서는 이벤트 핸들러로서 등록을 합니다. 등록 방법은 다음과 같습니다.

BEGIN_MESSAGE_MAP(콤보박스가 있는 창 클래스, 창 클래스의 기반 클래스)
    ON_CBN_SELENDOK(콤보박스의 식별 번호, &위에서 선언한 함수)
END_MESSAGE_MAP()

예를 들어 이렇게 합니다. Visual Studio가 자동으로 생성한 MFC 코드라면 BEGIN_MESSAGE_MAP ~ END_MESSAGE_MAP 부분은 cpp 파일 내에 있을 것입니다. 잘 찾아보면 보입니다.

BEGIN_MESSAGE_MAP(CMFCApplication1Dlg, CDialogEx)
    /* ... 중략 ... */
    ON_CBN_SELENDOK(IDC_COMBO1, &CMFCApplication1Dlg::OnComboBoxSelected)
    /* ... 중략 ... */
END_MESSAGE_MAP()

5-3. 그 외 ComboBox의 다양한 이벤트 유형

위의 5-1과 5-2 단계만 따라하면 ComboBox의 선택에 반응하여 호출되는 함수가 설정되었습니다. 그 외 사용자의 행동에 따라 정의된 몇 가지 이벤트가 더 있습니다.

ON_CBN_CLOSEUP
선택지를 보여주기 위해 펼쳐 있던 ListBox가 접히고 숨겨질 때 실행될 함수를 지정합니다. 이 이벤트는 ComboBox가 CBS_SIMPLE의 스타일을 가지고 있다면 사용되지 않습니다. 즉 일반적인 설정에서는 사용되지 않습니다.
ON_CBN_DBLCLK
사용자가 ListBox에 있는 선택지 중 하나를 더블클릭 했을 때 실행될 함수를 지정합니다. 이 이벤트는 ComboBox가 CBS_SIMPLE의 스타일을 가지고 있을 때만 사용됩니다. 참고로 ComboBox는 SetWindowStyle 함수에 의해 CBS_SIMPLE, CBS_DROPDOWN, CBS_DROPDOWNLIST 등의 다양한 스타일이 조합되어 지정될 수 있습니다. 자세한 내용은 MSDN의 Combo Box Styles(https://msdn.microsoft.com/ko-kr/library/windows/desktop/bb775796(v=vs.85).aspx) 레퍼런스를 참고하세요.
ON_CBN_DROPDOWN
사용자가 선택지 후보들을 보기 위해 DropDown 버튼을 눌러 ListBox를 펼쳤을 때 실행될 함수를 지정합니다.
ON_CBN_EDITCHANGE
사용자가 선택지 중에 원하는 후보가 없어서 EditBox에 직접 글자를 입력하고자 할 때, 화면에 수정된 텍스트가 보여지고 나서 실행될 함수를 지정합니다. ComboBox가 CBS_DROPDOWNLIST의 스타일을 가졌다면 이 이벤트는 사용할 수 없습니다.
ON_CBN_EDITUPDATE
사용자가 선택지 중에 원하는 후보가 없어서 EditBox에 직접 글자를 입력하고자 할 때, 화면에 수정된 텍스트가 보여지기 전에 실행될 함수를 지정합니다. ComboBox가 CBS_DROPDOWNLIST의 스타일을 가졌다면 이 이벤트는 사용할 수 없습니다.
ON_CBN_ERRSPACE
ON_CBN_KILLFOCUS
본 ComboBox가 포커스를 잃을 때의 작동을 지정합니다. 즉, 마우스로 이 ComboBox의 구성요소가 아닌 부위를 클릭하였거나 <Tab> 키를 눌러 본 ComboBox가 아닌 다른 컨트롤로 입력이 전환되었을 때 실행될 함수를 지정합니다.
ON_CBN_SELCHANGE
위의 선택지 중 하나가 선택되었을 때 실행될 함수를 지정합니다.
ON_CBN_SELENDCANCEL
사용자가 선택지 중에서 선택하는 것을 취소할 때 실행될 함수를 지정합니다. CBN_CLOSEUP 이벤트가 발생하기 전에 발생되는 것으로서 사용자의 관점에서 선택지 선택을 취소하였음을 표현하기 위해 정의된 이벤트입니다. (반면 CBN_CLOSEUP은 ListBox의 관점에서 후보자 목록이 접혔음을 표현하기 위해 정의된 이벤트입니다. 미묘한 차이가 있으니 잘 이해해 보시기 바랍니다.)
ON_CBN_SELENDOK
사용자가 선택지 중에서 한 후보를 선택했을 때 실행될 함수를 지정합니다. CBN_CLOSEUP 이벤트가 발생하기 전에 발생되는 것으로서 사용자의 관점에서 선택지 선택을 확정하였음을 표현하기 위해 정의된 이벤트입니다.
ON_CBN_SETFOCUS
본 ComboBox에 포커스가 활성화될 때의 작동을 지정합니다. 즉, 마우스로 이 ComboBox를 클릭하였거나 <Tab> 키를 눌러 본 ComboBox에 포커스가 맞춰진 경우 실행될 함수를 지정합니다.

 

6. 예제 프로그램

여기까지 해서 MFC의 CComboBox 컨트롤의 대략적인 사용법을 살펴보았습니다. 간단한 예제를 통해 함수들의 작동을 확인해보겠습니다.

6-1. 헤더 파일

/* 선택지는 4개 */
#define DUMMY_LEN 4

/* CMFCApplication1Dlg 대화 상자 */
/* MFC 프로젝트의 유형 및 Visual Studio의 버전에 따라 클래스 이름이 다를 수 있습니다. */
/* 여기에서는 대화상자 기반의 MFC 프로젝트로 테스트합니다. */
class CMFCApplication1Dlg : public CDialogEx
{
    /* ... 중략 ... */
    DECLARE_MESSAGE_MAP() /* 창이 메시지를 처리할 수 있도록 선언 */

public:
    CComboBox m_comboBox; /* ComboBox */
    CStatic m_static; /* 결과 문자열을 출력할 영역 */

private:
    void OnComboBoxSelected(); /* 드롭다운 메뉴에서 선택지를 클릭할 때 실행될 이벤트 핸들러 */
    void OnComboBoxEditChanged(); /* 텍스트 창에 직접 문자열을 넣을 때 실행될 이벤트 핸들러 */
}

6-2. 소스 파일

/* ... 중략 ... */
struct { TCHAR szLabel[32]; TCHAR szData[32]; } DUMMY_DATA[DUMMY_LEN] =
{
    { TEXT("가위"), TEXT("a scissor") },
    { TEXT("나비"), TEXT("a butterfly") },
    { TEXT("다리"), TEXT("a bridge") },
    { TEXT("라디오"), TEXT("a radio") }
};

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

/* 이벤트 핸들러 */
void CMFCApplication1Dlg::OnComboBoxSelected()
{
    TCHAR szTemp1[512];
    TCHAR szTemp2[512];
    int index;

    index = this->m_comboBox.GetCurSel();

    if ((index >= 0) && (index != CB_ERR))
    {
        if (m_comboBox.GetLBText(m_comboBox.GetCurSel(), szTemp1) > 0)
        {
            _stprintf(szTemp2, TEXT("'%s'의 영어는 '%s'입니다."), szTemp1, (LPCTSTR) this->m_comboBox.GetItemDataPtr(this->m_comboBox.GetCurSel()));
            this->m_static.SetWindowText(szTemp2);
        }
    }
}

void CMFCApplication1Dlg::OnComboBoxEditChanged()
{
    TCHAR szTemp1[512];
    TCHAR szTemp2[512];
    int index;

    this->m_comboBox.GetWindowText(szTemp1, sizeof(szTemp1) / sizeof(TCHAR));
    index = this->m_comboBox.FindStringExact(-1, szTemp1);

    if ((index >= 0) && (index != CB_ERR))
    {
        if (m_comboBox.GetLBText(index, szTemp1) > 0)
        {
            _stprintf(szTemp2, TEXT("'%s'의 영어는 '%s'입니다."), szTemp1, (LPCTSTR) this->m_comboBox.GetItemDataPtr(index));
        }
    }
    else
    {
        _stprintf(szTemp2, TEXT("'%s'는 모릅니다."), szTemp1);
    }

    this->m_static.SetWindowText(szTemp2);
}

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

/* 이벤트 등록 */
BEGIN_MESSAGE_MAP(CMFCApplication1Dlg, CDialogEx)
    ON_WM_SYSCOMMAND()
    ON_WM_PAINT()
    ON_WM_QUERYDRAGICON()
    ON_CBN_SELENDOK(IDC_COMBO1, &CMFCApplication1Dlg::OnComboBoxSelected)
    ON_CBN_EDITCHANGE(IDC_COMBO1, &CMFCApplication1Dlg::OnComboBoxEditChanged)
END_MESSAGE_MAP()

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

/* 선택지 세팅하기 */
BOOL CMFCApplication1Dlg::OnInitDialog()
{
    
    /* ... 중략 ... */
    /* TODO: 여기에 추가 초기화 작업을 추가합니다. */
    for (int i = 0; i < DUMMY_LEN; i++)
    {
        this->m_comboBox.AddString(DUMMY_DATA[i].szLabel);
        this->m_comboBox.SetItemDataPtr(i, DUMMY_DATA[i].szData);
    }
    /* ... 중략 ... */
}

실행 결과는 다음과 같습니다. 드롭다운 메뉴에서 선택지를 클릭할 경우 ON_CBN_ONSELENDOK로 지정한 이벤트 핸들러가 호출되고, 텍스트를 직접 입력할 경우 ON_CBN_EDITCHANGE로 지정한 이벤트 핸들러가 호출됩니다.

 

7. 관련 레퍼런스

CComboBox Class - MSDN
https://msdn.microsoft.com/ko-kr/library/12h9x0ch(v=vs.120).aspx
https://msdn.microsoft.com/en-us/library/12h9x0ch.aspx