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

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

코드그루에 있는 자료를 좀 수정해서 사용해봅니다.
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;
}

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

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바이트를 넘을 수 없다거나, 데이터를 삽입하는 속도가 엄청
    느리거나 하는 등의 문제를 해결할 때 사용한다.
    보통 삽입 속도 개션을 목적으로 주로 사용된다.


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







지금까지 진행한 내용을 바탕으로 간단한 색상보기 샘플을 제작해 보겠습니다.
실행한 모양은 아래 처럼 됩니다.

설정 조건
1스크롤바를 3 만들고 모든 범위는 0 - 255 까지이다.
2각각 좌측부터 RedGreen, Blue 삼원색을 제어한다.
3오른쪽 사각영역에 주어진 RGB 컬러대로 색칠을 해준다.

-----------------------------------------------------------------------------------
허데 파일의 내용.. 발췌

// Dialog Data
    
//{{AFX_DATA(CSampleDlg)
    
enum { IDD = IDD_SAMPLE_DIALOG };
    
CScrollBar m_scroll3;
    
CScrollBar m_scroll2;
    
CScrollBar m_scroll;
    
//}}AFX_DATA

    
// 색칠할 사각형 영역
    
CRect m_rect;
    
// 색상값
    
COLORREF m_rgb;
    
void ChangeColorBox(CScrollBarpBarUINT nPos);

-----------------------------------------------------------------------------------
소스 파일의 내용.. 발췌

// RGB 단위 값의 최소/최대
#define SCROLL_MIN  0
#define SCROLL_MAX  255

// COLORREF 부분값을 변경하기 위한 제작된 매크로
#define SetRValue(rgbr)      ((rgb&=0xFFFFFF00)|=(BYTE)r)
#define SetGValue(rgbg)      ((rgb&=0xFFFF00FF)|=((BYTE)g)<<8)
#define SetBValue(rgbb)      ((rgb&=0xFF00FFFF)|=((BYTE)b)<<16)

BOOL CSampleDlg::OnInitDialog()
{
    
CDialog::OnInitDialog();

    
// 스크롤바와 연결된 컨트롤 3개에 대하여 모두 기본값들을 설정한다.
    
m_scroll.SetScrollRange(SCROLL_MINSCROLL_MAX);
    
m_scroll.SetScrollPos(0);
    
m_scroll.EnableScrollBar(ESB_ENABLE_BOTH);

    
m_scroll2.SetScrollRange(SCROLL_MINSCROLL_MAX);
    
m_scroll2.SetScrollPos(0);
    
m_scroll2.EnableScrollBar(ESB_ENABLE_BOTH);

    
m_scroll3.SetScrollRange(SCROLL_MINSCROLL_MAX);
    
m_scroll3.SetScrollPos(0);
    
m_scroll3.EnableScrollBar(ESB_ENABLE_BOTH);

    
// 화면에 그릴 영역의 사각 크기를 구한다.
    
GetDlgItem(IDC_STATIC1)->GetWindowRect(m_rect);
    
ScreenToClient(m_rect);

    
// 기본 색상은 검은색이다.
    
m_rgb = RGB(0,0,0);

    
return TRUE;
}

// 정해진 색상으로 지정된 위치에 색칠한다.
void CSampleDlg::OnPaint()
{
    
CPaintDC dc(this);
    
dc.FillSolidRect(m_rectm_rgb);
}

// 스크롤 이벤트 핸들러
void CSampleDlg::OnVScroll(UINT nSBCodeUINT nPosCScrollBarpScrollBar)
{
    
if(!pScrollBar || !pScrollBar->m_hWnd)
        
return;

    
UINT nCurPos = pScrollBar->GetScrollPos();
    
switch(nSBCode)
    {
    
case SB_BOTTOM:
        
pScrollBar->SetScrollPos(SCROLL_MAX);
        
break;

    
// 중간 생략...

     
case SB_TOP:
        
pScrollBar->SetScrollPos(SCROLL_MIN);
        
break;
    }

    
// 색상값에 변경이 있으면 다시 색칠을 해야지..
   
ChangeColorBox(pScrollBarnCurPos);

    
CDialog::OnVScroll(nSBCodenPospScrollBar);
}

