'WTL'에 해당되는 글 6건

  1. 2007.08.30 [강좌] WTL로 프로그래밍하기#6 – 컨트롤 사용하기 1
  2. 2007.08.17 [강좌] WTL로 프로그래밍하기#5 – Message Map(2) 1
  3. 2007.08.17 [강좌] WTL로 프로그래밍하기#4 – Message Map(1)
  4. 2007.08.14 [강좌] WTL로 프로그래밍하기#3 – Mix-in class
  5. 2007.08.13 [강좌] WTL로 프로그래밍하기#2 – 위저드로 생성되는 코드
  6. 2007.08.06 [강좌] WTL로 프로그래밍하기#1 - WTL소개

[강좌] WTL로 프로그래밍하기#6 – 컨트롤 사용하기

개발 2007. 8. 30. 15:19

336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

저자 : hanburn

날짜 : 2007.08.29

환경 : WTL7.5, VS-2003

 

 

지난시간까지 정리를 하면(오랜만에 강좌를 쓰니까 기억이 않나서~ ) 위저드를 통해서 생성되는 코드의 뼈대를 살펴보고, 메시지맵에 대해서 알아 보았습니다. 그럼 다음단계로는 윈도우의 각종 컨트롤들을 사용하는 방법입니다. 컨트롤의 사용법이라고 해서 윈도우의 공용컨트롤들의 각각의 사용법을 알아보는 것이 아니라 MFC와 비교해서 컨트롤들의 일반적인 사용법을 애기하는 것입니다. 그럼 또 편한 말로 계속하겠습니다.

 

 

WTL에서 제공하는 컨트롤의 종류는 아래와 같습니다.

 

User controls: CStatic, CButton, CListBox, CComboBox, CEdit, CScrollBar, CDragListBox

Common controls: CImageList, CListViewCtrl (CListCtrl in MFC), CTreeViewCtrl (CTreeCtrl in MFC), CHeaderCtrl, CToolBarCtrl, CStatusBarCtrl, CTabCtrl, CToolTipCtrl, CTrackBarCtrl (CSliderCtrl in MFC), CUpDownCtrl (CSpinButtonCtrl in MFC), CProgressBarCtrl, CHotKeyCtrl, CAnimateCtrl, CRichEditCtrl, CReBarCtrl, CComboBoxEx, CDateTimePickerCtrl, CMonthCalendarCtrl, CIPAddressCtrl

Common control wrappers not in MFC: CPagerCtrl, CFlatScrollBar, CLinkCtrl (clickable hyperlink, available in XP and later)

 

이중에서 User / Common 그룹의 컨트롤들은 MFC에서 일상적으로 사용하던 것들이고 추가로 CPagerCtrl, CFlatScrollBar, CLinkCtrl등의 유용한 클래스들도 제공이 되고 있다. 윈도우 공용컨트롤들이야 윈도우에서 제공되는 것들을 MFC에서 랩핑한 방식 비슷하게 WTL도 랩핑하여 제공하고 있고 함수이름도 똑같이 제공한다. 다만, 파라미터의 순서가 조금 다른것도 있다.

 

윈도우 공용 컨트롤들을 사용하는 것은 리소스편집기에서 Dialog에 추가하거나 직접 코드에서 생성하여 사용하는 방법이 있는데, 이 방법에 대해서 자세하게 알아보자.

 

먼저 리소스에서 추가한 컨트롤을 제어 하려면 아래처럼 하면된다.

 

HWND hWndListBox = GetDlgItem(IDC_LIST1);

CListBox list1(hWndListBox);

CListBox list2;

list2.Attach(hWndListBox);

 

CListBox list3 = GetDlgItem(IDC_LIST1);

 

 

GetDlgItem으로 HWND를 얻어와서 생성자에 넣어 주거나 Attach 함수를 이용해서 hwnd를 붙여주면 된다. 또는 간편하게 GetDlgItem의 반환값을 직접 대입하는 식으로 써주면 된다. 그런뒤에는 CListBox의 멤버함수를 통해서 원하는 작업을 하면 된다. 여기서 한가지 알고 넘어갈 것은 list1,list2,list3 같은 래퍼클래스의(여기서는 CListBox) 인스턴스가 소멸된다고 해서 실제적인 CListBox가 파괴되는 것은 아니라는 점이다. 따라서 attach한 핸들을 detach할 필요가 없다.

 

코드에서 직접생성하는 경우는 더 쉽다. 아래는 CButton을 직접 생성하는 경우인데 m_btnTitle을 가지고 해당 버튼을 제어하면 된다.

 

m_btnTitle.Create(m_hWnd, NULL, NULL, WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 0, IDC_NES_WIDGET_BTN_TITLE);

 

컨트롤을 생성해서 제어를 하였으면 이제는 컨트롤에서 발생하는 이벤트를 처리해야 되는데, 이벤트 처리는 지난시간에 살펴본 메시지 핸들러를 통해서 제어하게 된다. 차일드 컨트롤에서 발생되는 이벤트는 부모위도로 WM_COMMAND, WM_NOTIFY 등의 메시지로 전달이 되므로 이 메시지를 잡아서 처리하면 된다. 간단하게 CListCtrl에서 발생하는 몇가지 이벤트처리 예를 보자

 

BEGIN_MSG_MAP_EX(CMainDlg)

        NOTIFY_HANDLER_EX(IDC_LIST, NM_DBLCLK, OnListDblClicked)

NOTIFY_HANDLER_EX(IDC_LIST, LVN_ITEMCHANGED, OnListItemchanged)

END_MSG_MAP()

 

