잠시 늦어졌던 장을 다시 시작하겠습니다.
회사도 좀 바밨었구, 흠.. 오랜만에 친구 몇명을 만나느라 주말도 좀 시간이 않나고..
이제서야 다시 글을 쓰게 되었습니다.

이번장은 리스트박스에 대하여 간단하게 설명해 봅니다.
리스트 박스는 이전 장에서 다룬 콤보박스에서도 언급되었었지만..

아주 단순하게 아이템을 열거해 놓고, 하나 혹은 여러개를 선택할 수 있는 직관적인
인터페이스를 제공해주는 컨트롤입니다.

컨트롤을 어느정도 다루다 보면 리스트박스(ListBox)보다는 리스트컨트롤(ListCtrl)을 주로
사용하게 되지만 컨트롤 자체의 기능은 나무랄데가 없습니다.


리스트 박스의 일반적인 속성은 스타일 페이지에 다 모여있습니다.
대부분의 속성들은 기존 장들과 겹치는 부분이 있으니,

Selection, Owner draw, Multi-column, Horizontal scroll
이 속성에 대하여 설명드리겠습니다.

1. Selection Property
선택 속성은 다음 그림처럼 4개의 속성으로 분리되어 있습니다

 - None
    이 속성은 아이템을 선택할 수 없고, 어떤 아이템에 포커스만 보여집니다.
    그림처럼 점선 박스만 보여지고, 사실 잘 사용되지 않는 속성입니다.

- Single
   이 속성은 아이템을 단 하나만 선택할 수 있습니다. 화면에 여러가지 아이템중에 단 하나만
   사용자가 선택할 수 있도록제공 함으로써, 중복선택을 인터페이스상에서 차단할 수 있습니다.

- Multiple
   이 속성은 아이템을 여러개 동시에 선택할 수 있습니다. 단 shift 혹은 ctrl 키와 조합하여
   선택하는 것이 아닌 마우스로 클릭하면 선택되고, 다시 클릭하면 해제되는 방법으로
   사용되기 때문에 상당히 불편하죠. 만약 여러개를 선택했다가 해제하려면 일일이 하나씩
   다 클릭해주어 해제시켜야 합니다.

- Extended
   확장 선택 속성으로써, 아이템을 여러개 동시에 선택할 수 있는건 Multiple과 같습니다. 단
   차이점은 Shift키와 Ctrl키를 마우스와 조합하여 선택할 수 있으며, 마우스 왼쪽 버튼만 클릭하면
   아이템을 하나만 선택할 수 있고, Shift키를 조합하여 누르면 이전 선택항목부터 현재까지 사이에
   있는 모든 아이템을 선택할 수 있습니다. 또한 Ctrl키와 조합하여 누르면 이전의 선택 항목은
   보존되고 있는 상태에서 Multiple 속성 처럼 추가적으로 아이템을 하나씩 선택하거나 해제가
   가능해집니다. 가장 일반적으로 사용되는 속성이죠.

 

2. Owner draw Property

MFC에서 제공해주는 확장 기능으로 다음과 같은 3가지가 있습니다. 몇몇 기본 컨트롤에서
제공해주는 기능으로 컨트롤의 내부를 사용자가 원하는 방식으로 구현할 수 있도록 그 바탕을
제공해 줌으로써 좀더 확장된 사용을 가능하게 해줍니다.

- No
   화장된 사용자 정의 그리기 기능을 사용하지 않고, 제공된 기능만을 사용함을 알립니다.
   일반적으로 모든 컨트롤의 디폴트 속성입니다.

- Fixed
   Owner Draw를 선택하게 되면 기본적으로 제공되는 가상함수
   virtual void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct);
   위 함수를 사용자가 재 정의하여 (오버라이딩), 주어진 정보를 기준으로 그림을 내맘대로
   그릴 수 있도록 제공해줍니다.
   단, Fixed일 경우는 각각의 아이템마다 높이가 일률적으로 같습니다.

- Variable
   Fixed 처럼 DrawItem을 이용하여 사용자가 직접 그림을 그려주는 부분은 동일하지만,
   virtual void MeasureItem(LPMEASUREITEMSTRUCT lpMeasureItemStruct);
   위의 가상함수를 이용하여 아이템의 높이도 개별적으로 다르게 설정할 수 있다는 점이 다릅니다.

