MFC 버전이 업그레이드 되면서, 예전 방법을 찾아서 프레임 배경에 이미지를 깔아보려고 했으나 구조가 변경되었는지 안먹네요. 지금 MSVS 2017로 작업 중 입니다...

아주 간단하게 작성해서 바로 써먹을 수 있는 예제를 하나 올립니다.


새로운 MFC 프로젝트를 생성합니다.


MDI 기본 구성으로 설정하고, Finish~


배경으로 사용할 이미지 입니다.


프로젝트 리소스에 이미지를 추가합니다. 이름은 IDB_BITMAP_BACK 으로 하였습니다.


CMainFrame 에 변수를 하나 선언합니다.

CBitmap m_bmp;


그리고, CMainFrame 의 OnCreate 에 코드를 한 줄 추가합니다. 

m_bmp.LoadBitmap(IDB_BITMAP_BACK);

CMainFrame 에 WM_PAINT 이벤트 핸들러를 추가하고, 아래와 같이 코드를 작성합니다.

void CMainFrame::OnPaint()
{
    CPaintDC dc(this);

    if (m_bmp.m_hObject)
    {
        BITMAP bm;
        CDC dcMem;

        CDC* pDC = m_wndClientArea.GetDC();
        VERIFY(m_bmp.GetObject(sizeof(bm), (LPVOID)&bm));

        dcMem.CreateCompatibleDC(pDC);
        CBitmap* pOldBMP = (CBitmap*)dcMem.SelectObject(&m_bmp);

        CRect rect;
        m_wndClientArea.GetClientRect(rect);

        pDC->FillSolidRect(rect, RGB(128, 128, 128));

        pDC->BitBlt((rect.right - bm.bmWidth) / 2,   // centered
            (rect.bottom - bm.bmHeight) / 2,
            bm.bmWidth,
            bm.bmHeight,
            &dcMem,
            0, 0,
            SRCCOPY);

        dcMem.SelectObject(pOldBMP);

        ::ReleaseDC(m_wndClientArea.m_hWnd, pDC->m_hDC);
    }
}


마지막으로 아래처럼 메인프레임 화면이 갱신될 때 MFC 내부에 정의된 배경을 관리하는 클라이언트도 함께 갱신하는 코드를 한 줄 넣어줍니다.

BOOL CMainFrame::PreTranslateMessage(MSG* pMsg)
{
    if (pMsg->message == WM_PAINT)
    {
        if (pMsg->hwnd == m_hWndMDIClient)
            Invalidate();
    }
    ...

    return CMDIFrameWndEx::PreTranslateMessage(pMsg);
}


이제 실행해 보시면, MDI 에서 차일드 창이 하나도 없는 상태일 때 아래 그림처럼 배경에 이미지가 깔립니다.











칼라다이알로그 박스는 꼭 확인 버튼을 눌러야 색상을 얻어올 수 있습니다.
이걸 색상을 변경할 때마다 다이알로그를 닫지 않고도, 색상을 얻어올 수 있도록
수정하여 보았읍니다.

첫번째 아래그림은 그냥 색상을 선택해 본거구요..

사용자 삽입 이미지



아래 그림은 창을 닫지 않은 상태에서 다른색상을 선택해 본것입니다.
사용자 삽입 이미지




스크롤바를 사용하다 보면 32767 unsigned short의 양의값을 넘어가는 경우가
간혹 발생할 수 있다.

그래픽 디자인 프로그램과 같은 것들을 설계할 때야 줌 팩터등을 이용하여 따로 처리하니
별 문제가 없겠지만 간단한 먼가를 만들 때 100000의 값을 써야한다면 먼가 설계를 해야하나?

그렇다면 우선 MSDN을 살펴보자.

CScrollBar::SetScrollRange
Set nMinPos and nMaxPos to 0 to hide standard scroll bars.
Do not call this function to hide a scroll bar while processing a scroll-bar notification message.

If a call to SetScrollRange immediately follows a call to the SetScrollPos member function, set bRedraw in SetScrollPos to 0 to prevent the scroll bar from being redrawn twice.

The difference between the values specified by nMinPos and nMaxPos

must not be greater
than 32,767

. The default range for a scroll-bar control is empty

일단 스크롤바의 SetScrollRange의 인자는 int 값인데, 어쩌구 저쩌구한 이유로 32767이 최대
라고 한다. 인자가 int라면 그렇다면 먼가를 처리하면 최대 양수 20억까지 쓸수 있다는 야근데..



SetScrollRange Function Win32API

 

However, because the SetScrollInfo, SetScrollPos, SetScrollRange, GetScrollInfo,
GetScrollPos, and GetScrollRange

functions support 32-bit scroll bar position data,

there is a way to circumvent the 16-bit barrier of the WM_HSCROLL and WM_VSCROLL


messages. See GetScrollInfo for a description of the technique.

MSDN에서 API의 도움말을 살펴보았다. Remark를 자세히 보다보니 32비트를 쓸 수 있는데
16비트 베리어에 쌓여 있단다.. 그렇다면 위의 두 메시지에서 오는 UINT nPos 가 정수
범위임에도 불구하고 쇼트크기를 가진다면 저걸 않쓰면 가능하지 않을까...


(16비트 베리어라는 의미는 내부적으로 먼가를 처리할 때 아무생각 없이 16비트를 썼다는
내용을 아주 점잔하게 표시한 것으로 추측된다.. 즉, 16비트 머신의 잔제가 남았다는 거겠죠.)

--------------------------------------------------------------------------------------

일단 여기까지 살펴보면 32비트를 쓸 수 있는데도 불구하고, 메시지 WM_VSCROLL과
WM_HSCROLL의 UINT nPOS는 16비트 최대값을 가진다고 결론을 내릴 수있다.
즉, 저걸 않쓰면 된다는 이야기겠고..

그럼 저걸 않쓰고 프로그램을 구성하면 되겠죠..
이전 장에 있던 샘플은 0 부터 100까지 크기를 가지는 샘플이었습니다.
이걸 그럼 위의 지식을 기반으로 0부터 1000000 크기를 가지는 샘플로 바꿔보죠.

#define SCROLL_MIN   0
#define SCROLL_MAX 

1000000


마이스크롤.SetScrollRange(SCROLL_MIN, SCROLL_MAX);
마이스크롤.SetScrollPos(50);
마이스크롤.EnableScrollBar(ESB_ENABLE_BOTH);

요렇게 설정을 해놓고 WM_VSCROLL 핸들러를 다음과 같이 수정해봅니다.
어짜피 이전 샘플에서도 nPos를 쓰는데는 스크롤바를 트래킹할 때만 썻으므로
이 샘플에서도 바뀌는 부분은 거기 밖에는 없습니다.

void CSampleDlg::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
    UINT nCurPos;
    if(pScrollBar->GetDlgCtrlID() == IDC_SCROLLBAR5)
    {
        nCurPos = pScrollBar->GetScrollPos();
        switch(nSBCode)
        {
       

// 중간 생략.. 원본과 다른바 없음.

       case SB_THUMBPOSITION:
       {
          SCROLLINFO si = {0};
          si.cbSize = sizeof(SCROLLINFO);
          si.fMask = SIF_TRACKPOS;
          ::GetScrollInfo(pScrollBar->m_hWnd, SB_CTL, &si);

          // 메시지의 nPos 대신에 그냥 트랙포스를 가져다 쓴다.


          pScrollBar->SetScrollPos(si.nTrackPos);
       }
       break;

       case SB_THUMBTRACK:
       {
          SCROLLINFO si = {0};
          si.cbSize = sizeof(SCROLLINFO);
          si.fMask = SIF_TRACKPOS;
          ::GetScrollInfo(pScrollBar->m_hWnd, SB_CTL, &si);

          // 메시지의 nPos 대신에 그냥 트랙포스를 가져다 쓴다.


          pScrollBar->SetScrollPos(si.nTrackPos);
       }
       break;
      // 아래도 생략.. 원본과 같음
    }
 
    CDialog::OnVScroll(nSBCode, nPos, pScrollBar);
}

헉.. 그냥 않쓰면 되네.. ㅜㅜ;;;
그럼 수고하세요..

scroll02.zip
0.03MB

 

 

아주 오랜만에 컨트롤에 대하여 올려봅니다.
자주 않쓰다보니.. ^^;

리스트컨트롤을 사용하다 보면 서브아이템도 에디팅해야 할때가 있습니다.
요거 하나 쓸라구 클래스 만들기도 귀찬죠.. (이놈에 귀차니즘 -_-)

