[VC++][MFC] List Control (CListCtrl) Column에 ComboBox 사용하기.


 리스트 컨트롤 스타일중 Report 로 이용시 컬럼 내부에 콤보 박스를 넣는 방법에 대해서 소개합니다.
버튼이나 특정 이벤트를 통해서 컬럼의 값을 변경하는 것이 아닌, 콤보 박스를 이용하여 리스트 컨트롤 내부에서 컬럼의 값을 변경한다는 것은 상당히 큰 매리트가 있습니다.

 좀더 독립적으로 모듈화시켜서 사용하기 편리하고 깔끔하게끔 코드를 짜고싶었는데, 아직까진 시간이 부족해서 1차로 포스팅 합니다.

 질문은 프로필에 보셨듯 Guest에서 받겠습니다.
급하면 resignation@nate.com 네이트로도 무례하게 접근만 안한다면 나름 친절하게 대답해드리도록 하지요~.



 소스는 두개의 클래스로 되어있습니다. CListCtrl 을 상속받는 클래스(이하 CAdvListCtrl)와, CComboBox를 상속받는 클래스(이하 CAdvComboBox)입니다.
 CAdvListCtrl 클래스의 경우엔 CAdvComboBox에서 발생하는 주요 이벤트 (Selection 변경, 키 입력, 포커스 드롭 등)를 받아 컬럼 내용을 처리해주는데 주 목적이 있습니다. 물론 리스트 컨트롤 포커싱(마우스클릭 등 포커스가 잡혔을때)시 CAdvComboBox 를 생성 혹은 삭제 해주는 역할도 있습니다.
 CComboBox 의 경우 위에 한번 언급한 주요 이벤트 발생시 CAdvListCtrl 로 이벤트를 날려주는데 주 목적이 있습니다.

 본 소스는 ComboBox 아이템이 하드코딩 되어있는데, 조만간 손을 봐서 아이템도 사용자가 원하는데로 추가 기입이 가능하도록 할 생각입니다.
 


AdvListCtrl.h
  1. #pragma once  
  2.   
  3. class CAdvListCtrl : public CListCtrl  
  4. {  
  5.     DECLARE_DYNAMIC(CAdvListCtrl)  
  6.   
  7. public:  
  8.     CAdvListCtrl();  
  9.     virtual ~CAdvListCtrl();  
  10.   
  11.     afx_msg void OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar);  
  12.     afx_msg void OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar);  
  13.     afx_msg void OnLvnEndlabeledit(NMHDR *pNMHDR, LRESULT *pResult);  
  14.     afx_msg void OnLButtonDown(UINT nFlags, CPoint point);  
  15.     afx_msg void OnLButtonUp(UINT nFlags, CPoint point);  
  16.   
  17.     int         HitTestEx(CPoint& point, int* nCol) const;  
  18.     void        SetColumnCombo(int nColumn);  
  19.   
  20. protected:  
  21.     DECLARE_MESSAGE_MAP()  
  22.   
  23.     int     m_nColumn;  
  24.     CComboBox*  ShowAdvComboBox(int nItem, int nCol, CStringList& lstItems, int nSel);  
  25. };  