3. Multi column Property
리스트 박스에서 제공해주는 속성중에 가장 직관성이 떨어지는 속성으로...
영문을 해석하면 멀티 컬럼을 지원해준다.. 라고 이해할 수 있습니다. 버뜨!!!~~

멀티컬럼이긴 합니다. 단, 사용자가 생각하는 리스트 컨트롤의 멀티 컬럼과는 개념이 좀 많이 다른
환경이라 첨에 좀 당황스럽다는 점만 빼면요.. -_-

아래는 속성을 적용해 놓은 화면입니다아이템 크기에 따라 그냥 지맘대로 쪼갭니다 스크롤바도
없고 아무것도 없습니다화면에 보이지 않는 아이템을 선택하려면 그냥 마우스로 드래그 해야하고
멀티로 아이템을 선택할 경우는 선택도 지지리 이상해지죠..

 컬럼의 너비는 SetColumnWidth()라는 함수를 이용하여 조절하실  있습니다.
또한  속성은 Owner draw 사용시에 Variable에서는 조합하여 사용하실  없습니다.

대충 사용목적을 생각해보면 쫍은 공간에 많은 항목을 넣어놓고하나씩 선택할 경우라면
어떻게든 써볼만 하겠다는 생각이 드네요.. ~

4. Horizonal scroll Property
 속성을 내용대로 이해하면 수평 스크롤바를 자동으로 지원해주는  같지만..
그렇지는 못하다수평 스크롤바를 이용할  있도록 내부적인 동작만 지원한다는 의미이다.

 아이템의 글자가 너무 길어 화면폭을 넘어갈 경우리스트박스에서는 그냥 잘려 보인다.
   속성을 첨부하고 아래와 같은 코드를 이용하여 수평 스크롤바를 생성할  있다.

// 주어진 글자가 리스트박스 내에서 길이가 얼마나 되는지 계산한다.
static int GetTextLenEx(CListBoxboxLPCTSTR lpszText)
{
    
CSize size;
    
CDC *pDC = box.GetDC();

    
// 현재 리스트박스의 폰트를 얻어와 DC 적용시킨다.
    
CFontpOld = 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), 0NULL);
        
size.cx += 2;
    }
    
pDC->SelectObject(pOld);
    
box.ReleaseDC(pDC);

    
// 구한 문자열의 Pixel 단위를 넘긴다.
    
return size.cx;
}

// 문자열을 리스트박스에 추가하는 함수.
static void AddStringEx(CListBoxboxCString str)
{
    
// 우선 리스트박스에 문자열을 추가시킨다.
    
box.AddString(str);

    
// 길이를 계산하여 기존 길이보다 넓으면 새로운 길이를 적용시킨다.
    
int iExt = GetTextLenEx(boxstr);
    
if (iExt > box.GetHorizontalExtent())
        
box.SetHorizontalExtent(iExt);
}

108.zip
0.03MB

이전에 이어 첵크박스가 가지고 있던 속성중에 주요한 속성들을 살펴보겠다.
속성 페이지의 Styles 에서 기존에 못보던 Tri State라는  속성을 보았을 것이다.

샘플을 수행한 화면이다.
아래 상위의 두개 첵크 박스는 모두 Tri State 속성을 부여하였고, 나머지는 아니다.

 

일반적으로는 첵크상태(BST_CHECKED)와 막연한 상태???(BST_INDETERMINATE)
언첵크 상태(BST_UNCHECKED)로 구분되며...
이번 장에서는 BST_INDETERMINATE에 대하여 자세히 살펴본다.

우선 장을 진행하기에 앞서 살펴보아야할 것들에 대하여 나열해 보겠다.
ON_COMMAND_RANGE
IsDlgButtonChecked
EnableWindow
CheckDlgButton

중요한 내용들이며 각각 다음과 같은 기능을 제공한다. 특히나 ON_COMMAND_RANGE UI를 다룰 경우 상당히 유용한 매크로이므로 알아두면 써먹을 곳이 많다.

