본문 바로가기

Language/MFC

뷰에 컨트롤 그리기(버튼컨트롤&리스트컨트롤)

Dialog 기반의 formView나 걍 Dialog 라면 문제 없이 컨트롤을 넣지만 일반 VIew에 컨트롤을 그리라고 하면 좀 막막하다. 젠장 이것때문에 엄청난 시간을 투자한것을 생각하면...ㅡㅡ

여기 나온 내용은 '비주얼 C++ 제대로 활용하기' 라는 책에서 참고한 겁니다.

1. 버튼 컨트롤

1) 뷰 클래스 헤더파일에 버튼을 위한 포인터를 하나 추가한다. 
         CButton *pButton;

2) 포인터를 만든 후, 버튼 객체를 new 연산자를 이용하여 객체를 생성한다. OnInitialUpdate() 함수는 뷰가 생성된 후 윈도우에 그려지기 바로 직전에 호출되는 함수이다. 따라서 여기서 버튼의 초기화를 한다. new 연산자는 필요하다면 생성자에 넣어도 된다.

void CMyCtrlView::OnInitialUpdate()
{
        CView::OnInitialUpdate();
       
        // TODO: Add your specialized code here and/or call the base class
        pButton = new CButton();
        pButton->Create("Test Button", BS_DEFPUSHBUTTON,
                CRect(0,0,200,50), this, 100);
}

3) 다음은 버튼을 화면에 나타나도록 OnDraw()에서 ShowWindow() 함수를 호출한다.

void CMyCtrlView::OnDraw(CDC* pDC)
{
        CMyCtrlDoc* pDoc = GetDocument();
        ASSERT_VALID(pDoc);
        // TODO: add draw code for native data here
        pButton->ShowWindow(SW_SHOW);
}

4) 소멸자에서 pButton에 할당한 메모리를 없앤다. 물론 OnInitialUpdate() 함수에 delete 연산자를 넣어도 된다.

CMyCtrlView::~CMyCtrlView()
{
        delete pButton;
}

 

(참고.1) View에 Child 윈도우를 만들 때

 우리는 종종 View에 버튼 이외에 다른 여러 컨트롤들을 추가시키게 될 상황이 발생할지 모른다. 이러한 컨트롤들은 View에서 호출되므로 View의 Child 윈도우가 되고 따라서 View는 이들 컨트롤의 부모 윈도우가 된다. CButton 클래스의 Create() 함수를 유의해서 보면 알겠지만 부모 윈도우의 포인터를 필요로 하는 것을 알 수 있다. 따라서 Create() 함수를 만약 다음과 같이 생성자에서 선언해 버리면 에러를 유발하게 된다.

CMyCtrlView::CMyCtrlView()
{
        pButton->Create("Test Button", BS_DEFPUSHBUTTON,
                CRect(0,0,200,50), this, 100);
}

 왜 그럴까?
 this 라는 값은 view의 자신을 가리키는 포인터로 아직 그 값이 확정되지 않았기 때문이다. 따라서 this 라는 값은 OnInitialUpdate() 함수가 호출된  시점에서 결정되어 있기 때문에 OnInitialUpdate() 함수에 넣은 것이다.

 

(참고.2) 버튼 컨트롤의 Create() 함수의 인자에 대해

 Create() 함수의 프로토타입은 다음과 같다.

 BOOL Create(LPCTSTR lpszCaption, DWORD dwStyle,
          const RECT& rect, CWnd* pParentWnd, UINT nID);

           lpszCaption   : 버튼에 나타날 문자열
           dwStyle          : 버튼의 스타일
           rect               : 버튼이 나타날 위치
           pParentWnd   : 버튼이 나타나게 될 윈도우, 즉 부모 윈도우,
                                보통 호출할 때 this 라는 포인터를 넘겨준다.
           nID                : 이 버튼을 식별하는 아이디

두 번째 파라미터인 dwStyle은 버튼의 스타일을 나타내는 값으로 다음과 같은 값들을 가질 수 있다.

BS_CHECKBOX
 - 네모난 사각형을 가진 체크 박스 모양이 된다.

BS_AUTOCHECKBOX
 - 버튼의 모양이 체크박스 모양으로 나타난다.

BS_AUTORADIOBUTTON
 - 버튼의 모양이 라디오 버튼 모양으로 나타난다.

BS_AUTO3STATE
 - 버튼의 모양이 3가지 상태를 가지는 체크박스의 모양으로 나타난다.

BS_DEFPUSHBUTTON
 - 가장 기본적인 버튼모양으로 마우스나 키보드로 누르면 눌림효과가 나타나는 버튼의 모양으로 만든다. 이 옵션은 다른 옵션을 선택하지 않았을 때 Default 옵션이 된다.

BS_GROUPBOX
 - 사각형을 그리게 되는데, 이 사각형 안에 있는 다른 버튼들은 그룹화된다.