LRESULT        OnListItemchanged(NMHDR* phdr);

LRESULT        OnListDblClicked(LPNMHDR pnmh);

 

 

위에서는 NM 계열의 일반적인 통지메시지와 ListCtrl의 통지메시지인 LVN_ITEMCHANGED를 처리하는 핸들러 함수의 모습이다. (.. 너무 쉽군 ^^)

 

다음으로는 컨트롤 관련해서 DDX라는 방법을 MFC에서 제공하기때문에 WTL에서도 DDX를 유사하게 제공하고 있다. 필자는 DDX/DDV 방법을 별로 사용하지 않지만 그래도 간단하게 알아보고 넘어가도록 하자.

 

DDX를 사용하기 위해서는 WTL의 설계 철학대로 CWinDataExchange 템플릿 클래스에서 상속을 받아야 한다. 그리고 메시지맵 비슷한 DDX맵을 추가해주어야 한다. DDX맵은 CWinDataExchange DoDataExchange 라는 함수를 재정의(override)하는 역할을 한다. 쉽게말하면 상속을 받았으면 꼭 추가를 해주어야 하는 매크로이다.(매크로대신 직접 코드를 써도 되긴 하지만.. ) 메시지맵에 익숙한 우리에게 DDX맵도 익숙할 수밖에 없다. DDX맵을 직접 보고 이야기 하도록 하자.

 

BEGIN_DDX_MAP(CMainDlg)

        DDX_CONTROL(IDC_EDIT, m_wndEdit)

        DDX_TEXT(IDC_EDIT, m_sEditContents)

DDX_FLOAT(IDC_EDIT, m_fEditFloat)

        DDX_FLOAT(IDC_EDIT, m_dEditDouble)

        DDX_CHECK(IDC_SHOW_MSG, m_bShowMsg)

        DDX_CONTROL(IDC_TREE, m_wndTree)

END_DDX_MAP()

 

시작과 끝은 DDX_MAP을 나타내고 중간에 있는 여러가지 DDX_로 시작하는 매크로들에 대해서 알아보자. 첫번째인 DDX_CONTROL은 컨트롤아이디에 해당하는 컨트롤과 변수를 연결시켜주는 역할을 한다. 두번째인 DDX_TEXT는 컨트롤과 변수를 연결시켜주는데, 여기서 변수는 CString, BSTR, CComBSTR 등이나 정적으로 할당된 char 배열도 가능하다. DDX_INT는 컨트롤과 정수 변수를 연결시켜주고, DDX_FLOAT는 컨트롤과 실수를 연결시켜준다. DDX_CHECK는 체크박스컨트롤과 intbool형 변수와 연결이 된고, DDX_RADIO는 라이오버튼과 int형 변수와 연결을 시켜준다.

 

이렇게 DDX_ 관련 매크로를 사용하여 컨트롤과 변수를 연결시킨 다음에는, 필요한 시점에 DoDataExchange() 함수를 호출해주면 MFC에서 UpdateData를 호출하는 것처럼 호출시 파라미터를 TRUE로 하면 컨트롤의 데이터가 변수로, 파라미터를 FALSE로 하면 변수의 값이 컨트롤로 전달이 된다.  그리고 여러가지 DDX_ 매크로 중에서 특정 컨트롤의 변수만 update하고 싶을때는 DoDataExchange의 두번째 인자로 컨트롤 아이디를 넣어주면 된다.

 

DoDataExchange(false, IDC_EDIT);

 

한가지 주의할 점은 DDX_ 매크로중에 잘못되 부분이 있다면 잘못된 부분의 아래에 있는 매크로는 작동이 안 되는 점이다.

 

여기까지 왔으면 다이얼로그 기반의 윈도우를 만들고 거기에 각종 컨트롤을 생성하고, 컨트롤을 제어하고, 컨트롤에서 발생하는 이벤트를 처리할수 있게 되었다. 이정도만 하면 왠만한 프로그램은 그냥 만들 수가 있을 것이다. ^^

 

 

프로그램을 하다보면 스킨등 입히거나 컨트롤의 기능을 확장하기위해서 subclassing을 해야 뭔가 그럴듯해 보이는 프로그램이 나오게 됩니다. 그래서 다음시간에는 Subclassing에 대해서 알아보도록 하겠습니다.  

 



:

[강좌] WTL로 프로그래밍하기#5 – Message Map(2)

개발 2007. 8. 17. 11:59

336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

WTL로 프로그래밍하기#5 – Message Map(2)

 

저자 : hanburn

날짜 : 2007.08.18

환경 : WTL7.5, VS-2003

 

 

지난시간에 WM_XXX 메시지 핸들러를 알아 보았는데, 이번에는 나머지에 대해서 알아보자.

일단 WM_COMMAND 메시지에 해당하는 핸들러로 COMMAND_HANDLER 가 있다.

 

 

BEGIN_MSG_MAP(CListDlg)

        COMMAND_HANDLER(IDCANCEL, BN_CLICKED, OnBnClickedCancel)

        COMMAND_HANDLER(IDOK, BN_CLICKED, OnBnClickedOk)    

END_MSG_MAP()

 

 

COMMAND_HANDLER 3개의 인자를 받는데, 각각 id, code, func 이다. Id 어떤 컨트롤(혹은 매뉴 id)에서 발생된 것인지를 나타내고, code 명령의 세부사항이다. code 각각 컨트롤 마다 다르기 때문에 이것또한 (MFC 비해서)번거로운 일이다. 일일이 msdn 참고해야 하기 때문이다. Win32 코딩을 했던 사람들은 익숙하겠지만, MFC 개발자에게는 낯설 수가 있다.