코드그루에 있는 자료를 좀 수정해서 사용해봅니다.
http://www.codeguru.com/cpp/controls/listview/editingitemsandsubitem/article.php/c1077/

위 자료인데요..
이것도 리스트를 서브클래싱해야되나서 좀 수정해서 걍 그런거 없이 써봅시다.

요건 에디트컨트롤 서브클래싱한 클래스인데요..
않한다면서도 필요하내요 -_-;;;

/////////////////////////////////////////////////
// 에디트컨트롤 헤더

class CLVEdit : public CEdit
{
// Construction
public:
   CLVEdit() { m_nEdit=-1; }
   void BeginEdit(CListCtrl* pList, int ndx);
   void EndEdit(CListCtrl* pList, NMHDR* pNMHDR);

// Attributes
public:
   CRect m_rc;
   BOOL  m_nEdit;

protected:
   //{{AFX_MSG(CLVEdit)
   afx_msg void OnWindowPosChanging(WINDOWPOS FAR* lpwndpos);
   //}}AFX_MSG
   DECLARE_MESSAGE_MAP()
};

///////////////////////////////////////////////////////////////
// 에디트 컨트롤 소스

BEGIN_MESSAGE_MAP(CLVEdit, CEdit)
   //{{AFX_MSG_MAP(CLVEdit)
   ON_WM_WINDOWPOSCHANGING()
   //}}AFX_MSG_MAP
END_MESSAGE_MAP()

void CLVEdit::OnWindowPosChanging(WINDOWPOS FAR* lpwndpos)
{
   lpwndpos->x=m_rc.left;
   lpwndpos->y=m_rc.top;

   if(m_rc.Width() > 0)
      lpwndpos->cx = m_rc.Width();
   if(m_rc.Height() > 0)
      lpwndpos->cy = m_rc.Height();

   CEdit::OnWindowPosChanging(lpwndpos);
}

void CLVEdit::BeginEdit(CListCtrl* pList, int ndx)
{
    CPoint posMouse;
    GetCursorPos(&posMouse);
    pList->ScreenToClient(&posMouse);

    LV_COLUMN lvc;
    lvc.mask=LVCF_WIDTH;

    CRect rcItem;
    pList->GetItemRect(ndx,rcItem,LVIR_LABEL);

    if(rcItem.PtInRect(posMouse))
        m_nEdit=0;

    int nCol=1;
    while(m_nEdit==-1 && pList->GetColumn(nCol,&lvc))
    {
        rcItem.left=rcItem.right;
        rcItem.right+=lvc.cx;

        if(rcItem.PtInRect(posMouse))
            m_nEdit=nCol;

        nCol++;
    }

    if(m_nEdit==-1)
       return;
   
    m_rc = rcItem;
    SetWindowText(pList->GetItemText(ndx, m_nEdit));
}

void CLVEdit::EndEdit(CListCtrl* pList, NMHDR* pNMHDR)
{
     LV_DISPINFO* pDispInfo=(LV_DISPINFO*)pNMHDR;
     CString sEdit=pDispInfo->item.pszText;
 
    if(!sEdit.IsEmpty())
       pList->SetItemText(pDispInfo->item.iItem,m_nEdit,sEdit);
 
    m_nEdit=-1;
    pList->SetItemState(pDispInfo->item.iItem,0,LVNI_FOCUSED|LVNI_SELECTED);
}



위의 헤더와 소스를 걍 Paste 하시거나 파일로 만들어서 첨부하시면 되구요..
다이알로그나 뷰등에서 리스트 컨트롤을 올려다가 사용하실 때..
다음과 같이 처리하시면 됩니다.

헤더에 아래를 하나 선언한다.
CLVEdit m_LVEdit;

소스에서 각 이벤트를 다음과 같이 핸들링한다.
void 마이다이알로그::OnBeginlabeleditList1(NMHDR* pNMHDR, LRESULT* pResult)
{
    LV_DISPINFO* pDispInfo=(LV_DISPINFO*)pNMHDR;
    *pResult=1;

    // 리스트컨트롤에 내장된 에디트컨트롤을 내가 맹글걸로 연결한다.
    HWND hWnd=(HWND)m_list.SendMessage(LVM_GETEDITCONTROL);
    ASSERT(hWnd!=NULL);
    VERIFY(m_LVEdit.SubclassWindow(hWnd));

    // 내부에서 좌표찾아서 에디트를 적당한 곳에 위치시킨다.
    m_LVEdit.BeginEdit(&m_list, pDispInfo->item.iItem);
    *pResult=0;
}

void 마이다이알로그::OnEndlabeleditList1(NMHDR* pNMHDR, LRESULT* pResult)
{
    LV_DISPINFO* pDispInfo=(LV_DISPINFO*)pNMHDR;
 
    // 에디트가 끝나면 그 값을 읽어다가, 선택된 셀에다가 쓴다.
    m_LVEdit.EndEdit(&m_list, pNMHDR);

    // 연결시킨걸 해제한다.
    VERIFY(m_LVEdit.UnsubclassWindow()!=NULL);

    *pResult=0;
}

InsertItem은 중요도도 높고, 다양한 양식을 제공함으로 별도로 취급한다.

1. int InsertItem(int nItem, LPCTSTR lpszItem);
2. int InsertItem(int nItem, LPCTSTR lpszItem, int nImage);
3. int InsertItem(const LVITEM* pItem);

샘플은 이전에 다루었던 장 중에서 하나씩 시작하기의 샘플을 수정하여 진행한다.
또한 LVITEM 구조체에 대한 설명은 링크를 참조한다.

1번과 2번에 대한 설명은 아이템의 인덱스, 레이블 문자열, 아이템의 이미지 인덱스와 같이
이해하기 쉽고 용법도 간단하므로 생략한다. (기존 장에서도 충분히 다루었다.)


아이템에 문자열만 넣기.
-----------------------------------------------------------------------------------
LVITEM item = {0};
item.mask = LVIF_TEXT;
item.iItem = 아이템 인덱스
item.pszText = 해당 문자열
리스트컨트롤.InsertItem(&item);
아주 간단하게 해당 아이템의 인덱스에 문자열만을 넣어 보았다.
item.mask 값에 LVIF_TEXT 가 설정되었는데, 해당 문자열을 처리함을 알려준다.

이미 들어있는 아이템의 문자열을 변경하려면 SetItemText를 이용하여 변경이 가능하고
그 값을 가져오려면 GetItemText를 이용하면 된다.


아이템에 문자열과 이미지 넣기.
-----------------------------------------------------------------------------------
LVITEM item = {0};
item.mask = LVIF_TEXT | LVIF_IMAGE;
item.iItem = 아이템 인덱스
item.pszText = 해당 문자열
item.iImage = 이미지 리스트의 해당 인덱스
리스트컨트롤.InsertItem(&item);
리스트 컨트롤에 이미지 리스트가 연결되어 있다면 LVIF_IMAGE 마스크를 넣어 이미리 리스트에해당하는 인덱스를 넣음으로써 컨트롤에 아이콘이 표시되도록 한다.

이미지 리스트를 연결하였음에도 불구하고, 이미지의 인덱스를 설정하지 않으면 기본값으로
0번 이미지가 그려짐에 주의하자.


한번 넣은 이미지를 변경하기 위한 별도의 메서드는 제공되지 않는다. 이미 넣은 이미지를
변경하기 위해서는 SetItem 메서드를 이용하여 변경이 가능하다.

예) 0번 아이템의 이미지를 5번 이미지로 바꾸기.

리스트컨트롤.SetItem(0, 0, LVIF_IMAGE, NULL, 5, 0, 0, 0);

LVITEM item = {0};
item.mask = LVIF_IMAGE;
item.iItem = 0;
item.iImage = 5;
리스트컨트롤.SetItem(&item);


아이템에 문자열과 부가 정보 넣기.
-----------------------------------------------------------------------------------
LVITEM item = {0};
item.mask = LVIF_TEXT | LVIF_PARAM;
item.iItem = 아이템 인덱스
item.pszText = 해당 문자열
item.lParam = 4바이트 숫자형 데이터 or 부가정보의 포인터
리스트컨트롤.InsertItem(&item);
부가 정보 필드인 LVITEM::lParam은 LPARAM 타입의 변수로 기본타입은 long 이다.

초기 아이템 삽입시 저 정보의 설정이 불필요하고 나중에 설정하거나 가져오려면
SetItemData 와 GetItemData 메서드를 이용하여 처리할 수 있다.


