API

API 이용 MDI 구현 - 발췌

디버그정 2008. 9. 4. 18:47
[Win32 - 김상형윈도우즈API정복-정리노트]MDI Win32

2008/09/02 09:59

복사 http://blog.naver.com/blue7red/100054479397

1.MDI

-

(1)정의

-동시에 여러 개의 문서를 열 수 있는 프로그램 형태를 의미한다.

-요즘은 프로그램을  MDI로 작성하는 것은 별로 권장되지는 않는다.


(2)MDI프로그램 구조

-프레임윈도우 : 메인윈도우

-클라이언트 윈도우

-차일드윈도우 :클라이언트윈도우의 자식윈도우다.


(3)시스템의 MDI지원

-일단 운영체제의 지원 중 가장 중요한 부분은 클라이언트 윈도우가 미리 만들어져 있다.




2.MDI기본예제

(1)예제

-

// 프레임 윈도우의 메시지 프로시저
LRESULT CALLBACK MDIWndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)
{
 CLIENTCREATESTRUCT ccs;
 MDICREATESTRUCT mcs;
 switch (iMessage) {
 case WM_CREATE:
  // MDI Client 윈도우 만듬
  ccs.hWindowMenu=GetSubMenu(GetMenu(hWnd), 1);
  ccs.idFirstChild=IDM_WINDOWCHILD;
  g_hMDIClient=CreateWindow("MDICLIENT", NULL, WS_CHILD | WS_VSCROLL |
   WS_HSCROLL | WS_CLIPCHILDREN,
   0,0,0,0,hWnd,(HMENU)NULL, g_hInst, (LPSTR)&ccs);
  ShowWindow(g_hMDIClient, SW_SHOW);
  return 0;
 case WM_COMMAND:
  switch (LOWORD(wParam)) {
  // 새로운 차일드 윈도우를 만든다.
  case ID_FILENEW:
   mcs.szClass="MDIExamChild";
   mcs.szTitle="Child";
   mcs.hOwner=g_hInst;
   mcs.x=mcs.y=CW_USEDEFAULT;
   mcs.cx=mcs.cy=CW_USEDEFAULT;
   mcs.style=MDIS_ALLCHILDSTYLES;
   SendMessage(g_hMDIClient, WM_MDICREATE, 0,
    (LPARAM)(LPMDICREATESTRUCT)&mcs);
   break;
  // 바둑판식 정렬
  case ID_WIN_TILE:
   SendMessage(g_hMDIClient, WM_MDITILE,
    (WPARAM)MDITILE_HORIZONTAL, 0);
   break;
  // 계단식 정렬
  case ID_WIN_CASCADE:
   SendMessage(g_hMDIClient, WM_MDICASCADE,
    (WPARAM)MDITILE_SKIPDISABLED, 0);
   break;
  // 아이콘 정렬
  case ID_WIN_ARRANGE:
   SendMessage(g_hMDIClient, WM_MDIICONARRANGE, 0, 0);
   break;
  }
  break;  // 여기서 "return 0"하면 안된다. 반드시 break;
 case WM_DESTROY:
  PostQuitMessage(0);
  return 0;
 }
 return(DefFrameProc(hWnd,g_hMDIClient,iMessage,wParam,lParam));
}

// 차일드 윈도우의 메시지 프로시저
LRESULT CALLBACK MDIChildProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)
{
 PAINTSTRUCT ps;
 HDC hdc;
 TCHAR str[128];
 switch (iMessage) {
 case WM_CREATE:
  wsprintf(str, "Child %d", ChildNum);
  SetWindowLong(hWnd, 0, ChildNum);
  ChildNum++;
  SetWindowText(hWnd, str);
  return 0;
 case WM_PAINT:
  hdc=BeginPaint(hWnd, &ps);
  wsprintf(str,"This is a MDI %dth Child window", GetWindowLong(hWnd, 0));
  TextOut(hdc,0,0,str,lstrlen(str));
  EndPaint(hWnd, &ps);
  return 0;
 // WM_DESTROY 메시지를 처리하지 않아도 된다.

 }
 return(DefMDIChildProc(hWnd,iMessage,wParam,lParam));
}


(2)구성요소 만들기


-윈도우 클래스가 MDICLIENT로 고정되어 있다


-클라이언트 윈도우는 운영체제에 미리정의되어 있으므로 윈도우클래스를 등록할 필요가 없다.


-클라이언트 윈도우에는 특별한 스타일이 필요하다.

-WS_CLIPCHIDREN스타일을 반드시 지정해야한다.

 그렇지 않으면 클라이언트 윈도우가 다시 그려질 때 차일드까지 같이 그려져야 하므로

 효율상 , 미관상 좋지 않다.

-MDI의 차일드윈도우는 메뉴를 가질 수 없으므로 lpzsMenuName은 반드시 null이어야한다.

-윈도우 클래스의 여분의 메모리는 차일드 윈도우의 고유정보를 기록하는데 사용할 수 있다.

-차일드 윈도우를 만들 때 클라이언트 윈도우로 WM_MDICREATE메시지를 보내면 된다.

 다른방법으로는 CreateMDIWindow함수를 사용해서 생성할 수도 있다.


(3)MDI의 메시지 처리

-WM_MDIACTIVE:차일드 윈도우 활성화

-WM_MDICASCADE:계단식 정렬

-WM_MDICREATE:차일드윈도우 생성

-WM_MDIDESTROY:차일드윈도우 파괴

-WM_MDIGETACTIVE:현재 활성화된 차일드의 핸들을 리턴한다.

-WM_MDIICONARRANGE:최소화된 아이콘 정렬

-WM_MDIMAXIMIZE:최대화

-WM_MDINEXT:지정한 차일드 윈도우의 앞 또는 뒤쪽 차일드 윈도우를 활성화시킨다.

-WM_MDIREFRESHMENU:Window메뉴를 리프레시시킨다. 이메시지 이후 DrawMenuBar함수를 호출하여 메뉴를 갱신해야된다.

-WM_MDIRESTORE:원래 크기로 복원

-WM_MDISETMENU:메뉴를 변경한다.

-WM_MDITITLE: 바둑판식 재정렬



TranslateMDISysAccel : WM_KEYDOWN메시지를 WM_SYSCOMMAND 메시지로 변경하여 MDI차일드 윈도우로 보내는 역할을 한다.


DefFrameProc : MDIWindowProc에서 처리하지 않은 메시지를 DefWindowProc이 아닌 DefFrameProc으로 전달한다.

=>WM_COMMAND,WM_MENUCHAR,WM_SETFOCUS,WM_SIZE메시지를 반드시 처리해야 한다.


설사 MDIWndProc에서 이 메시지 중 한 가지를 처리했다고 하더라도 반드시 DefFrameProc으로 전달하는 것이 좋다.

차일드 윈도우의 메시지 처리함수도 프레임 윈도우의 메시지도 DefMDIChildProc으로 전달되어야 한다.



(4)차일드 윈도우의 정렬

-MDITITLE_HORIZONTAL:수평으로 정렬

-MDITITLE_SKIPDISABLED: 사용금지된 차일드윈도우는 정렬에서 제외된다.

-MDITITLE_VERTICAL: 수직으로 정렬


(5)여분의 메모리

-각 차일드 윈도우는 일반적으로 같은 윈도우 프로시저를 공유하기 때문에 동작이 동일할 수 밖에 없다.

-차일드마다 다른 고유값은 차일드윈도우 클래스의 여분 메모리에 저장된다.


3.MDI고급

(1)MDI프레임의 작업영역

-

// 프레임 윈도우의 메시지 프로시저
LRESULT CALLBACK MDIWndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)
{
 CLIENTCREATESTRUCT ccs;
 MDICREATESTRUCT mcs;
 switch (iMessage) {
 case WM_CREATE:
  // MDI Client 윈도우 만듬
  ccs.hWindowMenu=GetSubMenu(GetMenu(hWnd), 1);
  ccs.idFirstChild=IDM_WINDOWCHILD;
  g_hMDIClient=CreateWindow("MDICLIENT", NULL, WS_CHILD | WS_VSCROLL |
   WS_HSCROLL | WS_CLIPCHILDREN,
   0,0,0,0,hWnd,(HMENU)NULL, g_hInst, (LPSTR)&ccs);
  ShowWindow(g_hMDIClient, SW_SHOW);
  hList=CreateWindow("listbox",NULL,WS_CHILD | WS_VISIBLE | WS_BORDER |
   LBS_NOINTEGRALHEIGHT,0,0,0,0,hWnd,(HMENU)1,g_hInst,NULL);
  hEdit=CreateWindow("edit",NULL,WS_CHILD | WS_VISIBLE | WS_BORDER |
   ES_MULTILINE,10,10,200,25,hWnd,(HMENU)2,g_hInst,NULL);
  SendMessage(hList,LB_ADDSTRING,0,(LPARAM)"리스트 박스입니다.");
  SetWindowText(hEdit,"에디트 박스입니다.");
  return 0;
 case WM_SIZE:
  if (wParam != SIZE_MINIMIZED) {
   MoveWindow(hList,0,0,200,HIWORD(lParam),TRUE);
   MoveWindow(hEdit,200,HIWORD(lParam)-100,LOWORD(lParam)-200,100,TRUE);
   MoveWindow(g_hMDIClient,200,0,LOWORD(lParam)-200,HIWORD(lParam)-100,TRUE);
  }
  return 0;
 case WM_COMMAND:
  switch (LOWORD(wParam)) {
  // 새로운 차일드 윈도우를 만든다.
  case ID_FILENEW:
   mcs.szClass="MDIExamChild";
   mcs.szTitle="Child";
   mcs.hOwner=g_hInst;
   mcs.x=mcs.y=CW_USEDEFAULT;
   mcs.cx=mcs.cy=CW_USEDEFAULT;
   mcs.style=MDIS_ALLCHILDSTYLES;
   SendMessage(g_hMDIClient, WM_MDICREATE, 0,
    (LPARAM)(LPMDICREATESTRUCT)&mcs);
   break;
  // 바둑판식 정렬
  case ID_WIN_TILE:
   SendMessage(g_hMDIClient, WM_MDITILE,
    (WPARAM)MDITILE_HORIZONTAL, 0);
   break;
  // 계단식 정렬
  case ID_WIN_CASCADE:
   SendMessage(g_hMDIClient, WM_MDICASCADE,
    (WPARAM)MDITILE_SKIPDISABLED, 0);
   break;
  // 아이콘 정렬
  case ID_WIN_ARRANGE:
   SendMessage(g_hMDIClient, WM_MDIICONARRANGE, 0, 0);
   break;
  }
  break;  // 여기서 "return 0"하면 안된다. 반드시 break;
 case WM_DESTROY:
  PostQuitMessage(0);
  return 0;
 }
 return(DefFrameProc(hWnd,g_hMDIClient,iMessage,wParam,lParam));
}

// 차일드 윈도우의 메시지 프로시저
LRESULT CALLBACK MDIChildProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)
{
 PAINTSTRUCT ps;
 HDC hdc;
 TCHAR str[128];
 switch (iMessage) {
 case WM_CREATE:
  wsprintf(str, "Child %d", ChildNum);
  SetWindowLong(hWnd, 0, ChildNum);
  ChildNum++;
  SetWindowText(hWnd, str);
  return 0;
 case WM_PAINT:
  hdc=BeginPaint(hWnd, &ps);
  wsprintf(str,"This is a MDI %dth Child window", GetWindowLong(hWnd, 0));
  TextOut(hdc,0,0,str,lstrlen(str));
  EndPaint(hWnd, &ps);
  return 0;
 // WM_DESTROY 메시지를 처리하지 않아도 된다.
 }
 return(DefMDIChildProc(hWnd,iMessage,wParam,lParam));
}


(2)복수 개의 차일드

-


#include <windows.h>
#include "resource.h"

// 프레임 윈도우와 차일드의 윈도우 프로시저
LRESULT CALLBACK MDIWndProc(HWND,UINT,WPARAM,LPARAM);
LRESULT CALLBACK MDIDrawProc(HWND,UINT,WPARAM,LPARAM);
LRESULT CALLBACK MDIEditProc(HWND,UINT,WPARAM,LPARAM);

// 전역 변수들
LPSTR lpszClass=TEXT("MultiMDI");
HINSTANCE g_hInst;
HWND g_hFrameWnd;
HWND g_hMDIClient;
HMENU hMenu1, hMenu2, hMenu3;
HMENU hMenu1W,hMenu2W,hMenu3W;
int EditNum=1;
int DrawNum=1;

// 텍스트 에디터 차일드의 개별 정보 구조체
struct tagEditData {
 int Num;
 HWND hEdit;
};

int APIENTRY WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance
    ,LPSTR lpszCmdParam,int nCmdShow)
{
 HWND hWnd;
 MSG Message;
 WNDCLASS WndClass;
 g_hInst=hInstance;

 // 메인 윈도우(프레임 윈도우) 클래스 등록
 WndClass.cbClsExtra=0;
 WndClass.cbWndExtra=0;
 WndClass.hbrBackground=(HBRUSH)COLOR_APPWORKSPACE+1;
 WndClass.hCursor=LoadCursor(NULL,IDC_ARROW);
 WndClass.hIcon=LoadIcon(NULL,IDI_APPLICATION);
 WndClass.hInstance=hInstance;
 WndClass.lpfnWndProc=MDIWndProc;
 WndClass.lpszClassName=lpszClass;
 WndClass.lpszMenuName=NULL;
 WndClass.style=0;
 RegisterClass(&WndClass);

 // 프레임 윈도우 만듬
 hWnd=CreateWindow(lpszClass,lpszClass,WS_OVERLAPPEDWINDOW,
  CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,
  NULL,(HMENU)NULL,hInstance,NULL);
 ShowWindow(hWnd,nCmdShow);
 g_hFrameWnd=hWnd;

 // 텍스트 에디터 차일드 윈도우 클래스 등록
 WndClass.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH);
 WndClass.lpszClassName=TEXT("MDIExamEdit");
 WndClass.lpfnWndProc=MDIEditProc;
 WndClass.hIcon=LoadIcon(NULL,IDI_ASTERISK);
 WndClass.lpszMenuName=NULL;
 WndClass.cbWndExtra=sizeof(DWORD_PTR);
 RegisterClass(&WndClass);

 // 그래픽 에디터 차일드 윈도우 클래스 등록
 WndClass.lpszClassName=TEXT("MDIExamDraw");
 WndClass.lpfnWndProc=MDIDrawProc;
 WndClass.hIcon=LoadIcon(NULL,IDI_ERROR);
 WndClass.lpszMenuName=NULL;
 WndClass.cbWndExtra=sizeof(DWORD);
 WndClass.style=CS_DBLCLKS;
 RegisterClass(&WndClass);

 while (GetMessage(&Message,NULL,0,0)) {
  if (!TranslateMDISysAccel(g_hMDIClient, &Message)) {
   TranslateMessage(&Message);
   DispatchMessage(&Message);
  }
 }
 return Message.wParam;
}

LRESULT CALLBACK MDIWndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)
{
 CLIENTCREATESTRUCT ccs;
 MDICREATESTRUCT mcs;
 switch (iMessage) {
 case WM_CREATE:
  // 각 차일드의 메뉴 핸들과 윈도우 메뉴를 미리 구해 놓는다.
  hMenu1=LoadMenu(g_hInst,MAKEINTRESOURCE(IDR_MENU1));
  hMenu2=LoadMenu(g_hInst,MAKEINTRESOURCE(IDR_MENU2));
  hMenu3=LoadMenu(g_hInst,MAKEINTRESOURCE(IDR_MENU3));
  hMenu1W=GetSubMenu(hMenu1,0);
  hMenu2W=GetSubMenu(hMenu2,1);
  hMenu3W=GetSubMenu(hMenu3,1);

  // 차일드가 없을 때는 hMenu1을 사용한다.
  SetMenu(hWnd,hMenu1);
 
  // 프레임 윈도우 만듬
  ccs.hWindowMenu=hMenu1W;
  ccs.idFirstChild=IDM_WINDOWCHILD;
  g_hMDIClient=CreateWindow(TEXT("MDICLIENT"), NULL, WS_CHILD | WS_VSCROLL |
   WS_HSCROLL | WS_CLIPCHILDREN,
   0,0,0,0,hWnd,(HMENU)NULL, g_hInst, (LPSTR)&ccs);
  ShowWindow(g_hMDIClient, SW_SHOW);
  return 0;
 case WM_COMMAND:
  switch (LOWORD(wParam)) {
  case ID_FILE_NEWEDIT:
   mcs.szClass=TEXT("MDIExamEdit");
   mcs.szTitle=TEXT("Edit");
   mcs.hOwner=g_hInst;
   mcs.x=mcs.y=CW_USEDEFAULT;
   mcs.cx=mcs.cy=CW_USEDEFAULT;
   mcs.style=MDIS_ALLCHILDSTYLES;
   SendMessage(g_hMDIClient, WM_MDICREATE, 0,
    (LPARAM)(LPMDICREATESTRUCT)&mcs);
   break;
  case ID_FILE_NEWDRAW:
   mcs.szClass=TEXT("MDIExamDraw");
   mcs.szTitle=TEXT("Draw");
   mcs.hOwner=g_hInst;
   mcs.x=mcs.y=CW_USEDEFAULT;
   mcs.cx=mcs.cy=CW_USEDEFAULT;
   mcs.style=MDIS_ALLCHILDSTYLES;
   SendMessage(g_hMDIClient, WM_MDICREATE, 0,
    (LPARAM)(LPMDICREATESTRUCT)&mcs);
   break;
  case ID_WIN_TILE:
   SendMessage(g_hMDIClient, WM_MDITILE,
    (WPARAM)MDITILE_HORIZONTAL, 0);
   break;
  case ID_WIN_CASCADE:
   SendMessage(g_hMDIClient, WM_MDICASCADE,
    (WPARAM)MDITILE_SKIPDISABLED, 0);
   break;
  case ID_WIN_ARRANGE:
   SendMessage(g_hMDIClient, WM_MDIICONARRANGE, 0, 0);
   break;
  }
  break;
 case WM_DESTROY:
  PostQuitMessage(0);
  return 0;
 }
 return(DefFrameProc(hWnd,g_hMDIClient,iMessage,wParam,lParam));
}

// 텍스트 에디터의 윈도우 프로시저
LRESULT CALLBACK MDIEditProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)
{
 TCHAR str[128];
 HWND hEdit;
 tagEditData *pED;
 switch (iMessage) {
 case WM_CREATE:
  // 캡션에 차일드 번호를 출력한다.
  wsprintf(str, TEXT("Edit %d"), EditNum);
  SetWindowText(hWnd, str);

  // 클라이언트 영역에 에디트 컨트롤을 배치한다.
  hEdit=CreateWindow(TEXT("edit"),NULL,WS_CHILD | WS_VISIBLE | ES_AUTOHSCROLL
   | ES_MULTILINE | ES_AUTOVSCROLL | WS_HSCROLL | WS_VSCROLL,
   0,0,0,0,hWnd,(HMENU)100,g_hInst,NULL);

  // 에디트 컨트롤의 핸들과 차일드 번호를 구조체에 작성한 후
  // 여분 메모리에 이 포인터를 저장한다.
  pED=(tagEditData *)malloc(sizeof(tagEditData));
  pED->hEdit=hEdit;
  pED->Num=EditNum;
  SetWindowLongPtr(hWnd,0,(LONG)pED);

  EditNum++;
  return 0;
 case WM_MDIACTIVATE:
  // 활성화될 때 자신의 메뉴를 프레임에 부착시킨다.
  if (lParam==(LPARAM)hWnd)
   SendMessage(g_hMDIClient,WM_MDISETMENU,
   (WPARAM)hMenu3,(LPARAM)hMenu3W);
  else
   SendMessage(g_hMDIClient,WM_MDISETMENU,
   (WPARAM)hMenu1,(LPARAM)hMenu1W);
  DrawMenuBar(g_hFrameWnd);
  return 0;


//구조체를 할당한 후 그 포인터를 저장하는 것이 여분 메모리를 활용하는 가장 일반적인 방법이다.
 case WM_SIZE:
  pED=(tagEditData *)GetWindowLongPtr(hWnd,0);
  MoveWindow(pED->hEdit, 0, 0, LOWORD(lParam), HIWORD(lParam),TRUE);
  return 0;
 case WM_SETFOCUS:
  pED=(tagEditData *)GetWindowLongPtr(hWnd,0);
  SetFocus(pED->hEdit);
  return 0;
 case WM_DESTROY:
  pED=(tagEditData *)GetWindowLongPtr(hWnd,0);
  free(pED);
  break;
 }
 return(DefMDIChildProc(hWnd,iMessage,wParam,lParam));
}

// 드로우의 윈도우 프로시저
LRESULT CALLBACK MDIDrawProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)
{
 HDC hdc;
 TCHAR str[128];
 static int x;
 static int y;
 static BOOL bNowDraw=FALSE;
 switch (iMessage) {
 case WM_CREATE:
  wsprintf(str, TEXT("Draw %d"), DrawNum);
  SetWindowLong(hWnd, 0, DrawNum);
  DrawNum++;
  SetWindowText(hWnd, str);
  return 0;
 case WM_MDIACTIVATE: //메뉴를 변경할 시점이다.: 차일드 윈도우 내에서 포커스가 이동할 때이기 때문이다.
  if (lParam==(LPARAM)hWnd)
   SendMessage(g_hMDIClient,WM_MDISETMENU,
   (WPARAM)hMenu2,(LPARAM)hMenu2W);

  else
   SendMessage(g_hMDIClient,WM_MDISETMENU,
   (WPARAM)hMenu1,(LPARAM)hMenu1W);

  DrawMenuBar(g_hFrameWnd);
  return 0;
 case WM_LBUTTONDOWN:
  x=LOWORD(lParam);
  y=HIWORD(lParam);
  bNowDraw=TRUE;
  return 0;
 case WM_MOUSEMOVE:
  if (bNowDraw==TRUE) {
   hdc=GetDC(hWnd);
   MoveToEx(hdc,x,y,NULL);
   x=LOWORD(lParam);
   y=HIWORD(lParam);
   LineTo(hdc,x,y);
   ReleaseDC(hWnd,hdc);
  }
  return 0;
 case WM_LBUTTONUP:
  bNowDraw=FALSE;
  return 0;
 case WM_LBUTTONDBLCLK:
  InvalidateRect(hWnd, NULL, TRUE);
  return 0;
 }
 return(DefMDIChildProc(hWnd,iMessage,wParam,lParam));
}