COMMAND_HANDLER 정의를 한번 보도록 하자.

 

#define COMMAND_HANDLER(id, code, func) \

if(uMsg == WM_COMMAND && id == LOWORD(wParam) && code == HIWORD(wParam)) \

{ \

        bHandled = TRUE; \

        lResult = func(HIWORD(wParam), LOWORD(wParam), (HWND)lParam, bHandled); \

        if(bHandled) \

               return TRUE; \

}

 

별로 특별한 것인 없다. ㅋㅋ

 

유사한 것으로는 COMMAND_ID_HANDLER COMMAND_CODE_HANDLER가 있는데, 차이점은 이름에서 알 수 있듯이 전자는 ID에 매칭이 되고, 후자는 CODE에 매칭이 되는 핸들러 이다. 각각의 매크로 정의를 보면 쉽게 알 수 있다.

 

 

#define COMMAND_ID_HANDLER(id, func) \

if(uMsg == WM_COMMAND && id == LOWORD(wParam)) \

{ \

        bHandled = TRUE; \

        lResult = func(HIWORD(wParam), LOWORD(wParam), (HWND)lParam, bHandled); \

        if(bHandled) \

               return TRUE; \

}

 

#define COMMAND_CODE_HANDLER(code, func) \

if(uMsg == WM_COMMAND && code == HIWORD(wParam)) \

{ \

        bHandled = TRUE; \

        lResult = func(HIWORD(wParam), LOWORD(wParam), (HWND)lParam, bHandled); \

        if(bHandled) \

               return TRUE; \

}

 

쉽게 설명하면 COMMAND_ID_HANDLER 는 코드는 무시하고 ID가 매칭되면 해당 핸들러를 호출해주는 것이고 COMMAND_CODE_HANDLER ID는 무시하고 코드가 매칭되면 해당 핸들러를 호출해주는 것이다. 3개를 섞어서 사용하다 보면 이상한 경우가 생길수가 있다. 아래의 예를 보면..

 

BEGIN_MSG_MAP(CListDlg)

        COMMAND_HANDLER(IDCANCEL, BN_CLICKED, OnBnClickedCancel)

        COMMAND_ID_HANDLER(IDOK, OnCommandOK)

        COMMAND_CODE_HANDLER(BN_CLICKED, OnCommandBnClicked)

ENG_MSG_MAP

 

위의 설명에 앞서서 메시지 맵에 들어있는 매크로의 순서대로 위에서부터 검사를 한다는 것을 알아야 한다. 위에서 cancel 버튼을 클릭한다고 하면 위에서부터 검사해서 첫번째에 걸리므로 OnBnClickedCancel이 호출되고, OK 버튼을 클릭했을때는 두번째에 걸리므로 OnCommandOK가 호출이 되는 것이다. 다른 버튼이 있다고 할 때, 그 버튼을 클릭하면 세 번째에 걸리므로 OnCommandClicked가 호출이 되는 것이다.

 

COMMAND 메크로 중에서 범위를 지정하는 것은 2개밖에 없다. COMMAND_RANGE_HANDLER COMMAND_RANGE_CODE_HANDLER 이렇게 두개가 있는데, 전자는 ID 범위를 받고, 후자는 코드 범위를 받는다.

 

 

그럼 이제 NOTIFY에 대해서 알아보도록 하자. NOTIFY 핸들러는 WM_NOTIFY메시지를 처리하는데 WM_COMMAND에서 NOTIFY로만 변경된거 외에는 메시지 핸들러는 똑같다. 똑같이 NOTIFY_HANDLER, NOTIFY_ID_HANDLER, NOTIFY_CODE_HANDLER, NOTIFY_RANGE_HANDLER, NOTIFY_RANGE_CODE_HANLDER 가 존재한다. 다른점은 핸들러 함수의 파라미터가 다르다.

 

BEGIN_MSG_MAP(CListDlg)

NOTIFY_HANDLER(IDC_FILE_LIST, NM_CLICK, OnNMClickFileList)

END_MSG_MAP()

 

LRESULT OnNMClickFileList(int /*idCtrl*/, LPNMHDR pNMHDR, BOOL& /*bHandled*/);

 


두번째 파라미터가 각각의 상황별로 포인터 변형되어 사용되는 것을 다 알 것 이니까,
이제는 MFC와 좀 다른 것(?) 들을 알아보도록 하자.

 

BEGIN_MSG_MAP(CListDlg)

REFLECT_NOTIFICATIONS()

        CHAIN_MSG_MAP(CBaseClass)

END_MSG_MAP()

 

위와 나온 매크로도 자주 쓰이는 것들이니까, 알아 두어야 한다. 먼저 CHAIN_MSG_MAP은 말그대로 메시지 맵에 체인을 걸어주는 것이다. 무슨말인고 하면,  CListDlg가 다른 클래스로부터 상속된 클래스라고 하면 베이스 클래스로 메시지 체인을 걸어주게 되는 것이다. CHAIN_MSG_MAP() 매크로가 맨 위에 있다면 메시지들은 일단 베이스 클래스에서 해당 메시지의 핸들러가 있는지 확인을 하고 없으면, CListDlg에서 처리를 하게되고 반대로 CHAIN_MSG_MAP 매크로가 메시지 맵의 마지막에 있으면, CListDlg에서 처리하지 않은 메시지 들에 대해서 베이스 클래스로 전달을 하는 역할을 하게 된다. 이 매크로는 상속관계에서 많이 쓰이고, Mix-in Template 클래스를 사용할 때도 사용해야 한다.

 