1. ON_COMMAND_RANGE
제시 : 만약에 버튼이 100개 있다면? OnButton1 ~ OnButton100 까지 100개를 다 만들것인가?
MFC
에서 제공해주는 아주 유용한 매크로로 여러개의 컨트롤 아이디를 하나의 함수로 매핑해주는
 
기능을 제공한다. 이 기능을 이용하면 연속적인 아이디를 가진 컨트롤을 몽땅 한 함수에서 처리할
 
수 있으므로 불필요한 함수의 증가를 막고, 코드 관리에 아주 유용하다.
, 기능에서 보아 짐작할 수 있겠지만.. 저 매크로는 자동화로 제공되지 않으므로 개발자가 직접
손수 타이핑해주어야 한다.

2. IsDlgButtonChecked
첵크 박스의 첵크 상태를 읽어내는 함수로 CWnd의 멤버 함수이다.

3. EnableWindow
CWnd
를 상속한 컨트롤의 활성화/비활성화를 처리해주는 멤버 함수이다.

4. CheckDlgButton
첵크 박스의 여러가지 첵크 상태를 임의로 변경해 줄 수 있는 CWnd의 멤버함수이다.

---------------------------------------------------------------------------------
다이알로그 헤더에 추가될 코드

 //
프로토콜 상위 첵크박스 상태.
 BOOL m_protocol;
 //
프로토콜 하위 첵크박스들의 상태.
 UINT m_protocol_sub;
 //
서포트 상위 첵크박스 상태
 BOOL m_support;
 //
서포트 하위 첵크박스들의 상태
 UINT m_support_sub;

 //
두개 그룹의 첵크 박스의 이벤트 핸들러를 묶어 놓은 Range 함수 2.
 // 리턴값은 LRESULT 이고, 인자는 UINT 타입의 인자 1개이다. 잘 기억해두자.
 afx_msg LRESULT OnCheckProtocolRange(UINT nID);
 afx_msg LRESULT OnCheckSupportRange(UINT nID);

---------------------------------------------------------------------------------
다이알로그 소스에 추가될 코드

// 변수 초기화
CSssDlg::CSssDlg(CWnd* pParent /*=NULL*/)
: 
CDialog(CSssDlg::IDD, pParent)
{
    
//{{AFX_DATA_INIT(CSssDlg)
    
//}}AFX_DATA_INIT
    
// Note that LoadIcon does not require a subsequent DestroyIcon in Win32
    
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);

    
m_protocol = FALSE;
    
m_support = FALSE;
    
m_protocol_sub = 0;
    
m_support_sub = 0;
}

// 메시지 등록
BEGIN_MESSAGE_MAP(CSssDlg, CDialog)
    
//{{AFX_MSG_MAP(CSssDlg)
    
//}}AFX_MSG_MAP

    
// 메시지 맵에는 아래와 같은 용법으로 등록한다.
    
// 첫번째는 시작되는 컨트롤 아이디,
    
// 두번째는 마지막 컨트롤 아이디.
    
// 세번째는 멤버함수 이름
    
ON_COMMAND_RANGE(IDC_CHECK1, IDC_CHECK3, OnCheckProtocolRange)
    
ON_COMMAND_RANGE(IDC_CHECK4, IDC_CHECK7, OnCheckSupportRange)
END_MESSAGE_MAP()


