본문으로 건너뛰기
Previous
Next
MFC 컨트롤 활용 | CButton·CEdit·CListCtrl 완벽 가이드

MFC 컨트롤 활용 | CButton·CEdit·CListCtrl 완벽 가이드

MFC 컨트롤 활용 | CButton·CEdit·CListCtrl 완벽 가이드

이 글의 핵심

MFC는 Win32 컨트롤을 C++ 클래스로 감쌌습니다. CListCtrl로 리스트뷰, CTreeCtrl로 트리뷰를 구현하고, DDX_Control로 다이얼로그 컨트롤과 연결합니다. 알림 메시지는 ON_NOTIFY로 처리합니다.

들어가며

MFC 컨트롤 클래스는 Win32 API 컨트롤을 객체 지향 방식으로 감싼 것입니다. C++ 멤버 함수로 컨트롤을 제어하며, 메시지 맵으로 이벤트를 처리합니다. 2000년대 Windows 애플리케이션 UI의 핵심이었습니다.

// MFC 컨트롤 사용 예
CListCtrl m_list;

// 초기화
m_list.InsertColumn(0, _T("이름"), LVCFMT_LEFT, 150);
m_list.InsertColumn(1, _T("나이"), LVCFMT_LEFT, 80);

// 항목 추가
int index = m_list.InsertItem(0, _T("홍길동"));
m_list.SetItemText(index, 1, _T("30"));

// 선택 항목 얻기
POSITION pos = m_list.GetFirstSelectedItemPosition();
int sel = m_list.GetNextSelectedItem(pos);

1. 기본 컨트롤

1.1 CButton

class CMyDialog : public CDialog
{
public:
    CButton m_btnStart;
    CButton m_btnStop;
    CButton m_checkAuto;
    
protected:
    virtual BOOL OnInitDialog()
    {
        CDialog::OnInitDialog();
        
        // 버튼 활성화/비활성화
        m_btnStart.EnableWindow(TRUE);
        m_btnStop.EnableWindow(FALSE);
        
        // 체크박스 상태 설정
        m_checkAuto.SetCheck(BST_CHECKED);
        
        // 버튼 텍스트 변경
        m_btnStart.SetWindowText(_T("시작"));
        
        return TRUE;
    }
    
    DECLARE_MESSAGE_MAP()
    
    afx_msg void OnBnClickedStart()
    {
        m_btnStart.EnableWindow(FALSE);
        m_btnStop.EnableWindow(TRUE);
        
        AfxMessageBox(_T("시작!"));
    }
    
    afx_msg void OnBnClickedCheck()
    {
        int state = m_checkAuto.GetCheck();
        if (state == BST_CHECKED) {
            AfxMessageBox(_T("자동 모드 ON"));
        } else {
            AfxMessageBox(_T("자동 모드 OFF"));
        }
    }
};

BEGIN_MESSAGE_MAP(CMyDialog, CDialog)
    ON_BN_CLICKED(IDC_BTN_START, &CMyDialog::OnBnClickedStart)
    ON_BN_CLICKED(IDC_CHECK_AUTO, &CMyDialog::OnBnClickedCheck)
END_MESSAGE_MAP()

1.2 CEdit

class CMyDialog : public CDialog
{
public:
    CEdit m_edit;
    
protected:
    virtual BOOL OnInitDialog()
    {
        CDialog::OnInitDialog();
        
        // 최대 글자 수 제한
        m_edit.SetLimitText(100);
        
        // 초기 텍스트
        m_edit.SetWindowText(_T("초기값"));
        
        // 선택 영역 설정
        m_edit.SetSel(0, -1);  // 전체 선택
        
        // 읽기 전용
        m_edit.SetReadOnly(TRUE);
        
        return TRUE;
    }
    
    afx_msg void OnEnChangeEdit()
    {
        CString text;
        m_edit.GetWindowText(text);
        
        // 실시간 글자 수 표시
        CString status;
        status.Format(_T("%d 자"), text.GetLength());
        SetDlgItemText(IDC_STATIC_COUNT, status);
    }
    