아이템에 상태 정보 넣기.
-----------------------------------------------------------------------------------
LVITEM item = {0};
item.mask = LVIF_TEXT | LVIF_STATE;
item.iItem = 아이템 인덱스
item.pszText = 해당 문자열
item.state = 상태값 조합
item.stateMask = 상태 마스크 플래그 조합
리스트컨트롤.InsertItem(&item);

상태 플래그는 다음과 같다.
LVIS_ACTIVATING
   지원 않함.
LVIS_CUT
   아이콘이 흐리게 보임 (cut & paste 동작시 주로사용)
LVIS_DROPHILITED
   drag&drop시 타겟 리스트 컨트롤일 경우 마우스 오버 상태에 따라 아이템을 하이라이트 시켜줌
LVIS_FOCUSED
   아이템에 포커스 사각형(쩜선 사각형)을 그려줌
LVIS_SELECTED
   아이템이 선택되어진 형태로 표시해줌
LVIS_STATEIMAGEMASK
   아이템의 이미지에 상태 이미지 선택할 수 있도록 함.
LVIS_OVERLAYMASK
   아이템에 오버레이 마스크용 아이콘을 겹쳐서 그려줌

볼드로 표기된 나머지는 상태 마스크와 상태값을 동일하게 넣으면 된다. 내용 자체도 이해하기
쉬우니 간단한 예만 적어본다.

예) 선택되어진 상태로 표시하기.
LVITEM item = {0};
item.mask = LVIF_TEXT | LVIF_STATE;
item.iItem = 아이템 인덱스
item.pszText = 해당 문자열
item.state = LVIS_FOCUSED;
item.stateMask = LVIS_FOCUSED;
리스트컨트롤.InsertItem(&item);

나머지 2개의 상태 마스크는 아래에 별도로 설명을 추가한다.


이미지에 오버레이 마스크 이미지 덧 씌우기.
-----------------------------------------------------------------------------------
LVITEM item = {0};
item.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_STATE;
item.iItem = 아이템 인덱스
item.pszText = 해당 문자열
item.iImage = 이미지 리스트의 해당 인덱스
item.state = INDEXTOOVERLAYMASK(오버레이 이미지 인덱스);
item.stateMask = LVIS_OVERLAYMASK;
리스트컨트롤.InsertItem(&item);
오버레이 이미지란 기본 이미지(아이콘) 위에, 다른 아이콘을 덧 씌워 그리는 것을 말한다.
탐색기를 살펴보면 단축 아이콘과 같은 경우

위 그림처럼 기본 이미지위에 오버레이 이미지가 그려져 있는 것을 알 수 있다.
이런 식으로 이미지를 두장 겹처서 그릴 때 사용하는 위와 같은 방법을 사용한다.

오버레이에 사용되는 이미지는 기본으로 연결된 이미지 리스트를 사용한다.


상태 이미지를 별도로 추가하기.
-----------------------------------------------------------------------------------
이건 코드를 달기 전에 먼저 간단한 설명을 전하고, 코드를 이용하여 나머지를 설명할 것이다.
리스트 하나 하나의 아이템이 어떤 상태에 따라 아이콘의 모양이 달라진다면 어떻게 구현할까?


(위 그림은 모두 폴더를 나타내는 아이콘이지만 상태에 따라 서로 다른 이미지를 가진다.)

1. 하나의 이미지 리스트에 몽땅 때려 놓고, 그 때 그 때 이미지를 변경한다.
2. LVSIL_STATE 로 이미지 리스트를 별도로 연결하여 놓고, 그 상태의 이미지를 넣는다.

1번 방법을 이용할 경우, 중간에 이미지가 새로 추가되거나 삭제되면 전체 인덱스 순서에
영향을 미치기 때문에 파급효과가 대단히 크다. 물론 많은 코드 수정도 불가피히다.

2번 방법을 이용할 경우, 이미 인덱스를 별도로 밖아 놓은 코드는 변경을 해야하지만 상태에 따른
이미지 변경 코드는 변화가 없다. 또한 기본 이미지리스트의 인덱스에 영향을 받지 않는다.

헤더파일
이미지리스트 작은이미지;
이미지리스트 큰이미지;
이미지리스트 상태이미지;

소스파일
리스트컨트롤.SetImageList(&작은이미지, LVSIL_SMALL);
리스트컨트롤.SetImageList(&큰이미지, LVSIL_NORMAL);
리스트컨트롤.SetImageList(&상태이미지, LVSIL_STATE);
위와 같이 기본 이미지 리스트와는 별도로 상태 이미지를 담은 이미지 리스트를 만들어 연결한다.

LVITEM item = {0};
item.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_STATE;
item.iItem = 아이템 인덱스
item.pszText = 해당 문자열
item.iImage = 이미지 리스트의 해당 인덱스
item.state = INDEXTOSTATEIMAGEMASK(상태 이미지 인덱스);
item.stateMask = LVIS_STATEIMAGEMASK;
리스트컨트롤.InsertItem(&item);


아이템에 이미지 여러개 넣기??? - 이게 정말 맞는 이해인지 모르겠다 (누가좀.. )
-----------------------------------------------------------------------------------

LVITEM item = {0};
item.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_INDENT;
item.iItem = 아이템 인덱스
item.pszText = 해당 문자열
item.iImage = 이미지 리스트의 해당 인덱스
item.iIndent = 아이템의 이미지 너비의 복수???
리스트컨트롤.InsertItem(&item);

(iIndent 변수는 common control version 4.7 부터 적용되는 기능입니다.)
리스트 컨트롤에서 레포트 스타일일 경우 첫번째 아이템에 여러개의 이미지가 있는 것을 본적이
있는가? (전 본 기억이 없는듯.. 이것도 오늘 공부해서 써본것입니다. -_-)

저 iIndent 값은 기본적으로 0이며, 저기 숫자를 넣으면, 첫번째 아이템이
저 숫자* 아이콘의 너비 만큼 띄워져서 그려진다.
(아래의 그림은 iIdent를 1로 준 상태입니다.)

그림에서 보는 것 처럼 왼쪽에 아이콘의 너비인 16 픽셀만큼 띄워져서 그림이 그려지고. iIndent 값을 높게 주면 그 배수만큼 띄워져서 그려지게 된다.
Custom Draw나 Owner Draw를 이용하면 저기 비워놓은 공간에 먼가 작업을 해줄 수 있겠지만
그냥 쓸 때는 전혀 의미가 없어보인다. 흠..
(누구 자세히 아시는분 계시면 코멘트좀 달아주세요..)

common control version 6.0 부터는 group 이라는 기능이 지원이 되는데.. 아쉽게도 MFC 6.0
에서는 지원이 되지 않습니다. 현재 깔린 툴이 6.0 뿐이라.. 제 컴에 vs2005를 깔게되면 이 내용에
group에 관련된 내용도 추가하도록 하겠습니다. ㅋ~



위의 내용들을 이용하여 제작된 샘플이다.
-----------------------------------------------------------------------------------

이전에 다루었던 장 중에서 하나씩 시작하기의 샘플을 재 사용(-_-;;;) 하였으며 기존과 달라진 점은 공유 폴더나 단축아이콘도 이미지가 정확하게 표현된다는 점이다.

변경된 내용은 다음과 같다.

BOOL CSampleDlg::OnInitDialog()
{
    ... 중간 생략
    int ndx = 0;
    CFileFind finder;
    SHFILEINFO sfi;
    BOOL bWorking = finder.FindFile("C:\\*.*");
    while (bWorking)
    {
        bWorking = finder.FindNextFile();
        SHGetFileInfo(finder.GetFilePath(),
            0,
            &sfi,
            sizeof(SHFILEINFO),
            SHGFI_DISPLAYNAME | SHGFI_TYPENAME| SHGFI_SYSICONINDEX | SHGFI_SMALLICON | SHGFI_ATTRIBUTES);
        ndx = AddItem2(finder, sfi, ndx);
    }
    // 찾은 파일의 파일속성 정보를 얻기 위하여 SHGFI_ATTRIBUTES 플래그가 추가되었다.

    ... 중간 생략
}