다음으로 REFLECT_NOTIFICATIONS() 매크로는 이름에서도 알수 있듯이 NOTIFY reflet 해주는 역할이다. 차일드 컨트롤 윈도우에서 발생하는 notify 메시지에 대해서 차일드 윈도으로 다시 보내주는 역할을 하는 것이다. MFC에서는 =NM_ 계열의 핸들러라가 있는 것을 알것이다. 이것이 바로 notify 메시지를 차이드가 자체적으로 처리하는대, WTL에서는 부모에서 위의 메크로를 써주어야 차일드에서 notify를 처리할 수 있는 것이다.

읽어 주셔서 감사합니다~



:

[강좌] WTL로 프로그래밍하기#4 – Message Map(1)

개발 2007. 8. 17. 11:53

336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

WTL로 프로그래밍하기#4 – Message Map(1)

 

저자 : hanburn

날짜 : 2007.08.17

환경 : WTL7.5, VS-2003

 

 

 

WTL에서 메시지 처리는 MFC와 유사하게 메시지 매크로를 이용하는데, 조금 다른 점이 라면 메시지 매크로가 헤더파일에 있다는 점입니다. 위치만 변경된 것이므로 뭐 크게 다를건 없구요..

메시지 맵은 BEGIN_MSG_MAP(클래스이름) / END_MSG_MAP() 매크로를 사용하여 MFC와 유사하게 처리합니다.

 

WTL에서는 메시지 종류에 따라서 크게 3가지로 분류하여 처리를 합니다.

 

WM_COMMAND

WM_NOTIFY

WM_계열의 일반 메시지

 

그럼 하나하나씩 알아보도록 하겠습니다. 먼저 일반 메시지 처리부터 살펴보도록 하죠.. WM_ 계열의 일반 메시지는 MESSAGE_HANDLER() 매크로를 이용하여 처리를 합니다. 함수 원형은 다음과 같다.

 

BEGIN_MSG_MAP(CMyDlg)

           MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)

END_MSG_MAP()

 

LRESULT OnInitDialog(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);

 

 

일반적인 메시지 핸들러 함수는 위에처럼 4개의 인자를 가지고 있는데, 첫번째 uMsg는 처리하는 메시지에 해당하구요, wParam, lParam은 각 메시지의 전달되는 값 그대로 이고, 마지막으로 bHandled는 해당 핸들러에서 메시지를 처리했는지 않했는지를 표시해준다. 예를 들어서 해당 메시지를 처리하고나서 ATL의 기본 WindowProc() 함수에서 해당 메시지를 처리하게 하려면 bHandled 값을 FALSE로 설정하면 된다. 이게 무슨 말인가?? 쉽게 설명하면 다음의 예를 보자

 

// MFC 버전

void CMyDialog::OnMouseMove(UINT nFlags, CPoint point)

{

        ...     // 어떤처리

        __super::OnMouseMove(nFlags, point);

}

// WTL 버전

LRESULT OnMouseMove (UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)

{

        ...     // 어떤처리

        bHandled = FALSE;

}

 

bHandle FALSE로 해주면 WM_MOUSEMOVE 메시지를 CMyDialog에서 처리를 않했다고 표시하는 것이다. 그래서 WM_MOUSEMOVE 메시지가 WindowProc으로 전달이 되는 것이다. MFC 에서는 WM_MOUSEMOVE 이벤트를 처리하고 베이스 클래스의 OnMouseMove를 호출해주는 것과 비요해서, WTL에서는 베이스 클래스의 가상함수를 호출하는 역활을 bHandled 변수가 담당하게 되는 것이다.

그럼 좀더 자세하게 MESSAGE_HANDLER의 정의부를 살펴보자.

 

#define MESSAGE_HANDLER(msg, func) \

if(uMsg == msg) \

{ \

        bHandled = TRUE; \

        lResult = func(uMsg, wParam, lParam, bHandled); \

        if(bHandled) \

               return TRUE; \

}

 

역시나 정의된 것을 직접 보는 게 제일 편하다. ㅋㅋ 해당 함수를 호출하기 전에 bHandled true로 설정하고 함수를 호출하고 나서 bHandled를 체크하여 return TRUE를 해주고 있구나. 그렇다면 WTL에서는 LRESULT 값이 무시가 되는 건가? 그렇다 현재 7.5 버전에서는 무시를 하고 있다. (END_MSG_MAP 정의 부분을 보면 알 수 있다.)

 

그리고 정의부를 본 김에 BEGIN_MSG_MAP과 함께 전체를 풀어보면 아래와 같다.

 

// BEGIN_MSG_MAP() 부분

public: \

BOOL ProcessWindowMessage(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT& lResult, DWORD dwMsgMapID = 0) \

{ \

        BOOL bHandled = TRUE; \

        (hWnd); \

        (uMsg); \

        (wParam); \

        (lParam); \

        (lResult); \

        (bHandled); \

        switch(dwMsgMapID) \

        { \

        case 0:

 

// MESSAGE_HANDLER()부분

if(uMsg == msg) \

{ \

                bHandled = TRUE; \

                lResult = func(uMsg, wParam, lParam, bHandled); \

                if(bHandled) \

                        return TRUE; \

}

 

// END_MSG_MAP 부분

        break; \

        default: \

        ATLTRACE(ATL::atlTraceWindowing, 0, _T("Invalid message map ID (%i)\n"), dwMsgMapID); \

        ATLASSERT(FALSE); \

        break; \

        } \

return FALSE; \

}

 

이렇게 메시지 메크로를 풀어보니까 그냥 메시지에 따라서 if문으로 구성되어 있는 간단한 처리방식인데, 사용하기 편하게 매크로로 정의를 한 것 뿐이다. 요건 MFC도 마찬가지다. ^^

 