// 색상값이 변경되면 어떤 스크롤바가 값이 얼마인지 계산을 해서
// RGB 다시 계산한  색칠한다.
void CSampleDlg::ChangeColorBox(CScrollBarpBarUINT nPos)
{
    
UINT nID = pBar->GetDlgCtrlID();
    
switch(nID)
    {
    
case IDC_SCROLLBAR5:
        
SetRValue(m_rgbnPos);
        
break;
    
case IDC_SCROLLBAR6:
        
SetGValue(m_rgbnPos);
        
break;
    
case IDC_SCROLLBAR7:
        
SetBValue(m_rgbnPos);
        
break;
    }

    
CClientDC dc(this);
    
dc.FillSolidRect(m_rectm_rgb);
}

93.zip
0.03MB

이렇게 간단하게 나마 스크롤바에 대하여 공부해 보았습니다.
실상 알고 보면 크게 어렵거나 한 부분은 거의 없습니다. 자료가 부족할 뿐 ^^;

다음에는 리스트 컨트를을 진행해 보도록 하겠습니다.
즐거운 한주 되세요.. ^^;

스크롤바에서 정보를 설정하거나 가져올  사용되는 구조체가 SCROLLINFO 이다.
winuser.h  정의되어 있으며 구조는 다음과 같다.

typedef struct tagSCROLLINFO
{
    
UINT   cbSize;
    
UINT    fMask;
    
int       nMin;
    
int       nMax;
    
UINT    nPage;
    
int       nPos;
    
int        nTrackPos;
}   
SCROLLINFOFAR *LPSCROLLINFO;
typedef SCROLLINFO CONST FAR *LPCSCROLLINFO;

 구조체는 GetScrollInfo / SetScrollInfo 함수를 이용하여 현재 스크롤바의 상태나 정보를
얻어오거나 설정할  사용된다.

cbSize     :   구조체의 크기
fMask       : 스크롤바의 속성을 결정짓는 플래그
   
SIF_ALL                         모든 속성의 조합
   
SIF_DISABLENOSCROLL  스클로바 전체 비활성화
   
SIF_PAGE                       페이지 사이즈
   
SIF_POS                         스크롤 박스의 위치
   
SIF_RANGE                     스크롤 범위의 최소/최대
   
SIF_TRACKPOS               드래깅 상태의 스크롤박스 현재 위치
nMin         : 스크롤범위의 최소값
nMax        :  스크롤 범위의 최대값
nPage       : 전체 스크롤바의 크기에 대한 이동막대기(Thumb) 절대 크기값
nPos         : 스크롤 박스의 현재값
nTrackPos : 드래깅 상태의 스크롤 박스의 현재값

여기서 살펴보면 나머지 다른 속성들은 직관적으로 이해할  있는 범위의 설명이다.
그런데 nPage라는 도대체 어떤 놈일까???

이전 예제를 이용하여 알아보도록 하자.
다음 그림은 기본적인 값이 이전 예제와 동일하고 page값이 디폴트로 0 샘플이다.

가운데 이동 막대기(Thumb) 크기는 기본값이다.
아래의 그림은 다음과 같은 코드를 추가하여 page 전체 범위의 1/2 50으로  샘플이다.

그림이 조금 달라보이지 않는가 페이지라고 하는 개념은 워드나 아래한글에서...
말하는 문서 전체의 페이지와 비슷한 개념이다.

위에 2번째 그림으로 스크롤바 정보를 예측해보면.. 아마도 현재 보이는 스크롤바의 2 정도
화면 크기가 아닐까..  0-100 사이이니까.. 현재 화면에 보이는 크기가 50정도..
만약 page 20으로 설정한다면 스크롤바크기의 1/5 되고.. 대략 시각적으로 보면
화면 기준으로 전체 다섯페이지 정도가 되겠구나 라고.. 예측할  있다.

전체 범위의 크기와 현재 화면상에 보여지는 스크롤바의 크기의 비율을 적당해 계산해서..
화면이 리사이징될 때마다 다시 계산해서 넣어주면...
화면의 크기와 스크롤바의 이동막대기 크기를 보고 대략 유추할  있게 된다.