int CSampleDlg::AddItem2(CFileFind& file, SHFILEINFO& sfi, int ndx)
{
    CString szTemp;
    CTime fTime;

    LVITEM item = {0};
   
    // 아이템의 상태 정보를 넣기 위하여 LVIF_STATE 플래그를 추가함.
    item.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_STATE;
    item.iItem = ndx;
    item.iSubItem = 0;
    item.pszText = sfi.szDisplayName;
    item.iImage = sfi.iIcon;
   
    // 단축 아이콘 속성일 경우.. 2번째 이미지를 오버레이 이미지로 사용함.
    if(sfi.dwAttributes & SFGAO_LINK)
    {
        item.state |= INDEXTOOVERLAYMASK(2);
        item.stateMask |= LVIS_OVERLAYMASK;
    }
    // 공뷰 폴더 속성일 경우.. 1번째 이미지를 오버레이 이미지로 사용함.
    else if(sfi.dwAttributes & SFGAO_SHARE)
    {
        item.state |= INDEXTOOVERLAYMASK(1);
        item.stateMask |= LVIS_OVERLAYMASK;
    }

    // 히든 속성일 경우는 별도이 이미지를 사용하지 않고, LVIS_CUT 상태를 이용하여
    // 흐릿하게 보이도록 함.

    if(sfi.dwAttributes & SFGAO_HIDDEN)
        item.state |= LVIS_CUT;

    ndx = m_list.InsertItem(&item);

    ... 중간 생략   
}



이전에 리스트 컨트롤을 이용한 간단한 샘플을 다루어 보았다.
이제 차근 차근 제공되는 메서드를 하나씩 알아가 보자.
전체를 다루기에는 MSDN이 울지 모르니 -_-;;;;; 주요 메서드만 다루어본다.


----------------------------------------------------------------------
CSize ApproximateViewRect(
CSize sz= CSize(-1,-1), int iCount= -1) const;
리스트 컨트롤이 양식과 들어있는 아이템을 계산하여 전체 아이템을 표현하기 위하여
얼마만한 공간(픽셀단위)가 필요한지 계산해 주는 메서드.

자주 쓰이진 않지만 스크롤바가 생기지 않도록 폼을 자동생성할 경우등에.. 필요할 수 있다.

sz = 예상한 크기를 넣어주는 것인데, 사실상 동작하지 않는다.
iCount = 아이템을 주어진 갯수일때 공간을 계산한다. -1 디폴트 이면 전체..


----------------------------------------------------------------------COLORREF GetBkColor() const;
BOOL SetBkColor(COLORREF cr);
리스트 컨트롤의 배경색상을 설정하고 가져올 수 있다. 단, 글자가 써지는 영역은 이 배경에
영향을 주지 않으므로 이 설정만 사용하면 모양이 다음과 같이 나온다.



----------------------------------------------------------------------COLORREF GetTextBkColor() const;
BOOL SetTextBkColor(COLORREF cr);
글자의 배경색상을 설정하거나 가져올 수 있다. 단 글자가 써질 영역에만 영향을 미친다.
위의 SetBkColor 과 같이 쓰면 다음 처럼 배경을 깔끔하게 처리할 수있다.



----------------------------------------------------------------------COLORREF GetTextColor() const;
BOOL SetTextColor(COLORREF cr);
글씨의 색상을 설정하거나 가져올 수 있다.



이렇게 리스트 컨트롤은 배경과 글씨, 글씨 배경색상을 설정할 수 있도록 메서드를 미리
제공해 주기 때문에 이전의 컨트롤처럼 색상을 바꾸기 위하여 이것 저것 부가 처리가 필요없다.


----------------------------------------------------------------------CImageList* GetImageList(int nImageList) const;
CImageList* SetImageList(CImageList* pImageList, int nImageListType);
리스트 컨트롤에 이미지 리스트를 연결하거나 가져올 수 있다. 이 함수에 대한 설명은 이전 장에서
진행했으므로 가볍게 생략한다.


----------------------------------------------------------------------
int GetItemCount() const;
현재 리스트 컨트롤에 들어있는 아이템의 갯수를 리턴한다.


----------------------------------------------------------------------
BOOL GetCheck(int nItem) const;
BOOL SetCheck(int
nItem, BOOL fCheck = TRUE);
컨트롤에 확장 스타일로 첵크 박스를 달았을 경우, 첵크를 하거나 끄거나 상태값을 읽어옴.

예를 들면
m_list.SetExtendedStyle(LVS_EX_CHECKBOXES);
이렇게 첵크 박스를 달아 놓고..

m_list.SetCheck(1, TRUE);
이런식으로 첵크를 할 수 있음.


----------------------------------------------------------------------DWORD GetExtendedStyle();
DWORD SetExtendedStyle(DWORD
dwNewStyle);
리스트 컨트롤에 제공되는 확장 스타일의 정보를 가져오거나 설정할 수 있다.

첵크박스를 달려면 LVS_EX_CHECKBOXES
전체 로우 선택을 할려면 LVS_EX_FULLROWSELECT  등의 옵션을 이용하여
제공되는 범위 내에서 설정을 적용할 수 있다.

만약 이미 적용되어 있는 설정을 제거하려면 다음과 같다.
DWORD dw = m_list.GetExtendedStyle();
dw &= ~LVS_EX_FULLROWSELECT;
m_list.SetExtendedStyle(dw);


----------------------------------------------------------------------
BOOL SetItemPosition(int
nItem, POINT pt);
BOOL GetItemPosition(int
nItem, LPPOINT lpPoint) const;
아이템의 포지션을 가져오거나, 설정할 수 있다..

아이콘 스타일이나, 스몰 아이콘 스타일일 경우는 사용자가 아이템을 드래그하여
임의의 위치로 옮기거나 하는 것이 가능하다.
즉, 드래그한 아이템의 인덱스와 드래그되는 좌표값을 읽어와서 배치가 가능하다는 이야기.

리스트나 레포트 타입일 경우는 한줄에 한 아이템씩 정해진 위치와 크기가 있기 때문에..
아이템의 좌표를 옮겨 버리면 가려저 보이지 않는다.



----------------------------------------------------------------------
int GetColumnWidth(int nCol) const;
BOOL SetColumnWidth(int
nCol, int cx);
이미 만들어져 있는 컬럼의 너비를 설정하거나, 값을 읽어온다.


----------------------------------------------------------------------
BOOL GetItemRect(int nItem, LPRECT lpRect, UINT nCode) const;
현재 주어진 아이템의 인덱스 nItem을 이용하여 영역 정보를 가져온다. 이 영역 정보는 nCode에
따라 값이 달라진다.

   LVIR_BOUND 아이템 전체 영역 = 아이콘 + 레이블
   LVIR_ICON    아이콘의 영역
   LVIR_LABEL  레이블의 영역

----------------------------------------------------------------------
BOOL GetViewRect(LPRECT lpRect) const;
현재 컨트롤이 있는 위치를 기준으로 (0, 0) 아이템이 보여지는 뷰 영역의 상대 좌표값을
읽어온다. 좌표값은 아이템이 보여지기 위한 전체 좌표값이다.
아이콘 및 스몰아이콘 뷰 스타일일 경우만 적용된다.


----------------------------------------------------------------------
int GetTopIndex() const;
int GetCountPerPage() const;

GetTopIndex는 현재 화면에 보여지는 최상위 아이템의 인덱스를 리턴한다.
GetCounterPerPage 는 현재 보여지는 화면(Page)의 아이템 갯수를 리턴한다.


----------------------------------------------------------------------
CSize SetIconSpacing(CSize
size);
CSize SetIconSpacing(int
cx, int cy);
아이콘과 아이콘의 정렬 간격을 강제로 설정한다. 잘 쓰이지 않는데.. 아이콘이나
스몰 아이콘 뷰 스타일일 경우는 레이블의 길이에 따라 지멋대로 정렬되는 경향이
있기 때문에 이를 강제적으로 셑팅해줄 때 주로 사용된다.




----------------------------------------------------------------------
BOOL GetBkImage(LVBKIMAGE* plvbkImage) const;
BOOL SetBkImage(HBITMAP
hbm, BOOL fTile = TRUE, int xOffsetPercent = 0, int yOffsetPercent = 0);
BOOL SetBkImage(LPTSTR
pszUrl, BOOL fTile = TRUE, int xOffsetPercent = 0, int yOffsetPercent = 0);
BOOL SetBkImage(LVBKIMAGE* plvbkImage);

배경에 이미지를 깔 수 있다. 말이 좋아 이미지 깔기지.. 그냥 이거 하나로는 야리꾸리한
배경이 나온다. 코드 그루나 프로젝트를 찾아봐도 리스트 뒤에 이미지 깔기는 좀 다른
방법을 이용하여 처리하는 것을 보아도 알 수 있다.

