[MFC] 사용자 정의 메시지 처리기 사용하기

Posted by 빵빵빵
2016/07/29 17:31 전산(컴퓨터)/PC-Windows



펌... 윈도우 메시지 처리에 대해 아주 정확하고 자세한 설명이 되어 있어서.... 퍼놓습니다. 절친 설명, 찰진 설명


출처 : http://six605.tistory.com/222


사용자가 정의한 메시지를 수신할 수 있는 윈도우는 크게 2가지가 있다.

1.     CWnd 의 파생 클래스

2.     CWinThread의 파생 클래스 : 사용자 인터페이스 클래스(User Interface Class)

 

 

 

 

, 윈도우를 출력하는 클래스가 사용자 정의 메시지를 받을 수 있다.

MFC의 계층 구조도 모르고 Document 에서 사용자 정의 메시지 처리기를 만들면 아마 다음과

같은 에러 메시지를 볼 수 있을 것이다.

 

LRESULT (__thiscall CWnd::* )(WPARAM,LPARAM)'()로 변환할 수 없습니다

 

인터넷을 뒤지면 아마 사용자 정의 메시지 처리기의 리턴값을 LRESULT 로 바꾸면 해결 된다고

하는 글이 가장 많은데  아마 그렇게 변경해 주어도 안되면 CWnd CWinThread의 파생 클래스

가 아닌 곳에서 구현하려 해서 에러가 나는 경우일 것이다.

 

CMainFrame CView의 파생 클래스 에서 사용자 정의 메시지 처리기를 만들어 주자 

 

위에서 언급한 사용자 정의 메시지 처리기 함수 리턴값에 대한 에러는

VC6.0 에서 .Net 이상의 컴파일러 사용시 나타나는 문제이다.

.Net 이상부터 MFC 에서는 메시지 처리기의 반환 형식 및 매개변수 형식을 보다 엄격하게

검사한다. 이로써 안전하지 않은 메시지 처리기를 오류 메시지로 플래그를 지정하여 발생할 수

있는 문제를 개발자에게 알린다.

MFC 에서 ON_MESSAGE, ON_REGISTERED_MESSAGE, ON_THREAD_MESSAGE,

ON_REGISTERED_THREAD_MESSAGE 에 대해 정적 캐시팅을 사용한다.

 

VC6.0 에서 ON_MESSAGE 또는 ON_REGISTERED_MESSAGE 에 대해 메시지 처리기 함수가

void 값을 리턴하였을 경우 .Net 이상에서는 컴파일 에러를 일으킨다.

, 메시지 처리기는 LRESULT 를 리턴해야 한다.

 

 

 

#  CWnd 파생 클래스 에서 사용자 정의 메시지 처리기 사용

 

1. 사용자 정의 메시지 정의

    const UINT UM_READ_RESULT = WM_USER + 1; 

 

2. 메시지 맵에 메시지 함수 Overriding

   - 구현파일 (*.cpp)

 

BEGIN_MESSAGE_MAP(CReadProgressThread, CWinThread)

    ON_MESSAGE(UM_PROGRESS_SET, OnSetProgress)

END_MESSAGE_MAP()

LRESULT CProgressThread::OnSetProgress(WPARAM _wParam, LPARAM _lParam)

{

       ...

}

 

      

    UM_PROGRESS_SET 사용자 정의 메시지를  OnSetProgress 함수와 연결

    수신 프로그램에 메시지가  도착 하므로 해당하는 메시지 핸들러가 수행 되도록

    메시지 핸들러를 오버라이딩

      

     ※ Message Map

        - 메시지와 메시지 핸들러(함수) 를 연결하는 역할

 

3. 사용자 정의 메시지 함수 작성

   - 헤더파일 (*.h)        

protected:

    afx_msg LRESULT OnSetProgress(WPARAM _wParam, LPARAM _lParam);

    DECLARE_MESSAGE_MAP()

 

 

 

# CWinThread 파생 클래스 에서 사용자 정의 메시지 처리기 사용

 