이것이 스크롤바의 page값의 의미이다.

아래의 코드는 스크롤바의 페이지값을 재설정하는 코드이다.

SCROLLINFO info = {sizeof(SCROLLINFO), SIF_PAGE0};
info.nPage = 50;
m_scroll.SetScrollInfo(&info);

115.zip
0.03MB

스크롤바를 달아만 놨었다.. 요지부동.. ㅎㅎ

스크롤바 컨트롤을 상속받아서 내부에서 처리하지 않는이상... 다이알로그 등에 올려놓고 쓰려면
WM_VSCROLL  WM_HSCROLL  메시지를 처리해서 하나 하나 직접 동작시켜 주어야 한다.

현재 길쭉한 나무 막대기 모양으로 만들었으니WM_VSCROLL 메시지를 이용하여 스크롤바가
원하는 동작을 하도록 만들어 보자.


그림 처럼 클래스 위저드를 이용하여 메시지 핸들러를 추가한다.

그전에 알아두어야 할것이winuser.h  디파인 되어있는 스크롤바의 동작에 관한 디파인이다.
우리가 사용할 WM_VSCROLL 메시지 핸들러의 정의부를 살펴보면 첫번째 인자에 전달되는
값이기도 하기 때문에  알고 있어야 한다.

afx_msg void OnVScroll(UINT nSBCodeUINT nPosCScrollBarpScrollBar);

아래에서 디파인이 같은 것들이 존재하는 이유는 결국 같은 동작이지만 수직 스크롤과 수평
스크롤을 구분해 보여주기 위하여 나누어 놓은것이다.
#define SB_LINEUP             0
#define SB_LINELEFT          0
#define SB_LINEDOWN         1
#define SB_LINERIGHT         1
#define SB_PAGEUP            2
#define SB_PAGELEFT         2
#define SB_PAGEDOWN        3
#define SB_PAGERIGHT        3
#define SB_THUMBPOSITION 4
#define SB_THUMBTRACK     5
#define SB_TOP                    6
#define SB_LEFT                  6
#define SB_BOTTOM             7
#define SB_RIGHT                 7
#define SB_ENDSCROLL        8

그러므로 수직 스크롤바에 대하여 설명하면 수평은 자동이해.. ^^;
#define SB_LINEUP              0 - 위의 버튼 누름
#define SB_LINEDOWN         1 - 아래 버튼 누름
#define SB_PAGEUP            2 - 버튼과 이동막대기(thumb)사이의 상단 공간 누름
#define SB_PAGEDOWN        3 - 버튼과 이동막대기(thumb)사이의 하단 공간 누름
#define SB_THUMBPOSITION 4 - 이동 막대기의 최종위치
#define SB_THUMBTRACK     5 - 이동 막대기가 계속 이동하고 있음
#define SB_TOP                    6 - 크기가 변하거나 업데이트  .. min 설정
#define SB_BOTTOM             7 - 크기가 변하거나 업데이트  .. max 설정
#define SB_ENDSCROLL        8 - 한단계의 스크롤 동작을 마침

아주 간단하게 동작에 대해 설명했는데.. 6,7,8번을 제외하고는 이해하는데  문제가 없다.
SB_TOPSB_BOTTOM 레인지를 다시 설정하거나 크기를 변경하거나   발생하는
것이라는데 거의 사용하지 않는다.

스크롤시에 버튼을  누르고 있거나 이동 막대기를 잡고 왔다 갔다 하다가 마지막에 마우스
버튼을  띠는 순간 SB_ENDSCROLL 발생한다마우스를 누르고 이리 저리 움직이면
내부 타이머에 의하여 연속적으로 이벤트가 발생하는데 이벤트가 떨어지면 전체적인
하나의 동작으 완료되었음을   있게된다.

보통  쓰이지는 않는데.. 연속적인 동작에는 관심없고 마지막 최종 동작에만 관심이 있을 경우
 이벤트를 이용하여 최종 포지션을 읽어와서 먼가 작업을 한다던가   주고 쓰인다.