    DECLARE_MESSAGE_MAP()
};

BEGIN_MESSAGE_MAP(CMyDialog, CDialog)
    ON_EN_CHANGE(IDC_EDIT, &CMyDialog::OnEnChangeEdit)
END_MESSAGE_MAP()

1.3 CStatic

class CMyDialog : public CDialog
{
public:
    CStatic m_staticLabel;
    
protected:
    virtual BOOL OnInitDialog()
    {
        CDialog::OnInitDialog();
        
        // 텍스트 변경
        m_staticLabel.SetWindowText(_T("새 레이블"));
        
        // 색상 변경 (서브클래싱 필요)
        // 또는 OnCtlColor로 처리
        
        return TRUE;
    }
    
    afx_msg HBRUSH OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
    {
        HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);
        
        if (pWnd->GetDlgCtrlID() == IDC_STATIC_LABEL) {
            pDC->SetTextColor(RGB(255, 0, 0));  // 빨간색 텍스트
            pDC->SetBkMode(TRANSPARENT);
        }
        
        return hbr;
    }
    
    DECLARE_MESSAGE_MAP()
};

BEGIN_MESSAGE_MAP(CMyDialog, CDialog)
    ON_WM_CTLCOLOR()
END_MESSAGE_MAP()

2. 리스트 컨트롤 (CListBox, CComboBox)

2.1 CListBox

class CMyDialog : public CDialog
{
public:
    CListBox m_listBox;
    
protected:
    virtual BOOL OnInitDialog()
    {
        CDialog::OnInitDialog();
        
        // 항목 추가
        m_listBox.AddString(_T("항목 1"));
        m_listBox.AddString(_T("항목 2"));
        m_listBox.AddString(_T("항목 3"));
        
        // 특정 위치에 삽입
        m_listBox.InsertString(0, _T("맨 앞 항목"));
        
        // 첫 번째 항목 선택
        m_listBox.SetCurSel(0);
        
        return TRUE;
    }
    
    afx_msg void OnLbnSelchangeList()
    {
        int sel = m_listBox.GetCurSel();
        if (sel != LB_ERR) {
            CString text;
            m_listBox.GetText(sel, text);
            
            CString msg;
            msg.Format(_T("선택: %s (인덱스: %d)"), text, sel);
            AfxMessageBox(msg);
        }
    }
    
    afx_msg void OnLbnDblclkList()
    {
        int sel = m_listBox.GetCurSel();
        if (sel != LB_ERR) {
            CString text;
            m_listBox.GetText(sel, text);
            AfxMessageBox(_T("더블클릭: ") + text);
        }
    }
    
    afx_msg void OnBnClickedAdd()
    {
        CString text;
        GetDlgItemText(IDC_EDIT_ITEM, text);
        
        if (!text.IsEmpty()) {
            m_listBox.AddString(text);
            SetDlgItemText(IDC_EDIT_ITEM, _T(""));
        }
    }
    
    afx_msg void OnBnClickedDelete()
    {
        int sel = m_listBox.GetCurSel();
        if (sel != LB_ERR) {
            m_listBox.DeleteString(sel);
            
            // 다음 항목 선택
            int count = m_listBox.GetCount();
            if (count > 0) {
                if (sel >= count) sel = count - 1;
                m_listBox.SetCurSel(sel);
            }
        }
    }
    
    DECLARE_MESSAGE_MAP()
};

BEGIN_MESSAGE_MAP(CMyDialog, CDialog)
    ON_LBN_SELCHANGE(IDC_LIST, &CMyDialog::OnLbnSelchangeList)
    ON_LBN_DBLCLK(IDC_LIST, &CMyDialog::OnLbnDblclkList)
    ON_BN_CLICKED(IDC_BTN_ADD, &CMyDialog::OnBnClickedAdd)
    ON_BN_CLICKED(IDC_BTN_DELETE, &CMyDialog::OnBnClickedDelete)
END_MESSAGE_MAP()

2.2 CComboBox