- 사용자 인터페이스 스레드 즉, CWinThread의 파생클래스가 사용자 정의 메시지를 수신 하는

    경우는 Message Map을 연결하는 부분과 메시지 핸들러 원형만 다르다.

 

1. 사용자 정의 메시지 정의

    const UINT UM_READ_RESULT = WM_USER + 1; 

 

2. 메시지 맵에 메시지 함수 Overriding

   - 구현파일 (*.cpp)

 

BEGIN_MESSAGE_MAP(CReadProgressThread, CWinThread)

    ON_THREAD_MESSAGE(UM_PROGRESS_SET, OnSetProgress)

END_MESSAGE_MAP()

 

void CProgressThread::OnSetProgress(WPARAM _wParam, LPARAM _lParam)

{

       ...

}   

 

 

3. 사용자 정의 메시지 함수 작성

   - 헤더파일 (*.h)         

protected:

    afx_msg void OnSetProgress(WPARAM _wParam, LPARAM _lParam);

    DECLARE_MESSAGE_MAP()

 

2016/07/29 17:31 2016/07/29 17:31

[MFC] Notification Message(통지 메시지, WM_NOTIFY) 에 대해서

Posted by 빵빵빵
2016/07/29 17:28 전산(컴퓨터)/PC-Windows



펌... 윈도우 메시지 처리에 대해 아주 정확하고 자세한 설명이 되어 있어서.... 퍼놓습니다. 절친 설명, 찰진 설명

출처 : http://anster.tistory.com/11



Notification Message




1. Background Knowledge


윈도우즈 프로그래밍시 프로그래머가 사용할 수 있는 컨트롤들은 여러가지가 있다. Static Box 부터 시작해서 List Box, List View, Button, Edit Control, Address Control 등등. 하지만 이러한 컨트롤들이 모두 처음부터 윈도우에 존재했던것은 아니다.

< 기본 컨트롤 >

윈도우 95 이전에 존재했던 컨트롤들을 기본(표준) 컨트롤이라 부른다. 기본 컨트롤은 16비트 윈도우의 경우 USER.EXE 에, 32비트 에서는 윈도우 XP 이전까지 USER32.DLL 에 의해서 구현되었으나 XP 이후 부터는 공통 컨트롤과 함께 COMCTL32.DLL 에 의해서 구현된다.

- Static Text
- Button
- Edit Control
- List Box
- Combo Box

< 공통 컨트롤 >

윈도우 95 이후부터 생긴 컨트롤들을 공통 컨트롤이라 부른다. 기본 컨트롤에 비해서 더 화려한 외관과 강력한 기능들을 제공한다. 16비트 윈도우에서는 COMMCTRL.DLL 에 의해서 구현되었고, 32비트에서는 COMCTL32.DLL 에 의해서 구현된다.

- Progress Bar
- List View Control
- Spin Control
- Command Bar Control
- Address Control
- 이외의 기본컨트롤을 제외한 모든 컨트롤




2. Notification Messge 란 무엇인가?

Notification Message 를 한글로 번역하면 통지 메시지 다. 무엇을 통지 해준다는 것일까? 부모 윈도우에게 자신의 상태가 변하였음을 통지해주는 것이다.

예를들어 다이얼로그가 있고, 그 안에 버튼이 존재한다고 하자. 사용자가 버튼을 눌렀다면 버튼의 눌려진 모양을 다시 그리도록 부모 윈도우에게 알려줘야 한다. 이때 자식 윈도우(버튼) 으로부터 부모 윈도우(다이얼로그) 에게 보내지는 메시지를 Notification Message 라 부른다.

우리가 프레임윈도우에 있는 메뉴를 선택했을때 발생하는 메시지인 WM_COMMAND 도 대표적인 Notification Message 다. 이 메시지가 발생했을때 전달되는 파라미터인 WPARAM 과 LPARAM 의 내용은 아래와 같다.

MESSAGE ID  WPARAM(32 Bit)
 LPARAM(32 Bit)
 WM_COMMAND BN_CLICKED (High) Control ID(Low) Control HWND