// 프로토콜 그룹의 첵크박스 핸들러
//  메시지 맵에 처럼 등록해 놓으면 IDC_CHECK1, IDC_CHDEK2, IDC_CHECK3 첵크시
// 모든 이벤트가 아래 함수로 온다물론 어떤 컨트롤을 건드렸는지는 nID  넘어온다.
LRESULT CSssDlg::OnCheckProtocolRange(UINT nID)
{
    
// 어떤 놈을 건드렸나?
    
switch(nID)
    {
    
// 나는 상위 프로토콜 그룹 첵크 박스
    
case IDC_CHECK1:
        {
            
// IDC_CHECK1  첵크되어있나?
            
//  코드는 Tri State 가진 첵크 박스는 첵크를 하면 3가지 상태를 모두 토글하므로
            
// IDC_CHECK1 사용자가 직접 클릭했늘 경우는 CHECK/UNCHECK상태만을
            
// 가지도록 처리해주는 루틴이다.
            
BOOL bChecked = IsDlgButtonChecked(IDC_CHECK1);
            
if(m_protocol == FALSE && bChecked)
                
CheckDlgButton(IDC_CHECK1, BST_CHECKED),
                
m_protocol = TRUE;
            
else if(m_protocol == TRUE && bChecked)
                
CheckDlgButton(IDC_CHECK1, BST_UNCHECKED),
                
m_protocol = FALSE;

            
// IDC_CHECK1  첵크되어 있다면 하부 첵크박스를 모두 활성화 시킨다.
            
// 아니라면 모두 비활성화 시키지...
            
GetDlgItem(IDC_CHECK2)->EnableWindow(m_protocol);
            
GetDlgItem(IDC_CHECK3)->EnableWindow(m_protocol);
            
return 0;
        }
    
// IDC_CHECK1 제외한 모든 첵크박스
    
default:
        {
            
// 현재 첵크박스가 몇번째 것인지 구한다.
            
int n = nID - IDC_CHECK2;
            
// 현재 첵크박스의 상태를 구한다.
            
BOOL bChecked = IsDlgButtonChecked(nID);

            
// 첵크 상태에 따라 m_protocol_sub  설정한다.
            
if(bChecked)
                
m_protocol_sub |= 0x01 << n;
            
else
                
m_protocol_sub &= ~(0x01 << n);

            
// 상태에 따라 IDC_CHECK1 첵크 상태를 변경시킨다.
            
if(m_protocol_sub)
                
CheckDlgButton(IDC_CHECK1, BST_INDETERMINATE);
            
else
                
CheckDlgButton(IDC_CHECK1, BST_CHECKED);
        }
    }

    
return 0;
}

// 서포트 그룹의 첵크박스 핸들러
// 코드의 내부 기능  동작은 OnCheckProtocolRange  동일하다.
// 단지 구분하기 위하여 따로 놓았을 ...
LRESULT CSssDlg::OnCheckSupportRange(UINT nID)
{
    
switch(nID)
    {
    
case IDC_CHECK4:
        {
            
BOOL bChecked = IsDlgButtonChecked(IDC_CHECK4);
            
if(m_support == FALSE && bChecked)
                
CheckDlgButton(IDC_CHECK4, BST_CHECKED),
                
m_support = TRUE;
            
else if(m_support == TRUE && bChecked)
                
CheckDlgButton(IDC_CHECK4, BST_UNCHECKED),
                
m_support = FALSE;

            
GetDlgItem(IDC_CHECK5)->EnableWindow(m_support);
            
GetDlgItem(IDC_CHECK6)->EnableWindow(m_support);
            
GetDlgItem(IDC_CHECK7)->EnableWindow(m_support);
            
return 0;
        }
    
default:
        {
            
int n = nID - IDC_CHECK5;
            
BOOL bChecked = IsDlgButtonChecked(nID);
            
if(bChecked)
                
m_support_sub |= 0x01 << n;
            
else
                
m_support_sub &= ~(0x01 << n);

            
if(m_support_sub)
                
CheckDlgButton(IDC_CHECK4, BST_INDETERMINATE);
            
else
                
CheckDlgButton(IDC_CHECK4, BST_CHECKED);
        }
    }

    
return 0;
}

 ------------------------------------------------------------------------------------
모두 7개의 첵크 박스를 하나씩 다 제어하려면 함수도 많아지고, 이만 저만 귀찬은 것이 아닐겁니다.
복잡한 UI는 수집개의 버튼과 수십개의 첵크박스, 에디트, 라디오, 에디트.. 를 동반하는데
일일이 하나씩 모두 할 필요는 없겠죠? ^^;

구지 함수를 2개가 아닌 1개로 해도 되지만, 이를 나눈 이유는 코드 수를 줄이는 것도 중요하지만
서로 다른 그룹임을 구분해 주는 것이 나중에 코드를 수정하거나 보완할때 수월하기 때문입니다.

57.zip
0.09MB



+ Recent posts