'개발'에 해당되는 글 73건

  1. 2007.08.17 [강좌] WTL로 프로그래밍하기#5 – Message Map(2) 1
  2. 2007.08.17 [강좌] WTL로 프로그래밍하기#4 – Message Map(1)
  3. 2007.08.14 [강좌] WTL로 프로그래밍하기#3 – Mix-in class
  4. 2007.08.13 [강좌] WTL로 프로그래밍하기#2 – 위저드로 생성되는 코드
  5. 2007.08.12 트레이 영역 구하는 함수 - GetTrayWndRect
  6. 2007.08.11 윈도우 탐색기(shell) 재시작시 Tray에 등록하기 1
  7. 2007.08.10 [TinyXML] 사용해보기 - xml로 만들기 3
  8. 2007.08.10 [TinyXML] 사용해보기 - 읽어오기 1
  9. 2007.08.10 [TinyXML] 소개 - 작고 가벼운 XML 라이브러리
  10. 2007.08.09 제목표시줄 숨기기

[강좌] 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 로부터 상속을 받고 있는데, 이것들은 다음 시간에 알아보도록 하겠습니다.




:

트레이 영역 구하는 함수 - GetTrayWndRect

개발 2007. 8. 12. 00:11

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

윈도우의 트레이 영역을 구하는 함수이다.
윈도우가 트레이로 최소화 될때 DrawAnimatedRects(m_hWnd, IDANI_CAPTION, rcFrom, rcTo); 함수를 이용하면 최소화 되는 것처럼 트레이 영역으로 이동하는 것을 볼 수 있다.



void GetTrayWndRect(RECT *pRect)
{
    HWND hwndTaskBar=::FindWindow(_T("Shell_TrayWnd"), NULL);
    if (hwndTaskBar){
        HWND hwndTray=::FindWindowEx(hwndTaskBar, NULL, _T("TrayNotifyWnd"), NULL);
        if (hwndTray)
            ::GetWindowRect(hwndTray, pRect);
        else
       {    //tray부분을 못찾으면 task바의 구석탱이를 그렇다고 믿게 하자.
            ::GetWindowRect(hwndTaskBar, pRect);
            pRect->left=pRect->right-20;
            pRect->top=pRect->bottom-20;
        }
    }
    else
    {   //task바를 못찾으면 그냥 화면 하단부
        int nWidth = GetSystemMetrics(SM_CXSCREEN);
        int nHeight = GetSystemMetrics(SM_CYSCREEN);
        SetRect(pRect, nWidth-40, nHeight-20, nWidth, nHeight);
    }
}


:

윈도우 탐색기(shell) 재시작시 Tray에 등록하기

개발 2007. 8. 11. 23:58

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

윈도우 탐색기가 가끔가다가 죽는 경우가 있는데, 보통은 자동으로 다시 실행이 된다.
자동으로 실행이 않되면 작업관리자를 실행시켜서 새작업 실행으로 explorer.exe 를 실행시키면 된다.
그런데 윈도우 탐색기가 다시 실행되면 Tray 영역에 있던 아이콘이 사라지게 된다. 그러나 MSN 같은 몇몇 프로그램은 Trya 에 아이콘이 있는데, 그것을 다음과 같이 하면 된다.


// 메시지 등록
UINT g_uShellRestart;
g_uShellRestart = RegisterWindowsMessage(__Text(“TaskbarCreated”));

// Message Map 에서
ON_REGISTERED_MESSAGE(g_uShellRestart, OnTrayShow)

// 메시지 핸들러
LRESULT CMyDlg::OnTrayShow(WPARAM wParam, LPARAM lParam)
{
    // TrayIcon을 다시 보여줍니다. ShowTray는 Tray를 보여주는 함수입니다.
    m_Tray.ShowTray();
}


위에서 보면 알겠지만, explorer가 다시 실행될때 TaskbarCreated 라는 메세지가 발생이 된다.
이것을 잡아서 처리하면 되는 것이다.


:

[TinyXML] 사용해보기 - xml로 만들기

개발 2007. 8. 10. 17:02

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

TimyXML 사용해보기 - xml로 만들기


저자 : hanburn
날짜 : 2007.08.10
환경 : VS-2003, TinyXML 2.5.3

앞에서는 xml 파일이나 xml문자열에서 파싱을 해서 원하는 정보를 뽑아오는 것을 해보았다.
그러면 이제는 반대로 설정이나 원하는 정보를 xml 형식으로 만들어 보자.
앞에서 사용한 xml을 만들어 보도록 하자