WTL에서는 MFC와 다르게 메시지를 unpack을 않해주고 있기 때문에, 직접 WPARAM, LPARAM에서 원하는 정보를 추출하여 사용하여야 한다. 이것이 조금 불편한 점이다. <atlcrack.h> 헤더파일을 추가해주면 MFC와 비슷하게 전달되는 파라미터를 각각의 메시지에 맞게 변환해준 값을 전달해 주는데, 이것도 조금 불편하다. 그 이유를 설명하면, 다음의 unpack을 사용하는 WTL의 메시지 매크로 모습을 보자.

 

BEGIN_MSG_MAP_EX(CMiniNewsWnd)

        MSG_WM_CREATE(OnCreate)

        MSG_WM_LBUTTONUP(OnLButtonUp)

        MSG_WM_MOUSEMOVE(OnMouseMove)

END_MSG_MAP()

 

LRESULT        OnCreate(LPCREATESTRUCT lpcs);

void           OnLButtonUp(UINT nID, CPoint pt);

void           OnMouseMove(UINT nFlag, CPoint pt);

 

이것만 보면 어랏! MFC와 똑같네~’ 라고 할 수 있는데, 맞다 똑같다. 우리가 쓰던인자 그대로 뽑아서 넘겨준다. 그런데 왜 불편하다는 거야? 라고 물어보면 대답은 다음과 같다. MFC에서는 해당 메시지마다 위자드가 자동으로 핸들러를 추가해주지만, WTL에서는 위자드를 통해서 핸들러를 추가하게 되면 기본적인 MESSAGE_HANDLER 형식으로 추가를 해주기 때문이다. MSG_WM_ 형식은 수동으로 추가를 해야 하는데, 누가 각각의 메시지의 파라미터를 다 외우고 다니나? 그래서 추가를 하려면, 해당 MSG_WM_XXX의 정의를 보고 추가해주어야 한다. 이때 또 난감한 것이 있으니 다음과 같은 경우이다.

 

 

#define MSG_WM_SETCURSOR(func) \

if (uMsg == WM_SETCURSOR) \

{ \

        SetMsgHandled(TRUE); \

        lResult = (LRESULT)func((HWND)wParam, (UINT)LOWORD(lParam), (UINT)HIWORD(lParam)); \

        if(IsMsgHandled()) \

               return TRUE; \

}

 

WM_SETCURSOR 핸들러를 추가하기 위해서 MSG_WM_SETCURSOR의 정의를 찾아본다. 하지만, 두번째, 세번째 인자는 lParam의 low/hi 워드를 표기 사는데 이것의 의미를 알기 위해서는 msdn을 참고해야 한다. , 각각 메시지 마다 파라미터의 의미를 대 외우지 않으면 msdn을 찾아야 하는 것은 필수이기 때문이다.

따라서 MESSAGE_HANDLER MSG_WM_XXX 중에서 맘에 드는 것을 선택적으로 사용하기 바란다. 참고로 필자는 두 개를 섞어서 사용한다.

 

그리고 MESSAGE_RANGE_HANDLER로 있다. 메시지의 범위를 처리하는 것인데, 사용자 정의 메시메시지 처리할 때 유용하다. 뭐 이건 MFC를 사용해 본 사람은 다 알고 있으리라 생각하고 넘어간다.

 

오늘은 여기서 짜르고 다음 시간에 나머지 부분에 대해서 알아보도록 하겠습니다.
읽어 주셔서 감사합니다~


PS: 이 글에대해서 링크 및 퍼가는 것을 허용합니다. 그러나 꼭 출처를 밝혀 주시기 바랍니다.



:

[강좌] WTL로 프로그래밍하기#3 – Mix-in class

개발 2007. 8. 14. 10:00

336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

WTL로 프로그래밍하기#3 – Mix-in class

 

저자 : hanburn

날짜 : 2007.08.14

환경 : WTL7.5, VS-2003

 

 

지난 시간에 위자드를 통해서 생성되는 코드들을 살펴 보았습니다. MainDlg의 코드를 살펴보다가 이번시간으로 내용을 넘겼는데, 그 이유는 먼저 살펴 볼 내용이 있어서 입니다. 그럼 시작도록 하겠습니다.

 

 

템플릿에 익숙하지 않은 분들이 계시다면 부지런히 공부를 하시기 바랍니다. ㅎㅎ 이번에는 template의 한가지 사용예제를 먼저 살펴보자. C++ 표준에서는 클래스의 상속리스트로 해당 클래스를 사용 할 수가 있다. 이게 무슨 말인가? 쉽게 아래처럼 사용이 가능하다는 것이다.

 

template <class T>

class B1

{

public:

        void SayHi()

        {

               T* pT = static_cast<T*>(this);

               pT->PrintClassName();

        }

 

        void PrintClassName()

        {

               cout<< "This is B1" << endl;

        }

};

 

class D1 : public B1<D1>

{

 

};

 

class D2 : public B1<D2>

{

public:

        void PrintClassName()

        {

               cout<< "This is D2" << endl;

        }

};

 

 

Class D1 : public B1<D1> 이렇게 템플릿클래스에 자신을 매개변수로 해서 상속을 받을 수가 있는 것이다. 이런 방법은 ATL, WTL에서 자주 사용되는 방법이므로 유의깊게 보기 바란다. 물론 이미 알고 있는사람들은 그냥 대충 넘어가거나 잘못된 내용이 있으면 태클을 걸어주길 바랍니다.^^