AdvListCtrl.cpp
  1. #include "stdafx.h"  
  2. #include "AdvListCtrl.h"  
  3. #include "AdvComboBox.h"  
  4.   
  5. IMPLEMENT_DYNAMIC(CAdvListCtrl, CListCtrl)  
  6.   
  7. CAdvListCtrl::CAdvListCtrl()  
  8. {  
  9.     m_nColumn = -1;  
  10. }  
  11.   
  12. CAdvListCtrl::~CAdvListCtrl()  
  13. {  
  14. }  
  15.   
  16.   
  17. BEGIN_MESSAGE_MAP(CAdvListCtrl, CListCtrl)  
  18.     ON_WM_HSCROLL()  
  19.     ON_WM_VSCROLL()  
  20.     ON_NOTIFY_REFLECT(LVN_ENDLABELEDIT, &CAdvListCtrl::OnLvnEndlabeledit)  
  21.     ON_WM_LBUTTONDOWN()  
  22.     ON_WM_LBUTTONUP()  
  23. END_MESSAGE_MAP()  
  24.   
  25. void CAdvListCtrl::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)  
  26. {  
  27.     if(GetFocus() != this) SetFocus();  
  28.     CListCtrl::OnHScroll(nSBCode, nPos, pScrollBar);  
  29. }  
  30.   
  31. void CAdvListCtrl::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)  
  32. {  
  33.     if(GetFocus() != this) SetFocus();  
  34.     CListCtrl::OnVScroll(nSBCode, nPos, pScrollBar);  
  35. }  
  36.   
  37. void CAdvListCtrl::OnLvnEndlabeledit(NMHDR *pNMHDR, LRESULT *pResult)  
  38. {  
  39.     NMLVDISPINFO *pDispInfo = reinterpret_cast<NMLVDISPINFO*>(pNMHDR);  
  40.     LVITEMW* pItem = &pDispInfo->item;  
  41.   
  42.     if(pItem->pszText != NULL)  
  43.     {  
  44.         SetItemText(pItem->iItem, pItem->iSubItem, pItem->pszText);  
  45.     }  
  46.   
  47.     *pResult = 0;  
  48. }  
  49.   
  50. void CAdvListCtrl::OnLButtonDown(UINT nFlags, CPoint point)  
  51. {  
  52.     int nIndex;  
  53.     CListCtrl::OnLButtonDown(nFlags, point);  
  54.   
  55.     int nColumn;  
  56.     if((nIndex = HitTestEx(point, &nColumn)) != -1)  
  57.     {  
  58.         UINT flag = LVIS_FOCUSED;  
  59.         if((GetItemState(nIndex, flag) & flag) == flag && m_nColumn == nColumn)  
  60.         {  
  61.             if(!(GetWindowLong(m_hWnd, GWL_STYLE) & LVS_EDITLABELS))  
  62.             {  
  63.                 CStringList lstItems;  
  64.                 lstItems.AddTail(L"Item 1");  
  65.                 lstItems.AddTail(L"Item 2");  
  66.   
  67.                 CString strGet = this->GetItemText(nIndex, m_nColumn);  
  68.                 ShowAdvComboBox(nIndex, m_nColumn, lstItems, 0);  
  69.                 SetItemState(nIndex, LVIS_SELECTED, LVIS_SELECTED);  
  70.             }  
  71.         }  
  72.         else  
  73.         {  
  74.             SetItemState(nIndex, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED);  
  75.         }  
  76.     }  
  77.   
  78. //  CListCtrl::OnLButtonDown(nFlags, point);  
  79. }  
  80.   
  81. void CAdvListCtrl::OnLButtonUp(UINT nFlags, CPoint point)  
  82. {  
  83. //  CListCtrl::OnLButtonUp(nFlags, point);  
  84. }  
  85.   
  86. int CAdvListCtrl::HitTestEx(CPoint& point, int* nCol) const  
  87. {  
  88.     int nColumn = 0;  
  89.     int nRow = HitTest(point, NULL);  
  90.   
  91.     if(nCol) *nCol = 0;  
  92.   
  93.     // LVS_REPORT Check  
  94.     if((GetWindowLong(m_hWnd, GWL_STYLE) & LVS_TYPEMASK) != LVS_REPORT)  
  95.         return nRow;  
  96.   
  97.     nRow = GetTopIndex();  
  98.     int nBottom = nRow + GetCountPerPage();  
  99.   
  100.     if(nBottom > GetItemCount())  
  101.         nBottom = GetItemCount();  
  102.   
  103.     CHeaderCtrl* pHeader = (CHeaderCtrl*)GetDlgItem(0);  
  104.     int nColumnCount = pHeader->GetItemCount();  
  105.   
  106.     for(; nRow <= nBottom; ++nRow)  
  107.     {  
  108.         CRect rect;  
  109.         GetItemRect(nRow, &rect, LVIR_BOUNDS);  
  110.   
  111.         if(rect.PtInRect(point))  
  112.         {  
  113.             for(nColumn = 0; nColumn < nColumnCount; ++nColumn)  
  114.             {  
  115.                 int nColWidth = GetColumnWidth(nColumn);  
  116.                 if(point.x >= rect.left && point.x <= (rect.left + nColWidth))  
  117.                 {  
  118.                     if(nCol) *nCol = nColumn;  
  119.                     return nRow;  
  120.                 }  
  121.   
  122.                 rect.left += nColWidth;  
  123.             }  
  124.         }  
  125.     }  
  126.   
  127.     return -1;  
  128. }  
  129.   
  130. void CAdvListCtrl::SetColumnCombo(int nColumn)  
  131. {  
  132.     m_nColumn = nColumn;  
  133. }  
  134.   
  135. CComboBox* CAdvListCtrl::ShowAdvComboBox(int nItem, int nCol, CStringList& lstItems, int nSel)  
  136. {  
  137.     if(!EnsureVisible(nItem, TRUE)) return NULL;  
  138.   
  139.     CHeaderCtrl* pHeader = (CHeaderCtrl*)GetDlgItem(0);  
  140.     int nColumnCount = pHeader->GetItemCount();  
  141.     if(nCol >= nColumnCount || GetColumnWidth(nCol) < 10)  
  142.         return NULL;  
  143.   
  144.     int offset = 0;  
  145.     for(int i = 0; i < nCol; ++i)  
  146.     {  
  147.         offset += GetColumnWidth(i);  
  148.     }  
  149.   
  150.     CRect rect;  
  151.     GetItemRect(nItem, &rect, LVIR_BOUNDS);  
  152.   
  153.     CRect rcClient;  
  154.     GetClientRect(&rcClient);  
  155.     if(offset + rect.left < 0 || offset + rect.left > rcClient.right)  
  156.     {  
  157.         CSize size;  
  158.         size.cx = offset + rect.left;  
  159.         size.cy = 0;  
  160.         Scroll(size);  
  161.         rect.left -= size.cx;  
  162.     }  
  163.   
  164.     rect.left += offset + 4;  
  165.     rect.right = rect.left + GetColumnWidth(nCol) - 3;  
  166.     int nHeight = rect.bottom - rect.top;  
  167.     rect.bottom += 5 * nHeight;  
  168.     if(rect.right > rcClient.right) rect.right = rcClient.right;  
  169.   
  170.     DWORD dwStyle = WS_BORDER | WS_CHILD | WS_VISIBLE |  
  171.         CBS_DROPDOWNLIST | CBS_DISABLENOSCROLL;  
  172.     CComboBox* pList = new CAdvComboBox(nItem, nCol, &lstItems, nSel);  
  173.     pList->Create(dwStyle, rect, this, IDC_ADVCOMBO);  
  174.     pList->SetItemHeight(-1, nHeight);  
  175.     pList->SetHorizontalExtent(GetColumnWidth(nCol));  
  176.   
  177.     return pList;  
  178. }  