아래는 실제로 이벤트 핸들러 내부에 동작을 구현해본 코드이다.
1버튼을 누를 경우는  아래로 +-1 이동하고
2이동막대기를 움직일 때는 움직인 만큼
3페이지 영역을 누를 때는 +-5 이동하도록 구현하였다.

void CSampleDlg::OnVScroll(UINT nSBCodeUINT nPosCScrollBarpScrollBar)
{
    
if(!pScrollBar || !pScrollBar->m_hWnd)
        
return;

    
if(pScrollBar->GetDlgCtrlID() == IDC_SCROLLBAR5)
    {
        
UINT nCurPos = pScrollBar->GetScrollPos();
        
switch(nSBCode)
        {
        
case SB_BOTTOM:
            
pScrollBar->SetScrollPos(SCROLL_MAX);
            
break;

        
case SB_ENDSCROLL:
            
break;

        
case SB_LINEDOWN:
            
nCurPos += 1;
            
if(nCurPos > SCROLL_MAX)
                
nCurPos = SCROLL_MAX;
            
pScrollBar->SetScrollPos(nCurPos);
            
break;

        
case SB_LINEUP:
            
nCurPos -= 1;
            
if(nCurPos < SCROLL_MIN)
                
nCurPos = SCROLL_MIN;
            
pScrollBar->SetScrollPos(nCurPos);
            
break;

        
case SB_PAGEDOWN
            
nCurPos += 5;
            
if(nCurPos > SCROLL_MAX)
                
nCurPos = SCROLL_MAX;
            
pScrollBar->SetScrollPos(nCurPos);
            
break;

        
case SB_PAGEUP:
            
nCurPos -= 5;
            
if(nCurPos < SCROLL_MIN)
                
nCurPos = SCROLL_MIN;
            
pScrollBar->SetScrollPos(nCurPos);
            
break;

        
case SB_THUMBPOSITION:
             
pScrollBar->SetScrollPos(nPos);
            
break;

        
case SB_THUMBTRACK:
            
pScrollBar->SetScrollPos(nPos);
            
break;

        
case SB_TOP:
             
pScrollBar->SetScrollPos(SCROLL_MIN);
            
break;
        }
    }

    
CDialog::OnVScroll(nSBCodenPospScrollBar);
}

112.zip
0.03MB

스크롤바만큼 심플한 프로퍼티를 가진 컴포넌트도 보기 힘들것이다.


속성이라곤 Align이라는 속성 딸랑 하나에.. 그것도 거의.. 전혀 사용되지 않는 속성이다.
None :
당연히 아무것도 없다.
Top/Left :
수직 스크롤바 일 경우 너비를 맘대로 정해도, 왼쪽 모서리에 기본 너비로 고정됨.
Bottom/Right :
수직 스크롤바 일경우 오른쪽 아래로 너비 고정됨.

아래 그림을 보자, 리소스 디자인 폼에서 디자인한 화면이다.

이걸 수행하면 아래 화면과 같은 실행화면이 나온다.


그림에서 보이는 사각 테두리는 이해를 돕기 위하여 넣은 외곽선이다.
무엇때문에 있는 속성인지는 몰라도 필요하니까 존재하겠지만, 거의 사용되지 않는
속성임에는 틀림없다. -_-;;;


, 다음으로 이벤트를 확인해보자.
없다 -_-;

그렇다면 이제 사용하면 되는 것인가?
글쎄 -_-?


일단 바로 위의 그림에서 오른쪽 2개를 제거하고, CScrollBar m_scroll 로 연결한다음
#define SCROLL_MIN  0
#define SCROLL_MAX  100

BOOL CSampleDlg::OnInitDialog()
{
    CDialog::OnInitDialog();

    //
스크롤바의 최소, 최대값을 설정한다.
    m_scroll.SetScrollRange(SCROLL_MIN, SCROLL_MAX);
   
    //
초기 스크롤바의 포지션을 설정한다. 임시로 50 넣어보았다.
    m_scroll.SetScrollPos(50);

    //
스크롤바에 달린 양측 버튼의 활성화 여부.
    m_scroll.EnableScrollBar(ESB_ENABLE_BOTH);
    return TRUE;
}