D1 SayHi()를 호출하면 D1 PrintClassName을 오버라이드하지 않았기 때문에 베이스인 B1 PrintClassName이 호출이 되고, D2 SayHi()를 호출하면 D2 PrintClassName이 오버라이드 되어서 D2 PrintClassName이 호출되는 것이다. 이건 바로 C++의 다형성을 구현하는 가상함수의 개념이 아닌가? 그런데 한가지 다른 점이 있다. 각각 실행될 때 어떻게 되는지 자세히 살펴보자.

 

먼저 D1의 경우에는 템플릿이 아래처럼 컴파일 시간에 확장될 것이다.


void B1<D1>::SayHi()

{

D1* pT = static_cast<D1*>(this);

 

    pT->PrintClassName();

}

 

그리고 D2의 경우에는 템플릿이 아래처럼 컴파일 시간에 확장될 것이다.


void B1<D2>::SayHi()

{

D2* pT = static_cast<D2*>(this);

 

    pT->PrintClassName();

}

 

중요한 점은 컴파일 시간에 어떤 함수가 불려질지 결정이 된다는 점이다. ? 가상함수는 실행시간에 다형성이 결정되는데, 컴파일 시점에 결정된다면 오히려 안좋은 것이 아닌가? 라고 의문을 가질 수 있는데, 반대로 컴파일 시점에 호출되는 함수가 결정된다는 것은 다음과 같은 이점이 있다.

1.     객체의 포인터를 필요로 하지 않는다.

2.     Vtbls 가 없어서 메모리가 절약되고 수행시간이 더 빠르다.

3.     Vtbl이 초기화되지 않아서 발생할 수 있는 문제점들이 없다. (널참조 같은)

4.     컴파일 시간에 문제점들이 해결될 수 있다.

 

위의 예제는 가상함수를 사용하지 않고 tempalate을 이용하여 다형성을 구현한것이다. 참 흥미로운 부분이다.

 

아래의 예제를 하나 더보도록 하자

 

template<class T>

class CSingleton

{

public:

       

        static T* getInstance(VOID)

        {

               if( TRUE == ::IsBadReadPtr(m_pInstance, sizeof(T)) )

                       m_pInstance = new T();

 

               return m_pInstance;

        }

 

        static VOID releaseInstance(VOID)

        {

               if( FALSE == ::IsBadReadPtr(m_pInstance, sizeof(T)) )

                       delete m_pInstance;

 

               m_pInstance = NULL;

        }

       

 

private:      

        static T* m_pInstance;

};

 

// Init static member

template<class T> T* CSingleton<T>::m_pInstance = NULL;

 

위의 클래스는 디자인패턴에서 Singleton을 구현한 template 클래스이다. 사용은 아래처럼 하면 간단하게 해당 클래스가 Singleton클래스로 동작을 하게 된다.

 

CMyClass : public Singleton<CMyClass>

{

Public:

}

 

이런 CSingeton 같은 template 클래스를 Mix-in class 라고 한다. Mix-in class는 해당 기능을 가지고 있고, 다른 클래스에 템플릿 상속을 통해서 쉽게 기능을 추가 할 수 있는 클래스 이다. WTL에서는 이런 방식으로 템플릿 클래스의 상속을 통해서 기능을 붙이는 방식으로 설계가 되어 있다. 오 놀라워라~

 

~ 그럼 다시 우리의 MainDlg.h를 살펴보면, 여러 가지 기능들을 상속으로 추가하는 모습들을 볼 수 있는데, 하나하나씩 살펴보도록 하자.

 

class CMainDlg :       public CDialogImpl<CMainDlg>,

public CUpdateUI<CMainDlg>,

                       public CMessageFilter,

public CIdleHandler