일단 내부적으로 OLE를 이용하므로 AfxOleInit() 를 꼭 호출해 주어야한다.

일단 비트맵은 리소스는 않된다. 이유는 알수 없지만 DIB와 DDB의 차이정도 일까?
m_list.SetBkImage(TEXT("C:\\a.bmp"),TRUE);
처럼 이미지를 직접 파일로 넣어주니 잘된다. 리소스를 넣어줄 때는 리턴값이 0이 나와
에러임을 알수 있는데 GetLastError()로 잡아도 0이 나온다.. -_-;;;;

배경을 깔면 머하나? 글자의 배경색이 지정되기 때문에 이를 제거하지 않으면, 이렇게
얄딱 꾸리한 배경이 된다..
Owner Draw Fixed 스타일을 이용하여 직접 다 그려주지 않으면 현재로써는 방법이 없다. -_-;
(누가 쉬운방법좀 있으면 알려주세요 -_-;;;)


허접한 글에 비수를 맞았습니다... 푸욱~~ 컥~
아~ 쪼메 더 많이 맞구 싶은데..
http://www.devpia.com/MAEUL/Contents/Detail.aspx?BoardID=51&MAEULNO=20&no=7703&page=1
역시 머라도 꺼내 놓으면 반응이 와서 너무 좋네요.. 예전 부터 컨트롤 쪽을 쭉 정리해보고
싶어서 시작한 글이지만.. 너무도 부족한게 많고 괜히 쫏기듯이 쓰게되서 아쉬웠는데..
역시 MSDN을 날림 발췌한듯한 글은 비난을 받아 마땅한거 같습니다.

혹시 참고 자료 날라갈까바.. 같이 첨부합니다.



----------------------------------------------------------------------
BOOL GetItem(LVITEM* pItem) const;
BOOL SetItem(const LVITEM* pItem);
BOOL SetItem(int nItem, int nSubItem, UINT nMask, LPCTSTR lpszItem,   int nImage, UINT nState, UINT nStateMask, LPARAM lParam);
아이템의 현재 설정되어 있는 값을 가져오거나, 주어진 설정대로 아이템에 적용할 수 있다.
LVITEM 구조체는 이전장에서 설명한 것을 토대로 하고, SetItem의 두번째에 나오는 파라미터는
구조체를 나열해 놓은것에 불과하다.

아래 함수들도 나름대로 기능이 있지만 위의 함수를 사용하기 편하도록 따로 뽑아놓은것에
지나지 않는다.

BOOL SetItemState(int nItem, LVITEM* pItem);
BOOL SetItemState(int nItem, UINT nState, UINT nMask);
UINT GetItemState(int nItem, UINT nMask) const;
CString GetItemText(int nItem, int nSubItem) const;
int GetItemText(int nItem, int nSubItem, LPTSTR lpszText, int nLen) const;
BOOL SetItemText(int nItem, int nSubItem, LPCTSTR lpszText);
BOOL SetItemData(int nItem, DWORD dwData);
DWORD GetItemData(int nItem) const;


----------------------------------------------------------------------
UINT GetSelectedCount() const;
POSITION GetFirstSelectedItemPosition() const;
int GetNextSelectedItem(POSITION&
pos) const;
현재 아이템이 선택되어져 있다면, 그 갯수를 가져오거나, 어떤 것들이 선택되어져 있는
그 아이템의 인덱스를 읽어 올 수 있도록 제공해주는 메서드이다. 예를 들면 다음과 같다.

POSITION pos = list.GetFirstSelectedItemPosition();
while(pos)
   int iItem = list.GetNextSelectedItem(pos);

이렇게 하여 선택되어진 모든 아이템의 인덱스를 얻어올 수 있다.



----------------------------------------------------------------------------------
이밖에도 여러가지 속성에 관한 메서드가 제공되지만, 그렇게 많이 사용되진 않습니다.
나중에 강좌를 진행하면서 나머지 사용되는 예가 나오면 그 때 그 때 설명을 하겠습니다.

다음은 동작(Operation) 메서드에 대하여 알아보겠습니다.


리스트 컨트롤을 다룰 경우 미리 알아두면 좋은 정보가 LVITEM 이라는 구조체에 대한 것이다.
컨트롤에 데이터를 그냥 넣고/빼고 할경우는 몰라도 되지만...

프로그래밍 적으로 이미지를 바꾼다던가,
선택한다던가..
포커스를 준다던가..
하이라이팅을 한다던가..
이미 있는 문자열을 바꾸고, 파라미터를 변경하고..

이러한 모든 작업을 아래의 구조체 하나로 해결할 수 있는 것이다.

주의) 아래의 구조체는 Visual Studio 98의 Service Pack 6를 적용한 형태이다.
서비스펙 버전에 따라 구조체가 다르며, 플랫폼 SDK 마다 다를 수 있다.

typedef struct tagLVITEMA
{
    UINT mask;              마스크용 플래그
    int iItem;                  아이템 인덱스
    int iSubItem;             서브 아이템 인덱스
    UINT state;              상태값
    UINT stateMask;      상태값 마스크용 플래그
    LPSTR pszText;       디스플레이용 텍스트
    int cchTextMax;       텍스트의 길이 
    int iImage;               이미지의 인덱스 
    LPARAM lParam;      저장할 파라미터
#if (_WIN32_IE >= 0x0300)
    int iIndent;
#endif
} LVITEMA, FAR* LPLVITEMA; or UNICODE LVITEMW, FAR* LPLVITEMW;
typedef struct tagLVITEMA
{
    UINT mask;              마스크용 플래그
    int iItem;                  아이템 인덱스
    int iSubItem;             서브 아이템 인덱스
    UINT state;              상태값
    UINT stateMask;      상태값 마스크용 플래그
    LPSTR pszText;       디스플레이용 텍스트
    int cchTextMax;       텍스트의 길이 
    int iImage;               이미지의 인덱스 
    LPARAM lParam;      저장할 파라미터

#if (_WIN32_IE >= 0x0300) // common control v4.7
    int iIndent;
#endif

#if (_WIN32_WINNT >= 0x501) // common control v 6.0
    int iGroupId;
    UINT cColumns; // tile view columns
    PUINT puColumns;

#endif
} LVITEMA, FAR* LPLVITEMA; or UNICODE LVITEMW, FAR* LPLVITEMW;


mask  이 구조체에 어떠한 값을 적용하려고 하는지 지정하는 플래그.
   LVIF_TEXT    pszText 값에 설정하거나 가져온다.
   LVIF_IMAGE  iImage  값에 설정하거나 가져온다.
   LVIF_IDENT   iDndent 값에 설정하거나 가져온다.
   LVIF_PARAM lParam 값에 설정하거나 가져온다.
   LVIF_STATE  state 값에 설정하거나 가져온다.
   LVIF_NORECOMPUTE
          컨트롤이 LVN_GETDISPINFO 메시지를 생성하지 못하게 한다. LVN_GETDISPINFO
          메시지는 나중에 다루겠지만 텍스트를 가공할 수 있도록 제공되는 이벤트이다.
          대신에 pszText에 LPSTR_TEXTCALLBACK 를 넣어야한다.

iItem 아이템의 인덱스를 나타낸다.
        어떤 아이템에 값을 적용하거나 가져오기 위하여 필수적으로 채워넣어야한다.

iSubItem 아이템의 서브 인덱스(컬럼 순서대로 0부터 n)를 나타낸다.
        LVIF_TEXT 플래그가 있을 경우는 필수적으로 설정하여야한다.

state 현 아이템에 상태값을 설정하거나 가져온다.
stateMask 어떤 상태값을 설정하거나 가져올 지 마스킹한다. 플래그는 다음과 같다.
   LVIS_FOCUSED                포커스를 준다.
   LVIS_SELECTED               선택한 표시를 한다.
   LVIS_CUT                          희뿌옇게 탐색기에서 잘라낸것 처럼 만든다.
   LVIS_DROPHILITED           드래그&드롭시 타켓을 하이라이트 시킨다.
   LVIS_OVERLAYMASK        오버레이 이미지 인덱스 8-11 bit (4bit)
   LVIS_STATEIMAGEMASK  스테이트 이미지 마스크 인덱스 12-15 bit (4bit)
        마지막 두개의 마스크는 state의 주어진 비트를 비트마스킹하여 값을 설정하거나 가져온다.