class CMyDialog : public CDialog
{
public:
    CComboBox m_combo;
    
protected:
    virtual BOOL OnInitDialog()
    {
        CDialog::OnInitDialog();
        
        // 항목 추가
        m_combo.AddString(_T("서울"));
        m_combo.AddString(_T("부산"));
        m_combo.AddString(_T("대구"));
        m_combo.AddString(_T("인천"));
        
        // 첫 번째 항목 선택
        m_combo.SetCurSel(0);
        
        return TRUE;
    }
    
    afx_msg void OnCbnSelchangeCombo()
    {
        int sel = m_combo.GetCurSel();
        if (sel != CB_ERR) {
            CString text;
            m_combo.GetLBText(sel, text);
            
            AfxMessageBox(_T("선택: ") + text);
        }
    }
    
    DECLARE_MESSAGE_MAP()
};

BEGIN_MESSAGE_MAP(CMyDialog, CDialog)
    ON_CBN_SELCHANGE(IDC_COMBO, &CMyDialog::OnCbnSelchangeCombo)
END_MESSAGE_MAP()

3. CListCtrl (리스트뷰)

3.1 기본 사용법

class CMyDialog : public CDialog
{
public:
    CListCtrl m_list;
    
protected:
    virtual BOOL OnInitDialog()
    {
        CDialog::OnInitDialog();
        
        // 확장 스타일
        m_list.SetExtendedStyle(LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES);
        
        // 컬럼 추가
        m_list.InsertColumn(0, _T("이름"), LVCFMT_LEFT, 150);
        m_list.InsertColumn(1, _T("나이"), LVCFMT_LEFT, 80);
        m_list.InsertColumn(2, _T("직업"), LVCFMT_LEFT, 150);
        
        // 항목 추가
        AddUser(_T("홍길동"), 30, _T("개발자"));
        AddUser(_T("김철수"), 25, _T("디자이너"));
        AddUser(_T("이영희"), 28, _T("기획자"));
        
        return TRUE;
    }
    
    void AddUser(const CString& name, int age, const CString& job)
    {
        int index = m_list.GetItemCount();
        
        // 첫 번째 컬럼 (항목 추가)
        m_list.InsertItem(index, name);
        
        // 나머지 컬럼
        CString strAge;
        strAge.Format(_T("%d"), age);
        m_list.SetItemText(index, 1, strAge);
        m_list.SetItemText(index, 2, job);
        
        // 항목 데이터 (사용자 정의 데이터)
        m_list.SetItemData(index, (DWORD_PTR)age);
    }
    
    DECLARE_MESSAGE_MAP()
    
    afx_msg void OnLvnItemchangedList(NMHDR* pNMHDR, LRESULT* pResult)
    {
        LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
        
        if (pNMLV->uNewState & LVIS_SELECTED) {
            int index = pNMLV->iItem;
            
            CString name = m_list.GetItemText(index, 0);
            CString age = m_list.GetItemText(index, 1);
            CString job = m_list.GetItemText(index, 2);
            
            CString msg;
            msg.Format(_T("%s, %s세, %s"), name, age, job);
            SetDlgItemText(IDC_STATIC_INFO, msg);
        }
        
        *pResult = 0;
    }
    
    afx_msg void OnNMDblclkList(NMHDR* pNMHDR, LRESULT* pResult)
    {
        LPNMITEMACTIVATE pNMIA = reinterpret_cast<LPNMITEMACTIVATE>(pNMHDR);
        
        int index = pNMIA->iItem;
        if (index != -1) {
            CString name = m_list.GetItemText(index, 0);
            AfxMessageBox(_T("더블클릭: ") + name);
        }
        
        *pResult = 0;
    }
};

BEGIN_MESSAGE_MAP(CMyDialog, CDialog)
    ON_NOTIFY(LVN_ITEMCHANGED, IDC_LIST, &CMyDialog::OnLvnItemchangedList)
    ON_NOTIFY(NM_DBLCLK, IDC_LIST, &CMyDialog::OnNMDblclkList)
END_MESSAGE_MAP()

3.2 정렬과 검색