{

 

 

먼저 CDialogImpl 클래스다. 이것은 CDialog를 실제적으로 구현하고 있는 클래스로 아래와 같은 상속구조를 가지고 있다.

 

CMainDlg : CDialogImpl : CDialgImplBaseT : CWindowImplRoot : TBase, CMessageMap

 

Public을 빼고 그냥 베이스가 되는 클래스를 오른쪽으로 표시한 것이다. Dialog도 윈도우니까 기본적인 윈도우의 구현인 CWindowImpl에서 상속된 것들을 쭉 타고 내려온다. WTL은 인터페이스와 구현을 CWindow CWindowImlp 이라는 2개의 클래스로 구분을 하였다는 것 정도만 알고 넘어가도록 하자. 하부구조를 다 설명하려면 WTL Architecture라는 강좌를 통해서 하도록 하겠다.

 

그 다음으로는 CUpdateUI 라는 클래스를 살펴보자. 이 클래스는 메뉴 등의 UI의 상태를 관리해주는 역할을 하는 것으로, 내부적으로 UI요소들을 map으로 관리하면서, BEGIN_UPDATE_UI_MAP 매크로를 통해서 해당 map을 구성하게 된다. 그리고 UIAddMenuBar(), UIAddToolBar(), UIAddStatusBar() 등의 함수를 통해서 map을 설정하게 되고, UIEnable(), UISetCheck() 등의 함수를 통해서 UI를 관리하게된다. 이것은 나중에 SDI 형태의 프로그램을 볼 때 더 자세하게 다루도록 하겠다.

 

그 다음으로는 CMessageFilter 클래스이다. 이것은 클래스 이름에서 알수 있듯이 메시지를 필터링 할수 있는 인터페이스 클래스이다.

 

class CMessageFilter

{

public:

        virtual BOOL PreTranslateMessage(MSG* pMsg) = 0;

};

 

클래스를 상속 받으면 반드시 PreTranslateMessage 재정의 해주어야 한다. MFC Pretranslate 함수와 같은 역할을 한다. 메시지가 전달되기 전에 먼저 처리해주는 역확을 한다.

 

다음은 CIdleHandler 인데, 이것도 이름에서 바로 있듯이 OnIdle 핸들러를 제공하는 것이다.

 

class CIdleHandler

{

public:

        virtual BOOL OnIdle() = 0;

};

 

그런데 단순히 CMessageFilter CIdlHandler를 상속만 받으면 어떻게 PretranslateMessage OnIdle이 호출될까 라고 궁금하지 안은가? 그 원리는 OnInitialDialog 에 있는 코드에 있다.

 

// register object for message filtering and idle updates

CMessageLoop* pLoop = _Module.GetMessageLoop();

ATLASSERT(pLoop != NULL);

pLoop->AddMessageFilter(this);

pLoop->AddIdleHandler(this);

 

위의 코드가 메시지 루프에 각각을 추가해주기 때문에 메시지를 전달하는 과정에서 수를 부리는 것이다. 이것을 보면 바로 Pretranslate 에는 OnInitialDialog 다음에 오는 메시지만 Filtering을 할 수 있게 된다. WM_INITDIALOG 같은 메시지는 안잡히게 되는 것이다.


그럼 다음에는 메시지 맵에 대해서 알아보도록 하겠습니다. ^^



:

[강좌] WTL로 프로그래밍하기#2 – 위저드로 생성되는 코드

개발 2007. 8. 13. 15:24

336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

WTL로 프로그래밍하기#2 – 위저드로 생성되는 코드

 

저자 : hanburn

날짜 : 2007.08.13

환경 : WTL7.5, VS-2003

 

 

지난시간에는 WTL에 대해서 간략하게 알아보고 설치하고 환경 설정하는 것 까지 알아보았다. 이제 WTL로 간단한 프로젝트를 만들어 보자. VS를 열면 WTL application wizard가 보일 것이다. 이것을 선택하여 시작하도록 하자.

사용자 삽입 이미지


그런 다음에는 너무나도 익숙한 화면이다. SDI, MDI, Dialog base 등 초기의 프로그램 형태를 선택할 수 있는 창이 나온다. MFC wizard와 상당히 유사한 모습이다. 아래 그림처럼 Dialog Base를 선택하고 Generate .CPP Files를 체크한 뒤에 Finish를 선택하면 된다.


사용자 삽입 이미지

이렇게 하면 위저드가 기본 골격 코드를 생성하는데, 생성된 코드를 한번 살펴 보면서 시작하자.

먼저 프로젝트이름.cpp 파일을 살펴보면 아래와 같은 코드가 보일 것이다.

 

int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/, LPTSTR lpstrCmdLine, int nCmdShow)

{

        HRESULT hRes = ::CoInitialize(NULL);

        ...

        hRes = _Module.Init(NULL, hInstance);

        ATLASSERT(SUCCEEDED(hRes));

 

        int nRet = Run(lpstrCmdLine, nCmdShow);

 

        _Module.Term();

        ::CoUninitialize();

 

        return nRet;

}


우리가 잘 알고있는 WinMain 함수이다. 여기서 살펴볼 것은 _Module 이라 전역객체 인데, MFC theApp 와 비슷한 놈이다. MFC에서는 CWinApp를 상속받아서 각 프로그램마다 한 개씩 있던 것을 보았을 것이다. WTL에서는
CAppModule클래스의 전역객체인데, 쉽게 생각하면 전체 프로그램의 관리자 역할로 보면 것이다. _Module.Init()/Term() 사이에 있는 Run() 이라는 함수가 윈도우 시스템의 메시지를 가지고 와서 배분을 해주는(DispatchMessage) 역할을 하는 부분이다. 전역함수인 Run 함수에 대해서 조금 자세히 알아보자

 

int Run(LPTSTR /*lpstrCmdLine*/ = NULL, int nCmdShow = SW_SHOWDEFAULT)

{

        CMessageLoop theLoop;

        _Module.AddMessageLoop(&theLoop);

 

        CMainDlg dlgMain;

        if(dlgMain.Create(NULL) == NULL)

        {

               ATLTRACE(_T("Main dialog creation failed!\n"));

               return 0;

        }

        dlgMain.ShowWindow(nCmdShow);

 

        int nRet = theLoop.Run();

        _Module.RemoveMessageLoop();

        return nRet;

}

 

웬지 MFC에서 InitInstance 부분과 유사한 느낌을 받을 것이다. 메인 다이얼로그를 생성하고 메시지루프를 처리하고..  WTL MFC보다 역할의 구분을 클래스로 잘 구분을 해놓은 느낌이다. Design Pattern Explained에서 말하는 역할 중심의 설계랄까? ^^

Run함수에서 CMessageLoop 객체를 만들고 _Module에 추가를 해주어서 메시지 펌핑을 받도록 처리하고 있다. CMessageLoop run함수에 우리가 익숙하게 보는 다음과 같은 코드가 들어있다.

 

bRet = ::GetMessage(&m_msg, NULL, 0, 0);

if(!PreTranslateMessage(&m_msg))

{

::TranslateMessage(&m_msg);

::DispatchMessage(&m_msg);

}

 

이렇게 함으로써 Dialog의 기본 골격 코드를 살펴 보았다. 그리고 WTL의 위자드에서 Dialog를 선택하고 DoModal을 체크하게 되면 더 간단하게 아래처럼 Dialog DoModal을 호출해 준다.

 

int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/, LPTSTR lpstrCmdLine, int nCmdShow)