처럼 설정해주고, 열심히 스크롤바를 눌러보자.
아무런 움직임도 없다 -_-;
그나마 가운데 막대기 드래그 하면 원하는 곳에 갔다가 놓으면 다시 재자리 -_-;;;


스크롤바는 있는 그대로 사용할 수 없는 컨트롤인것이다... ~
다음장에서는 스크롤바를 써보자.. ~

리스트 박스는 컨트롤을 오버라이드 하지 않고 할만한게 없어서..
간단하게 Owner Draw Fixed를 이용하여 셀 높이 조절과 문자열에 여백넣는
샘플을 만들어 보았습니다.


우선 리스트 박스를 하나 올리고, 속성을 위와 같이 준다.


위의 그림은 셀의 높이를 20픽셀로 준것이고, 왼쪽에 여백을 5픽셀 준것이다.

이러한 기능을 구현하는것은 컨트롤의 내부를 건드리지 않고서는 사실상 불가능하지만
구현 자체가 그리 어려운것은 아니다.

우선 CListBox를 상속한 CCustomListBox 클래스를 하나 생성한다.

class CCustomListBox : public CListBox
{
// Construction
public:
     
CCustomListBox();

// Attributes
public:
    
COLORREF    m_rgbTextm_rgbBack// 글자색과 배경색
    
UINT              m_nHeight;                  // 셀의 높이
    
CRect            m_szMargin;               // 글자의 마진

// Operations
public:

// Overrides
     
// ClassWizard generated virtual function overrides
     
//{{AFX_VIRTUAL(CCustomListBox)
     
//}}AFX_VIRTUAL

// Implementation
public:
     
virtual ~CCustomListBox();
     
// 가상함수를 아래 처럼 추가한다.
     
// 요건 아이템을 로우 단위로 그려주는넘.
     
virtual void DrawItem(LPDRAWITEMSTRUCT pDIStruct);
     
// 요건 아이템의 높낮이를 조절하는 
     
virtual void MeasureItem(LPMEASUREITEMSTRUCT lpMeasureItemStruct);
     
// Generated message map functions
protected:
     
//{{AFX_MSG(CCustomListBox)
     
//}}AFX_MSG

     
DECLARE_MESSAGE_MAP()
};

아래의 코드들을 실제로 구현되는 내용들이다.
추가적인 코드가 들어가는 함수만을 추려서 설명을 넣는다.
[
사실 이게 코드의 90%이다.]

// 생성자로 필요한 초기값을 넣는다.
CCustomListBox::CCustomListBox()
{
    
m_rgbText = RGB(25500);
    
m_rgbBack = RGB(2552550);
    
m_nHeight = 20;

    
m_szMargin = CRect(5000);
}

// 아이템을 그려주는 가상함수이다.
// Owner Draw속성을 주면 사용자가 재정의한 함수를 자동으로 호출해준다.
void CCustomListBox::DrawItem(LPDRAWITEMSTRUCT pDIStruct)
{
    
CDC dc;

    
// 코드를 간결하게 하기 위하여 전달된 HDC CDC 어태치한다.
    
if( !dc.AttachpDIStruct -> hDC ) )
        
return;

    
// 현재 전달된 아이템이 선택되어진 넘인이 확인한다.
    
ifpDIStruct -> itemState & ODS_SELECTED )
    {
        
// 속성에 맞게 글자색배경색상을 지정한다.
        
dc.SetTextColor((0x00FFFFFF & ~(GetSysColor(COLOR_WINDOWTEXT))));
        
dc.SetBkColor(GetSysColor(COLOR_HIGHLIGHT));
        
dc.FillSolidRect(&pDIStruct->rcItemGetSysColor(COLOR_HIGHLIGHT));
    }
    
else
    {
        
dc.SetTextColor(m_rgbText);
        
dc.SetBkColor(m_rgbBack);
        
dc.FillSolidRect(&pDIStruct->rcItemm_rgbBack);
    }

    
// 아이템의 인덱스가 -1 아니면
    