pszText 주어진 아이템의 문자열
   문자열을 설정할 때는 문자열의 포인터를 주면 되지만, 가져올 경우는 가져올 버퍼 포인터를
   설정하고 cchTextMax에 버퍼크기를 주어야한다.

cchTextMax  문자열 버퍼의 크기

iImage 이미지 리스트의 인덱스
    연결한 이미지 리스트의 번호를 설정하면 아이템에 아이콘이 보인다.

lParam 하나의 아이템에 부가정보가 필요할 경우 LPARAM 값에 설정할 수 있다. 4바이트 이므로
    값이 저보다 작을 경우는 직접 이용하고, 그보다 클 경우는 포인터를 변환하여 넣을 수 있다.

iIndent 아이템이 가진 이미지의 갯수. 하나일 경우는 디폴트 이지만 2 이상일 경우는 아이템이
    여러개의 이미지를 가진것으로 표기한다.


구조체 하나 하나에 대한 샘플을 만들면 이해에 도움이 되겠지만, 당장 필요하지 않으면
샘플이 있느나 마나, 이해에 도움이 되지 않는것 같네요..

개략적으로 이해하시면 될꺼구요, 각각의 쓰임은 강좌가 진행되는 대로 중간 중간에 필요한
내용이 나오면 추가할 것입니다.

#if (_WIN32_WINNT >= 0x501) // common control v 6.0
    int iGroupId;
    UINT cColumns; // tile view columns
    PUINT puColumns;

#endif
위 내용은 common control 6.0 이상에서 지원되고요..
불행히도 Platform SDK를 업그레이드를 하더라도 MFC 6.0에서는 지원을 하지 않습니다.
제 컴이 꼬져서 6.0만 깔아놓은 상태라..

그리고, 그룹에 관련된 기능은 아직 써본적이 없는 상태라.. 공부하려고 해도 툴이 필요한데
VS2005를 깔게 되면 다시 추가하도록 하겠습니다.


리스트 컨트롤은 많은 메서드를 제공하기 때문에 한방에 써내리기가 쉽지 않다 -_-;;;
간단한 샘플을 시작으로 각각의 메서드와 이용하는 방법들을 샘플을 통하여 알아보자.


위 그림이 리스트 컨트롤을 배워나갈 처음 샘플 화면이다. 익숙한 탐색기의 우측 리스트 부분을
흉내내어 이해하기 쉽도록 작성할 것이다.

이번 장에서 다룰 부분은 다음과 같다.
1. 시스템 이미지 리스트를 얻어와서 컨트롤에 연결하기
2. 컬럼 셑팅하기
3. 원하는 경로의 파일 정보를 가져오기(탐색기를 흉내내다 보니 필요한 작업)
4. 아이템을 리스트에 추가하기.
5. 라디오 버튼 제어하기


화면 구성은 위의 그림과 같다.


헤더에 추가된 내용
---------------------------------------------------------------------------------------
    // 리스트에 연결할 이미지 리스트 2개. 큰것 작은것
    CImageList m_imgShareSmall;
    CImageList m_imgShareLarge;

    // 시스템 이미지 리스트를 로딩한다.
    void ImagelistLoading();

    // 컬럼을 설정한다.
    void ColumnSetting();

    // 2가지 방식으로 아이템을 리스트에 추가한다.
    int  AddItem1(CFileFind& file, SHFILEINFO& sfi, int ndx);
    int  AddItem2(CFileFind& file, SHFILEINFO& sfi, int ndx);

    ...

    // 하단부 4개의 라디오 박스 컴맨드를 묶는다.
    afx_msg LRESULT OnRadioCommandRange(UINT nID);



소스에 추가된 내용
---------------------------------------------------------------------------------------
메시지 맵에 라이오 버튼을 그룹으로 묶어서 넣는다.
ON_COMMAND_RANGE(IDC_RADIO1, IDC_RADIO4, OnRadioCommandRange)


OnInitDialog 초기화 루틴에 기본 내용을 채운다.
BOOL CSampleDlg::OnInitDialog()
{
    CDialog::OnInitDialog();

    // 라디오 버튼 1-4개중에 4번째를 선택해준다.

    CheckRadioButton(IDC_RADIO1, IDC_RADIO4, IDC_RADIO4);

    // 시스템 이미지 리스트를 로딩한다.
    ImagelistLoading();

    // 컬럼을 설정한다.
    ColumnSetting();

    int ndx = 0;
    CFileFind finder;
    SHFILEINFO sfi;

    // CFileFind를 이용하여 C:\에 있는 파일과 폴더 정보를 읽어온다.
    BOOL bWorking = finder.FindFile("C:\\*.*");
    while (bWorking)
    {
        // 해당 파일정보를 로딩하고, 포지션을 다음으로 넘긴다.
        bWorking = finder.FindNextFile();

        // SHGetFileInfo 쉘함수를 이용하여 파일의 쉘정보를 가져온다.
        SHGetFileInfo(finder.GetFilePath(),
            0,
            &sfi,
            sizeof(SHFILEINFO),
            SHGFI_DISPLAYNAME | SHGFI_TYPENAME| SHGFI_SYSICONINDEX | SHGFI_SMALLICON);

        // 아이템을 리스트에 추가한다.
        ndx = AddItem1(finder, sfi, ndx);
    }
    return TRUE;
}


시스템 이미지 리스트를 읽어와 리스트에 연결한다.
사실 시스템 이미지를 사용한건 샘플이건 아니건 아이콘을 제작하는게 노가다이기 때문에 -_-;;;;
SHGetFileInfo 에 대한 설명은 링크에 제공되어 있다.
void CSampleDlg::ImagelistLoading()
{
    HIMAGELIST hImageList;
    SHFILEINFO sfi;
   
    // SHGetFileInfo 쉘함수를 이용하여 드라이브상의 모든 아이콘 정보를 가져와
    // 이미지 리스트로 만든다.

    hImageList = (HIMAGELIST)SHGetFileInfo((LPCSTR)"C:\\",
        0,
        &sfi,
        sizeof(SHFILEINFO),
        SHGFI_SYSICONINDEX | SHGFI_SMALLICON);
   
    if(hImageList)
        m_imgShareSmall.Attach(hImageList);
   
    hImageList = (HIMAGELIST)SHGetFileInfo((LPCSTR)"C:\\",
        0,
        &sfi,
        sizeof(SHFILEINFO),
        SHGFI_SYSICONINDEX | SHGFI_LARGEICON);
   
    if(hImageList)
        m_imgShareLarge.Attach(hImageList);
   
    // 만들어진 두개의 이미지 리스트를 상태에 맞게 리스트 컨트롤에 연결한다.
    m_list.SetImageList(&m_imgShareSmall, LVSIL_SMALL);
    m_list.SetImageList(&m_imgShareLarge, LVSIL_NORMAL);
}

CImageList* SetImageList(
   CImageList*
pImageList, // 이미지 리스트의 포인터
   int nImageListType           // 이미지 리스트의 상태 
);

nImageListType 에 올수 있는 플래그
LVSIL_NORMAL  큰 아이콘
LVSIL_SMALL     작은 아이콘
LVSIL_STATE      상태 아이콘


해당 컬럼 정보를 설정한다.
void CSampleDlg::ColumnSetting()
{
    m_list.InsertColumn(0, "이름", LVCFMT_LEFT, 180);
    m_list.InsertColumn(1, "크기", LVCFMT_RIGHT,100);
    m_list.InsertColumn(2, "종류", LVCFMT_LEFT, 150);
    m_list.InsertColumn(3, "수정한 날짜", LVCFMT_LEFT, 250);
}

int InsertColumn(
   int
nCol,                                   // 컬럼 인덱스
   LPCTSTR lpszColumnHeading,  // 컬럼의 디스플레이 이름
   int nFormat = LVCFMT_LEFT,  // 정렬 속성
    int nWidth = -1,                        // 너비 
   int nSubItem = -1                      // 서브아이템 인덱스
);

nFormat 에 나올 수 있는 플래그
LVCFMT_LEFT        왼쪽 정렬
LVCFMT_RIGHT      오른쪽 정렬
LVCFMT_CENTER   가운데 정렬