<?xml version="1.0" ?>
<MyApp>
    <!-- Settings for MyApp -->
    <Messages>
        <Welcome>Welcome to MyApp</Welcome>
        <Farewell>Thank you for using MyApp</Farewell>
    </Messages>
    <Windows>
        <Window name="MainFrame" x="5" y="15" w="400" h="250" />
    </Windows>
    <Connection ip="192.168.0.1" timeout="123.456000" />
</MyApp>


일단 xml 의 형식선언부터 시작해 보자..

TiXmlDocument doc; 
TiXmlElement* msg;
TiXmlDeclaration* decl = new TiXmlDeclaration( "1.0", "", "" ); 
doc.LinkEndChild( decl );



이렇게 하면 <?xml version="1.0" ?> 이 생성된다.
다음으로는, 서브 노드를 추가해보자.

TiXmlElement * root = new TiXmlElement( "MyApp" ); 
doc.LinkEndChild( root ); 


간단하다. 그리고 new로 생성한 TiXmlElement를 해제할 필요가 없다. 내부에서 자동으로 삭제를 해주기 때문이다. (편리하군~ ^^)
그런 다음 주석문장을 추가해 보자.

TiXmlComment * comment = new TiXmlComment();
comment->SetValue(" Settings for MyApp " ); 
root->LinkEndChild( comment ); 


주석은 TiXmlComment 클래스를 사용하면 된다. 편리하군..
다음으로는  Message 서브노드와 하위 노드및 데이터를 추가해보자

TiXmlElement * msgs = new TiXmlElement( "Messages" ); 
root->LinkEndChild( msgs ); 

msg = new TiXmlElement( "Welcome" ); 
msg->LinkEndChild( new TiXmlText( "Welcome to MyApp" )); 
msgs->LinkEndChild( msg ); 

msg = new TiXmlElement( "Farewell" ); 
msg->LinkEndChild( new TiXmlText( "Thank you for using MyApp" )); 
msgs->LinkEndChild( msg ); 



역시 쉽니다. 다음에는 노드를 추가하고 Attribute를 설정해보자
레벨을 맞추기 위해서 root의 하위로 추가 한것을 주의 깊게 봐야 한다.

TiXmlElement * windows = new TiXmlElement( "Windows" ); 
root->LinkEndChild( windows ); 

TiXmlElement * window;
window = new TiXmlElement( "Window" ); 
windows->LinkEndChild( window ); 
window->SetAttribute("name", "MainFrame");
window->SetAttribute("x", 5);
window->SetAttribute("y", 15);
window->SetAttribute("w", 400);
window->SetAttribute("h", 250);



다음은 마지막으로 Double 값 (소수점 값) 을 설정하는 예를 보자

TiXmlElement * cxn = new TiXmlElement( "Connection" ); 
root->LinkEndChild( cxn ); 
cxn->SetAttribute("ip", "192.168.0.1");
cxn->SetDoubleAttribute("timeout", 123.456); // floating point attrib



이렇게 하면 우리가 원하는 xml이 doc에 만들어 졌다. 이것을 파일로 저장할 수도 있고 xml 문자열로 만들수도 있다. 방법은 아래를..

// 파일로 저장
doc.SaveFile("text.xml");

//문자열로..
TiXmlPrinter printer;
printer.SetStreamPrinting();
Doc.Accept( &printer );
char* pAA = printer.CStr();                    // char* 를 반환한다.
std::string str = printer.Str();                  // std::string으로 반환한다.


이로써 간단하게 TinyXML의 사용법을 마칠까 한다.



:

[TinyXML] 사용해보기 - 읽어오기

개발 2007. 8. 10. 14:57

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


TimyXML 사용해보기 - xml에서 읽어오기


저자 : hanburn
날짜 : 2007.08.10
환경 : VS-2003, TinyXML 2.5.3


XML에 대해서는 이미 알고 있다고 가정합니다.
(모르면 공부하고 다시 오세요 ^^)

일단 XML을 파일에서 읽을수도 있고 문자열로 읽을 수도 있다.
아래 예에서 사용할 xml 파일은 다음과 같다.

<?xml version="1.0" ?>
<MyApp>
    <!-- Settings for MyApp -->
    <Messages>
        <Welcome>Welcome to MyApp</Welcome>
        <Farewell>Thank you for using MyApp</Farewell>
    </Messages>
    <Windows>
        <Window name="MainFrame" x="5" y="15" w="400" h="250" />
    </Windows>
    <Connection ip="192.168.0.1" timeout="123.456000" />
</MyApp>


먼저 파일에서 읽을때는,

TiXmlDocument document;
document.LoadFile(_File_Name_);