if(pDIStruct->itemID != -1)
    {
        
// 선택된 아이템의 문자열을 읽어온다.
        
CString m_SelText;
        
GetText(pDIStruct->itemIDm_SelText);

        
// 만약 선택된 아이템이 디저블 속성이면 글자 색상을 회색으로
        
if(pDIStruct->itemState & ODS_DISABLED)
            
dc.SetTextColor(::GetSysColor(COLOR_GRAYTEXT));

        
// 배경은 투명 속성으로
        
dc.SetBkMode(TRANSPARENT);

        
// 글자를 그릴 영역의 여백을 조절한다..
        
CRect rcText = pDIStruct->rcItem;
        
rcText.left += m_szMargin.left;
        
rcText.top += m_szMargin.top;
        
rcText.right -= m_szMargin.right;
        
rcText.bottom -= m_szMargin.bottom;

        
dc.DrawText(m_SelTextrcTextDT_VCENTER | DT_SINGLELINE);
    }

    
dc.Detach();
   
return;
}

// 아이템의 높이를 설정한다.
void CCustomListBox::MeasureItem(LPMEASUREITEMSTRUCT lpMeasureItemStruct)
{
    
lpMeasureItemStruct->itemHeight = m_nHeight;
}

차츰 컨트롤을 다뤄가다 보면 나름대로 이것 저것 해볼 욕심이 생길때가 있는데..
  컨트롤의 Owner Draw 이용하면 대부분의 구현이 가능합니다..
유명한 코드그루나 코드프로젝트 같은 사이트의 샘플들도 대략 이런식으로 구현되어
클래스로  꾸며놓았다가 필요할  쓰면 되는것이죠.

 

리스트 박스의 글자색과 배경색은 기존에 해온대로 WM_CTLCOLOR 이벤트를
이용하면 아주 쉽게 해결됩니다..

HBRUSH CSssDlg::OnCtlColor(CDCpDCCWndpWndUINT nCtlColor)
{
    
HBRUSH hbr = CDialog::OnCtlColor(pDCpWndnCtlColor);

    
if(nCtlColor == CTLCOLOR_LISTBOX)
    {
        
if(pWnd->GetDlgCtrlID() == IDC_LIST1)
        {
            
pDC->SetTextColor(RGB(25500));
            
pDC->SetBkColor(RGB(255,255,0));

            
// 노랑색으로 만든 브러쉬. - m_brh.CreateSolidBrush(RGB(255,255,0));
            
return m_brh;
        }
    }
    
return hbr;
}

88.zip
0.03MB


지금까지 다룬 방식으로 아주 쉽게 처리가 되었읍니다. -_-
강좌라고 할만한 껀덕지도 없읍니다.

하지만 컨트롤을 오버라이딩해서 별도로 구현하지 않는이상.. 리스트박스는 별로
다룰만한게 없다보니.. 휴~~

리스트 박스를 사용하면서 주로 쓰이는 기능이 대표적으로 아이템의 추가, 삽입, 삭제 및 선택 기능이다. 기능은 단순하지만, 리스트 박스의 속성에 따라 동작 특성이나 메서드의 응답이 달라진다.

아래의 함수는 리스트박스에 긴 문자열이 들어갔을 경우, 수평 스크롤바를 생성시키는 코드이다.
이전 장에서 다룬것을 조금 확장한 것이고, 앞으로 리스트박스에서 항상 쓰일 함수이다.

static int GetTextLenEx(CListBox& box, LPCTSTR lpszText)
{
    CSize size;
    CDC *pDC = box.GetDC();
   
    CFont* pOld = pDC->SelectObject(box.GetFont());
    if ((box.GetStyle() & LBS_USETABSTOPS) == 0)
    {
        size = pDC->GetTextExtent(lpszText, _tcslen(lpszText));
        size.cx += 3;
    }
    else
    {
        size = pDC->GetTabbedTextExtent(lpszText, _tcslen(lpszText), 0, NULL);
        size.cx += 2;
    }
    pDC->SelectObject(pOld);
    box.ReleaseDC(pDC);
   
    return size.cx;
}
static void AddStringEx(CListBox& box, CString str, int ndx = -1)
{
    if(ndx == -1)
        box.AddString(str);
    else
        box.InsertString(ndx+1, str);
    int iExt = GetTextLenEx(box, str);
    if (iExt > box.GetHorizontalExtent())
        box.SetHorizontalExtent(iExt);
}