BS_LEFTTEXT
 - 라디오 버튼이나 체크박스 스타일의 버튼을 만들 때 나타나게 될 텍스트가 라디오 버튼이나 체크박스의 왼쪽에 나오게 된다.

BS_OWNERDRAW
 - 사용자 정의 버튼을 그릴 때 사용된다. 이 옵션을 선택하면 프레임워크가 DrawItem() 이란 멤버함수를 호출하여 주는데, 이 함수에서 사용자가 버튼의 모양을 정의해 주어야 한다. (주의 : 이 스타일을 사용할 때는 CButton 클래스에서 유도된 CBitmapButton 클래스를 사용한다.)

BS_PUSHBUTTON
 - 사용자가 버튼을 눌렀을 때 WM_COMMAND 메시지가 발생하도록 버튼을 만들어 준다. WM_COMMAND 메시지가 발생한다는 것은 메뉴에 있는 어떤 기능과 동일한 역할을 하도록 할 때 필요한 기능이다. 예를 들면 툴바 버튼들이 그러하다.

BS_RADIOBUTTON
 - 작은 원 모양의 라디오 버튼을 만드는데, 그 원 옆에는 텍스트가 표시된다. 

BS_3STATE
 - 체크박스와 같은 버튼을 만들지만 어둡게(dimmed) 되는 상태를 하나 더 갖는다.

 

(참고.3) 버튼으로부터의 메시지 처리

 버튼의 상태를 알아낼 때 사용되는 멤버함수가 GetCheck() 이다.

 int GetCheck() const;
 리턴값 : 0 - 체크되지 않음
              1 - 체크됨
              2 - 상태가 결정되어 있지 않음 (3버튼 상태일 경우)

반대로 버튼의 상태를 강제적으로 결정해 주는 함수가 SetCheck() 이다.

 void SetCheck(int nCheck);
 인자 : nCheck - 0 : 체크표시를 없애는 기능
                 1 : 체크표시를 함
                 2 : 3버튼 상태일 경우 결정을 하지않고 내버려두는 기능

 

 리스트 박스 컨트롤 

1) 뷰 클래스 헤더파일에 버튼을 위한 포인터를 하나 추가한다. 
         CButton *pButton;

2) OnInitialUpdate() 함수 내용

void CMyCtrlView::OnInitialUpdate()
{
        CView::OnInitialUpdate();
       
        // TODO: Add your specialized code here and/or call the base class
        pListBox = new CListBox();
        pListBox->Create(LBS_STANDARD, CRect(210, 0, 450, 200), this, 200);
}

3) OnDraw()에서 ShowWindow() 함수를 호출

void CMyCtrlView::OnDraw(CDC* pDC)
{
        CMyCtrlDoc* pDoc = GetDocument();
        ASSERT_VALID(pDoc);
        // TODO: add draw code for native data here
        pListBox->ShowWindow(SW_SHOW);
}

4) 소멸자에서 할당된 메모리 제거

CMyCtrlView::~CMyCtrlView()
{
        delete pListBox;
}

5) 리스트 박스에 새로운 항목을 입력하기 위해 메뉴에 ID_TEST_ADD_LSTBX 라는 ID의 항목을 새로 만들고 여기에 연결될 메시지를 다음과 같이 클래스 위저드를 사용하여 만든다.

void CMyCtrlView::OnTestAddLstbx()
{
        // TODO: Add your command handler code here
        char *String[] = { "first string", "second string", "third string"};
        for(int i=0; i<3; i++) {
                pListBox->AddString(String[i]);
        }
}

6) 리스트 박스에서 사용자가 선택한 것을 얻어오는 작업을 해 보자. 여기서는 item을 마우스로 더블클릭하면 메시지 박스가 뜨도록 할 것이다. 다음은 뷰의 cpp 파일에 있는 메시지 맵 부분이다. 다음 한 줄을 추가한다.
 
IMPLEMENT_DYNCREATE(CMyCtrlView, CView)

BEGIN_MESSAGE_MAP(CMyCtrlView, CView)
        //{{AFX_MSG_MAP(CMyCtrlView)
        ON_COMMAND(ID_TEST_ADD_LSTBX, OnTestAddLstbx)

        ON_LBN_DBLCLK(200, OnListBoxDK)    // <---- 추가된 부분
        //}}AFX_MSG_MAP
        // Standard printing commands
        ON_COMMAND(ID_FILE_PRINT, CView::OnFilePrint)
        ON_COMMAND(ID_FILE_PRINT_DIRECT, CView::OnFilePrint)
        ON_COMMAND(ID_FILE_PRINT_PREVIEW, CView::OnFilePrintPreview)
END_MESSAGE_MAP()

7) 뷰의 헤더파일에 있는 다음 부분에 한 줄을 추가한다.