// 정렬 콜백
static int CALLBACK CompareFunc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
{
    // lParam1, lParam2: 항목의 ItemData
    // lParamSort: 정렬 기준 (컬럼 인덱스 등)
    
    int age1 = (int)lParam1;
    int age2 = (int)lParam2;
    
    return age1 - age2;  // 오름차순
}

afx_msg void OnBnClickedSort()
{
    m_list.SortItems(CompareFunc, 0);
}

afx_msg void OnEnChangeSearch()
{
    CString keyword;
    GetDlgItemText(IDC_EDIT_SEARCH, keyword);
    
    // 모든 항목 탐색
    for (int i = 0; i < m_list.GetItemCount(); i++) {
        CString text = m_list.GetItemText(i, 0);
        
        if (text.Find(keyword) != -1) {
            // 검색어 포함 → 선택
            m_list.SetItemState(i, LVIS_SELECTED, LVIS_SELECTED);
            m_list.EnsureVisible(i, FALSE);
            break;
        }
    }
}

4. CTreeCtrl (트리뷰)

4.1 기본 사용법

class CMyDialog : public CDialog
{
public:
    CTreeCtrl m_tree;
    CImageList m_imageList;
    
protected:
    virtual BOOL OnInitDialog()
    {
        CDialog::OnInitDialog();
        
        // 이미지 리스트 생성
        m_imageList.Create(16, 16, ILC_COLOR32 | ILC_MASK, 0, 1);
        // 아이콘 추가...
        m_tree.SetImageList(&m_imageList, TVSIL_NORMAL);
        
        // 루트 항목
        HTREEITEM hRoot = m_tree.InsertItem(_T("내 컴퓨터"));
        
        // 자식 항목
        HTREEITEM hC = m_tree.InsertItem(_T("C: 드라이브"), hRoot);
        HTREEITEM hD = m_tree.InsertItem(_T("D: 드라이브"), hRoot);
        
        // C 드라이브 하위
        m_tree.InsertItem(_T("Program Files"), hC);
        m_tree.InsertItem(_T("Windows"), hC);
        m_tree.InsertItem(_T("Users"), hC);
        
        // 루트 확장
        m_tree.Expand(hRoot, TVE_EXPAND);
        
        return TRUE;
    }
    
    DECLARE_MESSAGE_MAP()
    
    afx_msg void OnTvnSelchangedTree(NMHDR* pNMHDR, LRESULT* pResult)
    {
        LPNMTREEVIEW pNMTreeView = reinterpret_cast<LPNMTREEVIEW>(pNMHDR);
        
        HTREEITEM hItem = pNMTreeView->itemNew.hItem;
        CString text = m_tree.GetItemText(hItem);
        
        SetDlgItemText(IDC_STATIC_INFO, _T("선택: ") + text);
        
        *pResult = 0;
    }
    
    afx_msg void OnNMDblclkTree(NMHDR* pNMHDR, LRESULT* pResult)
    {
        // 더블클릭 위치
        CPoint pt;
        GetCursorPos(&pt);
        m_tree.ScreenToClient(&pt);
        
        UINT flags;
        HTREEITEM hItem = m_tree.HitTest(pt, &flags);
        
        if (hItem != NULL) {
            CString text = m_tree.GetItemText(hItem);
            AfxMessageBox(_T("더블클릭: ") + text);
        }
        
        *pResult = 0;
    }
};

BEGIN_MESSAGE_MAP(CMyDialog, CDialog)
    ON_NOTIFY(TVN_SELCHANGED, IDC_TREE, &CMyDialog::OnTvnSelchangedTree)
    ON_NOTIFY(NM_DBLCLK, IDC_TREE, &CMyDialog::OnNMDblclkTree)
END_MESSAGE_MAP()

5. 프로그레스바와 슬라이더

5.1 CProgressCtrl

class CMyDialog : public CDialog
{
public:
    CProgressCtrl m_progress;
    
protected:
    virtual BOOL OnInitDialog()
    {
        CDialog::OnInitDialog();
        
        // 범위 설정
        m_progress.SetRange(0, 100);
        m_progress.SetPos(0);
        
        return TRUE;
    }
    