AdvComboBox.h
  1. #pragma once  
  2.   
  3. class CAdvComboBox : public CComboBox  
  4. {  
  5.     DECLARE_DYNAMIC(CAdvComboBox)  
  6.   
  7. public:  
  8.     CAdvComboBox(int nItem, int nSubItem, CStringList* plstItems, int nSel);  
  9.     virtual ~CAdvComboBox();  
  10.   
  11.     virtual BOOL PreTranslateMessage(MSG* pMsg);  
  12.   
  13. protected:  
  14.     DECLARE_MESSAGE_MAP()  
  15.   
  16. public:  
  17.     afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);  
  18.     afx_msg void OnKillFocus(CWnd* pNewWnd);  
  19.     afx_msg void OnChar(UINT nChar, UINT nRepCnt, UINT nFlags);  
  20.     afx_msg void OnNcDestroy();  
  21.     afx_msg void OnCloseUp();  
  22.   
  23. private:  
  24.     int         m_nItem;  
  25.     int         m_nSubItem;  
  26.     CStringList m_lstItems;  
  27.     int         m_nSel;  
  28.     bool        m_bESC;  
  29. };  


AdvComboBox.cpp
  1. #include "stdafx.h"  
  2. #include "AdvComboBox.h"  
  3.   
  4. IMPLEMENT_DYNAMIC(CAdvComboBox, CComboBox)  
  5.   
  6. CAdvComboBox::CAdvComboBox(int nItem, int nSubItem, CStringList* plstItems, int nSel)  
  7. : m_nItem(nItem)  
  8. , m_nSubItem(nSubItem)  
  9. , m_nSel(nSel)  
  10. , m_bESC(false)  
  11. {  
  12.     m_lstItems.AddTail(plstItems);  
  13. }  
  14.   
  15. CAdvComboBox::~CAdvComboBox()  
  16. {  
  17. }  
  18.   
  19.   
  20. BEGIN_MESSAGE_MAP(CAdvComboBox, CComboBox)  
  21.     ON_WM_CREATE()  
  22.     ON_WM_KILLFOCUS()  
  23.     ON_WM_CHAR()  
  24.     ON_WM_NCDESTROY()  
  25.     ON_CONTROL_REFLECT(CBN_CLOSEUP, OnCloseUp)  
  26. END_MESSAGE_MAP()  
  27.   
  28. BOOL CAdvComboBox::PreTranslateMessage(MSG* pMsg)  
  29. {  
  30.     if(pMsg->message == WM_KEYDOWN)  
  31.     {  
  32.         if(pMsg->wParam == VK_RETURN || pMsg->wParam == VK_ESCAPE)  
  33.         {  
  34.             ::TranslateMessage(pMsg);  
  35.             ::DispatchMessage(pMsg);  
  36.   
  37.             return TRUE;  
  38.         }  
  39.     }  
  40.   
  41.     return CComboBox::PreTranslateMessage(pMsg);  
  42. }  
  43.   
  44. int CAdvComboBox::OnCreate(LPCREATESTRUCT lpCreateStruct)  
  45. {  
  46.     if (CComboBox::OnCreate(lpCreateStruct) == -1)  
  47.         return -1;  
  48.   
  49.     CFont* pFont = GetParent()->GetFont();  
  50.     SetFont(pFont);  
  51.   
  52.     for(POSITION pos = m_lstItems.GetHeadPosition(); pos != NULL; )  
  53.     {  
  54.         AddString((LPCTSTR)(m_lstItems.GetNext(pos)));  
  55.     }  
  56.     SetCurSel(m_nSel);  
  57.     SetFocus();  
  58.   
  59.     return 0;  
  60. }  
  61.   
  62. void CAdvComboBox::OnKillFocus(CWnd* pNewWnd)  
  63. {  
  64.     CComboBox::OnKillFocus(pNewWnd);  
  65.   
  66.     CString strTemp;  
  67.     GetWindowText(strTemp);  
  68.   
  69.     LV_DISPINFO dispinfo;  
  70.     dispinfo.hdr.hwndFrom   = GetParent()->m_hWnd;  
  71.     dispinfo.hdr.idFrom     = GetDlgCtrlID();  
  72.     dispinfo.hdr.code       = LVN_ENDLABELEDIT;  
  73.   
  74.     dispinfo.item.mask      = LVIF_TEXT;  
  75.     dispinfo.item.iItem     = m_nItem;  
  76.     dispinfo.item.iSubItem  = m_nSubItem;  
  77.     dispinfo.item.pszText   = m_bESC ? NULL : LPTSTR((LPCTSTR)strTemp);  
  78.     dispinfo.item.cchTextMax= strTemp.GetLength();  
  79.   
  80.     GetParent()->GetParent()->SendMessage(WM_NOTIFY, GetParent()->GetDlgCtrlID(), (LPARAM)&dispinfo);  
  81.   
  82.     PostMessage(WM_CLOSE);  
  83. }  
  84.   
  85. void CAdvComboBox::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)  
  86. {  
  87.     if(nChar == VK_ESCAPE || nChar == VK_RETURN)  
  88.     {  
  89.         if(nChar == VK_ESCAPE)  
  90.         {  
  91.             m_bESC = true;  
  92.         }  
  93.         GetParent()->SetFocus();  
  94.         return;  
  95.     }  
  96.   
  97.     CComboBox::OnChar(nChar, nRepCnt, nFlags);  
  98. }  
  99.   
  100. void CAdvComboBox::OnNcDestroy()  
  101. {  
  102.     CComboBox::OnNcDestroy();  
  103.   
  104.     delete this;  
  105. }  
  106.   
  107. void CAdvComboBox::OnCloseUp()  
  108. {  
  109.     GetParent()->SetFocus();  
  110. }