{

        HRESULT hRes = ::CoInitialize(NULL);

        ...

        hRes = _Module.Init(NULL, hInstance);

 

        int nRet = 0;

        {

               CMainDlg dlgMain;

               nRet = dlgMain.DoModal();

        }

 

        _Module.Term();

        ::CoUninitialize();

 

        return nRet;

}

 

보통 WTL으로 프로그래밍을 할 때, 기본적으로 생성되는 Dialog를 사용하지 않을 때는 첫번째 처럼 기본 틀을 생성하고, Dialog대신 내가 만든 윈도우를 Create 하고 Show를 해주게 되어서, 보통은 Domodal을 체크 않하고 많이 사용한다. (이것은 개인적인 취향이다. )

 

다음으로는 MainDlg.h 를 살펴보자. 실제적인 Dialog에 대핸 메시지 처리 및 기타 주요한 부분을 담당하는 부분이다.

 

class CMainDlg :       public CDialogImpl<CMainDlg>,

public CUpdateUI<CMainDlg>,

                       public CMessageFilter,

public CIdleHandler

{

public:

        enum { IDD = IDD_MAINDLG };

 

        virtual BOOL PreTranslateMessage(MSG* pMsg);

        virtual BOOL OnIdle();

 

        BEGIN_UPDATE_UI_MAP(CMainDlg)

        END_UPDATE_UI_MAP()

 

        BEGIN_MSG_MAP(CMainDlg)

               MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)

               COMMAND_ID_HANDLER(ID_APP_ABOUT, OnAppAbout)

               COMMAND_ID_HANDLER(IDOK, OnOK)

               COMMAND_ID_HANDLER(IDCANCEL, OnCancel)

        END_MSG_MAP()

 

        LRESULT OnInitDialog(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/);

        LRESULT OnAppAbout(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/);

        LRESULT OnOK(WORD /*wNotifyCode*/, WORD wID, HWND /*hWndCtl*/, BOOL& /*bHandled*/);

        LRESULT OnCancel(WORD /*wNotifyCode*/, WORD wID, HWND /*hWndCtl*/, BOOL& /*bHandled*/);

 

        void CloseDialog(int nVal);

};

 

여기서 조금 낯설 수가 있다. 평소에 잘 사용 안하던 다중상속을 사용한 것이다. 전형적인 ATL 스타일인 것이다. 여러가지 template class 로부터 상속을 받고 있는데, 이것들은 다음 시간에 알아보도록 하겠습니다.




:

[강좌] WTL로 프로그래밍하기#1 - WTL소개

개발 2007. 8. 6. 21:43

336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

WTL로 프로그래밍하기


저자 : hanburn
날짜 : 2007.08.06
환경 : WTL7.5, VS-2003
 

MFC로 프로그래밍을 해오다가 WTL을 접해보게 되었다. 예전부터 듣기는 했었는데, 직접 사용해 보니 가볍고 좋은 라이브러리인 것 같다. WTL의 장점이 뭐가 있을까? 아직 많이 써보지는 않아서 뭐라 딱히 말 할 수 없지만 일단 느껴본 것은 실행파일이 작다는 것이다. 간단한 Dialog 기반의 프로그램을 만들어서 컴파일하면 32KB 밖에 안된다. 물론 MFC를 사용 안하고 Win32로만 만들면 더 작아지겠지만, 프로그램 제작에 들어가는 편리함에 비한다면 충분히 감수할 만한 것 같다.

WTL Window Template Library으로 사용을 원만하게 하려면 template에 대해서 충분히 익숙해 저야 한다. 아직 template에 대해서 잘 모르더라도 너무 걱정하기 마시길.. 차차 익숙해 지겠지요..

 

WTL에 대한 ms 싸이트의 간단한 설명을 보면 다음과 같습니다.

 

Windows Template Library (WTL) is a library for developing Windows® applications and UI components. It extends ATL (Active Template Library) and provides a set of classes for controls, dialogs, frame windows, GDI objects, and more. This version provides support for Windows Vista and Visual Studio 2005, and also support for the Windows CE platform.

 

 

윈도우어플리케이션과 UI 컴포넌트를 개발하기 위한 라이브러리로, ATL을 확장하고 컨트롤, 다이얼로그, 프레임 윈도우 GDI 객체 등등을 제공한다고 하는 군요.. 한마디로 UI를 처리할 때 사용하는 라이브러리라는 것입니다.

 

사용하려면 일단 설치를 해야 되는데, 아래의 URL에서 다운받을 수 있다.

 

WTL 7.5 : http://www.microsoft.com/downloads/details.aspx?familyid=48CB01D7-112E-46C2-BB6E-5BB2FE20E626&displaylang=en

WTL 8.0 : http://www.microsoft.com/downloads/details.aspx?familyid=e5ba5ba4-6e6b-462a-b24c-61115e846f0c&displaylang=en&tm

 

(현재 최신 버전은 8.0이 나왔는데, 지금의 강좌에서는 7.5 버전을 기준으로 하겠습니다. )

 

설치하는 과정은 다운을 받아서 압축을 풀면, AppWiz및에 VS버전에 맞는 자바스크립트 셋업 파일을 클릭해주면 된다. 그리고 아래처럼 include 폴더를 추가해주면 끝이다.

 

사용자 삽입 이미지


그리고 앞으로 시작할 강좌는 www.codeproject.com/wtl/ 의 wtl4mfc 강좌를 참고로 하여 진행이 되는 강좌입니다. 사용하는 버전은 WTL 7.5를 사용하고 VS-2003에서 코딩이 이루어 집니다.


: