7장. MDI 프로그래밍 MDI 프로그래밍에 대한 내용을 배우도록 한다. 지금까지 배운 것은 생성된 윈도우에 단일 화면이 존재하는 형태였다. 이것을 SDI라고 부르고 Single Document Interface의 약자이다. 하나의 문서를 하나의 화면에 보여주는 형태의 프로그램이라는 뜻이다. 이제 복수개의 문서를 복수개의 화면에 출력하는 Multiple Document Interface 프로그래밍을 배울 것이다. SDI 프로그램의 종류로는 메모장이 있으며 MDI 프로그램의 종류로는 파워포인트나 엑셀 등이 여기에 속한다. 1/211 1
내용 MDI 기반 어플리케이션 차일드 윈도우 사용하기 차일드 윈도우 관리하기 2/211
MDI 기반 어플리케이션 MDI는 Multiple Document Interface의 약어로 윈도우 내에 여러 개의 문서 표현 가능 프레임 윈도우 클라이언트 윈도우 MDI는 Multiple Document Interface의 약어로 윈도우내에 복수개의 문서를 표현할 수 있는 프로그램의 형태를 말한다. 위의 예는 Adobe Reader이다. 이 프로그램은 복수개의 PDF파일을 Reader 윈도우 내에 표현할 수 있다. 이때 메인 윈도우 역할을 하는 큰 윈도우의 프레임을 프레임 윈도우라고 부른다. 프레임 윈도우에 속하는 영역은 윈도우의 타이틀바, 메뉴, 도구상자, 테두리 등이다. 프레임 윈도우내에 회색영역을 우리는 클라이언트 윈도우라고 부른다. 클라이언트 윈도우는 차일드 윈도우들의 부모역할을 한다. 내부에서 문서를 실제로 표현하는 작은 윈도우들을 차일드 윈도우라고 부른다. 차일드 윈도우 여러 개 가능 3/211 3
작성방법 리소스 편집 WinMain 함수 작성 메인 메뉴에 “창 관리” 메뉴항목 추가 메뉴에 차일드 윈도우 생성위한 “새문서” 메뉴항목 추가 WinMain 함수 작성 프레임 윈도우, 차일드 윈도우를 위한 윈도우 클래스 생성 두개의 윈도우 클래스 등록 프레임 윈도우 생성 클라이언트 윈도우 생성 코드 추가 MDI 프로그램을 작성하는 방법을 단계별로 알아보도록 한다. 일은 크게 4가지로 나눌 수 있다. 먼저는 리소스 편집, 두 번째는 WinMain함수 작성, 세 번째는 메인 메시지 처리 함수 WndProc 작성, 마지막으로 차일드 윈도우 메시지 처리함수 ChildWndProc 작성이다. 리소스 편집 메인 메뉴에 “창 관리” 메뉴항목을 추가해 준다. 이것은 메인 메뉴에 존재하는 Pop-up 메뉴 항목이다. 차일드 윈도우 생성을 위해 사용할 메뉴항목을 추가해 주는데 “파일”메뉴항목에 연결된 부메뉴에 “새문서” 메뉴항목을 추가해 준다. WinMain 함수 작성 WinMain함수 내에 프레임 윈도우와 차일드 윈도우를 위한 윈도우 클래스 변수들을 선언하고 각 변수의 필드에 값을 넣어주는 코드를 작성한다. 만들어진 윈도우 클래스 변수를 이용하여 2개의 윈도우 클래스를 등록한다. 프레임 윈도우를 생성한다. 클라이언트 윈도우를 생성하는 코드를 추가해 준다. 4/211 4
작성방법(계속) 3. WndProc 함수 작성 4. ChildWndProc 함수 작성 메시지 처리 부분에서 클라이언트 윈도우에게 차일드 윈도우 생성하도록 메시지 전송 4. ChildWndProc 함수 작성 차일드 윈도우 메시지 처리함수 작성 3. WndProc 함수 작성 메뉴에서 “새문서”메뉴항목을 선택했을 때 클라이언트 윈도우에게 차일드 윈도우를 생성하도록 메시지를 전송하는 코드를 작성한다. 4. ChildWndProc 함수 작성 차일드 윈도우에서 발생하는 메시지 처리하는 코드를 작성해 준다. 5/211 5
창관리 메뉴항목 추가 프로젝트에 생성된 메뉴 IDR_MENU1에 새로운 메뉴항목 “창 관리”를 만들어 준다. 이 메뉴항목은 Pop-up으로 만들고 나중에 부메뉴에 메뉴항목들을 추가할 것이다. 6/211 6
메뉴에 새문서 항목 추가 “파일”부메뉴에는 “새문서”메뉴항목을 만들어 준다. 이제 새문서를 선택하면 차일드 윈도우가 생성되도록 할 것이다. 7/211 7
윈도우 클래스 생성 및 등록 RegisterClassEx (&wndclass) ; WNDCLASSEX wndclass ; wndclass.cbSize = sizeof(wndclass) ; wndclass.style = CS_HREDRAW | CS_VREDRAW ; wndclass.lpfnWndProc = FrameWndProc; // ChildWndProc; wndclass.cbClsExtra = 0 ; wndclass.cbWndExtra = 0 ; wndclass.hInstance = hInstance ; wndclass.hIcon = LoadIcon(NULL,IDI_APPLICATION); wndclass.hCursor = LoadCursor(NULL,IDC_ARROW) ; wndclass.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH); wndclass.lpszMenuName = MAKEINTRESOURCE(IDR_MENU1) ; wndclass.lpszClassName = “Class Name”; // “Child Class Name”; wndclass.hIconSm = LoadIcon(NULL,IDI_APPLICATION); RegisterClassEx (&wndclass) ; WinMain함수에 프레임을 위한 윈도우클래스와 차일드윈도우를 위한 윈도우 클래스를 만들어서 등록하여야 한다. 위의 코드는 프레임 윈도우를 위한 윈도우 클래스를 만들어서 등록하는 과정을 보여 주고 있다. 여기서 메시지 처리함수의 이름을 FrameWndProc라고 정했는데 이것은 지금까지 사용했던 WndProc와 동일한 것이다. 차일드 윈도우의 메시지 처리 함수 ChildWndProc와 분명하게 구분하기 위해 Frame을 붙여준 것이다. 클래스의 이름은 여기서 붙여준 것을 따르지 않아도 된다. 하지만 프레임 윈도우의 클래스이름과 차일드 윈도우의 클래스이름은 구분해 주어야 한다. 나머지 필드들은 프레임 윈도우나 차일드 윈도우가 동일한 값을 가진다. 8/211 8
어플리케이션 인스턴스 복사 WinMain()함수에서 넘겨받은 hInstance 값을 FrameWndProc()함수에서도 사용되기 때문에 전역변수를 이용하여 복사해줌 HINSTANCE hInst; hInst = hInstance; 어플리케이션 인스턴스인 hInstance값이 FrameWndProc에서 사용된다. 이를 위해 여기서는 HINSTANCE타입의 전역변수 hInst를 만들어 사용하고 있다. WinMain에서 전역변수 hInst에 hInstance를 복사한 후 FrameWndProc에서 hInst를 이용하면 된다. 만약 전역변수를 이용하기 싫다면 FrameWndProc함수내에 case WM_CREATE: 에서 ((LPCREATESTRUCT)lParam)->hInstance 의 값을 저장하여 사용하면 된다. 9/211 9
클라이언트 윈도우 생성 차일드 윈도우를 만들고 삭제하고 관리하는 윈도우로서 FrameWndProc의 WM_CREATE에서 생성함 static HWND hwndClient ; CLIENTCREATESTRUCT clientcreate ; switch (iMsg) { case WM_CREATE : clientcreate.hWindowMenu = GetSubMenu(GetMenu(hwnd),1); clientcreate.idFirstChild = 100 ; hwndClient = CreateWindow ("MDICLIENT", NULL, WS_CHILD | WS_CLIPCHILDREN | WS_VISIBLE, 0, 0, 0, 0, hwnd, NULL, hInst,(LPSTR) &clientcreate) ; ShowWindow(hwndClient, SW_SHOW); 이제 클라이언트 윈도우를 프레임 윈도우에 만들어 주어야 한다. 클라이언트 윈도우를 만드는 시점은 윈도우가 생성될 때이다. 이를 위해 필요한 변수는 HWND타입 변수 hwndClient와 CLIENTCREATESTRUCT타입 변수 clientcreate이다. WM_CREATE메시지가 오면 clientcreate의 hWindowMenu 필드에 우리가 만든 메뉴의 두 번째 부메뉴를 저장한다. 이것은 두 번째 부메뉴 “창관리”에 생성되는 차일드 윈도우 문서 이름들을 연결할 것이기 때문이다. 이후에 보면 알겠지만 열린 문서의 이름인 차일드 윈도우의 타이틀명들이 “창관리”부메뉴에 메뉴항목으로 붙게 된다. 먼저 GetMenu(hwnd)를 통해 프레임 윈도우에 붙어있는 메뉴를 얻어온다. 그 다음 GetSubMenu를 통해 “창관리”부메뉴를 얻어온다. GetSubMenu는 메뉴의 부메뉴를 얻어오기 위한 함수로 숫자는 0부터 시작한다. idFirstChild필드는 추가되는 메뉴항목의 ID에 붙여줄 숫자로써 여기서 부여된 100부터 증가하며 붙여준다. 그 다음 CreateWindow함수를 이용해서 클라이언트 윈도우를 만들어준다. 클라이언트 윈도우의 클래스 이름은 MDICLIENT이다. 이것은 이미 등록 된 것이다. 다른 파라메터들에 대한 자세한 설명은 생략 하겠다. 클라이언트 윈도우를 만든 다음 ShowWindow를 이용하여 나타내면 프레임 윈도우에 클라이언트 윈도우가 나타나게 된다. 10/211 10
차일드 윈도우 생성 메시지 전송 메뉴에서 새문서를 선택했을때 차일드 윈도우 생성하는 코드를 FrameWndProc의 MW_COMMAND에 추가함 MDICREATESTRUCT mdicreate ; HWND hwndChild ; switch (iMsg) { case WM_COMMAND: switch(LOWORD(wParam)) { case ID_NEW: mdicreate.szClass = "Child Window Class Name" ; mdicreate.szTitle = "Child Window Title Name" ; mdicreate.hOwner = hInst ; mdicreate.x = CW_USEDEFAULT ; mdicreate.y = CW_USEDEFAULT ; mdicreate.cx = CW_USEDEFAULT ; mdicreate.cy = CW_USEDEFAULT ; mdicreate.style = 0 ; mdicreate.lParam = 0 ; hwndChild = (HWND) SendMessage (hwndClient, WM_MDICREATE, 0,(LPARAM) (LPMDICREATESTRUCT) &mdicreate) ; return 0 ; } break; 이제 메뉴에서 “새문서”를 선택했을 때 어떻게 차일드 윈도우가 만들어지는가를 설명하겠다. 차일드 윈도우를 만들기 위해서 MDICREATESTRUCT타입 변수 mdicreate와 HWND타입 변수 hwndChild가 필요하다. “새문서”를 선택하면 WM_COMMAND메시지가 도착한다. LOWORD(wParam)에는 ID_NEW가 저장되어 있을 것이다. 이때 mdicreate 변수에 차일드 윈도우를 만들기 위한 값들을 셋팅한다. szClass에는 사용할 차일드 윈도우 클래스 이름을 넣어주고, szTitle에는 등장할 차일드 윈도우의 타이틀명을 넣어준다. hOwner에는 차일드 윈도우의 주인이기 때문에 우리 프로그램의 어플리케이션 인스턴스인 전역변수 hInst를 넣어준다. 이 hInst에는 WinMain에서 hInstance값을 복사해서 가지고 있다. (x,y)는 윈도우가 나타날 위치좌표이고 cx, cy는 차일드 윈도우의 폭과 높이 값이다. 4개 변수에 모두 WM_USEDEFAULT를 넣어줬기 때문에 커널이 원하는 위치에 원하는 크기로 나타낸다. 이제 SendMessage 를 이용해서 클라이언트 윈도우에게 WM_MDICREATE 메시지를 보낸다. 이것은 클라이언트 윈도우에게 mdicreate의 내용을 가지고 차일드 윈도우를 만들라고 하는 메시지이다. 이렇게 하고 나면 차일드 윈도우가 클라이언트 영역에 나타나게 된다. 11/211 11
차일드 윈도우 메시지 처리함수 LRESULT CALLBACK ChildWndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam) { switch (iMsg) case WM_CREATE: break; case WM_DESTROY: return 0; } return DefMDIChildProc (hwnd, iMsg, wParam, lParam); 마지막으로 차일드 윈도우에서 발생하는 메시지 처리 함수를 만들어 준다. 함수이름이 ChildWndProc일뿐 기존 WndProc와 별반 다를 바 없다. 단지 메시지를 처리하고 나서 마지막에 호출하는 함수가 DefWindowProc (hwnd, iMsg, wParam, lParam);이 아니고 DefMDIChildProc (hwnd, iMsg, wParam, lParam); 이라는 것만 다르다. 12/211 12
차일드 윈도우 관리하기 다음과 같이 메뉴항목을 변경 13/211 클라이언트 윈도우에 여러 개의 차일드 윈도우가 만들어지면 관리가 필요하다. 예를 들면, 정렬, 창닫기 등이 여기에 속한다. 이와 같은 차일드 윈도우 관리를 위해 여기서 메뉴항목들을 만들었다. 위의 그림과 같이 “창관리”부메뉴에 “바둑판식 정렬”, “계단식 정렬”, “아이콘정렬”, “창닫기”메뉴항목들을 만들어 준다. 각 메뉴항목에 대한 Caption과 ID는 위의 그림과 같이 설정해 준다. 13/211 13
차일드 윈도우 관리하기 다음과 같이 메뉴항목을 변경 14/211 14
메뉴항목에 대한 메시지 처리 case WM_COMMAND: switch(LOWORD(wParam)) { case ID_WINDOW_CLOSE : hwndChild = (HWND) SendMessage (hwndClient, WM_MDIGETACTIVE, 0, 0) ; if (SendMessage (hwndChild, WM_QUERYENDSESSION,0,0)) SendMessage (hwndClient, WM_MDIDESTROY, (WPARAM) hwndChild, 0) ; return 0 ; case ID_TILE : SendMessage (hwndClient, WM_MDITILE, 0, 0) ; 이제 각 메뉴항목에 대한 메시지를 처리하는 코드를 작성해 준다. 먼저 “창닫기”에 대해 알아보도록 한다. “창닫기” 메뉴항목을 선택했다면 클라이언트 윈도우에서 Active상태에 있는 차일드 윈도우를 닫아준다. 여러 차일드 윈도우들중 Active상태인 것은 하나 뿐이다. “창닫기” 메뉴항목을 선택했기 때문에 LOWORD(wParam)의 값은 ID_CLOSE이다. 먼저 해야 할 일은 현재 Active상태에 있는 차일드 윈도우를 알아내는 것이다. 이것을 위해 클라이언트 윈도우 hwndClient에게 WM_MDIACTIVE메시지를 보내야 한다. 이때 반환되는 값이 Active 차일드 윈도우의 핸들 값이다. 그리고, 차일드 윈도우를 종료하기 위해 종료해도 가능한지 알아보기 위한 메시지 WM_QUERYENDSESSION를 보낸다. 종료해도 된다면 TRUE를 반환하기 때문에 이제 비로소 종료메시지인 WM_MDIDESTROY를 보낸다. 종료메시지는 클라이언트 윈도우에게 보낸다는 것을 명심해야 한다. 두 번째로 “바둑판식 정렬”메뉴항목을 선택했을 때를 알아보도록 한다. LOWORD(wParam)이 ID_TILE이기 때문에 위에서는 클라이언트 윈도우에게 WM_MDITITLE메시지를 보내고 있다. 이 메시지가 차일드 윈도우를 바둑판식으로 정렬하라는 메시지이다. 15/211 15
메뉴항목에 대한 메시지 처리 case ID_CASCADE : SendMessage (hwndClient, WM_MDICASCADE, 0, 0); return 0 ; case ID_ARRANGE : SendMessage (hwndClient, WM_MDIICONARRANGE, 0, 0); case ID_EXIT : PostQuitMessage (0); } break; “계단식 정렬”메뉴항목을 선택했다면 LOWORD(wParam)이 ID_CASCADE이고 클라이언트 윈도우에게 계단식으로 정렬하라는 메시지를 보내게 된다. 이 때 보내는 메시지는 WM_MDICASCADE이다. “아이콘정렬”은 차일드 윈도우가 아이콘 상태에 있을 때 아이콘들을 자동 정렬할 때 사용한다. 이를 위해 보내는 메시지는 WM_MDIICONARRANGE이다. 16/211 16
연습문제 1 실습제목 차일드 윈도우들의 타이틀 이름 다양화 실습내용 앞에 있는 실습에서 차일드 윈도우의 타이틀명을 다음과 같이 다양하게 하시오. 앞의 예제에서는 차일드 윈도우의 타이틀 이름이 동일하였다. 이제 차일드 윈도우마다 붙여지는 타이틀 이름을 달리하는 실습을 수행한다. 메뉴에서 “새파일” 메뉴항목을 반복적으로 선택하면 차일드 윈도우가 계속 만들어지는데 차일드 윈도우의 이름을 “%d번 Child Window”라고 붙여준다. %d에는 숫자를 1부터 시작하여 붙여준다. 17/211 17
연습문제 2 실습제목 MDI를 활용한 메모장 실습내용 이전에 만들었던 10줄 메모장을 MDI버전으로 확장하시오. 18/211 이제 MDI를 활용한 메모장 프로그램을 작성해 보겠다. 물론, 실제 메모장보다는 기능상으로 부족하지만 10줄 짜리 글을 입력하고 편집할 수 있는 메모장이다. “새문서” 메뉴항목을 선택하면 새로운 차일드 윈도우가 뜨고 거기에 10줄짜리 글을 입력 및 편집할 수 있어야 한다. 2장에서 10줄짜리 메모장을 만들었던 것을 기억할 것이다. 그 코드의 WndProc에 있는 부분들을 본 프로그램의 ChildWndProc에 넣어주면 된다. 18/211 18