우선 리스트 박스 하나를 single 선택 모드로 예제를 하나 작성하였다.
리스트 박스를 선택할  속성에 따라 동작이 다름에 주의하여야 한다.


각각의 기능을 구현한 함수는 다음과 같다.

void CSssDlg::OnButton1()
{
    
AddStringEx(m_list"하나");  AddStringEx(m_list"");
    
AddStringEx(m_list"");     AddStringEx(m_list"");
    
AddStringEx(m_list"다섯");  AddStringEx(m_list"여섯");
    
AddStringEx(m_list"일곱");  AddStringEx(m_list"여덟");
    
AddStringEx(m_list"아홉");  AddStringEx(m_list"임의의 아이템을 10개를 리스트 박스에 삽입함.");
}

void CSssDlg::OnButton2()
{
    
UpdateData(TRUE);
    
AddStringEx(m_listm_str_insertm_list.GetCurSel());
}

void CSssDlg::OnButton3()
{
    
UpdateData(TRUE);

    
m_list.SetCurSel(m_sel_program);
}

void CSssDlg::OnButton4()
{
    
m_list.DeleteString(m_list.GetCurSel());
}

void CSssDlg::OnButton5()
{
    
m_list.ResetContent();
}
single 
선택 모드일 경우는 아이템을 하나씩만 선택할  있기 때문에선택이나 삭제가
위처럼 수월하게 이루어진다.

하지만 multiple extended  다중 선택할 경우.. GetCurSel 응답이 약간 달라진다.
single
   
아이템이 선택되었을 경우 : 선택된 아이템의 인덱스
   
아이템이 선택되어지지 않았을 경우 : -1
multiple or extended
   
아이템이 선택되었을 경우 : 최종 선택된 아이템의 인덱스
   
아이템이 선택되어지지 않았을 경우 : 0

일단 다중 선택이 가능한 상태에서 그럼 어떻게 여러개의 아이템이 선택되어 졌는지를
인식하고 항목들을 가져   있는지 살펴보자.

void DoSomething(CListBoxbox)
{
    
// 몇개의 아이템이 선택되어졌는지 카운트
    
int nCount = box.GetSelCount();

    
// 선택되어진 아이템이 하나도 없으면 리턴.
    
if(nCount <= 0)
        
return;

    
// 배열을 하나 만들고크기를 설정한다.
    
CArray<int,intaryListBoxSel;
    
aryListBoxSel.SetSize(nCount);

    
// 아이템중에서 선택되어진 인덱스를 배열에 읽어온다.
    
box.GetSelItems(nCountaryListBoxSel.GetData());

    
for(int i=0i<nCounti++)
    {
         
// 선택된걸 하나씩 뽑아서 먼가 처리를 하겠지...
         
int sel = aryListBoxSel[i];

        
// do something...
    }
}

위의 함수에서 보았듯이 여러개의 아이템이 선택되어져 있을 경우먼저 선택 아이템 카운트를
읽어온다음 인덱스를 배열에 읽어오는 과정을 거친다.

그럼 다중 선택일 경우에 선택아이템을 지우는 버튼의 코드를 수정해보자.

싱글 선택일 경우는 아래와 같았다.
void CSssDlg::OnButton4()
{
    
m_list.DeleteString(m_list.GetCurSel());
}

멀티 선택일 경우는 다음과 같다.
void CSssDlg::OnButton4()
{
    
int nCount = box.GetSelCount();
    
if(nCount <= 0)
        
return;

    
CArray<int,intaryListBoxSel;
    
aryListBoxSel.SetSize(nCount);
    
box.GetSelItems(nCountaryListBoxSel.GetData());

    
// 앞에꺼 부터 지우게 되면 뒤쪽의 저장해 놓은 인덱스 정보가 틀어지게 되므로
    
// 뒤에꺼 부터 앞으로 하나씩 지우면 된다.
    
for(int i=nCount-1i>=0i--)
          
m_list.DeleteString(aryListBoxSel[i]);
}

86.zip
0.03MB

+ Recent posts