// Generated message map functions
protected:
        //{{AFX_MSG(CMyCtrlView)
        afx_msg void OnTestAddLstbx();

        afx_msg void OnListBoxDK();  // <---- 추가된 부분
        //}}AFX_MSG
        DECLARE_MESSAGE_MAP()
};

8) 추가한 메시지 맵에 연결되는 핸들러를 직접 만든다. 뷰의 cpp 파일에 추가.

void CMyCtrlView::OnListBoxDK()
{
        char buffer[100];
        int sel = pListBox->GetCurSel();
        pListBox->GetText(sel, buffer);
        MessageBox(buffer);
}

 

(참고.1) 리스트 박스 컨트롤의 Create() 함수의 인자에 대해

 리스트 박스 컨트롤의 Create() 함수의 프로토 타입은 다음과 같다.

 BOOL Create(DWORD dwStyle, const RECT& rect,
           CWnd* pParentWnd, UINT nID);

 이 함수의 첫 번째 인자인 dwStyle은 리스트 박스의 스타일을 정의하는 부분이다. 다음은 가능한 스타일의 목록이다.

LBS_EXTENDEDSEL
 - 사용자로 하여금 여러 개의 아이템을 동시에 선택할 수 있도록 해준다.

LBS_HASSTRINGS
 - 사용자가 정의한 리스트 박스에서 리스트 박스의 아이템이 문자열로 되어있음을 나타낸다. 이렇게 함으로써 GetText() 멤버함수를 호출했을 때 문자열을 알아낼 수 있게 된다.

LBS_MULTICOLUMN
 - 컬럼(column)이 여러개인 리스트 박스를 만든다. 리스트 박스의 각 컬럼은 SetColumnWidth() 멤버함수로 결정할 수 있다.

LBS_MULTIPLESEL
 - 선택한 문자열이 토글되도록 한다.

LBS_NOINTEGERALHEIGHT
 - 리스트 박스의 크기를 사용자가 제시한 크기와 동일하게 만든다. 왜냐하면 보통 윈도우에서 리스트 박스를 그릴 때 아이템이 완전히 나타나도록 크기를 조절하기 때문이다.

LBS_NOREDRAW
 - 리스트 박스가 변화되었을 때 그 내용을 반영해 주지 않도록 한다. 이 스타일을 다시 바꾸고 싶다면 WM_SETREDRAW 메시지를 보내주면 된다.

LBS_NOTIFY
 - 사용자가 리스트의 아이템을 클릭하거나 더블클릭했을 때 부모윈도우가 메시지를 받도록 해 준다.

LBS_OWNERDRAWFIXED
 - 리스트 박스의 소유자가 그 내용을 그리도록 하는 옵션으로 모든 아이템은 동일한 높이를 가지고 있다.

LBS_OWNERDRAWVARIABLE
 - 리스트 박스의 소유자가 그 내용을 그리도록 하는데 리스트박스의 아이템들은 서로 다른 높이를 가질 수 있도록 해 준다.

LBS_SORT
 - 리스트 박스에 있는 스트링들을 알파벳으로 정렬할 수 있도록 해준다.

LBS_STANDARD
 - 리스트 박스에 있는 스트링은 알파벳 순으로 정렬이 되고 부모 윈도우는 LBS_NOTIFY 스타일을 준 것처럼 메시지를 받게 된다. 보통 이 스타일을 많이 사용한다.

LBS_USETABSTOPS
 - 리스트 박스가 Tab 문자를 인식하도록 해준다.

LBS_WANTKEYBOARDINPUT
 - 리스트 박스의 소유자, 즉 호출하는 쪽이 WM_VKEYTOITEM 또는 WM_CHARTOITEM 메시지를 받도록 해준다. 이 스타일을 사용하는 리스트 박스가 입력 포커스를 가지고 있을 때 키보드 입력에 대해서 특별한 루틴을 수행할 수 있도록 해준다.

LBS_DISABLENOSCROLL
 - 리스트 박스가 충분한 아이템을 가지고 있지 않을 때 리스트 박스 스크롤바가 작동하지 않도록 해준다. 이 스타일을 적용하지 않을 때는 스크롤바가 충분한 아이템을 가지고 있지 않을 때 숨겨지게 된다.

 

(참고.2) 위의 6번에서 사용가능한 메시지 핸들러 종류

            Map Entry                                                 Function ProtoType
ON_LBN_DBLCLK( <id>, <memberFxn> )           afx_msg void memberFxn();
ON_LBN_ERRSPACE( <id>, <memberFxn> )       afx_msg void memberFxn();
ON_LBN_KILLFOCUS( <id>, <memberFxn> )      afx_msg void memberFxn();
ON_LBN_SELCHANGE( <id>, <memberFxn> )     afx_msg void memberFxn();
ON_LBN_SETFOCUS( <id>, <memberFxn> )       afx_msg void memberFxn();