WPARAM 의 상위 16비트에는 BN_CLICKED 와 같은 Notification Code 가, 하위 16비트에는 어떤 컨트롤에서 발생했는지 IDC_BUTTON1 과 같은 컨트롤의 ID 값이 들어간다.

LPARAM 의 32비트 값은 메시지가 발생한 컨트롤의 핸들이다. 이를테면 버튼이 눌렸을때 눌려진 버튼의 HWND 값이다.




2. 왜 WM_NOTIFY 가 생겨나게 되었는가?

메시지를 받고 보낼때 사용하는 파라미터는 WPARAM, LPARAM 두가지이므로 위의 WM_COMMAND 통지 메시지에서는 더이상의 추가적인 정보를 부모 윈도우에게 보낼 수 없다. 예를들면, 버튼이 눌려졌을때 커서의 위치가 어느지점이었는지 Point 구조체로 만들어 보내고 싶다고 해도 파라미터를 모두 사용했기에 보낼 수 없다.

이러한 이유에서 윈도우 3.x 에서는 추가적인 통지메시지들을 만들었다. 그 대표적인 예가 아래의 메시지들이다.

- WM_CTLCOLOR
- WM_VSCROLL
- WM_HSCROLL
- WM_DRAWITEM
- WM_MEASUREITEM
- WM_COMPAREITEM
- WM_DELETEITEM
- WM_CHARTOITEM
- WM_VKEYTOITEM

윈도우 버전이 올라감에 따라 더 강력한 기능을 가진 공통 컨트롤(COMCTL32.DLL)들이 추가되었고 이제 전혀 다른종류의 추가적인 데이터를 포함하는 통지 메시지가 필요해 졌다. 그렇다면 또 다시 WM_*과 같은 또다른 통지 메시지 만들어야 하는가? 그렇다면 수많은 컨트롤들이 포함되었을때 통지 메시지는 얼마나 많아지겠는가?

이러한 문제를 해결하기 위해 Win32 API 디자이너는 WM_*과 같은 수 많은 메시지를 추가하는 방법 대신에 WM_NOTIFY 단 하나의 메시지만 추가하는 방법을 선택했다. WM_NOTIFY 는 파라미터를 어떻게 사용하길래 추가적인 데이터를 메시지와 같이 보낼 수 있을까? 아래 그림을 확인 해 보자.

MESSAGE ID  WPARAM(32 Bit)
 LPARAM(32 Bit)
 WM_NOTIFY Control ID NMHDR *


NMHDR 구조체를 살펴보자.

typedef struct tagNMHDR
{
    HWND     hwndFrom; // HWND of the Control
    UINT_PTR idFrom; // Control ID
    UINT     code; // Notification Code
} NMHDR;
typedef NMHDR FAR * LPNMHDR;
 

WM_COMMAND 메시지의 LPARAM 에서 사용헀던 HWND 와 WPARAM 에서 사용헀던 Notification Code, Control ID 값이 있다. LPARAM 인자가 NMHDR * 이므로 윈도우는 처음 3개의 인자만 NMHDR 구조체와 같게 하거나, 아니면 NMHDR 구조체를 포함하는 구조체를 만들어 추가적인 데이터를 보내곤 한다.

typedef struct tagPointNMHDR
{
    HWND     hwndFrom;
    UINT_PTR idFrom;
    UINT     code;    
    Point    point;
} PointNMHDR;

typedef struct tagPointNMHDR
{
    NMHDR    nmHdr;
    Point    point;
} PointNMHDR;


NMHDR 을 포함하는 구조체를 만들어 메시지 핸들러에서 인자로 사용함으로써 추가적인 데이터를 보낼 수 있다.




3. 어떻게 WM_NOTIFY 메시지를 사용하는가?

WM_NOTIFY 메시지는 메시지 맵에 ON_NOTIFY 혹은 ON_NOTIFY_RANGE 매크로를 등록함으로써 사용할 수 있다. ON_NOTIFY 매크로의 형식은 다음과 같다.

                                   ON_NOTIFY ( wNotifyCode, ControlID, memberFxn )



ON_NOTIFY 매크로에서 사용하는 WM_NOTIFY 메시지의 핸들러는 아래와 같다.


                   afx msg void memberFxn ( NMHDR * pNotifyStruct, LRESULT * result )



만약, 다수의 컨트롤로 부터 오는 동일한 코드의 WM_NOTIFY 메시지를 하나의 핸들러에서 다루고 싶다면 ON_NOTIFY_RANGE 매크로를 사용하면 된다.

             ON_NOTIFY_RANGE ( wNotifyCode, ControlIDFrom, ControlIDTo, memberFxn )


ON_NOTIFY_RAGNE 매크로는 Control ID From 부터 Control ID To 까지의 모든 윈도우로 부터 온 wNotifyCode 의 WM_NOTIFY 메시지를 memberFxn 핸들러에서 처리합니다. 이때 주의할점은 원하는 윈도우들의 컨트롤 ID가 순차적이어야 한다는 것.




4. 컨트롤 별 통지 메시지 처리법

컨트롤의 Notification Message 를 처리하는 방법은 컨트롤 마다 다르다. 기본 컨트롤의 경우 WM_COMMAND 와 같은 Notification Message 가 발생하고, 공통 컨트롤의 경우 WM_NOTIFY Notification Message 발생한다. 이러한 메시지를 처리하는 방법은 두가지가 있다.


< 가상 함수에서 Notification Message 처리 >

Notification Message 를 처리하는 가상함수는 어떠한 핸들러보다 먼저 호출된다.


    < 기본 컨트롤 >


기본 컨트롤의 경우 CWnd 의 OnCommand 함수에서 통지 메시지인 WM_COMMAND 메시지를 처리하면 된다. 하지만 모든 WM_COMMAND 메시지가 이곳으로 오기때문에 특정 컨트롤만을 위한 작업을 이곳에 코딩하는 것은 비 효율적이다.

virtual BOOL OnCommand(
    WPARAM wParam, // High 16 : Code, Low 16 : Control ID
    LPARAM lParam  // Control HWND
);


    < 공통 컨트롤 >

공통 컨트롤의 경우 CWnd 의 OnNotify 함수에서 통지 메시지인 WM_NOTIFY 를 처리한다.

virtual BOOL OnNotify( 
    WPARAM wParam, // Control ID
    LPARAM lParam, // NMHDR *
    LRESULT* pResult  
);



< 메시지 핸들러에서 Notification Message 처리 >


     < 기본 컨트롤 >

기본 컨트롤의 경우 ON_CONTROL 매크로를 사용하면 된다. 이때는 공통 컨트롤에서 WM_NOTIFY 를 위해 사용하는 NM_* 메시지를 사용하지 않는다. BN_CLICKED 나 LBN_SELCHANGE 와 같은 기본 컨트롤을 위한 Notification Code를 사용한다.


    < 공통 컨트롤 >

공통 컨트롤의 경우 위해서 언급했듯이 ON_NOTIFY 매크로를 사용하면 된다.




참고 문헌



블로그 및 웹페이지

1. http://ko.wikipedia.org/wiki/%EC%9C%88%EB%8F%84_API  (윈도우 API 위키백과)
2. http://www.tipssoft.com/bulletin/tb.php/FAQ/63 (팁스소프트 WM_NOTIFY 1)
3. http://www.tipssoft.com/bulletin/tb.php/FAQ/65 (팁스소프트 WM_NOTIFY 2)
4. http://msdn.microsoft.com/en-us/library/749htf6k%28v=VS.100%29.aspx (MSDN TN061 - WM_NOTIFY)
5. http://codemaker.egloos.com/1825628 (컨트롤의 종류에 따른 통지 메시지 처리법)
6. http://program.egloos.com/1555310 (윈도우 메시지)

2016/07/29 17:28 2016/07/29 17:28