    afx_msg void OnBnClickedStart()
    {
        // 타이머로 진행률 시뮬레이션
        SetTimer(1, 100, NULL);
    }
    
    afx_msg void OnTimer(UINT_PTR nIDEvent)
    {
        if (nIDEvent == 1) {
            int pos = m_progress.GetPos();
            
            if (pos >= 100) {
                KillTimer(1);
                AfxMessageBox(_T("완료!"));
                m_progress.SetPos(0);
            } else {
                m_progress.SetPos(pos + 1);
                
                CString text;
                text.Format(_T("%d%%"), pos + 1);
                SetDlgItemText(IDC_STATIC_PERCENT, text);
            }
        }
        
        CDialog::OnTimer(nIDEvent);
    }
    
    DECLARE_MESSAGE_MAP()
};

BEGIN_MESSAGE_MAP(CMyDialog, CDialog)
    ON_BN_CLICKED(IDC_BTN_START, &CMyDialog::OnBnClickedStart)
    ON_WM_TIMER()
END_MESSAGE_MAP()

5.2 CSliderCtrl

class CMyDialog : public CDialog
{
public:
    CSliderCtrl m_slider;
    
protected:
    virtual BOOL OnInitDialog()
    {
        CDialog::OnInitDialog();
        
        // 범위 설정
        m_slider.SetRange(0, 100);
        m_slider.SetPos(50);
        
        // 틱 표시
        m_slider.SetTicFreq(10);
        
        UpdateVolumeDisplay();
        
        return TRUE;
    }
    
    void UpdateVolumeDisplay()
    {
        int pos = m_slider.GetPos();
        
        CString text;
        text.Format(_T("볼륨: %d"), pos);
        SetDlgItemText(IDC_STATIC_VOLUME, text);
    }
    
    afx_msg void OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
    {
        if (pScrollBar == (CScrollBar*)&m_slider) {
            UpdateVolumeDisplay();
        }
        
        CDialog::OnHScroll(nSBCode, nPos, pScrollBar);
    }
    
    DECLARE_MESSAGE_MAP()
};

BEGIN_MESSAGE_MAP(CMyDialog, CDialog)
    ON_WM_HSCROLL()
END_MESSAGE_MAP()

6. 컨트롤 서브클래싱

6.1 숫자만 입력 가능한 에디트

// NumericEdit.h
class CNumericEdit : public CEdit
{
public:
    DECLARE_MESSAGE_MAP()
    
    afx_msg void OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
    {
        // 숫자, 백스페이스, 딜리트만 허용
        if (isdigit(nChar) || nChar == VK_BACK || nChar == VK_DELETE) {
            CEdit::OnChar(nChar, nRepCnt, nFlags);
        } else {
            // 다른 키는 무시 (삐 소리)
            MessageBeep(MB_ICONHAND);
        }
    }
};

BEGIN_MESSAGE_MAP(CNumericEdit, CEdit)
    ON_WM_CHAR()
END_MESSAGE_MAP()

// 사용
class CMyDialog : public CDialog
{
public:
    CNumericEdit m_editAge;  // CEdit 대신 CNumericEdit
    
protected:
    virtual void DoDataExchange(CDataExchange* pDX)
    {
        CDialog::DoDataExchange(pDX);
        DDX_Control(pDX, IDC_EDIT_AGE, m_editAge);
    }
};

같이 보면 좋은 글 (내부 링크)

이 주제와 연결되는 다른 글입니다.

  • MFC 기초 | Microsoft Foundation Class 시작 가이드
  • MFC 다이얼로그 | CDialog·DDX/DDV 완벽 가이드
  • Windows API 컨트롤 | 버튼·에디트·리스트박스 완벽 가이드

이 글이 도움이 되셨나요? MFC 컨트롤 활용을 마스터하는 데 도움이 되었기를 바랍니다!

다음 글에서는 MFC GDI+ 그래픽 처리를 다루겠습니다. 🎨