문자열로 읽을때는,

TiXmlDocument document;
document.Parse(szXML);


너무 쉽다. 그런 다음에는 원하는 값을 찾아오면 된다.
먼저 노드와 엘리먼트를 가지고 오는 방법을 알아보자.
( 노드는 자식을 가지고 있는 것이고 엘리먼트는 마지막에 있는 놈이다. )

TiXmlElement* pRoot = document.FirstChildElement("MyApp");
if( NULL == pRoot ) return FALSE;   // 해당 Element가 없으면 널이므로.. 체크해주는게 좋다.


그럼, <Welcome> 태그로 가서 데이터를 가지고 와보자.

pElement = pRoot->FirstChildElement("Message");
pElement = pElement->FirstChildElement("Welcome");
char* pAA = pElement->Value();                 // pAA 은  "Welcome" 이다.
pAA = pElement->GetText();                       // pAA 은  "Welcome to MyApp" 이다.


이번에는 다음에 windows 태그로 가서 name 속성을 읽어 와보자.

pElement = pRoot->FirstChildElement("Windows");
char* pAA = pElement->Attribute("name");   // pAA에 "MainFrame"이 들어온다.
int x;
pElement->Attribute("x", &x);                    // x 에 숫자 5가 들어온다.


그다음에는 루프를 돌면서 child 노드를 순환하는 방법이다.


pNode = pRoot->FirstChild("sub_node");
 for( pNode ; pNode ; pNode = pNode->NextSibling())
 {
  pAA = pNode->Value();  

  pElement = pNode->FirstChildElement("item");  
  pAA = pElement->GetText();
}


 




 



:

[TinyXML] 소개 - 작고 가벼운 XML 라이브러리

개발 2007. 8. 10. 14:22

336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.
TinyXML은 간단하고 작은 C++ XML 파서 입니다.
사용하기가 편리하고 다른 프로그램으로 이식하기도 수월한 편입니다.


온라인 도움말 : http://www.grinninglizard.com/tinyxmldocs/index.html
소스를 받는곳 : http://sourceforge.net/projects/tinyxml/


설치하려면.. 일단 소스 받는곳에서 소스를 환경에 맞게 받는다.
윈도우 버전으로 받으면 VC++6.0 작업화일과 VS.NET 용 솔루션 파일이 포함되어 있다.


사용하는 방법은 2가지로 나눌수 있다.  ( 그리고 각각 STL을 사용하는 버전과 아닌 버전으로 되어 있다. )

1. 해당 소스를 컴파일 하여 생성되는 Tinyxml.lib와 헤더파일을 이용하는 방법
   - 이방법은 제공되는 솔루션 파일( tinyxml.sln) 을 열어서 빌드만 하면 된다.
   - 사용하는 프로젝트가 멀티 쓰레드이면 런타임 라이브러리를 멀티 쓰레드로 변경해야 한다.
      (기본 설정은 싱글 쓰레드로 되어 있다.
   - tinyXml.lib 는 스태틱 라이브러리로 컴파일시에 exe에 포함되므로 따로 배포를 않해도 된다.

2. 소스를 직접 포함시켜서 사용하는 방법.
   - 필요한 파일을 프로젝트에 파일 추가로 등록한다.
      (tinystr.h(cpp), tinyxml.h(cpp), tinyxmlerror.cpp, tinyxmlparse.cpp 총 6개다 )
   - 컴파일을 하려고 하면 precompile header 관련하여 에러가 난다.
   - 프로젝트 설정에서 cpp의 (3개 파일) precompile header 사용을 빼버린면 된다.
  
사용자 삽입 이미지

어떻게 보면 거의 같은 방법이다. ㅎㅎ
개인적으로 2번째 방법을 선호한다. 개발중에 cpp 소스를 가끔 보게 되므로..
그럼 다음 글에서는 간단한 사용법들을 알아보자. 


:

제목표시줄 숨기기

개발 2007. 8. 9. 10:00

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

간단한 코드이지만 가끔 생각이 안날때가 있다.. ㅋㅋ

원리는 간단하다. 윈도우의 확장스타일(WS_EX_) 을 얻어와서
속성만 변경해 주면 된다.

아래는 예제 코드이다.

HideApplicationTitleBar()
{
 DWORD dwStyle = GetWindowLong(m_hWnd, GWL_EXSTYLE);
 dwStyle &= ~WS_EX_APPWINDOW;
 dwStyle |= WS_EX_TOOLWINDOW;
 SetWindowLong(m_hWnd, GWL_EXSTYLE, dwStyle);
}


 



: