메시지 매핑 함수를 만드는 방법과 자식 윈도우를 만들고 자식윈도우에서 발생하는 메시지 이벤트에 대해서 설명합니다. 윈도우를 프로그래밍은 메시지 처리를 기반으로 프로그램을 제작합니다. 일반적인 메시지는 마법사 중의 하나인 클래스 위저드를 이용하여 만듭니다. 이외에 자식 윈도우를 만들고 이 자식 윈도우에서 발생하는 메시지 이벤트는 직접 코딩해야 합니다. 이런 방법들을 본 장을 통해 윈도우에서 메시지 처리 함수를 어떻게 만드는가를 이해할 수 있을 것입니다.

윈도 프로그램의 대부분의 모듈은 메시지에 의해서 처리됩니다. 사실 모든 내용이 메시지 처리를 어떻게 하는가가 전부라고 해도 과언이 아닙니다. 그림 1-2-1은 애플리케이션 프로그램에서 메시지를 처리하는 전체적인 형태입니다.

키보드에서 문자가 입력되고 이 문자를 화면에 출력하는 프로그램을 작성한다고 할 경우, 그림 1-2-1의 ⑥과 같이 키보드에서 입력된 문자가 윈도 시스템으로 들어갑니다. 이때 윈도 시스템은 애플리케이션 프로그램에 WM_KEYDOWN 메시지와 WM_CHAR 메시지를 애플리케이션 프로그램에 보내주게 됩니다. 이때 애플리케이션은 입력된 문자값이 무엇인가를 알고 난 후 화면 처리 출력 모듈을 호출하여 화면에 입력된 문자를 출력하게 하는 것입니다. 윈도가 새로 만들어질 때 윈도 시스템에서는 WM_CREATE 메시지를 애플리케이션에 보내주고, 애플리케이션 프로그램은 윈도가 새로 만들어지면 그에 여러 초기화 모듈을 만듭니다. 필요한 메모리를 할당하고, 또한 윈도에 부속되는 자식 윈도들도 다시 만드는 등 여러 처리를 하게 되는 것입니다. 이렇게 처리를 하고 난 후에 바로 윈도가 화면에 출력될 때 윈도 시스템은 화면을 출력하라는 WM_PAINT 메시지를 애플리케이션에 보내주고 이 메시지에 해당하는 화면 출력 모듈을 새로 만들게 됩니다. 그림 1-2-1과 같은 루틴을 SDK로 작성한다면 다음과 같습니다.

LESULT CALLBACK WndProc(HWND hwnd, UINT iMsg,WPARAM wParam, LPARAM lParam)

{

HDC hdc;

PAINTSTRUCT ps;

switch(iMsg)

{

case WM_CREATE:

: 윈도가 만들어질 때 초기화 모듈

그림 1-2-1의 ④ 항목

break;

case WM_PAINT:

: 화면 출력 모듈

그림 1-2-1의 ① 항목

break;

case WM_KEYDOWN:

: 키 입력시 처리 모듈

break;

case WM_CHAR :

: 문자 입력시 처리 모듈

그림 1-2-1의 ③ 항목

break;

case WM_DESTROY :

: 윈도가 없어질 때 모듈

그림 1-2-1의 ⑤ 항목

break;

:

}

:

}

즉 한 개의 윈도에서 메시지를 받고 이 메시지에 해당하는 모듈을 모두 switch문으로 설정한 것입니다. MFC에서는 이 모듈을 모두 분리하였습니다. 그림 1-2-2는 MFC에서 메시지를 처리할 경우의 구성 도표입니다.

그림 1-2-2는 CWnd를 상속받은 CMyWnd라는 클래스가 있을 때 이 클래스에서 화면 출력 모듈과 키보드 입력 모듈 윈도 생성시 모듈 등을 만드는 방법을 설정한 도표입니다.

그림 1-2-2처럼 메시지를 처리하기 위해서는 선언 매크로와 수행 매크로 두 개를 쌍으로 설정해야 합니다. 그림 1-2-2의 ① 항목이 선언 매크로이며 ② 항목이 수행 매크로입니다. 보통 선언 매크로는 클래스를 정의할 때 설정되며 수행 매크로는 소스 파일에 설정합니다. 쉽게 생각하면 다음과 같은 방식으로 설정된다는 의미입니다.

class CMyWnd: public CWnd

{

선언 매크로

DECLARE_MESSAGE_MAP()

}

// 소스 파일

수행 매크로

BEGIN_MESSAGE_MAP(CMyWnd,CWnd)

:

END_MESSAGE_MAP()

MFC로 윈도 프로그램을 작성할 경우 보통 헤더는 .h에, 소스는 .cpp에 설정합니다. 위의 예처럼 CMyWnd라는 클래스를 만들었으면 일반적으로 MyWnd.h에는 클래스 선언이 들어가고(이것을 헤더라고 합니다.) MyWnd.cpp에는 소스가 설정됩니다. 이렇게 되었을 때 메시지 선언 매크로는 h 파일에 설정되며, 메시지 수행 매크로는 cpp 파일에 설정됩니다. 그림 1-2-2처럼 CMyWnd에서 화면 출력 모듈, 키보드 입력 모듈, 윈도 생성시 모듈을 작성하고자 한다면 선언 매크로와 수행 매크로를 만든 후에 세 가지 작업이 필요합니다. 그림 1-2-2의 ③과 ④와 ⑤, ⑥, ⑦ 항목이 세 가지 작업입니다. CWnd 클래스에는 윈도에 사용하는 기본적인 메시지 처리 함수를 내장하고 있습니다. 화면 출력시 사용하는 함수를 OnPaint(), 키보드 입력 처리 함수를 OnKeyDown(...), 윈도 생성시 처리하는 모듈을 OnCreate(...) 등으로 가지고 있다는 의미입니다. 이렇게 가지고 있을 때 CWnd에 가지고 있는 메시지 처리 함수를 사용하지 않고 자신이 직접 만들어서 사용하고자 할 경우에 해당 함수를 다시 오버라이딩하면 됩니다. 이렇게 오버라이딩하기 위해서 그림 1-2-2는 ③ 항목처럼 //{{AFX_MSG(CMyWnd) 다음 줄에 각각 함수를 선언한 것입니다. afx_msg의 의미는 메시지 함수의 선언을 나타냅니다. 이렇게 처리한 다음 소스 파일에서 수행 매크로(그림 1-2-2의 ② 항목)를 선언한 후에 해당 메시지와 맞는 매크로를 선언합니다. OnPaint에 해당하는 매크로는 ON_WM_PAINT()이며, OnKeyDown()에 해당하는 매크로는 ON_WM_KEYDOWN()이고, OnCreate()에 해당하는 매크로는 ON_WM_CREATE()입니다. 이렇게 해당 매크로를 헤더에 ③ 항목을 설정하듯이 대응되게 설정합니다. 이렇게 설정한 것이 그림 1-2-2의 ④ 항목입니다. 이렇게 설정한 후에 소스에 해당 함수들을 만들고 그 안에 처리 모듈을 설정하면 됩니다. 그림 1-2-2에서 ⑤, ⑥, ⑦이 이 해당 함수입니다.

전 항목에서 MFC로 메시지 처리를 할 경우 먼저 선언과 수행 매크로를 설정한 뒤에 상속받은 윈도의 메시지 처리 함수를 오버라이딩하고 그에 해당하는 메시지 매크로를 설정해야 한다고 하였습니다. 이렇게 프로그램을 하려면 윈도의 메시지 처리 함수와 그에 해당하는 매크로 등을 알고 있어야 합니다. SDK로 프로그램을 작성할 경우 정의된 메시지 값(WM_PAINT, WM_KEYDOWN 등등)도 외우기 힘든데 MFC로 프로그래밍할 때는 그에 해당하는 함수와 매크로를 더 알고 있어야 한다는 결론이 나옵니다. 윈도에서 사용되는 메시지도 외우기 힘든데 이것을 어느 세월에 외우겠습니까? 게다가 그림 1-2-2에서 보는 것처럼 헤더에 선언하는 함수형과 매크로에 선언하는 함수, 그리고 실제 모듈이 정확하게 일치되어야만 메시지 처리를 할 수가 있는데, 이것 자체가 매우 불편한 □노가다급의 프로그래밍□입니다. 그러나 Visual C++ 에서는 클래스 위저드(ClassWizard)라는 항목이 있어서 이러한 복잡한 루틴을 단순하게 버튼만 눌러주면 자동으로 처리해 줍니다. 클래스 위저드(ClassWizard)는 이 밖에 멤버 함수와 변수를 만들어 주는 등 그밖에 다양한 내용의 소스를 코딩해 줍니다. 이제 하나의 예제 프로그램을 작성해 가면서 클래스 위저드의 기능을 알아보겠습니다.

ExMessage1 프로그램 만들기

ExMessage1은 AppWizard로 프로젝트를 만들 때 SDI 형태로 만듭니다. Step1에서 Single Document Interface 항목을 선택하고 나머지 부분은 설정하지 않습니다. 그냥 Next 버튼을 계속 눌러서 끝에 가서 Fininsh 버튼을 누르든지 또는 Step1에서 Finish 버튼을 클릭하든지 그것은 여러분 마음대로 하기 바랍니다. 이렇게 만든 다음 메뉴의 View 항목에서 ClassWizard 항목을 클릭하거나 핫키로 Ctrl+W 키를 누르면 그림 1-2-3과 같은 항목이 나타납니다.

그림 1-2-3과 같은 프로퍼티 시트 형태의 대화 상자가 클래스 위저드입니다. 현재 프로퍼티 시트에서 선택된 항목이 MessageMaps입니다. 클래스 위저드의 기능 중 대표적인 기능으로 메시지에 의한 함수를 자동적으로 설정해 줍니다. ① 항목은 현재 프로젝트에 설정되어 있는 클래스에서 CCmdTarget을 상속받은 클래스 리스트들이 나온 것입니다. CCmdTarget을 상속받아야만 메시지 처리를 할 수 있기 때문입니다. 프로젝트를 만들고 난 후에 ① 콤보 상자에는 CExMessage1App(애플리케이션 클래스), CExMessage1View(View 윈도), CExMessage1Doc(도큐먼트), CMainFrame(메인프레임), CAboutDlg(프로그램 안내 대화 상자 클래스)의 다섯 개가 출력될 것입니다. 이제 여기에서 간단하게 메시지 처리 함수를 만들어 보겠습니다. 먼저 키보드에서 키를 눌렀을 때 □키가 눌렸음□이라는 메시지 상자가 출력되도록 해보겠습니다. 그림 1-2-3의 ①번 리스트 상자에서 클래스를 CExMessage1View 항목을 선택합니다. 다음 ⑦번 리스트 윈도에서 CExMessage1View를 선택합니다. 그 다음 ⑤번 메시지 리스트에서 WM_KEYDOWN을 선택합니다. 이렇게 하면 ③번 버튼 항목이 활성화됩니다. 이 버튼을 클릭하면 ⑥번 리스트에 □OnKeyDown      ON_WM_KEYDOWN□이라는 내용이 삽입됩니다. 그림 1-2-4는 이 데이터가 설정된 상태의 화면입니다.

그림 1-2-4와 같은 상태에서 그림 1-2-3의 ⑥ 항목 버튼을 클릭하면 ExMessage 1View.cpp 파일이 열리면서 다음과 같은 함수에 커서가 위치됩니다.

void CExMessage1View::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)

{

// TODO: Add your message handler code here and/or call default

CView::OnKeyDown(nChar, nRepCnt, nFlags);

위의 소스가 바로 그림 1-2-2에서 ⑥ 항목에 해당하는 것입니다. 위의 소스에서 키보드에서 한 개의 키를 눌렀을 때 메시지 상자가 나타나고 □키가 눌렸음□이라는 문자가 출력되도록 해보겠습니다. 위의 소스에 다음과 같이 코딩합니다.

void CExMessage1View::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)

{

// TODO: Add your message handler code here and/or call default

AfxMessageBox(□키가 눌렸음□);

CView::OnKeyDown(nChar, nRepCnt, nFlags);

}

AfxMessageBox

int AfxMessageBox( LPCTSTR lpszText, UINT nType = MB_OK, UINT nIDHelp = 0 );

int AFXAPI AfxMessageBox( UINT nIDPrompt, UINT nType = MB_OK, UINT nIDHelp = (UINT) -1 );

내용

화면에 대화 상자가 출력되면서 lpszText로 설정된 문자열이나 자원의 문자열 테이블에서 nIDPrompt인 문자열을 출력합니다. nType은 대화 상자에 설정되는 버튼들입니다. nIDHelp는 현재 상태에서 F1 키를 눌러 도움말을 실행하였을 때에 해당되는 도움말 ID를 의미합니다.

nType에 설정할 수 있는 값

MB_ABORTRETRYIGNORE : Abort,Retry,Ignore 버튼이 설정되는 형태의 메시지 상자

MB_OK : OK 버튼만 설정되는 메시지 상자

MB_OKCANCEL : OK와 Cancel 버튼이 설정되는 메시지 상자

MB_RETRYCANCEL : Retry,Cancel 버튼이 설정되는 메시지 상자

MB_YESNO : Yes,No : 버튼이 설정되는 메시지 상자

MB_YESNOCANCEL : Yes,No,Cancel 버튼이 설정되는 메시지 상자

리턴값

메시지 상자를 종료하기 위해서 선택한 버튼이 무엇인지 알 수 있는 값을 리턴합니다.

IDABORT : Abort 버튼이 선택되었을 경우

IDCANCEL : Cancel 버튼이 선택되었을 경우

IDIGNORE : Ignore 버튼이 선택되었을 경우

IDNO : No 버튼이 클릭되었을 경우

IDOK : Ok 버튼이 클릭되었을 경우

IDRETRY : Retry 버튼이 클릭되었을 경우

IDYES : Yes 버튼이 클릭되었을 경우

위와 같이 한 후 F7 키(메뉴 Build 항목에서 Build ExMessage1 항목 선택)를 눌러서 컴파일한 후에 Ctrl+F5 키(메뉴 Build 항목에서 Execute ExMessage1.exe 항목선택)를 눌러서 실행시키고 키보드에서 아무 키나 눌렀을 때 그림 1-2-5와 같은 화면이 출력됩니다.

지금까지와 같은 방법으로 View 윈도가 만들어질 때와 좌측 마우스 버튼을 클릭했을 때의 함수를 만듭니다. View 윈도가 만들어질 때는 그림 1-2-3에서 ① 항목을 CMessage1View로 놓은 다음 ⑤번 항목에서 WM_CREATE를 선택하여 함수를 만듭니다. 좌측 마우스 버튼을 클릭했을 때는 그림 1-2-3에서 ① 항목을 CMessage1View로 놓은 다음 ⑤번 항목에서 WM_LBUTTONDOWN을 선택하여 함수를 만듭니다. 이렇게 설정된 함수에 AfxMessageBox 함수를 이용하여 □윈도가 만들어질 때□, □좌측 마우스 버튼을 클릭하였을 때□의 두 문자열이 해당 메시지가 설정되면 출력되도록 합니다. 이 함수들은 모두 Message1View.cpp에 설정됩니다. 이렇게 만들어진 함수는 다음과 같습니다.

// WM_CREATE 메시지 처리 함수

int CExMessage1View::OnCreate(LPCREATESTRUCT lpCreateStruct)

{

if (CView::OnCreate(lpCreateStruct) == -1)

return -1;

// TODO: Add your specialized creation code here

AfxMessageBox(□윈도가 만들어질 때□);

return 0;

}

// WM_LBUTTONDOWN 메시지 처리 함수

void CExMessage1View::OnLButtonDown(UINT nFlags, CPoint point)

{

// TODO: Add your message handler code here and/or call default

AfxMessageBox(□좌측 마우스 버튼을 클릭하였을 때□);

CView::OnLButtonDown(nFlags, point);

}

위와 같이 코딩한 후에 컴파일하고 실행시키면 각 메시지가 수행될 때마다 해당되는 메시지 상자가 출력될 것입니다.

클래스 위저드의 기능 및 메시지 매핑

전 항목을 통해서 클래스 위저드의 기능을 대충 알 수 있었을 것입니다. 보통 클래스 위저드의 기능이라고 하면 메시지 매핑 기능을 우선적으로 이야기합니다. 이외에도 클래스 위저드는 다양한 많은 기능을 포함하고 있습니다. 그림 1-2-6은 클래스 위저드의 다양한 기능에 대한 설명입니다.

지금까지 설명한 것은 메시지 매핑 기능으로써 클래스에 메시지 처리 함수를 설정하는 방법에 대해서 설명한 것입니다. 그 외에도 다섯 가지의 기능이 있습니다. ② 메뉴 항목을 설정하면 클래스에 멤버를 설정할 수 있으며, ③과 ④는 OLE와 ActiveX에 대한 기능을 설정하는 것입니다. ⑤는 현재 클래스의 정보를 출력하는 기능을 합니다. 앞으로 본 책에서는 이 클래스의 각 기능에 대해서 자세히 설명하겠습니다. 기억해야 할것은 클래스 위저드가 하는 것은 필요한 내용을 클래스 헤더와 소스에 자동적으로 기록을 하는 것 뿐입니다. 따라서 클래스 위저드를 코딩한 내용이 어떤 것인가를 알 필요가 있습니다.

이 부분에 대해서는 그림 1-2-2에 자세히 설명하였습니다. 이 내용을 ExMessage1 프로젝트의 소스를 가지고 보겠습니다. 먼저 그림 1-2-2의 ①과 ②의 내용입니다. ①의 내용은 ExMessage1View.h에 보면 다음과 같은 내용이 클래스 선언 마지막 부분에 있을 것입니다.

DECLARE_MESSAGE_MAP()

②의 내용은 ExMessage1View.cpp의 소스의 상단 부분에 다음과 같이 설정되어 있을 것입니다.

BEGIN_MESSAGE_MAP(CExMessage1View, CView)

//{{AFX_MSG_MAP(CExMessage1View)

ON_WM_KEYDOWN() // WM_KEYDOWN 메시지 매크로

ON_WM_CREATE() // WM_CREATE 메시지 매크로

ON_WM_LBUTTONDOWN() // WM_LBUTTONDOWN 메시지 매크로

//}}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()

BEGIN_MESSAGE_MAP과 END_MESSAGE_MAP 사이에 우리가 만든 메시지의 매크로를 설정하였습니다. 즉 문자키를 눌렀을 때의 WM_KEYDOWN, 윈도가 만들어질 때의 ON_WM_CREATE, 좌측 마우스 버튼을 클릭했을 때의 ON_WM_ LBUTTONDOWN 메시지 처리가 설정되어 있는 것입니다. 이 메시지 처리 위아래 부분에 //{{AFX_MSG_MAP(CExMessage1View)과 //}}AFX_MSG_MAP이 있을 것입니다. 이것은 클래스 위저드 등 Visual C++에서 현재 소스의 내용이 어떻게 설정되어 있는가의 정보를 주석으로 설정한 것입니다. 위의 주석을 해석하면 지금부터 //}}AFX_MSG_MAP이 끝날 때까지의 내용이 클래스 위저드에 의해서 메시지 처리 기능이 설정되었다는 의미입니다. 이 주석 아랫부분에 ON_COMMAND에 대한 메시지 세 개가 있을 것입니다. 이것은 클래스 위저드가 설정한 내용이 아닙니다. 애플리케이션 위저드(AppWizard)로 프로젝트를 만들 때 자동적으로 설정한 메시지 매크로입니다. AppWizard에서 프린트 기능을 설정하면 위의 메시지가 처리됩니다. ON_COMMAND에 대해서는 다음 항목에서 더 자세히 설명할 것입니다.

이제 ③ 항목의 내용을 보겠습니다. ③ 항목의 내용은 Message1View.h에 다음과 같이 기록되어 있을 것입니다.

//{{AFX_MSG(CExMessage1View)

afx_msg void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags);

afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);

afx_msg void OnLButtonDown(UINT nFlags, CPoint point);

//}}AFX_MSG

각각 메시지 처리 함수를 헤더에 다음과 같이 설정한 것입니다. 메시지 처리 함수들은 중요 정보를 인자로 넘겨주도록 설정되어 있습니다. OnKeyDown 함수는 어떤 문자가 눌려졌는가와 반복 카운트, 그리고 스캔 코드값들을 전해 줍니다. OnCreate 함수는 윈도를 만들 때 필요한 구조체인 CREATESTRUCT의 포인터를 넘겨줍니다. 그리고 좌측 OnLButtonDown 함수는 마우스 버튼과 특정키가 눌려졌는가에 대한 정보와 마우스가 눌려졌을 때 위치 좌표를 넘겨줍니다. 이런 형태로 클래스 위저드에 의해서 만든 함수들은 메시지와 함께 필요한 정보를 넘겨주는 것입니다.

지금까지는 메시지를 어떻게 만들 것인가에 대해서 설명하였습니다. 이제 윈도에서 설정되는 중요 메시지들에 대해서 설명하겠습니다. 이 메시지들은 클래스 위저드로 만들 수 있는 기본 메시지들을 의미합니다. 다음은 기본 메시지에 대한 설명입니다.

CalcWindowRect

선언 매크로	없음

선언 함수	void CalcWindowRect(LPRECT lpClientRect, UINT nAdjustType = adjustBorder);

CalcWindowRect 함수는 윈도를 만들 때 필요한 윈도 영역을 설정하는 크기를 알려줍니다. 이 함수는 헤더에는 메시지 주석 안에 설정되어 있지 않습니다.( //{{AFX_MSG(CExMessage1View)와 //}}AFX_MSG 사이에 설정되지 않는다는 의미입니다.) 이 함수는 윈도가 만들어진 후에(WM_CREATE 함수 실행 이후에) 실행되는 함수로써 이 함수에 의해서 내부적으로 호출되는 SDK 함수는 AdjustWindowRectEx입니다. AdjustWindowRectEx는 CreateWindowEx 함수 호출 후에 클라이언트 영역에 요구되는 크기를 알려주는 함수입니다. 즉 CalcWindowRect는 내부에 이 함수가 내장되어 움직이는 형태로 클라이언트 윈도가 요구되는 최소 크기를 요구하는 설정 함수입니다. lpClientRect는 현재 설정되어 있는 클라이언트 영역의 크기 좌표이며, nAdjustType은 스크롤바를 설정할 것인가를 선택하는 플러그입니다. 이 값이 0이면 스크롤바를 설정하지 않으며, 이 값이 1일 경우에는 스크롤바를 설정합니다.

Create

Create 함수는 CalcWindowRect와 같이 메시지 주석 안에 설정되어 있지 않습니다.(직접 헤더를 확인해 보면 알 수 있습니다.) 이 함수는 윈도를 만들려고 할 경우 수행되는 함수입니다. 이때 넘겨주는 인자는 다음과 같습니다.

선언 매크로	없음

선언 함수	virtual BOOL Create(LPCTSTR lpszClassName, LPCTSTR lpszWindowName, DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID, CCreateContext* pContext = NULL);

lpszClassName : 윈도 클래스명

lpszWindowName : 윈도명

dwStyle : 만들어질 윈도 스타일

rect : 윈도 크기

pParentWnd : 부모 윈도 포인터

nID : 자식 윈도 ID

pContext : 콘텍스트 핸들

위의 함수 내부에는 CWnd::Create 함수가 호출되도록 되어 있습니다. 이 내용을 삭제하면 안됩니다. 이것을 삭제하면 윈도가 만들어지지 않습니다. Create 함수 안에는 윈도를 만드는 CWnd::Create 함수가 들어 있습니다. 모든 윈도들은 클래스명과 스타일, 그리고 콘텍스트 핸들을 이용하여 다양한 형태로 설정되기 때문에 이 인자의 값을 변경하고, 결국에는 윈도의 원초적인 클래스인 CWnd::Create 함수를 호출하기 때문입니다.

DefWindowProc

선언 매크로	없음

선언 함수	LRESULT DefWindowProc(UINT message, WPARAM wParam, LPARAM lParam);

DefWindowProc는 본 윈도와 연결된 메시지 프로시저 함수입니다. SDK에서 프로그램을 할 경우 메인 윈도와 함께 쌍으로 만든 WndProc(본 장 1항목 참조)를 그대로 MFC에서도 사용할 수 있도록 한 것입니다. 이 함수에 대한 인자도 메시지와 WPARAM과 LPARAM을 그대로 알려줍니다.

DestroyWindow

선언	매크로 없음

선언 함수	BOOL DestroyWindow();

DestroyWindow 함수는 윈도를 삭제하고자 할 때 수행되는 함수입니다. Create 함수와 쌍으로 만들어진 함수입니다. 이 두 함수는 메시지 처리에 의해서 만들어지는 것이 아니라 내부적으로 호출할 때 사용되는 함수입니다. 조금 쉽게 설명하자면, 어떤 종료 메뉴를 선택하면 MFC 내부에서는 해당 윈도를 삭제하기 위해서 DestroyWindow를 호출합니다. 이것을 호출하면 이 윈도 클래스는 상속받은 MFC 클래스의 DestroyWindow 함수를 호출합니다. 만일 현재 윈도에서 DestroyWindow 함수가 호출되기 이전에 파일을 정리한다든지 기타 메모리를 해제한다든지 하는 것이 필요할 때 이 함수를 만들고 이전 처리를 한 다음 자신이 상속받은 MFC 클래스의 DestoryWindow 함수를 호출합니다. Create 함수 또한 이것과 같은 의미입니다. 메뉴에서 New 항목을 선택하면 MFC 내부에서는 해당 윈도의 Create 함수를 호출하기 때문입니다.

DoDataExchange

선언 매크로	없음

선언 함수	void DoDataExchange(CDataExchange* pDX);

DoDataExchange 함수는 자원에서 자식 윈도를 설정하고 이것을 클래스 내부의 윈도와 연결할 때 사용합니다. 보통 현재 윈도가 자식 윈도를 가지고 있을 경우 이 자식 윈도를 특정 윈도 포인터와 연결한다는 의미입니다. 이 안에 사용하는 함수로는 DDX_Text나 DDX_Control이 있는데, DDX_Text는 현재 자식 윈도 ID와 문자열을 연결시켜 주며 DDX_Control은 현재 자식 윈도를 컨트롤 클래스와 연결시켜 줍니다. 후에 자원을 이용하여 프로그래밍하는 방법에서 자세히 설명할 것입니다.

GetScrollBarCtrl

선언 매크로	없음

선언 함수	CScrollBar* GetScrollBarCtrl(int nBar)

GetScrollBarCtrl 함수는 현재 윈도에 설정되어 있는 스크롤바를 넘겨주는 함수입니다. 이 함수에 의해서 현재 스크롤바 윈도 포인터를 넘겨줍니다. 클래스 위저드에 의해서 만들지 않고 현재 윈도의 스크롤바 윈도 핸들을 얻고자 한다면 이 함수를 수정하여 사용합니다. 이것을 클래스 위저드의 메시지 매핑에 첨가한 이유는 스크롤바 윈도로 설정된 것이 아닌 자신의 윈도로 만들 경우 리턴되는 윈도 핸들이 다르기 때문입니다. nBar는 플러그로써 SB_HORZ일 경우는 수평 스크롤바이고 SB_VERT일 경우는 수직 스크롤바입니다.

OnActivateView

선언 매크로	없음

선언 함수	void OnActivateView(BOOL bActivate, CView* pActivateView, CView*  pDeactiveView);

OnActiateView 함수는 CView에 설정되어 있는 함수로써 View가 활성화되었을 때 수행되는 함수입니다. bActivate 플러그가 0이면 현재 View는 활성 상태가 아니며, 1이면 활성 상태라는 의미입니다. 그리고 pActivateView는 이 함수가 실행되면서 활성화되는 윈도 포인터이며, pDeactiveView는 비활성화되는 윈도 포인터입니다. MDI일 경우 여러 개의 윈도가 활성화되고 비활성화될 때 이 함수는 유용하게 쓰일수가 있습니다. 즉 현재 비활성화되는 윈도가 어떤 윈도인지 알 수 있으며, 또한 현재 윈도가 절대로 비활성화되게 하고 싶지 않을 경우에는 OnActivateView를 가로채어 MFC 클래스 함수에서 OnActivateView 호출을 하지 않도록 하면 되기 때문입니다. 이런 방법의 예는 다음과 같습니다.

void CTestView::OnActivateView(BOOL bActivate, CView* pActivateView, CView* pDeactiveView)

{

if(!mbNoActive) // 만일 mbNoActive가 1로 설정되어 있다면 아래 함수

// 호출 불가능

CView::OnActivateView(bActivate, pActivateView, pDeactiveView);

}

OnBeginPrinting

선언 매크로	없음

선언 함수	void OnBeginPrinting(CDC* pDC, CPrintInfo* pInfo);

OnBeginPrint 함수는 CView에만 있는 함수입니다. AppWizard를 이용하여 프로그램을 만들고, OnDraw 함수에 여러 그래픽을 출력하고, □파일□ 메뉴에서 □인쇄□ 항목을 선택하면 OnBeginPrint 함수가 실행됩니다. 이 함수는 인자로 프린터 DC와 프린터 정보 클래스 구조체 CPrintInfo를 넘겨줍니다. 이 두 개를 이용하여 여러 페이지를 프린트할 경우에 이용합니다.

OnCmdMsg

선언 매크로	없음

선언 함수	BOOL OnCmdMsg(UINT nID, int nCode, void* pExtra, AFX_CMDHAND LERINFO* pHandlerInfo);

이 함수는 CCmdTarget 함수에 내장되어 있는 함수입니다. 이 함수는 OLE 메시지 및 그 밖에 윈도에 설정되는 명령 메시지들이 계속적으로 전달됩니다. nID는 명령 ID이며, nCode는 명령 notification 코드입니다. pExtra는 그 밖의 여러 정보를 설정해줄 때 사용하는 데이터 포인터이며, AFX_CMDHANDLEFINFO는 핸들러 정보 구조체입니다. MFC 클래스는 여러 개의 윈도 조합으로 템플릿을 설정합니다. 이렇게 설정하여 프로그래밍이 되기 때문에 자신에게 들어온 모든 명령 메시지들이 다른 윈도에 직접적으로 연결될 필요가 있습니다. 이럴 경우 OnCmdMsg 함수를 이용하여 다른 윈도로 전달하는 방법을 취할 수 있습니다. 다음은 OnCmdMsg 함수를 이용하여 다른 윈도로 명령 메시지를 전달하는 예입니다.

BOOL CTestView::OnCmdMsg(UINT nID, int nCode, void* pExtra,

AFX_CMDHANDLERINFO* pHandlerInfo){

// 만일 다른 View가 있고 이쪽으로 명령 메시지를 전달하고자 한다면

if ((m_pActiveAnother != NULL)

m_pActiveAnother->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))

return TRUE;

// 그렇지 않을 경우 CView의 OnCmdMsg를 사용한다.

return CView::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo);}

OnCommand

선언 매크로	없음

선언 함수	BOOL OnCommand(WPARAM wParam, LPARAM lParam);

OnCommand 함수는 메뉴 항목에서 특정 항목을 선택하거나 버튼과 같은 자식 윈도를 클릭했을 때 발생하는 WM_COMMAND를 받은 메시지입니다. 이 메시지에 의해서 어느 메뉴에서, 어느 자식 윈도에서 발생되었는가를 알아낼 수 있습니다. MFC로 프로젝트를 만들었을 경우 현재 View가 자식 윈도를 가지고 있지 않다면 이 메시지는 MainFrame에서만 발생됩니다. View가 자식 윈도를 가질 경우(Create 함수를 사용하여 자식 윈도를 만들었을 경우나 View가 FormView일 경우) 에는 View에서 WM_COMMAND 메시지가 발생됩니다. 이 내용은 SDK에 대한 부분으로써 필자가 쓴 「Windows Programming Bible」에 자세하게 설명하였습니다. 본 장에서 작성한 예제 ExMessage1에서 보면 View 자체가 CView이며, 자식 윈도가 없기 때문에 이 메시지는 발생되지 않고 CMainFrame에서 발생됩니다. 즉 메인프레임에서 발생된다는 의미입니다. 그 이유는 메인프레임은 메뉴와 툴바를 가지고 있기 때문입니다. 메뉴 중 특정 항목을 선택하면 WM_COMMAND를 메인프레임에 발생하고 메인프레임은 자신의 하부 윈도에서 메뉴나 특정 자식 윈도에서 발생하는 WM_COMMAND 메시지를 받아서 처리하는 부분이 있는가를 확인합니다. 이렇게 확인해서 있으면 해당 자식 윈도의 함수를 호출합니다. AppWizard를 이용하여 프로젝트를 만들었을 때 □파일□ 메뉴 항목의 □인쇄□ 항목을 클릭하면 이것을 최초로 받는 윈도는 메인프레임입니다. 이 메인프레임은 현재 자신의 자식 윈도 중 □인쇄□ 항목을 사용하여 구동하는 함수가 있는가를 찾아봅니다. 이때 View의 메시지 매핑 수행 매크로 안에는 다음과 같은 내용이 있을 것입니다.

ON_COMMAND(ID_FILE_PRINT, CView::OnFilePrint) <--□인쇄□ 항목을 선택하였을때 실행되는 함수 연결

ON_COMMAND(ID_FILE_PRINT_DIRECT, CView::OnFilePrint)

ON_COMMAND(ID_FILE_PRINT_PREVIEW, CView::OnFilePrintPreview)

메인프레임은 View에 연결되어 있는 OnFilePrint 함수를 실행합니다. ExMessage1View.cpp의 메시지 매크로 안에 ON_COMMAND 함수 세 개가 왜 연결되어 있는가를 이제는 이해할 수 있을 것입니다. 다음 항목에서 이 메뉴 항목을 선택하였을 때의 예제를 다시 다룰 것입니다.

OLE 드래깅 메시지

선언 매크로	없음

선언 함수	DROPEFFECT OnDragEnter(COleDataObject* pDataObject, DWORD dwKeyState, CPoint point);

DROPEFFECT OnDragOver(COleDataObject* pDataObject, DWORD dwKeyState, CPoint point);

BOOL OnDrop(COleDataObject* pDataObject, DROPEFFECT dropEffect, CPoint point);

void OnDragLeave();

OLE 드래깅 메시지는 윈도에서 특정 파일을 선택하여 이것을 해당 윈도에 옮겼을 때 발생되는 메시지입니다. OnDrawEnter는 특정 파일을 선택하여 끓어서 현재 윈도에 들어왔을 때 발생하는 메시지이며, OnDrawOver는 현재 파일 위에서 드래깅될 때이며, OnDrawLeave는 현재 파일을 떠났을 때입니다.OnDrop은 선택된 파일을 현재 윈도에서 놓았을 때 발생합니다. 이 부분은 OLE에서 드래깅 기능을 설명할 때 자세하게 설명할 것입니다.

OnInitialUpdate

선언 매크로        없음

선언 함수           void OnInitialUpdate();

OnInitialUpdate 함수는 현재 윈도가 초기화될 때 발생합니다. 보통 이 함수를 이용할 경우는 FormView에서 여러 자식 윈도를 초기화할 때 사용합니다. 그 이유는 FormView에서 자식 윈도들의 여러 정보를 초기화하기 위해서는 그 윈도가 만들어져 있어야 하는데, 만든 후에 바로 설정되는 함수가 OnInitialUpdate이기 때문입니다. 예를 들어서, 자식 윈도로 에디터 상자가 있고 윈도가 만들어지자마자 에디터 상자에 □안녕하세요□라는 글자를 입력하기 위해서는 OnInitialUpdate 함수에서 설정해야 하기 때문입니다. FormView를 만드는 항목에서 이 함수를 또 한번 설명할 것입니다.

OnNotify

선언 매크로        없음

선언 함수           BOOL OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult);

OnNotify 메시지는 WM_NOTIFY 메시지가 수행될 때 같이 수행되는 함수입니다. WM_NOTIFY는 보통 컨트롤들이 발생시키는 메시지들입니다. 리스트 컨트롤, 트리 컨트롤 등등이 자신의 윈도에서 특정 이벤트가 발생되면 이 내용을 부모 윈도에 전달할 때 WM_NOTIFY 메시지를 발생시킵니다. WM_NOTIFY 메시지에 대해서는 필자가 쓴 책 「Windows Programming Bible」에 자세하게 나와 있습니다.

OnPreparePrinting

선언 매크로       없음

선언 함수          BOOL OnPreparePrinting(CPrintInfo* pInfo);

OnPreparePrinting 메시지는 □인쇄□ 항목을 선택하였을 때 프린터를 준비하기 위해 설정되는 메시지입니다. 이때 준비가 제대로 되어 있지 않으면 에러로 처리될 수 있으며, 준비가 완료되었을 경우에는 DoPreparePrinting 함수를 호출하여 인쇄 준비를 마칠 수 있습니다.

PreCreateWindow

선언 매크로      없음

선언 함수         BOOL PreCreateWindow(CREATESTRUCT& cs);

OnPreCreateWindow 메시지는 윈도가 만들어지기 바로 이전에 호출되는 메시지 입니다. 이 메시지를 이용하여 윈도 스타일을 바꿀 수 있습니다. 예를 들어, 현재 윈도의 크기와 위치 등 스타일을 바꾸고자 할 때는 이 메시지를 가로채고 넘겨오는 CREATESTRUCT 구조체의 정보를 바꿔 윈도를 바꿀 수 있습니다.

PreTranslateMessage

PreTranslateMessage 함수는 메시지가 자신의 윈도에 들어와서 각 메시지 처리  루틴으로 들어가기 이전에 처리되는 메시지입니다. 이 함수는 현재 메시지를 가로채서 다른 메시지로 전환할 수 있습니다. 예를 들어, 키보드에서 □A□ 키를 누르면 마우스 버튼을 눌린 메시지로 전환하고자 할 경우 MSG 구조체의 인자 pMsg의 멤버인 message의 값을 WM_CHAR에서 WM_LBUTTON으로 전환시켜 주면 됩니다.

선언 매크로       없음

선언 함수          BOOL PreTranslateMessage(MSG* pMsg);

다른 일반 메시지

지금까지 설명한 메시지 함수들은 SDK에서는 보지 못한 함수들입니다. 클래스 위저드에 설정되어 있는 나머지 부분들은 SDK에서 본 함수들일 것입니다. 이 메시지들에 대한 설명은 표1과 같습니다. 더 자세한 부분들은 본 책을 공부하면서 계속적으로 설명할 것입니다.

표1   다른 일반 메시지 리스트

메시지	매크로	함수	설명

WM_CREATE	ON_WM_CREATE()	int OnCreate(LPCREA	윈도가 만들어졌을 때 발생하는

TESTRUCT lpCreate	메시지. Create 함수 실행 이후 발생함

Struct);

WM_DESTROY	ON_WM_DESTROY()	OnDestroy();	윈도가 소멸될 때 발생하는 메시지

WM_HSCROLL	ON_WM_HSCROLL()	OnHScroll(UINT nSB	수평 스크롤바에서 이벤트가

Code, UINT nPos,	발생하였을 때

CScrollBar* pScrollBar);

WM_KEYDOWN	ON_WM_KEYDOWN()	void OnKeyDown(UINT	키보드에서 키가 눌려졌을 때

nChar, UINT nRepCnt,

UINT nFlags);

메시지	매크로	함수	설명

WM_KEYUP	ON_WM_KEYUP()	void OnKeyUp(UINT	키보드에서 키가 떼어졌을 때

nChar, UINT nRepCnt,

UINT nFlags);

WM_KILLFOCUS	ON_WM_KILLFOCUS()	void OnKillFocus	현재 윈도에서 포커스가 사라졌을 때

(CWnd* pNewWnd);

WM_LBUTTO	ON_WM_LBUTT	void OnLButtonDblClk	좌측 마우스 버튼 더블 클릭

NDBLCLK	ONDBLCLK()	(UINT nFlags,

CPoint point);

WM_LBUTTON	ON_WM_LBUTTON	void OnLButtonDown	좌측 마우스 버튼 클릭

DOWN	DOWN()	(UINT nFlags,

CPoint point);

WM_LBUTTONUP	ON_WM_LBUTTONUP()	void OnLButtonUp	좌측 마우스 버튼을 놓았을 때

(UINT nFlags,

CPoint point);

WM_MOUSEMOVE	ON_WM_MOU	void OnMouseMove	마우스가 움직일 때

SEMOVE()	(UINT nFlags,

CPoint point);

WM_PAINT	ON_WM_PAINT()	void OnPaint();	화면 출력시

WM_RBUTTONDBLCLK	ON_WM_RBUTTO	void OnRButtonDblClk	우측 마우스 버튼 더블 클릭

NDBLCLK()	(UINT nFlags, CPoint point);

WM_RBUTTON	ON_WM_RBUTTON	void OnRButtonDown	우측 마우스 버튼 눌림

DOWN	DOWN()	(UINT nFlags,

CPoint point);

WM_RBUTTONUP	ON_WM_RBUT	void OnRButtonUp	우측 마우스 버튼 떼어짐

TONUP()	(UINT nFlags,

CPoint point);

WM_SETCURSOR	ON_WM_SETC	BOOL OnSetCursor	마우스 커서가 설정될 때

URSOR()	(CWnd* pWnd, UINT

nHitTest, UINT message);

WM_SETFOCUS	ON_WM_SETFOCUS()	void OnSetFocus	현재 윈도에 포커스가 설정될 때

(CWnd* pOldWnd);

WM_SIZE	ON_WM_SIZE()	void OnSize(UINT n	윈도 크기가 바뀔 때

Type, int cx, int cy);

WM_TIMER	ON_WM_TIMER()	void OnTimer(UINT	타이머가 발생될 경우

nIDEvent);

WM_VSCROLL	ON_WM_VSCROLL()	void OnVScroll(UINT	수직 스크롤바에서 이벤트가 발생할 때

nSBCode, UINT nPos,

CScrollBar* pScrollBar);

지금까지는 클래스 위저드 항목에서 해당 클래스를 선택하였을 때 발생되는 메시지 리스트에 대한 함수를 만드는 방법에 대해서 설명하였습니다. 즉 그림 1-2-3의 ⑨번 항목에서 해당 클래스명을 선택하고 그림 1-2-3의 ⑤번에 나타나는 메시지 리스트에 대해 설명한 것입니다. 본 항목에서는 ⑨번 항목에서 클래스명 외에 나타나는 Object IDs들이 무엇이며, 이 ID들의 이벤트에 대한 설정은 어떻게 하는가에 대해서 설명하겠습니다.

⑨번 항목의 클래스명 외에 설정되는 ID들은 메뉴 항목에서 설정된 ID나 FormView를 이용하여 자식 윈도를 만들었을 때의 자식 윈도 ID들입니다. 이외에 MFC 내부에서 사용하는 명령 ID들입니다.

메뉴 ID 선택시의 함수 설정

메뉴가 선택되었을 때 선택된 내용을 메시지 상자로 출력하는 예제를 만들어 보겠습니다. 본 예제는 ExMessage2입니다.

ExMessage2는 AppWizard를 이용하여 프로젝트를 만들 때 SDI 형태로 프로젝트를 만듭니다.

이렇게 프로젝트를 만든 다음 다음과 같은 방법으로 프로그램을 진행합니다.

자원에서 메뉴 불러오기

프로젝트 워크스페이스(Project Workspace) 윈도의 하단 버튼에서 ResourceView 항목을 선택합니다. 이렇게 선택한 후에 ExMessage2 resource 항목리스트 중 Menu 항목을 선택합니다. 이때 자원 ID가 IDR_MAINFRAME인 항목이 나올 것입니다. 그림 1-2-7은 ResourceView의 Menu 항목에서 IDR_MAINFRAME을 선택한 화면입니다.

프로젝트를 만든 후에 ResourceView 항목을 선택한 상태에서 그림 ①과 같은 화면이 출력되면 Menu 항목에서 IDR_MAINFRAME를 선택한 후(그림 1-2-7에서 ② 항목 참조) 좌측 마우스 버튼을 더블 클릭합니다. 그러면 그림 1-2-7에서 ③ 항목이 나타납니다. 이 메뉴는 ExMessage2에 설정된 메뉴의 형태입니다. 그림 1-2-7과 같은 상태에서 □취소□ 항목을 선택하였을 때 □취소 항목 선택!□이라는 내용이 메시지 상자로 출력되도록 해보겠습니다.

클래스 위저드를 이용하여 메뉴 항목 선택시 실행되는 함수 만들기

그림 1-2-7과 같이 출력되었을 때 ③ 항목의 □취소□ 메뉴를 마우스 버튼으로 선택합니다. 이렇게 선택한 후 Visual C++의 View 메뉴에서 ClassWizard 항목을 실행시키거나, Ctrl+W 키를 누르면 그림 1-2-8과 같은 화면이 출력됩니다.

그림 1-2-8과 같은 클래스 위저드가 실행될 때 ② 항목에는 CMainFrame이 기본 클래스로 설정됩니다. 이 항목은 메뉴가 선택되었을 때 실행되는 클래스를 의미합니다. 우리가 만드는 예제는 View 클래스에서 수행되는 함수를 만들고자 합니다. 따라서 그림 1-2-8과 같이 ② 항목의 클래스명을 ExMessage2View로 설정합니다. 이렇게 설정하면 그림 ① 항목의 클래스명이 CExMessage2View로 설정되고 그 밖의 여러 ID들이 나오게 됩니다. Classwizard를 실행시키면 그림 ① 항목처럼 ID_EDIT_UNDO가 선택됩니다. 이 의미는 □취소□ 항목의 ID가 ID_EDIT_UNDO이기 때문에 이 항목이 선택된 것입니다. 그렇다면 □취소□ 항목의 ID가 ID_EDIT_UNDO인지 어떻게 알 수 있는가? 그림 1-2-7에서 □취소□ 항목을 선택한 후에 Alt+Enter 키를 누르면 그림 1-2-9와 같은 화면이 출력될 것입니다.

그림 1-2-9는 보통 자원의 속성을 알려주는 대화 상자로 앞으로 자주 보게 될 것입니다. 여기에서 화면에 출력되는 문자열은 ② 항목이며, 메뉴에서 □취소□ 항목이 선택되었을 때 상태바에 출력될 문자열은 ③이며, 이 항목의 ID는 ①입니다. 즉 ① 이 ID_EDIT_UNDO이고 그림 1-2-8의 상태는 메뉴에서 ID가 ID_EDIT_UNDO인 □취소□ 항목을 선택하였을 때 발생되는 메시지를 만드는 형태입니다. 그림 1-2-8의 ②, ③, ④와 같은 방법으로 함수를 만든 형태가 그림 1-2-10입니다.

그림 1-2-10과 같은 상태에서 ① 항목처럼 해당 함수를 마우스로 더블 클릭하면 ExMessage2View.cpp 파일이 열리면서 다음과 같은 소스에 커서가 위치하게 됩니다.

void CExMessage2View::OnEditUndo()

{

// TODO: Add your command handler code here

}

OnEditUndo 함수가 바로 메뉴에서 □취소□ 항목을 선택하였을 때 실행되는 함수입니다.

OnEditUndo 함수를 만든 다음 AfxMessageBox 함수를 이용하여 화면에 □취소 항목 선택□이라는 내용을 출력해 보도록 하겠습니다. 전에 만든 OnEditUndo 함수를 다음과 같이 수정합니다.

void CExMessage2View::OnEditUndo()

{

// TODO: Add your command handler code here

AfxMessageBox(□취소 항목 선택□);

}

위와 같이 한 후에 컴파일하여 실행하고 메뉴의 편집 항목에서 □취소□ 항목을 선택하면 그림 1-2-11과 같은 화면이 출력됩니다.

메뉴 항목에 의해 수행되는 함수 분석

메뉴 항목을 선택하였을 때 클래스 위저드에 의해 자동으로 만들어지는 형태는 그림 1-2-12와 같습니다.

클래스 위저드를 이용하여 메뉴 항목을 선택하였을 때 실제 코딩되는 방식은 그림 1-2-12처럼 헤더에 먼저 ①과 같이 함수형이 afx_msg 형태로 기록되며, ②와 같이 메시지 매크로에 ON_COMMAND형으로 선언됩니다. ON_COMAND로 선언될 경우 첫 번째 인자에는 ID가, 두 번째 인자에는 이 ID와 연결되는 함수형이 선언됩니다. 마지막 부분으로 여러분이 실제 해당 항목을 선택하였을 때 수행하는 내용의 함수가 ③과 같이 만들어집니다. 만일 메뉴 항목을 선택하였을 때 실행되는 함수를 삭제하고자 할 경우 위 ①, ②, ③ 항목을 모두 제거하면 간단하게 해당 함수를 제거할 수 있습니다.

지금까지의 결과로 우리는 메시지가 수행될 때에 해당하는 함수를 만들 수 있었습니다. 여기서 한 가지 의문을 가지게 됩니다. 메뉴의 □취소□ 항목을 선택하면 해당하는 함수를 메인프레임에 만들고, View 윈도에 만들고, 애플리케이션 클래스에 만들었을 때 어느 함수가 수행되는가입니다. 또는 키를 눌렀을 경우 발생하는 함수를 메인프레임에 만들고 View 윈도에 만들었을 때 어느 함수가 실행되며, 그리고 어떤 순서로 메시지가 처리되는가입니다. ExMessage2에 이 기능을 넣어서 실제 프로그램을 구동하면서 답을 얻도록 하겠습니다.

일반적인 메시지 처리 순서

ExMessage2에 키보드에서 키를 눌렀을 때 View 클래스에서 수행하는 함수를 만들어 보겠습니다. 클래스 위저드를 실행시키고 Class Name 항목에서 CExMessage2 View를 선택하고(그림 1-2-3의 ①), Object IDs 항목에서 CExMessage2View를 선택하고(그림 1-2-3의 ⑨), Messages에서 WM_KEYDOWN을 선택하여(그림 1-2-3의 ⑤) 함수를 만들고 Edit Code 버튼을 클릭하면 해당 함수가 다음과 같이 만들어 질것입니다.

void CExMessage2View::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)

{

// TODO: Add your message handler code here and/or call default

CView::OnKeyDown(nChar, nRepCnt, nFlags);

}

이 함수에 AfxMessageBox 함수를 이용하여 □키가 눌렸음□이라는 메시지를 출력합니다. 다음은 OnKeyDown 함수를 수정한 형태입니다.

void CExMessage2View::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)

{

// TODO: Add your message handler code here and/or call default

AfxMessageBox(□키가 눌렸음□);

CView::OnKeyDown(nChar, nRepCnt, nFlags);

}

위처럼 한 상태에서 컴파일하고 실행하면 키보드에서 키가 눌렸을 때 메시지 상자가 출력되면서 □키가 눌렸음□이라는 문자열이 나오게 됩니다. 이제 클래스 위저드를 이용하여 메인프레임에서 이와 똑같은 함수를 만들어 보도록 하겠습니다.

클래스 위저드를 실행시켜 Class Name 항목에서 CMainFrame을 선택하고(그림 1-2-3의 ①), Object IDs 항목에서 CMainFrame을 선택하고(그림 1-2-3의 ⑨), Messages에서 WM_KEYDOWN을 선택하여(그림 1-2-3의 ⑤) 함수를 만들고 Edit Code 버튼을 클릭하면 해당 함수가 다음과 같이 만들어질 것입니다.

void CMainFrame::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)

{

// TODO: Add your message handler code here and/or call default

CFrameWnd::OnKeyDown(nChar, nRepCnt, nFlags);

}

이 함수에서 AfxMessageBox 함수를 이용하여 □메인프레임 : 키가 눌렸음□이라는 것을 출력하도록 수정합니다. 이렇게 수정한 형태는 다음과 같습니다.

void CMainFrame::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)

{

// TODO: Add your message handler code here and/or call default

AfxMessageBox(□메인프레임 : 키가 눌렸음□);

CFrameWnd::OnKeyDown(nChar, nRepCnt, nFlags);

}

위와 같이 하고 컴파일한 후에 실행하여 키보드를 누르면 그림 1-2-13과 같이 □키가 눌렸음□이라는 메시지만 출력됩니다. 즉 View 클래스에 설정된 메시지만 처리된다는 의미입니다.

여기에서 우리가 알 수 있는 것은 메인프레임 메시지는 무시되고 View 윈도 메시지만 처리된다는 것입니다. 일반적인 메시지에서의 처리 루틴은 그림 1-2-14와 같은 형태로 움직입니다.

즉 메시지가 전달되면 현재 활성화되어 있는 View가 먼저 이 메시지를 처리합니다. 그림 1-2-14에서 ① 항목처럼 View 클래스에서 해당 메시지를 처리하는 함수가 있는가 확인한 다음 함수가 없을 경우에는 메인프레임 클래스에 처리 권한을 넘겨주는 것입니다. 메인프레임은 이 메시지에 처리 함수가 있는가 확인하고(② 항목), 함수가 있으면 함수 내용을 수행하고 함수가 없으면 윈도 시스템에서 알아서 처리하도록 설정해줍니다. 이와 같은 형태라면 한 개의 메시지는 네 개의 클래스 중 단 한 개의 클래스에서만 처리하게 된다는 문제점이 생깁니다. 이 부분을 해결할 수 있는 방법은 여러 가지입니다. 윈도 메시지를 처리한 다음 해당 메시지를 다시 자신의 부모 윈도로 넘겨주는 방법이 일반적입니다. 이것은 본 책의 SDI 프레임 구조와 MDI 프레임 구조에서 자세히 설명할 것입니다. 메인프레임에서도 View에서도 모두 키보드가 눌려졌을 때 메시지가 처리되는 루틴을 만들려면 PreTranslateMessage를 이용하면 됩니다. ExMessage2에서 클래스 위저드를 이용하여 메인프레임과 View 클래스에 PreTranslateMessage 함수를 만들고 다음과 같이 이 함수를 수정합니다.

메인프레임 클래스

BOOL CMainFrame::PreTranslateMessage(MSG* pMsg)

{

// TODO: Add your specialized code here and/or call the base class

if(pMsg->message==WM_KEYDOWN) // 키가 눌려진 메시지이면

{

AfxMessageBox(□메인프레임 : 키가 눌렸음□);

}

return CFrameWnd::PreTranslateMessage(pMsg);

}

View 클래스

BOOL CExMessage2View::PreTranslateMessage(MSG* pMsg)

{

// TODO: Add your specialized code here and/or call the base class

if(pMsg->message==WM_KEYDOWN) // 키가 눌려진 메시지이면

{

AfxMessageBox(□키가 눌렸음□);

}

return CView::PreTranslateMessage(pMsg);

}

위와 같이 설정한 후에 컴파일하고 실행한 후 키보드에서 키를 누르면 먼저 View의 PreTranslateMessage 함수가 실행되고 메인프레임 클래스의 PreTranslateMessage 함수가 실행됩니다. 그런 후에 View의 OnKeyDown 함수가 실행되는 것입니다. PreTranslateMessage 함수는 메시지가  들어왔을 경우 처리되기 이전에 수행되는 함수입니다. 이 함수는 각 View 클래스에 상관없이 메인프레임에서도 수행되기 때문에 View 클래스와 메인클래스에서 동시에 구동할 수 있습니다. 이때 들어오는 인자 pMsg의 구조체형인 MSG는 내부 멤버로 다음과 같은 형태를 가지고 있습니다.

typedef struct tagMSG {

HWND   hwnd;         // 윈도 핸들

UINT   message;   // 메시지

WPARAM wParam;    // WPARAM값

LPARAM lParam;    // LPARAM값

DWORD  time;    // 메시지가 도착된 시간

POINT  pt; // 마우스 위치 좌표

} MSG;

MSG 구조체에서 message에는 메시지값이 설정되어 있습니다. 이 값이 WM_KEYDOWN이면 키가 눌려졌다는 의미이고, 본 예제에서는 키가 눌려졌을 때 메인프레임과 View 클래스에서 동시에 메시지 상자를 출력하도록 한 것입니다.

ExMessage2의 소스

CMainFrame 헤더 (MainFrame.h)

// MainFrm.h : interface of the CMainFrame class

//

//////////////////////////////////////////////////////////////

#if !defined(AFX_MAINFRM_H__E9762049_1A7E_11D2_A6C0_00403320A530__INCLUDED_)

#define AFX_MAINFRM_H__E9762049_1A7E_11D2_A6C0_00403320A530__INCLUDED_

#if _MSC_VER > 1000

#pragma once

#endif //  _MSC_VER > 1000

class CMainFrame : public CFrameWnd

{

protected: // create from serialization only

CMainFrame();

DECLARE_DYNCREATE(CMainFrame)

// Attributes

public:

// Operations

public:

// Overrides

// ClassWizard generated virtual function overrides

//{{AFX_VIRTUAL(CMainFrame)

public:

virtual BOOL PreCreateWindow(CREATESTRUCT& cs);

virtual BOOL PreTranslateMessage(MSG* pMsg);

//}}AFX_VIRTUAL

// Implementation

public:

virtual ~CMainFrame();

#ifdef _DEBUG

virtual void AssertValid() const;

virtual void Dump(CDumpContext& dc) const;

#endif

protected:  // control bar embedded members

CStatusBar  m_wndStatusBar;

CToolBar    m_wndToolBar;

// Generated message map functions

protected:

//{{AFX_MSG(CMainFrame)

afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);

afx_msg void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags);

//}}AFX_MSG

DECLARE_MESSAGE_MAP()

};

//////////////////////////////////////////////////////////////

//{{AFX_INSERT_LOCATION}}

// Microsoft Visual C++ will insert additional declarations immediately before the previous line.

#endif

// !defined(AFX_MAINFRM_H__E9762049_1A7E_11D2_A6C0_00403320A530__INCLUDED_)

CMainFrame 소스 (MainFrame.cpp)

// MainFrm.cpp : implementation of the CMainFrame class

//

#include □stdafx.h□

#include □ExMessage2.h□

#include □MainFrm.h□

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

//////////////////////////////////////////////////////////////

// CMainFrame

IMPLEMENT_DYNCREATE(CMainFrame, CFrameWnd)

BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)

//{{AFX_MSG_MAP(CMainFrame)

ON_WM_CREATE()

ON_WM_KEYDOWN()

//}}AFX_MSG_MAP

END_MESSAGE_MAP()

static UINT indicators[] =

{

ID_SEPARATOR,           // status line indicator

ID_INDICATOR_CAPS,

ID_INDICATOR_NUM,

ID_INDICATOR_SCRL,

};

//////////////////////////////////////////////////////////////

// CMainFrame construction/destruction

CMainFrame::CMainFrame()

{

// TODO: add member initialization code here

}

CMainFrame::~CMainFrame()

{

}

int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)

{

if (CFrameWnd::OnCreate(lpCreateStruct) == -1)

return -1;

if (!m_wndToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE

| CBRS_TOP

| CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY |

CBRS_SIZE_DYNAMIC) ||

!m_wndToolBar.LoadToolBar(IDR_MAINFRAME))

{

TRACE0(□Failed to create toolbar\n□);

return -1;      // fail to create

}

if (!m_wndStatusBar.Create(this) ||

!m_wndStatusBar.SetIndicators(indicators,

sizeof(indicators)/sizeof(UINT)))

{

TRACE0(□Failed to create status bar\n□);

return -1;      // fail to create

}

// TODO: Delete these three lines if you don□t want the toolbar to

// be dockable

m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);

EnableDocking(CBRS_ALIGN_ANY);

DockControlBar(&m_wndToolBar);

return 0;

}

BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)

{

if( !CFrameWnd::PreCreateWindow(cs) )

return FALSE;

// TODO: Modify the Window class or styles here by modifying

// the CREATESTRUCT cs

return TRUE;

}

//////////////////////////////////////////////////////////////

// CMainFrame diagnostics

#ifdef _DEBUG

void CMainFrame::AssertValid() const

{

CFrameWnd::AssertValid();

}

void CMainFrame::Dump(CDumpContext& dc) const

{

CFrameWnd::Dump(dc);

}

#endif //_DEBUG

//////////////////////////////////////////////////////////////

// CMainFrame message handlers

// 키보드 입력시 수행되는 함수

void CMainFrame::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)

{

// TODO: Add your message handler code here and/or call default

AfxMessageBox(□메인프레임 : 키가 눌렸음□);

CFrameWnd::OnKeyDown(nChar, nRepCnt, nFlags);

}

// 메시지가 처리되기 이전에 수행되는 함수

BOOL CMainFrame::PreTranslateMessage(MSG* pMsg)

{

// TODO: Add your specialized code here and/or call the base class

if(pMsg->message==WM_KEYDOWN)

{

AfxMessageBox(□메인프레임 : 키가 눌렸음□);

}

return CFrameWnd::PreTranslateMessage(pMsg);

}

CExMessage2View 헤더 (ExMessage2View.h)

// ExMessage2View.h : interface of the CExMessage2View class

//

//////////////////////////////////////////////////////////////

#if !defined(AFX_EXMESSAGE2VIEW_H__E976204D_1A7E_11D2_

A6C0_00403320A530__INCLUDED_)

#define AFX_EXMESSAGE2VIEW_H__E976204D_1A7E_11D2_A6C0_

00403320A530__INCLUDED_

#if _MSC_VER > 1000

#pragma once

#endif //  _MSC_VER > 1000

class CExMessage2View : public CView

{

protected: // create from serialization only

CExMessage2View();

DECLARE_DYNCREATE(CExMessage2View)

// Attributes

public:

CExMessage2Doc* GetDocument();

// Operations

public:

// Overrides

// ClassWizard generated virtual function overrides

//{{AFX_VIRTUAL(CExMessage2View)

public:

virtual void OnDraw(CDC* pDC);  // overridden to draw this view

virtual BOOL PreCreateWindow(CREATESTRUCT& cs);

virtual BOOL PreTranslateMessage(MSG* pMsg);

protected:

virtual BOOL OnPreparePrinting(CPrintInfo* pInfo);

virtual void OnBeginPrinting(CDC* pDC, CPrintInfo* pInfo);

virtual void OnEndPrinting(CDC* pDC, CPrintInfo* pInfo);

//}}AFX_VIRTUAL

// Implementation

public:

virtual ~CExMessage2View();

#ifdef _DEBUG

virtual void AssertValid() const;

virtual void Dump(CDumpContext& dc) const;

#endif

protected:

// Generated message map functions

protected:

//{{AFX_MSG(CExMessage2View)

afx_msg void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags);

afx_msg void OnEditUndo();

//}}AFX_MSG

DECLARE_MESSAGE_MAP()

};

#ifndef _DEBUG  // debug version in ExMessage2View.cpp

inline CExMessage2Doc* CExMessage2View::GetDocument()

{ return (CExMessage2Doc*)m_pDocument; }

#endif

//////////////////////////////////////////////////////////////

//{{AFX_INSERT_LOCATION}}

// Microsoft Visual C++ will insert additional declarations immediately before the previous line.

#endif //  !defined(AFX_EXMESSAGE2VIEW_H__E976204D_1A7E_11D2_ A6C0_00403320A530__INCLUDED_)

CExMessage2View 소스 (ExMessage2View.cpp)

// ExMessage2View.cpp : implementation of the CExMessage2View class

//

#include □stdafx.h□

#include □ExMessage2.h□

#include □ExMessage2Doc.h□

#include □ExMessage2View.h□

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

//////////////////////////////////////////////////////////////

// CExMessage2View

IMPLEMENT_DYNCREATE(CExMessage2View, CView)

BEGIN_MESSAGE_MAP(CExMessage2View, CView)

//{{AFX_MSG_MAP(CExMessage2View)

ON_WM_KEYDOWN()

ON_COMMAND(ID_EDIT_UNDO, OnEditUndo)

//}}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()

//////////////////////////////////////////////////////////////

// CExMessage2View construction/destruction

CExMessage2View::CExMessage2View()

{

// TODO: add construction code here

}

CExMessage2View::~CExMessage2View()

{

}

BOOL CExMessage2View::PreCreateWindow(CREATESTRUCT& cs)

{

// TODO: Modify the Window class or styles here by modifying

// the CREATESTRUCT cs

return CView::PreCreateWindow(cs);

}

/////////////////////////////////////////////////////////////////////////

// CExMessage2View drawing

void CExMessage2View::OnDraw(CDC* pDC)

{

CExMessage2Doc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

// TODO: add draw code for native data here

}

//////////////////////////////////////////////////////////////// CExMessage2View printing

BOOL CExMessage2View::OnPreparePrinting(CPrintInfo* pInfo)

{

// default preparation

return DoPreparePrinting(pInfo);

}

void CExMessage2View::OnBeginPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)

{

// TODO: add extra initialization before printing

}

void CExMessage2View::OnEndPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)

{

// TODO: add cleanup after printing

}

//////////////////////////////////////////////////////////////

// CExMessage2View diagnostics

#ifdef _DEBUG

void CExMessage2View::AssertValid() const

{

CView::AssertValid();

}

void CExMessage2View::Dump(CDumpContext& dc) const

{

CView::Dump(dc);

}

CExMessage2Doc* CExMessage2View::GetDocument() // non-debug version is inline

{

ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CExMessage2Doc)));

return (CExMessage2Doc*)m_pDocument;

}

#endif //_DEBUG

////////////////////////////////////////////////////////////// // CExMessage2View message handlers

void CExMessage2View::OnEditUndo()

{

// TODO: Add your command handler code here

AfxMessageBox(□취소 항목 선택□);

}

void CExMessage2View::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)

{

// TODO: Add your message handler code here and/or call default

AfxMessageBox(□키가 눌렸음□);

CView::OnKeyDown(nChar, nRepCnt, nFlags);

}

BOOL CExMessage2View::PreTranslateMessage(MSG* pMsg)

{

// TODO: Add your specialized code here and/or call the base class

if(pMsg->message==WM_KEYDOWN)

{

AfxMessageBox(□키가 눌렸음□);

}

return CView::PreTranslateMessage(pMsg);

}

지금까지는 클래스 위저드에 의해서 발생되는 모든 메시지 처리에 대한 방법과 그 처리 순서를 설명하였습니다. 클래스 위저드가 모든 메시지 처리를 가능하게 해줄 수 있는 것은 아닙니다. 자식 윈도에서 발생하는 메시지나 사용자 처리 등은 직접 코딩으로 작성해 주어야 합니다. 본 항목에서는 자식 윈도에서 발생하는 메시지 처리를 하기 위한 자식 윈도를 만드는 방법을 설명합니다. 자식 윈도를 만들어야 자식 윈도에서 발생하는 이벤트 메시지를 처리할 수 있기 때문입니다. 모든 윈도들은 CWnd를 상속받았으며, 이런 윈도들을 만들기 위해서는 Create 함수를 사용합니다. 이 Create 함수는 자식 윈도의 성격에 따라 다르게 설정되어 있습니다. 버튼은 버튼 나름대로, 리스트 상자는 리스트 상자 특성에 따라, 그리고 에디터 상자는 에디터 상자 특성에 따라 다르게 Create 함수가 설정되어 있습니다. 중요한 것은 CWnd 클래스를 상속받은 윈도들은 다음과 같은 방법으로 만들 수 있다는 것입니다.

쥱 Create 함수를 이용하여 윈도를 만든다.

쥲 ShowWindow 함수를 이용하여 윈도를 화면에 출력시킨다.

예를 들어서 CButton 클래스형인 m_pButton이라는 변수가 있을 경우, 이 버튼을 출력하기 위해서는 다음과 같은 방법을 사용하여 화면에 출력시키면 된다는 의미입니다.

m_pButton.Create(□버튼□,WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON ,CRect(10,10,100,50),this,100);

m_pButton.ShowWindow(SW_SHOW);

CButton의 멤버 함수인 Create는 처음에는 버튼에 출력할 문자열을, 두 번째는 윈도 스타일, 세 번째는 윈도 위치와 크기, 네 번째는 부모 윈도 포인터, 다섯 번째는 이 윈도의 ID를 설정한 것입니다. 본 항목에서는 이렇게 여러 자식 윈도를 생성하는 방법에 대해서 설명합니다.

버튼 만들기

버튼을 만들 경우에는 CButton 클래스를 사용합니다. 이 클래스를 이용해서 만들 수 있는 버튼은 다양합니다. 체크 버튼, 라디오 버튼, 그룹 상자, 일반 버튼, 정적 윈도 등 모두 버튼을 이용해서 만들 수 있습니다. 제일 먼저 해야 할 일은 CButton의 형을 가진 변수를 설정하는 것입니다.

CButton m_pButton;

위와 같이 m_pButton이라는 변수가 CButton 클래스의 형을 가지도록 한 다음 버튼을 만들기 위해서 Create 함수를 이용하여 버튼을 만듭니다. CButton::Create 함수는 다음과 같습니다.

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

lpszCaption : 버튼에 출력할 문자열 인자

dwStyle : 윈도 스타일은 기본적으로 WS_CHILD | WS_VISIBLE 값을 설정한다. 이두 개의 값은 □자식 윈도이면서 화면에 보인다□라는 의미

rect : 상자 영역 구조체로써 좌측(left), 상단(top), 우측(right), 하단(bottom)의 값을 가지고 있는 구조체

pParentWnd : 부모 윈도 포인터

nID : 현재 버튼의 ID

lpszCaption은 버튼에 출력할 문자열입니다. 이 값에 문자열을 대입하면 간단합니다. dwStyle이란 윈도 스타일을 의미합니다. 기본적으로 첨가해야 할 스타일은 WS_CHILD | WS_VISIBLE입니다. 즉 자식 윈도이면서 화면에 보이라는 의미입니다. 만일 처음에는 화면에 보이지 않게 하려면 WS_CHILD만 설정하여도 됩니다. 이것과 함께 버튼의 스타일을 가지고 있는 BS 계열의 스타일을 포함해야 합니다.

표1은 BS 계열의 스타일 리스트입니다.

표1   BS 계열의 스타일 리스트

버튼 스타일	설명

BS_CHECKBOX	체크 상자 버튼 형태 출력시

BS_GROUPBOX	그룹 상자

BS_PUSHBUTTON	일반적인 버튼

BS_DEFPUSHBUTTON	기본적으로 사용하는 버튼

BS_RADIOBUTTON	라디오 버튼

BS_3STATE	세 가지 상태가 설정되는 버튼

BS_AUTOCHECKBOX	자동으로 체크 상태가 전환되는 체크 버튼

BS_AUTORADIOBUTTON	자동으로 체크 상태가 전환되는 라디오 버튼

BS_LEFTTEXT	본 플러그가 체크 버튼과 라디오 버튼에 결합될 경우 문자가 버튼 좌측에 설정됨

BS_OWNERDRAW	사용자 그리기 설정

pParentWnd는 부모 윈도의 포인터입니다. 다음은 nID인데, 이것은 현재 윈도의 ID를 의미합니다. ID라는 의미는 이 윈도의 이름이라는 뜻입니다. 이름을 정해주지 않으면 특정 버튼을 지정할 때 매우 어렵습니다. 예를 들어서 (0,0,100,100) 좌표에 버튼이 있다고 가정합시다. 이름이 없으면 □거, 우측 상단에 있는 버튼!□ 이렇게 해야 합니다. 그러나 아이디를 100으로 설정하면 간단하게 □100번 버튼□이라고 할 수 있는 것입니다. 이 ID는 여러분이 원하는 대로 설정할 수 있습니다. 다만 다른 ID와 충돌하지 않도록 하면 됩니다. 즉 현재 윈도에 설정된 ID들은 다 달라야 한다는 것입니다. 버튼이 다섯 개가 있는데 모든 ID가 100번이라면 어떤 버튼인지 알 수가 없기 때문입니다.

Create 함수를 이용하여 윈도를 만든 다음에 화면에 출력하기 위해서 ShowWindow 함수를 이용합니다. 이 함수는 CWnd 클래스의 멤버 함수입니다. 그런데 CButton 클래스에서 사용할 수 있는 이유는 CButton 클래스가 CWnd 클래스를 상속받았기 때문입니다.

CWnd:ShowWindow(int nCmdShow)

nCmdShow는 화면에 윈도를 나타낼 때 사용하는 플러그입니다. 이 플러그 리스트는 표2와 같습니다.

표2   nCmdShow 플러그 리스트

nCmdShow 플러그	내용

SW_HIDE	화면에 감춘다.

SW_MINIMIZE	윈도를 최소 상태로 출력한다.

SW_SHOW	화면에 보인다.

SW_SHOWMAXIMIZED	모니터 전체 스크린에 꽉 차게 윈도를 출력한다.

SW_SHOWMINIMIZED	아이콘 형태로 출력한다.

SW_SHOWNORMAL	보통 윈도로 출력한다.

Create 함수 후에 화면에 윈도를 보여주기 위해서는 ShowWindow(SW_SHOW)를 사용하면 됩니다. 이런 형태로 버튼을 만들 수 있습니다.

ExChildWnd 예제에 버튼 삽입

ExChildWnd 프로젝트를 하나 만들어서 View 클래스에 자식 윈도로 버튼을 하나 만들어 보겠습니다. ExChildWnd 클래스는 AppWizard를 이용하여 만들 때 SDI형으로 제작합니다. 이렇게 만들고 나면 View 클래스명은 CExChildWndView입니다. 이 클래스 헤더에 CButton 클래스형의 변수를 선언합니다. 일반적으로 CExChildWndView 클래스의 헤더 파일은 ExChildWndView.h일 것입니다. 이 헤더에 다음과 같이 선언합니다.

CButton m_pButton;

위와 같이 선언한 후에 클래스 위저드를 이용하여 CExChildWndView에서 OnInitialUpdate 함수를 만듭니다. 이렇게 만든 후에 OnInitialUpdate 함수에 다음과 같이 코딩합니다.

RECT rect={10,10,100,50};

m_pButton.Create(□버튼□,WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON ,rect,this,100);

m_pButton.ShowWindow(SW_SHOW);

이것은 일반적인 버튼 형태로 화면 (10,10,100,50) 위치에 버튼을 출력하라는 의미입니다.

위와 같이 한 후에 컴파일하고 실행하면 그림 1-2-15와 같은 화면이 출력됩니다.

그림 1-2-15처럼 되는 이유는 CButton 클래스의 변수 m_pButton을 View 클래스, 즉 CExChildWndView에 설정했기 때문입니다.(View 윈도가 어떤 것인지는 전에 설명했습니다.) 화면 위치를 10,10이라고 했기 때문에 View 윈도의 (10,10) 좌표에서부터 시작하여 (100,50) 좌표까지로 버튼 윈도가 나타납니다. Create를 실행할 때 첫 번째 인자, 즉 문자열을 □버튼□이라고 하였기 때문에 화면에 버튼이 출력되며 자식 윈도이면서(WS_CHILD) 화면에 보이고(WS_VISIBLE), 그리고 일반 버튼(BS_PUSHBUTTON) 스타일을 설정했기 때문에 이 형태의 버튼이 나온 것입니다. 이 버튼의 아이디는 Create 함수의 마지막 값이 100입니다. 이 값의 이용에 대해서는 뒤에 언급할 것입니다.

리스트 상자 윈도 만들기

리스트 상자는 여러 개의 데이터를 리스트 형식으로 출력할 수 있는 윈도를 의미합니다. 리스트 상자를 출력하는 클래스가 CListBox입니다. 이 클래스의 CListBox:: Create 함수는 다음과 같습니다.

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

dwStyle : 윈도 스타일

rect : 윈도가 출력되는 좌표

pParentWnd : 부모 윈도 포인터

nID : 윈도 ID

dwStyle은 리스트 상자를 출력하는 값입니다. 표3은 리스트 상자 스타일 리스트입니다.

표3  리스트 상자 스타일

윈도 스타일	내용

LBS_EXTENDEDSEL	다중 선택할 경우 Shift와 마우스를 이용할 수 있게 한다.

LBS_MULTIPLESEL	여러 개의 아이템을 선택할 수 있도록 한다.

LBS_OWNERDRAWFIXED	사용자 그리기, 리스트 상자 크기가 고정된 상태

LBS_OWNERDRAWVARIABLE	사용자 그리기, 리스트 상자 크기가 변함

LBS_SORT	데이터가 가나다 순으로 정렬됨

LBS_STANDARD	표준 리스트 상자

이제 ExChildWnd에 리스트 상자를 출력해 보도록 하겠습니다. 먼저 CExChild WndView 헤더(ExChildWndView.h)에 다음과 같이 리스트 상자 클래스의 변수를 추가합니다.

CListBox m_pListBox;

위와 같이 선언하고 버튼을 출력한 View 클래스의 OnInitialUpdate() 함수에 다음과 같은 내용을 추가합니다.

RECT rect1={10,100,200,200};

m_pListBox.Create(WS_CHILD | WS_VISIBLE | LBS_STANDARD,rect1,this,200);

m_pListBox.ShowWindow(SW_SHOW);

// 데이터 삽입

m_pListBox.AddString(□data1□);

m_pListBox.AddString(□data2□);

m_pListBox.AddString(□data3□);

CListBox의 멤버 함수 중에 AddString 함수는 리스트 상자에 데이터를 설정하는 함수입니다. AddString(□data1□);이라고 하면 리스트 상자에 □data1□이라는 문자열이 삽입됩니다.

m_pListBox.Create 함수를 실행시킬 때 표준 리스트 상자 스타일로 화면에 (10,100,200,200)의 좌표 위치에 ID가 200인 형태로 윈도를 만든 것입니다. 이렇게 만든 후에 ShowWindow 함수를 이용하여 윈도를 출력하고, 이 윈도에 CListBox::AddString 함수를 이용하여 □data1□, □data2□, □data3□ 값을 입력하였습니다.

이와 같이 한 후에 컴파일하고 실행하면 그림 1-2-16과 같은 화면이 출력됩니다.

에디터 상자 윈도 만들기

에디터 상자란  편집이 가능한 상자를 의미합니다. 쉽게 이야기하면 노트패드와 같이 키보드에서 문자를 입력했을 때 이것이 출력되는 상자를 의미합니다. 에디터 상자를 담당하는 클래스는 CEdit입니다. CEdit 클래스의 Create 함수는 다음과 같습니다.

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

dwStyle : 에디터 상자 스타일

rect : 에디터 상자가 위치하는 영역 좌표

nID : 에디터 상자 ID

dwStyle은 에디터 상자의 스타일값들입니다. 이 값의 리스트는 표4와 같습니다.

표4   에디터 상자 스타일

에디터	상자 스타일 내용

ES_AUTOHSCROLL	문자를 입력하면 수평으로 자동적으로 스크롤된다.

ES_AUTOVSCROLL	문자를 여러 줄 입력시 수직으로 자동 스크롤이 발생한다.

ES_CENTER	문자가 중앙을 기점으로 정렬된다.

ES_LEFT	문자가 좌측을 기점으로 정렬된다.

ES_RIGHT	문자가 우측을 기점으로 정렬된다.

ES_MULTILINE	여러 라인을 입력할 수 있도록 한다.

ES_PASSWORD	비밀 번호를 입력하는 형태

ES_READONLY	읽기 전용, 키보드에서 문자가 입력되지 않는다.

ES_WANTRETURN	에디터 상자에서 Enter 키를 받는다.

이제 CExChildWndView에 에디터 상자를 추가시켜 보겠습니다. 에디터 상자를 추가시키는 방법은 지금까지 윈도를 만든 방법과 같습니다. 제일 먼저 CExChildWnd View 헤더(ExChildWndView.h)에 다음과 같이 형을 선언합니다.

CEdit m_pEditBox;

다음으로 CExChildWndView의 OnInitialUpdate 함수에 다음과 같이 기록하여 에디터 상자를 출력합니다.

RECT rect2={210,10,400,100};

//

m_pEditBox.Create(

WS_CHILD | WS_VISIBLE |

ES_MULTILINE // 여러 라인 입력

| ES_AUTOHSCROLL | ES_AUTOVSCROLL| // 자동 수직, 수평 스크롤

WS_BORDER |WS_VSCROLL, // 외곽선과 수직 스크롤바 설정

rect2,this,300);

m_pEditBox.ShowWindow(SW_SHOW);

위와 같이 한 후에 컴파일하고 실행하면 그림 1-2-17과 같이 결과가 출력됩니다.

CWnd를 상속받은 윈도 출력

지금까지 버튼, 리스트 상자, 에디터 상자를 출력하는 방법에 대해서 설명하였습니다. MFC에서 지원하는 윈도들은 이외에도 매우 많습니다. 앞 장을 공부할 때 CWnd를 상속받은 윈도들을 보면 알 수 있을 것입니다. View 윈도를 제외한 모든 윈도들은 Create 함수를 이용하여 출력할 수 있습니다. 다만 Create에 설정되는 인자가 모두 다릅니다. 인자의 순서가 다르기도 하고 각 스타일이 다르기도 합니다. 본 책에서는 자식 윈도을 만드는 방법에 대해서 모두 설명하지 않습니다. 다만 Create를 이용하여 윈도를 만드는 방법을 설명하겠습니다.

예를 들어서 콤보 상자를 만들려고 한다면 먼저 콤보 상자를 만드는 클래스를 알아야 합니다. 이 클래스가 어떤 것인가는 앞 장에서 설명하였습니다. 콤보 상자를 만드는 클래스는 CComboBox입니다. Help 메뉴에서 Serach 항목을 선택하고 CComboBox를 입력합니다. 이렇게 입력하면 CComboBox class members라는 항목이 나타날 것입니다. 이항목을 클릭하면 그림 1-2-18과 같은 형태가 나옵니다.

여기에서 Initialization에 Create 함수가 있을 것입니다. 이 항목을 클릭합니다. 이때 나타나는 화면은 그림 1-2-19와 같습니다.

그림 1-2-19의 화면이 CComboBox::Create 함수에 대한 설명입니다. dwStyle은 윈도 스타일이며, 이 스타일 리스트를 보려면 combo-box sytles를 참조하라는 말부터 각 인자의 사용법에 대해서 설명이 나와 있습니다. 이 설명을 참고하여 Create 함수를 이용하여 윈도를 만들면 됩니다.

Create 함수는 SDK의 CreateWindow와 같은 기능을 합니다. 자식 윈도를 만드는 부분이 이해가 가지 않는다면 필자가 쓴 책 「Windows Programming Bible」의 자식 윈도 컨트롤 항목을 참조하기 바랍니다.

본 항목에서는 자식 윈도에서 발생하는 메시지를 처리하는 방법에 대해서 설명하겠습니다. 전 항목에서 만든 예제 ExChildWnd로 버튼이 눌려지거나 띄어지는 이벤트, 리스트 상자에서 특정 항목을 선택하는 이벤트, 에디터 상자에서 데이터가 변하거나 포커스가 사라지는 형태의 이벤트를 알 수 있는 방법을 ExChildWnd 프로그램을 수정해 가면서 설명하겠습니다.

자식 윈도 이벤트 메시지

자식 윈도 이벤트 메시지 형태는 그림 1-2-20과 같습니다.

자식 윈도에서 이벤트가 발생했을 때 수행되는 함수를 만들려면 먼저 ①과 같이 소스 헤더 부분에 afx_msg라는 형을 앞에 쓰고, 자식 윈도에서 특정 이벤트가 발생했을 때 수행하는 함수를 선언합니다. 다음 ②와 같이 소스의 상위 부분에 있는 메시지 매크로 선언 사이에 메시지 매크로명과 ID, 그리고 ①에서 선언한 함수명을 설정합니다. 이렇게 한 후에 ③처럼 해당 함수를 만들고 이 함수에 내용을 기록하면 됩니다.

예를 들어 보겠습니다. ExChildWnd 예제에서 버튼이 클릭되었을 때 □버튼이 클릭됨□이라는 메시지를 출력하려면, 먼저 헤더에 그림 1-2-20의 ① 항목처럼 afx_msg void OnButtonDown()이라는 함수를 선언하고, ② 항목처럼 메시지 매크로에 ON_BN_CLICKED(100,OnButtonDown)이라고 선언한 다음 소스에 다음과 같이 선언합니다.

void CExChildWndView::OnButtonDown()

{

AfxMessageBox(□버튼이 클릭됨□);

}

실제 위와 같이 하고 프로그램을 컴파일하여 실행시켜 버튼을 클릭하면 그림 1-2-21과 같은 화면이 출력됩니다.

그림 1-2-20에서 보듯이 함수명은 우리가 만들 수 있습니다. 또한 메시지 매크로명에 Create 함수에 설정한 ID값과 우리가 만든 함수명을 설정하면 됩니다. 그러나 우리가 모르는 것이 있습니다. 바로 메시지 매크로명입니다. 버튼이 눌려졌을 때는 ON_BN_CLICKED인데, 이것은 MFC에 정의되어 있는 메시지 매크로입니다. 이제 이 매크로들에는 어떤 것들이 있는가를 알아보겠습니다.

버튼 이벤트 메시지 매크로

버튼 이벤트 메시지 매크로는 표5와 같습니다.

표5   버튼 이벤트 메시지 매크로

이벤트 메시지 매크로	내용

ON_BN_CLICKED	버튼이 클릭되었을 때

ON_BN_DISABLE	버튼이 비활성화되었을 때

ON_BN_DOUBLECLICKED	버튼을 더블 클릭했을 때

ON_BN_PAINT	버튼의 내용이 화면에 출력될 때

ON_BN_HILITE	버튼이 하이라이트될 때

ON_BN_UNHILITE	버튼의 하이라이트가 해제될 때

버튼이 클릭된 예제는 전 항목에서 설명하였습니다. 버튼이 비활성화된다는 의미는 현재 버튼이 회색 형태로 출력되는 것을 의미합니다. 보통 버튼에 대한 메시지 매크로는 ON_BN_ 계열로 설정됩니다. 이 매크로를 사용하는 방법은 그림 1-2-20과 같은 방법으로 설정합니다.

에디터 상자 이벤트 메시지 매크로

에디터 상자에서 이벤트 메시지 매크로는 표6과 같습니다.

표6   에디터 상자 이벤트 메시지 매크로

이벤트 메시지 매크로	내용

ON_EN_CHANGE	에디터 상자에서 데이터가 수정되었을 경우

ON_EN_ERRSPACE	에디터 상자에서 더 이상 데이터를 넣을 메모리가 없을 경우

ON_EN_HSCROLL	수평으로 스크롤될 때

ON_EN_KILLFOCUS	현재 에디터 상자에서 포커스가 사라질 때

ON_EN_MAXTEXT	에디터 상자의 최대 문자열일 경우

ON_EN_SETFOCUS	에디터 상자에 포커스가 설정되었을 때

ON_EN_VSCROLL	수직으로 스크롤될 때

ExChildWnd 예제에서 에디터 상자 이벤트 메시지 매크로를 사용하여 예제를 수정해 보겠습니다.

에디터 상자에 포커스가 맞추어졌을 때 에디터 상자에 □에디터 상자에 포커스 맞추어짐□ 문자열을 삽입해 보겠습니다. 먼저 ExChildWnd 클래스의 헤더에 다음과 같이 선언합니다.

afx_msg void OnEditFocus();

위와 같이 선언하고 메시지 매크로 선언부에 다음과 같이 선언합니다.

ON_EN_SETFOCUS(300,OnEditFocus)

위와 같이 선언하면 ID가 300번인 에디터 상자에서 포커스가 설정되었을 때 OnEditFocus라는 함수가 수행된다는 의미입니다. 이 함수에 다음과 같이 하면 □에디터 상자에 포커스 맞추어짐□이라는 문장이 에디터 상자에 삽입됩니다.

void CExChildWndView::OnEditFocus()

{

m_pEditBox.SetWindowText(□에디터 상자에 포커스 맞추어짐□);

}

SetWindowText 함수는 CWnd의 멤버 함수로써 현재 윈도의 캡션바에 문자열을 출력하는 함수입니다. 이 함수를 에디터 상자에 적용시키면 에디터 상자에 문자열을 설정하는 형식이 됩니다.

또 하나의 메시지 매크로를 삽입해 보겠습니다. 이번에는 에디터 상자에서 포커스가 사라졌을 경우 현재 에디터 상자에 입력된 데이터가 메시지 상자로 출력되는 형태를 만들어 보겠습니다.

먼저 헤더에 다음과 같이 설정합니다.

afx_msg void OnEditKillFocus();

위와 같이 선언하고 메시지 매크로부에 다음과 같이 선언합니다.

ON_EN_KILLFOCUS(300,OnEditKillFocus)

이제 ExChildWndView.cpp에 다음과 같은 함수를 등록합니다.

void CExChildWndView::OnEditKillFocus()

{

CString data;

m_pEditBox.GetWindowText(data);

AfxMessageBox(data);

}

GetWindowText는 CWnd의 멤버 함수로써 현재 윈도 캡션바에 있는 문자열을 얻는 기능을 합니다. 이 함수를 에디터 상자에 적용시키면 현재 입력된 데이터가 들어오게 됩니다. CString은 문자열 처리 클래스입니다. 이 클래스는 앞으로 계속 사용하면서 익히게 될 것입니다. 문자열을 처리하는 대표적인 클래스로 생각하면 됩니다. CString 클래스의 변수인 data를 설정하고 이 인자를 GetWindowText에 설정하면 현재 에디터 상자에 입력된 문자들이 모두 data에 저장됩니다. 이렇게 한 후 AfxMessageBox를 호출시키면 data에 입력된 문자들이 화면에 출력됩니다.

위와 같이 하고 컴파일하여 실행시시키고 에디터 상자에 마우스를 클릭하면 에디터 상자 안으로 □에디터 상자에 포커스 맞추어짐□이라는 글자가 삽입됩니다. 후에 리스트 상자나 버튼을 클릭하면 포커스가 사라지면서 그림 1-2-22와 같이 입력된 문자열이 메시지 상자로 출력됩니다.

리스트 상자 이벤트 메시지 매크로

리스트 상자 이벤트 메시지 매크로는 표7과 같습니다.

표7   리스트 상자 이벤트 메시지 매크로

이벤트 메시지 매크로	내용

ON_LBN_DBLCLK	리스트 상자에서 특정 항목을 더블 클릭했을 때

ON_LBN_ERRSPACE	리스트 상자에 더 이상의 메모리가 없을 경우

ON_LBN_KILLFOCUS	리스트 상자에서 포커스가 사라질 때

ON_LBN_SELCHANGE	리스트 상자에서 선택이 바뀌었을 때

ON_LBN_SETFOCUS	리스트 상자에 포커스가 설정되었을 때

ExChildWnd의 리스트 상자에서 특정 항목을 더블 클릭했을 때 현재 설정된 항목의 내용이 출력되는 예를 작성해 보겠습니다. 먼저 헤더에 다음과 같이 함수를 선언합니다.

afx_msg void OnListBoxDbClick();

다음 ExChildWndView.cpp의 매크로 선언부에 다음과 같이 선언합니다.

ON_LBN_DBLCLK(200,OnListBoxDbClick)

이제 다음과 같은 함수를 ExChildWndView.cpp에 설정합니다.

void CExChildWndView::OnListBoxDbClick()

{

CString data;

int num;

// 현재 선택된 리스트 번호를 얻고

num=m_pListBox.GetCurSel();

// 현재 선택된 리스트 번호를 넘겨주고 문자열을 data에 얻는다.

m_pListBox.GetText(num,data);

AfxMessageBox(data);

}

GetCurSel 함수는 CListBox의 멤버 함수로 현재 선택된 리스트의 번호를 리턴합니다. 이 번호를 GetText의 첫 번째 인자로 넘겨주고 CString형의 변수인 data를 두 번째 인자로 넘겨주면 data에 현재 선택된 리스트 상자의 문자열이 넘어오게 됩니다. 이데이터를 메시지 상자로 출력하면 됩니다.

이렇게 한 후 컴파일하여 실행하고 리스트 상자에서 특정 아이템을 더블 클릭하면 그림 1-2-23과 같은 화면이 출력됩니다.

다른 이벤트 메시지 매크로

본 항목에서 다루지 않은 여러 종류의 메시지 매크로가 있습니다. 대표적으로는 CComboBox를 컨트롤하는 메시지 매크로입니다. 콤보 상자의 이벤트 메시지 매크로는 리스트 상자와 거의 같습니다. 다만 메시지 매크로에서 ON_LBN이 ON_CBN이 되는 것만 다릅니다. 표8은 콤보 상자에서 설정하는 이벤트 메시지 매크로입니다.

표8  콤보 상자 이벤트 메시지 매크로

이벤트 메시지 매크로	내용

ON_CBN_DBLCLK	콤보 상자에서 특정 항목을 더블 클릭했을 때

ON_CBN_ERRSPACE	콤보 상자에 더 이상의 메모리가 없을 경우

ON_CBN_KILLFOCUS	콤보 상자에서 포커스가 사라질 때

ON_CBN_SELCHANGE	콤보 상자에서 선택이 바뀌었을 때

ON_CBN_SETFOCUS	콤보 상자에 포커스가 설정되었을 때

ON_CBN_EDITCHANGE	콤보 상자 상단에서 문자를 입력하였을 때

ON_CBN_DROPDOWN	콤보 상자에서 리스트를 보기 위해 드롭다운 버튼을 클릭했을 때

콤보 상자를 만들고 출력시키는 부분은 여러분이 직접 해보기 바랍니다. 본 장에서 제작한 ExChildWnd에 콤보 상자를 만들고, 이 콤보 상자에 특정 항목을 선택하였을 때 함수를 만들고, 메시지 상자로 선택한 문자열을 출력하는 방법에 대해서 직접 제작해보십시오. 콤보 상자에 대한 메시지 매크로를 사용하였다면 본 항목까지의 내용을 이해한 것입니다.

이벤트 메시지는 두 개의 메시지와 wParam과 lParam값에 의해서 다시 분리되어 얻어집니다. 지금까지 사용한 리스트 상자, 버튼, 에디터 상자, 콤보 상자 등은 WM_COMMAND 메시지와 함께 들어오는 wParam의 상위 비트를 이벤트 메시지를 이용하여 어떤 메시지를 분석하고, 메시지 이벤트 매크로와 결합하여 분리한 것입니다. WM_COMMAND에 의해서 수행되는 함수를 클래스 위저드로 만들 때는 OnCommand 항목을 클릭하면 됩니다.

이때 OnCommand는 다음과 같은 형태로 만들어집니다.

BOOL CExChildWndView::OnCommand(WPARAM wParam, LPARAM lParam)

{

// TODO: Add your specialized code here and/or call the base class

return CView::OnCommand(wParam, lParam);

}

SDK 프로그램을 해보았다면 위의 의미를 대충 짐작할 수 있을 것입니다. WM_COMMAND의 메시지에 의해 수행되는 함수가 OnCommand이며, 이 함수에 들어오는 인자 wParam과 lParam에 의해서 어떤 이벤트인지를 알 수 있습니다. wParam의 상위 16비트에는 이벤트 메시지값이 설정되어 있으며, 하위 16비트에는 현재 발생된 이벤트 메시지의 윈도 ID가 들어 있습니다. ExChildWnd 프로젝트의 리스트 상자에서 항목을 더블 클릭했을 경우 이것을 OnCommand에서 만든다면 다음과 같은 루틴이 됩니다.

BOOL CExChildWndView::OnCommand(WPARAM wParam, LPARAM lParam)

{

// TODO: Add your specialized code here and/or call the base class

switch(LOWORD(wParam)) // 자식 윈도 아이디에 따라서 분리

{

case 200: // 200은 리스트 상자 ID

switch(HIWORD(wParam))

{

case LBN_DBLCLK: // 리스트 상자에서 더블 클릭하면

{

CString data;

int num;

// 현재 선택된 리스트 번호를 얻고

num=m_pListBox.GetCurSel();

// 현재 선택된 리스트 번호를 넘겨주고

// 문자열을 data에 얻는다.

m_pListBox.GetText(num,data);

AfxMessageBox(data);

}

break;

}

break;

}

return CView::OnCommand(wParam, lParam);

}

위의 내용대로 하면 스위치문이 두 개가 중복되고 다소 프로그램이 복잡해질 것입니다. MFC에서는 이런 복잡성을 단순화하기 위해서 메시지 매크로를 이용하여 각각의 함수로 분리한 것입니다.

ExChildWnd에 콤보 상자를 만들고 이 콤보 상자에 항목이 변경되었을 때 현재 선택한 항목이 어떤 것인가를 출력하는 예를 추가해 보겠습니다. 이때에는 OnCommand 함수를 이용하겠습니다.

먼저 ExChildWndView.h의 OnInitialUpdate() 함수 안에 콤보 상자를 만드는 내용을 코딩합니다. 코딩 내용은 다음과 같습니다.

RECT rect3={210,200,400,300}; // 콤보 상자 출력 위치

// 윈도 만들기

m_pComboBox.Create(WS_CHILD | WS_VISIBLE|CBS_DROPDOWN ,rect3,this,400);

m_pComboBox.ShowWindow(SW_SHOW);

// 콤보 상자에 데이터를 넣는다.

m_pComboBox.AddString(□cdata1□);

m_pComboBox.AddString(□cdata2□);

m_pComboBox.AddString(□cdata3□);

위와 같이 코딩한 후에 클래스 위저드를 이용하여 CExChildWndView 클래스에서 OnCommand 함수를 만들고, 그 함수에 다음과 같이 코딩하여 콤보 상자에서 항목이 선택되었을 때 메시지 상자가 출력되어 선택된 항목의 문자열을 출력하도록 합니다.

BOOL CExChildWndView::OnCommand(WPARAM wParam, LPARAM lParam)

{

// TODO: Add your specialized code here and/or call the base class

switch(LOWORD(wParam))

{

case 400: // 콤보 상자 ID

switch(HIWORD(wParam)) // 이벤트 메시지

{

case CBN_SELCHANGE: // 새로운 항목이 선택되었을 때

{

// 현재 선택된 항목 번호를 얻는다.

int num=m_pComboBox.GetCurSel();

CString data;

// num 항목의 데이터를 얻는다.

m_pComboBox.GetLBText(num,data);

// 메시지 상자로 출력

AfxMessageBox(data);

}

break;

}

break;

}

return CView::OnCommand(wParam, lParam);

}

위와 같이 한 후 컴파일하여 프로그램을 실행하고 콤보 상자에서 특정 항목을 선택하면 1-2-24와 같은 결과가 출력됩니다.

이벤트 메시지는 WM_COMMAND 외에 WM_NOTIFY에 의해서 이벤트 메시지가 넘어옵니다. 이때 wParam에는 윈도 ID가 설정되며, lParam에는 LPNMHDR 구조체의 포인터가 넘겨져옵니다. WM_NOTIFY에 의해서 발생되는 것은 컨트롤 클래스들입니다. 여기서의 컨트롤 클래스는 Windows 95 이후부터 만들어졌던 리스트 컨트롤, 트리 컨트롤, 탭 컨트롤, 슬라이더, 스핀 등등을 의미합니다. 이 부분에 대한 내용은 뒤의 컨트롤 클래스 Chapter에서 자세히 설명하겠습니다.

지금까지는 MFC에서 제공하는 메시지와 이벤트에 대해서 설명하였습니다. 때로는 사용자가 메시지를 만들어서 이것을 보내고자 할 경우가 있습니다. 본 항목에서는 사용자가 메시지를 만들고 이것을 이용하여 함수를 만드는 방법에 대해서 설명하겠습니다. 사용자 메시지를 만드는 방법은 그림 1-2-25와 같습니다.

그림 1-2-25에서 보는 것과 같이 먼저 사용자 메시지를 선언해야 합니다. WM_USER 이후의 값들은 모두 사용자가 정의할 수 있도록 되어 있기 때문에 WM_USER 이후의 값을 이용하여 사용자 메시지를 만들면 됩니다. 그림 1-2-25의 ① 항목이 바로 사용자 메시지를 정의하는 항목입니다. ①의 EX처럼 만일 WM_MYMESSAGE라는 새로운 메시지를 만들고자 한다면 #define문을 이용하여 WM_USER 이후의 값을 선언합니다. WM_USER+2는 WM_USER값에서 2만큼 더 큰 값을 WM_MYMESSAGE로 선언한다는 의미입니다. 이렇게 선언한 다음 이 메시지가 발생했을 때 수행되는 함수를 ② 항목처럼 헤더부에 설정합니다. 이때 afx_msg는 같으나 함수형이 void가 아닌 long으로 해주어야 합니다. 사용자 메시지에 의해서 어떤 결과가 있을 때 이 결과를 포인터로 받기 위함입니다. 또한 메시지의 기본 인자인 WPARAM과 LPARAM을 두 개의 인자로 설정해주어야 합니다. 이렇게 설정한 후에 메시지 매크로에는 ③ 항목처럼 ON_MESSAGE 매크로를 이용하여 정의한 메시지명과 함수명을 결합하여 선언합니다. 이렇게 한 후에 ② 항목에서 선언한 함수를 소스부에 만들고 해당 메시지가 수행되었을 때의 함수를 만들어 주면 됩니다.

사용자가 만든 메시지는 윈도 시스템에서 호출되지 않습니다. 즉 사용자가 만든 메시지는 사용자가 불러야 합니다. 이때 사용하는 함수가 SendMessage입니다. 이 함수는 CWnd의 멤버 함수로서 그 형은 다음과 같습니다.

LRESULT SendMessage( UINT message, WPARAM wParam = 0, LPARAM lParam = 0 );

message : 보내고자 하는 메시지

wParam : 메시지 정보와 함께 보내주는 WPARAM 인자

lParam : 메시지 정보와 함께 보내는 LPARAM 인자

그림 1-2-25의 예에서 만일 OnMyMessage 함수를 호출하고자 한다면 다음과 같이 하면 됩니다.

SendMessage(WM_MYMESSAGE,0,0);

ExChildWnd 예제에 사용자 메시지를 추가하겠습니다. 지금까지 설명한 방법을 그대로 이용하면 됩니다.

먼저 ExChildWndView.h(CExChildWndView 헤더)에 다음과 같이 설정합니다.

afx_msg long OnMyMessage(WPARAM wParam, LPARAM lParam);

이제 그림 1-2-25의 ① 항목을 설정합니다. 본 예제에서는 ExChildWndView.cpp에 선언하였습니다.

#define WM_MYMESSAGE WM_USER+2

다음 ExChildWndView.cpp의 매크로 선언부에 다음과 같이 매크로를 정의합니다.

ON_MESSAGE(WM_MYMESSAGE,OnMyMessage)

위와 같이 정의한 후에 OnMyMessage 함수를 다음과 같이 만듭니다.

long CExChildWndView::OnMyMessage(WPARAM wParam, LPARAM lParam)

{

CString p;

p=(LPCSTR)lParam; // lParam으로 넘겨준 포인터의 문자열을 CString에 넘긴다.

AfxMessageBox(p);

return 1;

}

OnMyMessage 함수는 lParam에 설정된 문자열 포인터를 받아서 이것을 CString으로 넘긴 다음 이것을 메시지 상자로 출력하는 기능을 합니다. 간단하게 보면 OnMyMessage라는 함수는 문자열을 메시지 상자로 출력하는 기능을 합니다.

이제 WM_MYMESSAGE를 발생시켜서 OnMyMessage 함수를 호출하도록 해보겠습니다.

버튼이 눌려졌을 때 수행하는 OnButtonDown() 함수에 다음과 같은 내용을 추가합니다.

CString temp;

temp=□사용자 메시지 전송□;

SendMessage(WM_MYMESSAGE,0,(LPARAM)(LPCSTR)temp);

CString형인 temp에 □사용자 메시지 전송□이라는 문장을 넣고 이것을 LPARAM에 넘기면서 WM_MYMESSAGE를 호출한 것입니다. 여기에서 두 개의 형 전환이 일어나는데, temp라는 CString형 앞에 [LPCSTR]이라는 형 전환을 하면 문자열 포인터 형태로 전환됩니다. 이렇게 전환된 값을 다시 [LPARAM]형으로 전환한 것입니다. 이렇게 전환하여 OnMyMessage에 넘기면 다시 OnMyMessage는 [LPARAM]형을 CString형으로 전환하여 메시지 상자에 출력합니다.

위처럼 코딩한 후에 컴파일하고 실행하여 버튼을 클릭하면 처음에는 □버튼이 클릭됨□이라는 문장이 메시지 상자로 출력되고, 그 다음에는 OnMyMessage 함수가 실행되면서 □사용자 메시지 전송□이라는 문장이 메시지 상자로 출력됩니다.

그림 1-2-26은 □사용자 메시지 전송□이라는 문장이 출력된 결과입니다.

ExChildWnd의 중요 소스

View 클래스 헤더

// ExChildWndView.h : interface of the CExChildWndView class

//

//////////////////////////////////////////////////////////////////////////

#if

!defined(AFX_EXCHILDWNDVIEW_H__5C17652D_1BEA_11D2_A6C0_00403320A530__INCLUDED_)

#define AFX_EXCHILDWNDVIEW_H__5C17652D_1BEA_11D2_A6C0_00403320A530__INCLUDED_

#if _MSC_VER > 1000

#pragma once

#endif //  _MSC_VER > 1000

class CExChildWndView : public CView

{

protected: // create from serialization only

CExChildWndView();

DECLARE_DYNCREATE(CExChildWndView)

// Attributes

public:

CExChildWndDoc* GetDocument();

// Operations

public:

// Overrides

// ClassWizard generated virtual function overrides

//{{AFX_VIRTUAL(CExChildWndView)

public:

virtual void OnDraw(CDC* pDC);  // overridden to draw this view

virtual BOOL PreCreateWindow(CREATESTRUCT& cs);

virtual void OnInitialUpdate();

protected:

virtual BOOL OnPreparePrinting(CPrintInfo* pInfo);

virtual void OnBeginPrinting(CDC* pDC, CPrintInfo* pInfo);

virtual void OnEndPrinting(CDC* pDC, CPrintInfo* pInfo);

// WM_COMMAND  메시지 발생시 수행하는 함수

virtual BOOL OnCommand(WPARAM wParam, LPARAM lParam);

//}}AFX_VIRTUAL

// Implementation

public:

// 자식 윈도 콤보 상자

CComboBox m_pComboBox;

// 자식 윈도 에디터 상자

CEdit m_pEditBox;

// 자식 윈도 리스트 상자

CListBox m_pListBox;

// 자식 윈도 버튼

CButton m_pButton;

virtual ~CExChildWndView();

#ifdef _DEBUG

virtual void AssertValid() const;

virtual void Dump(CDumpContext& dc) const;

#endif

protected:

// Generated message map functions

protected:

//{{AFX_MSG(CExChildWndView)

// NOTE - the ClassWizard will add and remove member functions here.

//   DO NOT EDIT what you see in these blocks of generated code !

afx_msg void OnButtonDown(); // 버튼이 눌려졌을 때

afx_msg void OnEditFocus(); // 에디터 상자에서 포커스가 설정되었을 때

afx_msg void OnEditKillFocus(); // 에디터 상자에서 포커스가 사라졌을 때

afx_msg void OnListBoxDbClick(); // 리스트 상자에서 더블 클릭했을 때

afx_msg long OnMyMessage(WPARAM wParam, LPARAM lParam);

// 사용자 메시지 처리 함수

//}}AFX_MSG

DECLARE_MESSAGE_MAP()

};

#ifndef _DEBUG  // debug version in ExChildWndView.cpp

inline CExChildWndDoc* CExChildWndView::GetDocument()

{ return (CExChildWndDoc*)m_pDocument; }

#endif

//////////////////////////////////////////////////////////////////////////

//{{AFX_INSERT_LOCATION}}

// Microsoft Visual C++ will insert additional declarations immediately before the previous line.

#endif //

!defined(AFX_EXCHILDWNDVIEW_H__5C17652D_1BEA_11D2_A6C0_00403320A530__INCLUDED_)

View 클래스 소스

// ExChildWndView.cpp : implementation of the CExChildWndView class

//

#include □stdafx.h□

#include □ExChildWnd.h□

#include □ExChildWndDoc.h□

#include □ExChildWndView.h□

#define WM_MYMESSAGE WM_USER+2

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

//////////////////////////////////////////////////////////////////////////

// CExChildWndView

IMPLEMENT_DYNCREATE(CExChildWndView, CView)

// 메시지 매크로 선언

BEGIN_MESSAGE_MAP(CExChildWndView, CView)

//{{AFX_MSG_MAP(CExChildWndView)

// NOTE - the ClassWizard will add and remove mapping macros here.

// DO NOT EDIT what you see in these blocks of generated code!

ON_BN_CLICKED(100,OnButtonDown) // 버튼 클릭

ON_EN_SETFOCUS(300,OnEditFocus) // 에디터 상자 포커스 설정

ON_EN_KILLFOCUS(300,OnEditKillFocus) //에디터 상자 포커스 사라짐

ON_LBN_DBLCLK(200,OnListBoxDbClick) // 리스트 상자 더블 클릭

ON_MESSAGE(WM_MYMESSAGE,OnMyMessage) // 사용자 메시지

//}}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()

//////////////////////////////////////////////////////////////////////////

// CExChildWndView construction/destruction

CExChildWndView::CExChildWndView()

{

// TODO: add construction code here

}

CExChildWndView::~CExChildWndView()

{

}

BOOL CExChildWndView::PreCreateWindow(CREATESTRUCT& cs)

{

// TODO: Modify the Window class or styles here by modifying

// the CREATESTRUCT cs

return CView::PreCreateWindow(cs);

}

//////////////////////////////////////////////////////////////////////////

// CExChildWndView drawing

void CExChildWndView::OnDraw(CDC* pDC)

{

CExChildWndDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

// TODO: add draw code for native data here

}

//////////////////////////////////////////////////////////////////////////

// CExChildWndView printing

BOOL CExChildWndView::OnPreparePrinting(CPrintInfo* pInfo)

{

// default preparation

return DoPreparePrinting(pInfo);

}

void CExChildWndView::OnBeginPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)

{

// TODO: add extra initialization before printing

}

void CExChildWndView::OnEndPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)

{

// TODO: add cleanup after printing

}

//////////////////////////////////////////////////////////////////////////

// CExChildWndView diagnostics

#ifdef _DEBUG

void CExChildWndView::AssertValid() const

{

CView::AssertValid();

}

void CExChildWndView::Dump(CDumpContext& dc) const

{

CView::Dump(dc);

}

CExChildWndDoc* CExChildWndView::GetDocument() // non-debug version is inline

{

ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CExChildWndDoc)));

return (CExChildWndDoc*)m_pDocument;

}

#endif //_DEBUG

//////////////////////////////////////////////////////////////////////////

// CExChildWndView message handlers

void CExChildWndView::OnInitialUpdate()

{

CView::OnInitialUpdate();

// TODO: Add your specialized code here and/or call the base class

// 버튼 만들기

RECT rect={10,10,100,50};

m_pButton.Create(□버튼□,WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON ,rect,this,100);

m_pButton.ShowWindow(SW_SHOW);

// 리스트 상자 만들기

RECT rect1={10,100,200,200};

m_pListBox.Create(WS_CHILD | WS_VISIBLE | LBS_STANDARD, rect1,this,200);

m_pListBox.ShowWindow(SW_SHOW);

// 데이터 삽입

m_pListBox.AddString(□data1□);

m_pListBox.AddString(□data2□);

m_pListBox.AddString(□data3□);

// 에디터 상자 만들기

RECT rect2={210,10,400,100};

m_pEditBox.Create(

WS_CHILD | WS_VISIBLE |

ES_MULTILINE // 여러 라인 입력

| ES_AUTOHSCROLL | ES_AUTOVSCROLL| // 자동 수직, 수평 스크롤

WS_BORDER |WS_VSCROLL, // 외곽선과 수직 스크롤바 설정

rect2,this,300);

m_pEditBox.ShowWindow(SW_SHOW);

// 콤보 상자 만들기

RECT rect3={210,200,400,300}; // 콤보 상자 출력 위치

// 윈도 만들기

m_pComboBox.Create(WS_CHILD | WS_VISIBLE|CBS_DROPDOWN ,rect3, this,400);

m_pComboBox.ShowWindow(SW_SHOW);

// 콤보 상자에 데이터를 넣는다.

m_pComboBox.AddString(□cdata1□);

m_pComboBox.AddString(□cdata2□);

m_pComboBox.AddString(□cdata3□);

}

// 버튼이 눌려졌을 때

void CExChildWndView::OnButtonDown()

{

AfxMessageBox(□버튼이 클릭됨□);

CString temp;

temp=□사용자 메시지 전송□;

SendMessage(WM_MYMESSAGE,0,(LPARAM)(LPCSTR)temp);

}

// 에디터 상자에 포커스가 설정되었을 때

void CExChildWndView::OnEditFocus()

{

m_pEditBox.SetWindowText(□에디터 상자에 포커스 맞추어짐□);

}

// 에디터 상자에서 포커스가 사라졌을 때

void CExChildWndView::OnEditKillFocus()

{

CString data;

m_pEditBox.GetWindowText(data);

AfxMessageBox(data);

}

// 리스트 상자에서 더블 클릭했을 때

void CExChildWndView::OnListBoxDbClick()

{

CString data;

int num;

// 현재 선택된 리스트 번호를 얻고

num=m_pListBox.GetCurSel();

// 현재 선택된 리스트 번호를 넘겨주고 문자열을 data에 얻는다.

m_pListBox.GetText(num,data);

AfxMessageBox(data);

}

// WM_COMMAND에 의해서 수행되는 함수

BOOL CExChildWndView::OnCommand(WPARAM wParam, LPARAM lParam)

{

// TODO: Add your specialized code here and/or call the base class

switch(LOWORD(wParam))

{

case 400: // 콤보 상자 ID

switch(HIWORD(wParam)) // 이벤트 메시지

{

case CBN_SELCHANGE: // 새로운 항목이 선택되었을 때

{

int num=m_pComboBox.GetCurSel();

CString data;

m_pComboBox.GetLBText(num,data);

AfxMessageBox(data);

}

break;

}

break;

}

return CView::OnCommand(wParam, lParam);

}

// 사용자 메시지 처리 함수

long CExChildWndView::OnMyMessage(WPARAM wParam, LPARAM lParam)

{

CString p;

p=(LPCSTR)lParam;

AfxMessageBox(p);

return 1;

}

지금까지는 하나의 애플리케이션 안에서 필요한 메시지를 받고 그에 해당하는 함수를 만드는 것에 대해 설명하였습니다. 때로는 다른 애플리케이션에서 발생하는 메시지를 가로채고 싶을 경우도 있으며, 시스템에서 제공하는 메시지를 가로채고 싶을 경우도 있을 것입니다. 이렇게 다른 메시지를 가로채는 것을 메시지 훅킹(Message Hooking)이라고 합니다. 본 항목에서는 이런 메시지를 훅킹하는 방법에 대해서 설명하겠습니다. 본 항목은 중□고급자에 해당하는 항목입니다. 본 항목에서는 프로젝트를 만드는 방법이나 각 항목과 변수를 연결하는 방법에 대해서는 설명하지 않습니다. 초보자는 그냥 읽어만 보던지 생략하고 다음 항목으로 넘어가도 됩니다.

메시지 훅킹의 기본 방법

메시지 훅킹의 방법을 도표화한 그림이 1-2-27입니다.

메시지 훅킹을 하고자 할 경우 제일 먼저 SetWindowsHookEx 함수를 이용합니다. 이 함수를 이용하여 가로채고자 하는 메시지와 윈도를 설정합니다. 이 함수에 의해서 설정된 메시지들은 SetWindowsHookEx의 두 번째 인자에 의해 설정된 프로시저가 가로채어 필요한 메시지를 처리합니다. 그림 1-2-27의 ① 항목이 바로 메시지를 가로채기 위해서 설정하는 항목입니다. ① 항목의 ex)를 보면 SetWindowsHookEx의 두 번째 인자에 HookMouseProc라는 함수를 설정한 것을 볼 수 있을 것입니다. 이 프로시저 함수가 그림 1-2-27의 ② 항목이며, ① 항목이 실행된 후에 가로채고자 하는 메시지들은 HookMouseProc 안으로 들어옵니다. 이때 HookMouseProc 함수 안에서 필요한 메시지를 처리하면 됩니다. 메시지 훅킹을 종료하고자 할 경우 그림 1-2-27의 ③ 항목처럼 UnhookWindowsHookEx 함수를 이용하여 종료하면 됩니다.

이제 각 함수에 대해서 알아보겠습니다. 먼저 SetWindowsHookEx 함수입니다. 이 함수의 설명은 다음과 같습니다.

HHOOK SetWindowsHookEx(  int idHook,        // 훅킹 설정 플러그

HOOKPROC lpfn,     // 훅킹을 수행하는 프로시저 함수

HINSTANCE hMod,    // 훅킹하고자 하는 애플리케이션 인스턴스

DWORD dwThreadId   // 애플리케이션 인스턴스의 스레드 ID

);

idHook은 어떤 메시지를 가로챌 것인가를 설정하는 플러그입니다. 이 설정 플러그에 설정할 값들은 표9와 같습니다.

표9    dHook에 설정할 플러그 리스트

idHook	내용

WH_CALLWNDPROC	시스템이 애플리케이션에 전달하는 메시지들을 가로채고자 할 경우 이때 프로시저 함수는 CallWndProc 함수 형태로 만들어야 한다.

WH_CALLWNDPROCRET	시스템이 애플리케이션에 메시지를 전달하고 이 수행이 끝난 다음 이 메시지를 가로채고자 할 경우 이때 만드는 프로시저 함수는 CallWndRetProc 함수 형태로 만들어야 한다.

WH_CBT	CBT(Computer-based training)의 통지 메시지를 가로채고자 할 경우 이때 만드는 프로시저 함수는 CBTProc 형태이어야 한다.

WH_DEBUG	디버깅 메시지를 가로채고자 할 경우 이때 만드는 프로시저 함수는 DebutProc 형태이어야 한다.

WH_FOREGROUNDIDLE	Foreground Idle 형태의 메시지를 가로채고자 할 경우 이때 만드는 프로시저 함수는  ForegroundIdleProc 형태이어야 한다.

WH_GETMESSAGE	메시지 큐에 설정된 메시지를 받고자 할 경우 이때 만드는 프로시저 함수는 GetMsgProc 형태이어야 한다.

WH_JOURNALRECORD	시스템에서 마우스나 키보드 메시지를 가로채고자 할 경우 이때 만드는 프로시저 함수는 JournalRecordProc 형태이어야 한다.

WH_JOURNALPLAYBACK	WH_JOURNALRECORD에 의해서 가로채어진 메시지를 Play할 때 사용함. 이때 만드는 프로시저 함수는 JournalPlaybackProc 형태이어야 한다.

WH_KEYBOARD	키보드에서 눌려진 값들을 가로챌 때 사용함. 이때 만드는 프로시저 함수는 KeyboardProc  형태이어야 한다.

WH_MOUSE	마우스 이벤트를 가로챌 때 사용. 이때 만드는 프로시저 함수는 MouseProc 형태이어야 한다.

lpfn은 설정할 프로시저 함수이며, hMod는 메시지를 가로채고자 하는 애플리케이션 인스턴스이며, dwThreadId는 애플리케이션 스레드 ID입니다.

SetWindowsHookEx 함수는 시스템에서와 각 스레드에서 동시에 메시지를 가로챌 수 있으며, 또는 시스템에서만 메시지를 가로챌 수도 있습니다. 각 스레드에서 메시지를 가로챌 수 있다는 의미는 애플리케이션의 메시지를 가로챌 수 있다는 뜻입니다. 시스템에서만 메시지를 가로챌 수 있는 경우는 WH_JOURNALPLAYBACK과 WH_JOURNALRECORD, 그리고 표에서 설명하지 않은 WH_SYSMSGFILTER의 세 개입니다. 이때에는 애플리케이션 인스턴스값을 갖지 않아야 합니다. 애플리케이션 인스턴스값을 갖지 말라는 뜻은 인자 hMod가 0이라는 의미가 아니라 hMod값이 GetModuleHandle(NULL) 상태로 되어야 한다는 의미입니다. 두 번째로 dwThreadId는 스레드를 참조하지 않기 때문에 0으로 설정하여 시스템 메시지를 얻을 수 있습니다. 시스템 메시지를 가로채는 경우에는 일반 애플리케이션 프로그램에서 호출이 가능하나 시스템 메시지와 스레드 메시지를 동시에 사용할 경우에는 DLL로 만들어야 합니다. 이 이유에 대해서는 PART 2의 5장에서 설명합니다. 다만 시스템 메시지만을 가로채는 세 개의 모드 외의 메시지를 가로챌 경우에는 꼭 DLL로 만들어야 합니다.

스레드에서, 즉 애플리케이션에서 메시지를 가로챌 경우에는 먼저 윈도 핸들을 얻고 이 핸들을 이용하여 인스턴스와 스레드 아이디를 얻은 다음 SetWindowsHookEx 함수를 이용하면 됩니다. 윈도 핸들을 얻는 데는 여러 방법이 있습니다. 현재 마우스가 위치한 윈도의 핸들을 얻고자 할 때는 WindowFromPoint 함수를, 윈도 클래스 이름이나 윈도 이름을 이용할 경우에는 FindWindow 함수를 이용하면 됩니다. 이렇게 얻은 윈도 핸들을 이용하여 인스턴스를 얻고자 할 경우에는 GetWindowLong 함수를 이용하여 얻으면 됩니다. 이 값이 hMod가 되는 것입니다. 또한 스레드 아이디를 얻고자 할 경우에는 GetWindowThreadProcessId 함수를 사용하면 됩니다. 이렇게 하여 메시지 훅킹 함수를 설정할 수 있습니다.

이렇게 훅킹 함수를 설정한 다음 가로채기를 하는 프로시저에서 해당 메시지에 의한 정보를 얻을 수 있으며, 또한 해당 메시지를 무시할 수도 있습니다. PaintShop 같은 프로그램에서 화면을 캡처할 경우, 캡처를 시작하면 자신의 윈도는 아이콘화되며 우측 마우스 버튼이나 F12 키를 누를 때까지 메시지를 가로채고 있습니다. 이때 우측 마우스 버튼이나 F12 키를 누르면 해당 메시지를 가로채고 그 메시지를 메시지 큐에서 삭제합니다. 이 방식이 메시지 훅킹 함수를 이용하여 메시지를 가로채고 해당 메시지를 메시지 큐에서 삭제한 형태입니다. 이런 루틴을 SDK 형태로 만든 예가 그림 1-2-28입니다.

그림 1-2-28은 마우스에서 우측 마우스 버튼을 클릭하였을 때를 탐지하는 루틴입니다. 이때 메시지 훅킹을 설정하는 함수를 스레드로 하였습니다. 그 이유는 모든 윈도에서 우측 마우스 버튼을 클릭했을 때 메시지를 가로채야 하기 때문입니다. 이때 스레드로 구동되는 함수가 그림 1-2-28의 ②입니다. 이 스레드에서는 마우스가 이동될 때마다 해당 윈도를 얻고 이 윈도의 인스턴스와 스레드 ID를 얻어서 메시지 훅킹 함수를 설정합니다. 이렇게 하나의 스레드가 돌면서 메시지 훅킹 함수가 설정되면 그림 1-2-28의 ③ 항목의 함수가 실행되고, 여기에서 우측 마우스 버튼을 클릭하면 PeekMessage 함수를 이용하여 해당 메시지를 큐에서 제거한 후 이 이벤트를 알려주고자 하는 윈도에 SendMessage 함수를 이용하여 메시지를 보냅니다. 이런 방법으로 메시지를 가로챌 수 있습니다. 이 방법 외에 DLL을 이용하여 제작하는 방법이 있습니다. 이 방법은 PART 2의 5장에서 자세히 설명합니다.

훅킹 프로시저

표9에서 보듯이 해당되는 프로시저 함수들의 형태가 다른 것처럼 보입니다. 그러나 프로시저 함수 형태는 다르지 않습니다. 모든 메시지 훅킹 프로시저 함수는 다음과 같은 형태를 가지고 있습니다.

LRESULT CALLBACK 프로시저 함수명(

int nCode; // 훅 코드

WPARAM wParam; // WPARAM값

LPARAM lParam; // lParam값

};

nCode는 훅 코드입니다. 대부분의 프로시저들은 이 코드를 참조하여 현재 wParam과 lParam에 정보가 있는지 없는지를 알아냅니다. nCode값이 HC_ACTION일 경우에는 wParam과 lParam에 정보가 있는 것입니다. 정보가 있을 경우 wParam과 lParam에 어떻게 정보가 들어가 있는가를 알고자 할 경우 표9에 있는 프로시저 함수를 찾아보면 된다는 의미입니다. 예를 들어, WH_JOURNALRECORD를 이용하여 메시지 훅킹을 하였다면 wParam의 값은 사용하지 않고 lParam에 EVENTMSG의 포인터 형태로 값을 전해줍니다. 즉, 각 프로시저에 들어오는 정보들이 해당 훅킹 플러그에 따라서 다르게 변화된다는 의미입니다. nCode값이 HC_ACTION이 아닐 경우에는 필수적으로 이 메시지를 다른 훅킹 프로시저에 넘겨주어야 합니다. 이때 사용하는 함수가 CallNextHookEx입니다. 이 함수의 형태는 다음과 같습니다.

LRESULT CallNextHookEx(  HHOOK hhk,      // 훅킹 핸들

int nCode,      // 훅 코드

WPARAM wParam,  // wParam

LPARAM lParam   // lParam);

hhk는 SetWindowsHookEx에 의해서 만들어진 핸들을 넘겨받은 값이며, 나머지는 프로시저에 들어온 값을 그대로 넘겨주는 것입니다. 실제로 프로시저 함수를 만들 때는 CallNextHookEx를 다음과 같은 방법으로 자주 사용합니다.

LRESULT CALLBACK 프로시저 함수명(

int nCode; // 훅 코드

WPARAM wParam; // WPARAM값

LPARAM lParam; // lParam값

}

{

if(nCode=HC_ACTION)

{

:메시지 처리

}

else // 다음 훅 프로시저에 데이터를 넘긴다.

CallNextHookEx(hHook,nCode,wParam,lParam);

키보드 마우스 이벤트 가로채기 예제 ExMsgHook

ExMsgHook은 시스템에서 마우스와 키보드 이벤트를 가로채는 예제입니다. 이것은 DLL 형태가 아니고 그냥 애플리케이션에서 제작한 형태입니다. 전에 설명한 루틴(그림 1-2-28)은 고급 난이도의 시스템 메시지 가로채기입니다. 좀더 단순하게 메시지를 가로채는 예제를 만들어 보겠습니다. ExMsgHook은 대화 상자 형태의 프로그램을 만들고 이 프로그램에서 마우스와 키보드 이벤트를 가로채는 예입니다.

ExMsgHook의 전체적인 프로그램 형태는 그림 1-2-29와 같습니다.

ExMsgHook에는 마우스 버튼이 눌려졌을 때 결과가 출력되는 에디터 상자와 메시지 가로채기 시작 버튼과 종료 버튼이 있는 대화 상자입니다. 이 대화 상자에서 메시지 가로채기 시작 버튼을 클릭하면 마우스나 키보드 메시지를 가로채어서 버튼이 눌려졌을 경우 눌려진 결과가 에디터 상자로 출력되게 되어 있습니다.

제일 먼저 대화 상자 폼을 그림 1-2-29의 □ 형태로 만든 다음 두 개의 에디터 상자와 연결되는 멤버 변수를 그림 1-2-29의 ① 항목과 같이 연결합니다. 이것을 연결하는 방법은 본 파트의 4장에서 설명할 것입니다. 이렇게 연결한 후 먼저 HOOK 버튼을 클릭하면 발생하는 이벤트 함수를 만들고 그림 1-2-29의 ③ 항목처럼 SetWindows HookEx 함수를 이용하여 메시지 훅킹을 합니다. 이때 SetWindowsHookEx 함수에 의해서 리턴된 훅킹 핸들값을 저장하기 위해서는 다음과 같이 전역 변수를 설정해야 합니다.

HHOOK hHook;

위와 같이 선언한 변수에 SetWindowsHookEx에 의해서 넘겨진 핸들을 받습니다.

Stop 버튼을 클릭하면 그림 1-2-29의 ② 항목처럼 hHook에 설정된 메시지 훅킹을 해제합니다.

SetWindowsHookEx 함수에서 설정한 메시지 가로채기 플러그값은 WH_JOUR- NALRECORD입니다. 이 플러그를 설정하면 DLL을 사용하지 않아도 됩니다. 이 플러그를 사용하면 시스템에서 바로 키보드나 마우스 메시지 이벤트를 받을 수 있기 때문입니다. 이렇게 메시지 훅킹을 선언하고 난 후 메시지 훅킹이 될 때 처리하는 함수는 그림 1-2-29의 ④ 항목과 같이 만듭니다. HookMouseProc 함수에서 처리하는 내용은 좌측 마우스 버튼이나 우측 마우스 버튼 이벤트 또는 키보드 이벤트가 발생되었을 때 해당값을 대화 상자에 알려주기 위해서 WM_USER+4의 사용자 메시지를 발생시킨 것입니다. 이렇게 사용자 메시지를 발생시키면 이에 해당하는 메시지 함수를 만들어야 합니다. 전 항목에서 사용자 메시지를 만드는 방법에 대해서 배웠습니다. 이와 같은 방법으로 메시지 함수를 만들고 설정하는 형태가 그림 1-2-29의 ⑤, ⑥, ⑦입니다. 이렇게 메시지 함수를 만들고 난 후에 wParam에 넘겨준 값(1이면 우측 마우스 버튼, 2이면 좌측 마우스 버튼, 3이면 키보드 눌림)에 따라 에디터 상자와 연결된 변수에 문자열을 설정하고 이것을 출력하는 형태입니다.

ExMsgHook 프로그램 소스

ExMsgHook의 중요 소스 부분은 ExMsgHookDlg.h와 ExMsgHookDlg.cpp입니다. 이외의 나머지 부분은 제공되는 CD를 참조하기 바랍니다.

// ExMsgHookDlg.h : header file

//

#if !defined(AFX_EXMSGHOOKDLG_H__A771276B_22F9_11D2_8B60_006097AD9DDC__INCLUDED_)

#define AFX_EXMSGHOOKDLG_H__A771276B_22F9_11D2_8B60_006097AD9DDC__INCLUDED_

#if _MSC_VER > 1000

#pragma once

#endif //  _MSC_VER > 1000

///////////////////////////////////////////////////////////////////////// // CExMsgHookDlg dialog

class CExMsgHookDlg : public CDialog

{

// Construction

public:

CExMsgHookDlg(CWnd* pParent = NULL); // standard constructor

// Dialog Data

//{{AFX_DATA(CExMsgHookDlg)

enum { IDD = IDD_EXMSGHOOK_DIALOG };

CString m_strMouse;

CString m_strKeyboard;

//}}AFX_DATA

// ClassWizard generated virtual function overrides

//{{AFX_VIRTUAL(CExMsgHookDlg)

protected:

virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support

//}}AFX_VIRTUAL

// Implementation

protected:

HICON m_hIcon;

// Generated message map functions

//{{AFX_MSG(CExMsgHookDlg)

virtual BOOL OnInitDialog();

afx_msg void OnPaint();

afx_msg HCURSOR OnQueryDragIcon();

afx_msg void OnHook();

afx_msg long OnHookMsg(WPARAM wParam,LPARAM lParam);

afx_msg void OnStop();

//}}AFX_MSG

DECLARE_MESSAGE_MAP()

};

//{{AFX_INSERT_LOCATION}}

// Microsoft Visual C++ will insert additional declarations immediately before the previous line.

#endif //  !defined(AFX_EXMSGHOOKDLG_H__A771276B_22F9_11D2_8B60_ 006097AD9DDC__INCLUDED_)

// ExMsgHookDlg.cpp : implementation file

//

#include □stdafx.h□

#include □ExMsgHook.h□

#include □ExMsgHookDlg.h□

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

/////////////////////////////////////////////////////////////////////////

// CExMsgHookDlg dialog

CExMsgHookDlg::CExMsgHookDlg(CWnd* pParent /*=NULL*/)

: CDialog(CExMsgHookDlg::IDD, pParent)

{

//{{AFX_DATA_INIT(CExMsgHookDlg)

m_strMouse = _T(□□);

m_strKeyboard = _T(□□);

//}}AFX_DATA_INIT

// Note that LoadIcon does not require a subsequent DestroyIcon in Win32

m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);

}

void CExMsgHookDlg::DoDataExchange(CDataExchange* pDX)

{

CDialog::DoDataExchange(pDX);

//{{AFX_DATA_MAP(CExMsgHookDlg)

DDX_Text(pDX, IDC_EDIT2, m_strMouse);

DDX_Text(pDX, IDC_EDIT3, m_strKeyboard);

//}}AFX_DATA_MAP

}

BEGIN_MESSAGE_MAP(CExMsgHookDlg, CDialog)

//{{AFX_MSG_MAP(CExMsgHookDlg)

ON_WM_PAINT()

ON_WM_QUERYDRAGICON()

ON_BN_CLICKED(IDC_HOOK, OnHook)

ON_BN_CLICKED(IDC_STOP, OnStop)

//}}AFX_MSG_MAP

ON_MESSAGE(WM_USER+4,OnHookMsg)

END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////

// CExMsgHookDlg message handlers

BOOL CExMsgHookDlg::OnInitDialog()

{

CDialog::OnInitDialog();

// Set the icon for this dialog.  The framework does this automatically

// when the application□s main window is not a dialog

SetIcon(m_hIcon, TRUE);   // Set big icon

SetIcon(m_hIcon, FALSE);  // Set small icon

return TRUE;  // return TRUE  unless you set the focus to a control

}

// If you add a minimize button to your dialog, you will need the code below

// to draw the icon.  For MFC applications using the document/view model,

// this is automatically done for you by the framework.

void CExMsgHookDlg::OnPaint()

{

if (IsIconic())

{

CPaintDC dc(this); // device context for painting

SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0);

// Center icon in client rectangle

int cxIcon = GetSystemMetrics(SM_CXICON);

int cyIcon = GetSystemMetrics(SM_CYICON);

CRect rect;

GetClientRect(&rect);

int x = (rect.Width() - cxIcon + 1) / 2;

int y = (rect.Height() - cyIcon + 1) / 2;

// Draw the icon

dc.DrawIcon(x, y, m_hIcon);

}

else

{

CDialog::OnPaint();

}

}

// The system calls this to obtain the cursor to display while the user drags

// the minimized window.

HCURSOR CExMsgHookDlg::OnQueryDragIcon()

{

return (HCURSOR) m_hIcon;

}

HHOOK hHook=NULL;

LRESULT CALLBACK HookMouseProc(int nCode, WPARAM wParam, LPARAM lParam)

{

LRESULT lResult = 1;

HWND hwnd;

if (nCode == HC_ACTION)

{

if(((PEVENTMSG) lParam)->message == WM_RBUTTONDOWN ||

((PEVENTMSG) lParam)->message == WM_RBUTTONUP)

{

hwnd=FindWindow(NULL,□ExMsgHook□);

SendMessage(hwnd,WM_USER+4,1,1);

return lResult;

}

else

if(((PEVENTMSG) lParam)->message == WM_LBUTTONDOWN)

{

hwnd=FindWindow(NULL,□ExMsgHook□);

SendMessage(hwnd,WM_USER+4,2,2);

return lResult;

}

else

if(((PEVENTMSG) lParam)->message == WM_KEYDOWN)

{

hwnd=FindWindow(NULL,□ExMsgHook□);

SendMessage(hwnd,WM_USER+4,3,3);

return lResult;

}

}

return CallNextHookEx(hHook, nCode, wParam, lParam);;

}

void CExMsgHookDlg::OnHook()

{

hHook =

SetWindowsHookEx(WH_JOURNALRECORD,HookMouseProc,GetModuleHandle(NULL), 0);

}

long CExMsgHookDlg::OnHookMsg(WPARAM wParam,LPARAM lParam)

{

m_strMouse=□메시지 없음□;

m_strKeyboard=□메시지 없음□;

switch(wParam)

{

case 1:

m_strMouse=□우측 마우스□;

break;

case 2:

m_strMouse=□좌측 마우스□;

break;

case 3:

m_strKeyboard=□키보드□;

break;

}

UpdateData(FALSE);

return 1;

}

void CExMsgHookDlg::OnStop()

{

if(hHook)

{

UnhookWindowsHookEx(hHook);

hHook = NULL;

}

}