라디오 버튼의 클릭 이벤트 모음
헤당 라디오 버튼을 누르면 리스트 컨트롤의 아래의 뷰 형태로 변환한다.
LRESULT CSampleDlg::OnRadioCommandRange(UINT nID)
{
    switch(nID)
    {
    case IDC_RADIO1:
        m_list.ModifyStyle(LVS_TYPEMASK, LVS_ICON);
        break;
    case IDC_RADIO2:
        m_list.ModifyStyle(LVS_TYPEMASK, LVS_SMALLICON);
        break;
    case IDC_RADIO3:
        m_list.ModifyStyle(LVS_TYPEMASK, LVS_LIST);
        break;
    case IDC_RADIO4:
        m_list.ModifyStyle(LVS_TYPEMASK, LVS_REPORT);
        break;
    }
    return 0;
}


아이템 추가하기 함수 1번 타입
리스트 컨트롤에 아이템을 추가하는 함수는 4개가 사용되는데 그중에 일반적으로 가장 많이
사용되는 함수 2개를 뽑아서 샘플로 만들었다.

int CSampleDlg::AddItem1(CFileFind& file, SHFILEINFO& sfi, int ndx)
{
    CString szTemp;
    CTime fTime;
   
    // 주어진 정보대로 아이템을 삽입한다.
    ndx = m_list.InsertItem(ndx, sfi.szDisplayName, sfi.iIcon);
 
    // 디렉토리가 아니면 사이즈 정보를 1번 서브아이템의 텍스트로 추가한다.
    if(!file.IsDirectory())
    {
        szTemp.Format("%d KB", file.GetLength64()/1024);
        m_list.SetItemText(ndx, 1, szTemp);
    }

    // 확장자에 따른 타입 이름을 2번 서브아이템의 텍스트로 추가한다.
    m_list.SetItemText(ndx, 2, sfi.szTypeName);

    // 파일의 최종 쓰여진 시간 정보를 읽어서 3번 서브아이템의 텍스트로 추가한다.
    file.GetLastWriteTime(fTime);
    szTemp.Format("%04d-%02d-%02d %02d:%02d:%02d",
        fTime.GetYear(),
        fTime.GetMonth(),
        fTime.GetDay(),
        fTime.GetHour(),
        fTime.GetMinute(),
        fTime.GetSecond());

    m_list.SetItemText(ndx, 3, szTemp);
    return ++ndx;
}

int InsertItem(
   int nItem,                 // 아이템 인덱스
   LPCTSTR lpszItem// 아이템의 텍스트
   int nImage                // 아이템에 연결할 이미지 인덱스
);

BOOL SetItemText(
   int nItem,             // 아이템 인덱스   
   int nSubItem,         // 서브 아이템 인덱스
   LPCTSTR lpszText   // 아이템의 텍스트
);



아이템 추가하기 함수 2번 타입
LVITEM 구조체를 이용하여 아이템을 추가하는 설명
int CSampleDlg::AddItem2(CFileFind& file, SHFILEINFO& sfi, int ndx)
{
    CString szTemp;
    CTime fTime;

    LVITEM item;
    item.mask = LVIF_TEXT | LVIF_IMAGE;
    item.iItem = ndx;
    item.iSubItem = 0;
    item.pszText = sfi.szDisplayName;
    item.iImage = sfi.iIcon;
    ndx = m_list.InsertItem(&item);

    // 동일한 내용이므로 중간 생략

    return ++ndx;
}


int InsertItem(
   const LVITEM* pItem
);

typedef struct _LVITEM {
    UINT mask;                 아이템 적용할 마스크
    int iItem;                     아이템 인덱스
    int iSubItem;                서브아이템 인덱스
    UINT state;                  상태 플래그
    UINT stateMask;          상태 플래그 마스크
    LPTSTR pszText;         아이템 텍스트 
    int cchTextMax;           아이템 텍스트의 길이
    int iImage;                   이미지의 인덱스
    LPARAM lParam;         추가할 확장 데이터의 포인트
#if (_WIN32_IE >= 0x0300)
    int iIndent;                   여러개의 아이템 이미지를 가질 경우 카운터
#endif
#if (_WIN32_IE >= 0x560)
    int iGroupId;                 그룹 아이템 아이디
    UINT cColumns;            그룹 대상으로 지정할 컬럼 아이디
    PUINT puColumns;        컬럼 인덱스의 배열
#endif
} LVITEM, *LPLVITEM;

그룹에 대한 내용은 추후에 자세하게 설명할것이므로, 간단하게 구조체 설명을 마감한다.


일단 이렇게 리스트에 대한 한 발을 내딛었습니다. 초기 샘플치곤 좀 화려하게 시작했지만..
리스트 컨트롤의 여러가지 기능을 잘 표현 하려면 어떤 샘플을 선택하는 지도 중요하거든요..

그럼 다음에..


리스트 컨트롤하면 빠질 수 없는 것이 바로 이미지 리스트이다.
리스트에 아이콘이 없다면 앙꼬 없는 찐빵이요.. 오아시스 없는 사막이다. -_-;;;

MFC에서 제공하는 컨트롤중에 이미지 리스트를 이용하는 컨트롤은 보통 2가지 사용한다.
MFC의 CImageList 와 API를 직접 이용하는 HIMAGELIST 이다.

본 강좌는 MFC를 위주로 작성하므로, CImageList를 이용하여 앞으로의 샘플들을 작성한다.

CImageList에 대한 자세한 언급은 추후로 미루며 기본적으로 어떻게 이미지 리스트를 만드는지,
그리고 이미지를 추가하거나 Attach하는지 등에 대하여 먼저 다루어 본다.


이미지 리스트 생성
-------------------------------------------------------------------------------------BOOL Create(int cx, int cy,UINT nFlags, int nInitial, int nGrow);
BOOL Create(UINT nBitmapID,int cx,int nGrow,COLORREF crMask);
BOOL Create(LPCTSTR lpszBitmapID,int cx,int nGrow,COLORREF crMask);
BOOL Create(CImageList& imagelist1,int nImage1,CImageList& imagelist2,int nImage2,int dx,int dy);
BOOL Create(CImageList* pImageList
);

이 5가지 중에 보통 위의 2가지를 주로 사용하며 설명은 다음과 같다.

BOOL Create(int cx, int cy,UINT nFlags, int nInitial, int nGrow);
  cx - 아이콘의 너비
  cy - 아이콘의 높이
  nFlags - 아이콘에 적용될 설정 플래그
     ILC_COLOR - 다른 ILC_COLOR? 가 선언되지 않았을 경우 디폴트로 동작함-ILC_COLOR4
     ILC_COLOR4 - 4 bit DIB
     ILC_COLOR8 - 8 bit DIB
     ILC_COLOR16 - 18 bit DIB
     ILC_COLOR24 - 24 bit DIB
     ILC_COLOR32 - 32 bit DIB
     ILC_COLORDDB - Device Dependents Bitmap
     ILC_MASK - 마스크 컬러값

  ninitial - 초기치 크기
  nGrow - 공간이 모자랄 경우 컨테이너의 확장 단위

BOOL Create(UINT nBitmapID,int cx,int nGrow,COLORREF crMask);
  nBitmapID - 이미지리스트에 넣을 연속 이미지가 들어있는 비트맵의 리소스 아이디
  cx - 비트맵을 쪼갤 너비 단위
  nGrow -  공간이 모자랄 경우 컨테이너의 확장 단위
  crMask - 마스킹하여 제거할 컬러값


이미지 리스트에 추가
-------------------------------------------------------------------------------------
이미지를 Add 메서드를 이용하여 추가할 경우는 한번에 한개씩만 가능하다. 또한 비트맵이나
아이콘을 직접 추가할 수 있다. 크기는 초기에 설정해 놓은 크기대로 잘려서 추가된다.

int Add(CBitmap* pbmImage,CBitmap* pbmMask);
int Add(CBitmap*
pbmImage,COLORREF crMask);
int Add(HICON
hIcon);
  pbmImage - CBitmap 포인터(이미지를 가지고 있는)
  pbmMask -  직접 마스킹할 마스킹 비트맵 포인터
  crMask - 마스킹할 컬러
  hIcon - 아이콘 핸들


이미지 리스트에 Attach
-------------------------------------------------------------------------------------
Win32 API에서 제공되는 HIMAGELIST 를 사용할 수 있도록 제공되는 인터페이스이다.
주로 다른 곳에서 사용되고 있는 이미지 리스트를 가져다 혹은 빌려서 쓸때 사용한다.

BOOL
Attach(HIMAGELIST hImageList);

이건 나중에 시스템의 이미지 리스트를 가져와서 리스트 컨트롤에 붙혀가지고 시스템 아이콘을
사용할 경우 예제를 보여줄 것이다.


이미지 리스트의 사용
-------------------------------------------------------------------------------------
보통 컨트롤에서는 이미지 리스트를 자동으로 연결해주는 SetImageList 와 같은 함수가 있다.
이와는 별도로 리스트 내부에서 원하는 아이콘을 뽑아다 사용할 경우는 다음 메서드를 이용한다.

HICON ExtractIcon(int nImage);

주의. ExtrancIcon을 이용하여 리턴된 HICON 핸들은 이미지 리스트의 내부 핸들의 포인터를
리턴하는것이 아니라, 복사한 핸들을 리턴하기 때문에.. 다 사용하고 나면 CloseHandle()을
이용하여 꼭 해재하여 주어야한다.

이 함수를 루프나 혹은 드로잉 함수에서 그 때 그때, 불러서 사용한다면 길지 않은 시간이
흐른 후 시스템의 리소스가 부족합니다 라는 메시지와 다운 되는 컴퓨터를 보게 될것이다.



첨가. 아이콘을 직접 DC를 이용하여 그릴 때
-------------------------------------------------------------------------------------
가끔 아이콘을 직접 화면에 그려주어야 할 경우가 있을 것이다. 보통 CDC::DrawIcon
이용하여 그리게 되는데, 이것 무조껀 16*16으로 맞추어 그려 버린다. -_-;;;

BOOL DrawIconEx(
  HDC hdc,
  int xLeft,
  int yTop,
  HICON hIcon,
  int cxWidth,
  int cyWidth,
  UINT istepIfAniCur,
  HBRUSH hbrFlickerFreeDraw,
  UINT diFlags
);

Win32 API 중에 위의 DrawIconEx를 이용하여 그리게 되면 원하는 형태와 크기대로 정확하게
화면에 그려줄 수 있다.

보통 자료량이 좀 많은 데이터나, 혹은 하나의 자료가 여러 속성을 가지고 있을 경우 이러한
자료를 효율적으로 보여지는데 리스트 컨트롤이 많이 사용된다.

윈도우 탐색기를 예로 들어보면 수월하게 이해할 수 있을 것이다.

그냥 갯수나 살필 필요가 있을 경우는 그냥 상태바를 보지만..
무슨 파일들이 있나 살펴볼 때는 스몰아이콘이나, 리스트 형식으로 보게되고..
데이터의 세세한 정보를 살펴볼 때는 레포트 타입으로 자료를 접하게 된다...


이렇게 아이콘과 디스플레이용 이름 그리고, 필요한 추가 정보를 하나의 화면에서 다양한 형태로
사용자에게 제공해 줌으로써 사용자가 쉽게 정보를 분류하고 선택할 수 있도록 제공된다.

일단 비주얼 스튜디오의 리소스 편집창에 리스트 컨트롤을 하나 올려보자.

기본적으로 컨트롤을 올려놓으면 커다라 아이콘 모양이 나오고, 색깔이 들어있는 모양을 보여준다.
여러가지 속성페이지가 보이는데, 관심을 가질 페이지는 StylesMore Styles 이다.

속성페이지 - Style
--------------------------------------------------------------------------------------

1. View
    - 사용자가 컨트롤을 바라보는 시각을 나타낸다.
       Small Icon과 List 타입은 구분이 좀 모호해서, 이해를 돕기 위해 이미지를 삽입하였다.

    Icon : 큰 아이콘 모양으로 화면에 보인다. 기본 32*32이다.

    Small Icon : 작은 아이콘타입으로 화면에 자유분방하게 보여줄 수 있다?  
                      정해진 크기를 가지는 Icon 타입을 제외하고, 화면에 가장 많은 정보를 보여준다.
                      특별히 아이콘을 정리해주거나 하지 않으면 중구난방, 지저분해 보인다.
                      요즘은 거의 사용되지 않는다.         

    List : 디스플레이 이름의 길이만큼 아이템 넓이를 확보하여 우로 스크롤 하면서 보여준다.
            아이템의 디스플레이 문자열이 길 경우 한 화면에 보여지는 데이터량이 줄어들 수 있다.


    Report : 아이콘과 디스플레이 이름 및 지정된 세부정보를 레포트 형식으로 볼 수 있다.

2. Align
   - 데이터를 넣어야할 공간이 존재할 때 추가된 데이터를 화면 상단(Top)부터 채워 나가던가,
     아니면 좌측(Left)부터 우측으로 채워나간다던가.. 그러한 걸 결정한다.
     Icon과 Small Icon 뷰 스타일에서만 적용된다.

3. Sort 
  - 데이터가 추가되면 디스플레이 이름을 기준으로 아이템을 정렬할 것인지를 설정할 수 있다.
     리스트 박스와는 다르게 InsertItem 밖에는 없기 때문에, 추가와 삽입에 따른 차이는 없다.
     ListReport 뷰 스타일에서만 적용되며, 그냥 냅두기(None), 내림차순(Descending),
     오름차순(Ascending)을 지원한다.

4. Single selection
  - 아이템을 선택할 경우 단 하나만 선택할 수 있도록 제어한다. 디폴트는 다중 선택이다.

5. Auto arrange
  - IconSmall Icon 뷰 스타일에만 적용되는 것으로써, 아이템을 추가하거나 삭제할 경우
    자동으로 공간을 계산해서 재 배치해준다. 디폴트는 지우면 지우는 대로 빵구난 형태가 된다.

6. No label wrap
 - Icon 뷰 스타일일 경우에만 적용되고, 이 옵션이 첵크되면 디스플레이 이름(앞으로 레이블)이
   길 경우 한줄로 죽 늘여뜨려준다. 디폴트는 문자열을 축약해 보여주는 형태이며 포커스를 가질
   경우 전체 문자열을 아이템의 너비로 아래로 죽 늘여서 보여준다.


7. Edit labels

  - 레이블을 인플레스 모드의 에디트 컨트롤을 이용하여 직접 수정할 수 있다. 이럴 경우 부모
    윈도우는 LVN_ENDLABELEDIT 메시지를 적절히 처리해 주어야한다.

8. No scroll
  - 이름 그대로 아이템이 화면을 넘칠만큼 많아도 스크롤바를 생성시켜 주지 않는다.
   
9. No column header
  - Report 뷰 스타일에서만 적용되며, 레포트의 컬럼 양식을 나타내는 헤더 컨트롤을 숨겨
     보여지거나 여타 다른 동작을 막아준다.

10 . No sort header
  - Report 뷰 스타일에서만 적용되며, 헤더 컨트롤을 클릭할수 없도록 한다. 보통은 컬럼 헤더의
    클릭 이벤트를 처리해 아이템을 소트해주거나 하는 작업을 하는데 사용된다.

11. Show selection always
  - 아이템을 선택하여 놓았을 경우 선택 마크가 남게되는데, 해당 컨트롤이나 부모 위도우가
    포커스를 잃었을 경우 선택마크가 사라져 보이고, 다시 포커스가 오면 돌아온다. 이 옵션을
    켜 놓으면 포커스를 읽더라도 옅은 회색으로 항상 선택되어졌던 아이템을 볼 수 있다.


속성페이지 - More Styles
--------------------------------------------------------------------------------------


1. Owner draw fixed
  - 아이템의 드로우를 내부적으로 직접 처리할 수 있도록 해준다.

2. Share image list
  - 리스트 컨트롤을 파괴할 때 이미지 리스트를 지우지 않는다. 여러개의 리스트 컨트롤을 띄워
    놓고 하나의 이미지 리스트를 이용하여 아이콘을 보여줄 경우 유용하다

3. Border
  - 바닥으로 약간 가라 앉게 보이는 모양을 만들어 준다. -_-

4. Owner data
  - 가상 리스트 컨트롤이라고도 불리우는데, 리스트 컨트롤 내부에서 제공해주는 자료 구조가
    아닌 사용자가 직접 작성한 자료구조를 이용하여 데이터를 처리할 수 있다.

    리스트 컨트롤의 레이블은 256바이트를 넘을 수 없다거나, 데이터를 삽입하는 속도가 엄청
    느리거나 하는 등의 문제를 해결할 때 사용한다.
    보통 삽입 속도 개션을 목적으로 주로 사용된다.


다음 장에서는 기본적인 용법에 대하여 다루어 보겠습니다.
리스트 컨트롤은 그 기능만큼이나 많은 메서드를 제공해 주기 때문에 알것도 배울것도 많죠.
여하튼 복잡한 컨트롤임에는 틀림없습니다. ^^;







+ Recent posts