여러 문서 인터페이스 사용

이 섹션에서는 다음 작업을 수행하는 방법을 설명합니다.

이러한 작업을 설명하기 위해 이 섹션에는 일반적인 MDI(다중 문서 인터페이스) 애플리케이션인 Multipad의 예제가 포함되어 있습니다.

자식 및 프레임 창 클래스 등록

일반적인 MDI 애플리케이션은 프레임 창과 자식 창에 대해 하나씩 두 개의 창 클래스를 등록해야 합니다. 애플리케이션이 둘 이상의 문서 형식(예: 스프레드시트 및 차트)을 지원하는 경우 각 형식에 대한 창 클래스를 등록해야 합니다.

프레임 창의 클래스 구조는 MDI가 아닌 애플리케이션의 기본 창에 대한 클래스 구조와 유사합니다. MDI 자식 창의 클래스 구조는 다음과 같이 비MDI 애플리케이션의 자식 창 구조와 약간 다릅니다.

  • 사용자가 MDI 자식 창을 일반 애플리케이션 창인 것처럼 최소화할 수 있으므로 클래스 구조에 아이콘이 있어야 합니다.
  • MDI 자식 창에는 자체 메뉴가 있을 수 없으므로 메뉴 이름은 NULL이어야 합니다.
  • 클래스 구조는 창 구조에 추가 공간을 예약해야 합니다. 이 공간을 사용하여 애플리케이션은 파일 이름과 같은 데이터를 특정 자식 창과 연결할 수 있습니다.

다음 예제에서는 Multipad가 프레임 및 자식 창 클래스를 등록하는 방법을 보여 줍니다.

BOOL WINAPI InitializeApplication() 
{ 
    WNDCLASS wc; 
 
    // Register the frame window class. 
 
    wc.style         = 0; 
    wc.lpfnWndProc   = (WNDPROC) MPFrameWndProc; 
    wc.cbClsExtra    = 0; 
    wc.cbWndExtra    = 0; 
    wc.hInstance     = hInst; 
    wc.hIcon         = LoadIcon(hInst, IDMULTIPAD); 
    wc.hCursor       = LoadCursor((HANDLE) NULL, IDC_ARROW); 
    wc.hbrBackground = (HBRUSH) (COLOR_APPWORKSPACE + 1); 
    wc.lpszMenuName  = IDMULTIPAD; 
    wc.lpszClassName = szFrame; 
 
    if (!RegisterClass (&wc) ) 
        return FALSE; 
 
    // Register the MDI child window class. 
 
    wc.lpfnWndProc   = (WNDPROC) MPMDIChildWndProc; 
    wc.hIcon         = LoadIcon(hInst, IDNOTE); 
    wc.lpszMenuName  = (LPCTSTR) NULL; 
    wc.cbWndExtra    = CBWNDEXTRA; 
    wc.lpszClassName = szChild; 
 
    if (!RegisterClass(&wc)) 
        return FALSE; 
 
    return TRUE; 
} 

프레임 및 자식 창 만들기

창 클래스를 등록한 후 MDI 애플리케이션은 창을 만들 수 있습니다. 먼저 CreateWindow 또는 CreateWindowEx 함수를 사용하여 프레임 창을 만듭니다. 프레임 창을 만든 후 애플리케이션은 CreateWindow 또는 CreateWindowEx를 사용하여 클라이언트 창을 다시 만듭니다. 애플리케이션은 MDICLIENT를 클라이언트 창의 클래스 이름으로 지정해야 합니다. MDICLIENT 는 시스템에서 정의한 미리 등록된 창 클래스입니다. CreateWindow 또는 CreateWindowExlpvParam 매개 변수는 CLIENTCREATESTRUCT 구조를 가리킵니다. 이 구조체에는 다음 표에 설명된 멤버가 포함되어 있습니다.

멤버 설명
hWindowMenu MDI 자식 창을 제어하는 데 사용되는 창 메뉴에 대한 핸들입니다. 자식 창이 만들어지면 애플리케이션은 창 메뉴에 제목을 메뉴 항목으로 추가합니다. 그러면 사용자는 창 메뉴에서 제목을 클릭하여 자식 창을 활성화할 수 있습니다.
idFirstChild 첫 번째 MDI 자식 창의 식별자를 지정합니다. 만든 첫 번째 MDI 자식 창에 이 식별자가 할당됩니다. 추가 창은 증분된 창 식별자를 사용하여 만들어집니다. 자식 창이 제거되면 시스템은 창 식별자를 즉시 다시 할당하여 범위를 연속으로 유지합니다.

 

자식 창의 제목이 창 메뉴에 추가되면 시스템에서 자식 창에 식별자를 할당합니다. 사용자가 자식 창의 제목을 클릭하면 프레임 창은 wParam 매개 변수의 식별자가 포함된 WM_COMMAND 메시지를 받습니다. 프레임 창 메뉴의 메뉴 항목 식별자와 충돌하지 않는 idFirstChild 멤버에 대한 값을 지정해야 합니다.

다중 패드의 프레임 창 프로시저는 WM_CREATE 메시지를 처리하는 동안 MDI 클라이언트 창을 만듭니다. 다음 예제에서는 클라이언트 창을 만드는 방법을 보여줍니다.

case WM_CREATE: 
    { 
        CLIENTCREATESTRUCT ccs; 
 
        // Retrieve the handle to the window menu and assign the 
        // first child window identifier. 
 
        ccs.hWindowMenu = GetSubMenu(GetMenu(hwnd), WINDOWMENU); 
        ccs.idFirstChild = IDM_WINDOWCHILD; 
 
        // Create the MDI client window. 
 
        hwndMDIClient = CreateWindow( "MDICLIENT", (LPCTSTR) NULL, 
            WS_CHILD | WS_CLIPCHILDREN | WS_VSCROLL | WS_HSCROLL, 
            0, 0, 0, 0, hwnd, (HMENU) 0xCAC, hInst, (LPSTR) &ccs); 
 
        ShowWindow(hwndMDIClient, SW_SHOW); 
    } 
    break; 

자식 창의 제목이 창 메뉴의 아래쪽에 추가됩니다. 애플리케이션이 AppendMenu 함수를 사용하여 창 메뉴에 문자열을 추가하는 경우 창 메뉴가 다시 칠해질 때(자식 창을 만들거나 제거할 때마다) 자식 창의 제목으로 이러한 문자열을 덮어쓸 수 있습니다. 창 메뉴에 문자열을 추가하는 MDI 애플리케이션은 InsertMenu 함수를 사용하고 자식 창의 제목이 이러한 새 문자열을 덮어쓰지 않은지 확인해야 합니다.

WS_CLIPCHILDREN 스타일을 사용하여 MDI 클라이언트 창을 만들어 창이 자식 창 위에 그리는 것을 방지합니다.

기본 메시지 루프 작성

MDI 애플리케이션의 기본 메시지 루프는 MDI가 아닌 애플리케이션 처리 가속기 키와 유사합니다. 차이점은 MDI 메시지 루프가 애플리케이션 정의 가속기 키를 확인하거나 메시지를 디스패치하기 전에 TranslateMDISysAccel 함수를 호출한다는 것입니다.

다음 예제에서는 일반적인 MDI 애플리케이션의 메시지 루프를 보여줍니다. 오류가 있는 경우 GetMessage 는 -1을 반환할 수 있습니다.

MSG msg;
BOOL bRet;

while ((bRet = GetMessage(&msg, (HWND) NULL, 0, 0)) != 0)
{
    if (bRet == -1)
    {
        // handle the error and possibly exit
    }
    else 
    { 
        if (!TranslateMDISysAccel(hwndMDIClient, &msg) && 
                !TranslateAccelerator(hwndFrame, hAccel, &msg))
        { 
            TranslateMessage(&msg); 
            DispatchMessage(&msg); 
        } 
    } 
}

TranslateMDISysAccel 함수는 WM_KEYDOWN 메시지를 WM_SYSCOMMAND 메시지로 변환하고 활성 MDI 자식 창으로 보냅니다. 메시지가 MDI 가속기 메시지가 아닌 경우 함수는 FALSE를 반환합니다. 이 경우 애플리케이션은 TranslateAccelerator 함수를 사용하여 애플리케이션에서 정의한 가속기 키를 눌렀는지 여부를 확인합니다. 그렇지 않은 경우 루프는 메시지를 적절한 창 프로시저로 디스패치합니다.

프레임 창 프로시저 작성

MDI 프레임 창의 창 프로시저는 비MDI 애플리케이션의 기본 창과 비슷합니다. 차이점은 프레임 창 프로시저가 처리하지 않는 모든 메시지를 DefWindowProc 함수가 아닌 DefFrameProc 함수에 전달한다는 것입니다. 또한 프레임 창 프로시저는 다음 표에 나열된 메시지를 포함하여 처리하는 일부 메시지를 전달해야 합니다.

메시지 응답
WM_COMMAND 사용자가 선택하는 MDI 자식 창을 활성화합니다. 이 메시지는 사용자가 MDI 프레임 창의 창 메뉴에서 MDI 자식 창을 선택할 때 전송됩니다. 이 메시지와 함께 제공되는 창 식별자는 활성화할 MDI 자식 창을 식별합니다.
WM_MENUCHAR 사용자가 ALT+ – (빼기) 키 조합을 누르면 활성 MDI 자식 창의 창 메뉴를 엽니다.
WM_SETFOCUS 키보드 포커스를 MDI 클라이언트 창에 전달하여 활성 MDI 자식 창으로 전달합니다.
WM_SIZE MDI 클라이언트 창의 크기를 새 프레임 창의 클라이언트 영역에 맞게 조정합니다. 프레임 창 프로시저가 MDI 클라이언트 창의 크기를 다른 크기로 조정하는 경우 메시지를 DefWindowProc 함수에 전달하면 안 됩니다.

 

멀티패드의 프레임 창 프로시저를 MPFrameWndProc이라고 합니다. MPFrameWndProc에서 다른 메시지를 처리하는 것은 MDI가 아닌 애플리케이션의 메시지와 유사합니다. 멀티패드의 WM_COMMAND 메시지는 로컬로 정의된 CommandHandler 함수에 의해 처리됩니다. 멀티패드가 처리하지 않는 명령 메시지의 경우 CommandHandler는 DefFrameProc 함수를 호출합니다. 멀티패드가 기본적으로 DefFrameProc 을 사용하지 않는 경우 창의 메뉴 항목을 클릭하여 보낸 WM_COMMAND 메시지가 손실되므로 사용자는 창 메뉴에서 자식 창을 활성화할 수 없습니다.

자식 창 프로시저 작성

프레임 창 프로시저와 마찬가지로 MDI 자식 창 프로시저는 기본적으로 메시지를 처리하기 위해 특수 함수를 사용합니다. 자식 창 프로시저에서 처리하지 않는 모든 메시지는 DefWindowProc 함수가 아닌 DefMDIChildProc 함수로 전달되어야 합니다. 또한 MDI가 올바르게 작동하려면 애플리케이션이 메시지를 처리하더라도 일부 창 관리 메시지를 DefMDIChildProc에 전달해야 합니다. 다음은 애플리케이션이 DefMDIChildProc에 전달해야 하는 메시지입니다.

메시지 응답
WM_CHILDACTIVATE MDI 자식 창의 크기가 조정, 이동 또는 표시될 때 활성화 처리를 수행합니다. 이 메시지를 전달해야 합니다.
WM_GETMINMAXINFO MDI 클라이언트 창의 현재 크기에 따라 최대화된 MDI 자식 창의 크기를 계산합니다.
WM_MENUCHAR 메시지를 MDI 프레임 창에 전달합니다.
WM_MOVE MDI 클라이언트 스크롤 막대가 있는 경우 다시 계산합니다.
WM_SETFOCUS 활성 MDI 자식 창이 아닌 경우 자식 창을 활성화합니다.
WM_SIZE 특히 MDI 자식 창을 최대화하거나 복원하기 위해 창 크기를 변경하는 데 필요한 작업을 수행합니다. 이 메시지를 DefMDIChildProc 함수에 전달하지 못하면 바람직하지 않은 결과가 생성됩니다.
WM_SYSCOMMAND 창(이전의 시스템) 메뉴 명령(SC_NEXTWINDOW, SC_PREVWINDOW, SC_MOVE, SC_SIZESC_MAXIMIZE 처리합니다.

 

자식 창 만들기

MDI 자식 창을 만들려면 애플리케이션에서 CreateMDIWindow 함수를 호출하거나 MDI 클라이언트 창에 WM_MDICREATE 메시지를 보낼 수 있습니다. 애플리케이션은 WS_EX_MDICHILD 스타일과 함께 CreateWindowEx 함수를 사용하여 MDI 자식 창을 만들 수 있습니다. 단일 스레드 MDI 애플리케이션은 두 방법 중 하나를 사용하여 자식 창을 만들 수 있습니다. 다중 스레드 MDI 애플리케이션의 스레드는 CreateMDIWindow 또는 CreateWindowEx 함수를 사용하여 다른 스레드에서 자식 창을 만들어야 합니다.

WM_MDICREATE 메시지의 lParam 매개 변수는 MDICREATESTRUCT 구조체에 대한 원거리 포인터입니다. 구조체에는 창의 가로 및 세로 위치를 나타내는 xy와 창의 가로 및 세로 범위를 나타내는 cxcy의 4차원 멤버가 포함됩니다. 이러한 멤버는 애플리케이션에서 명시적으로 할당하거나 CW_USEDEFAULT 설정될 수 있습니다. 이 경우 시스템은 계단식 알고리즘에 따라 위치, 크기 또는 둘 다를 선택합니다. 어쨌든 4개 멤버를 모두 초기화해야 합니다. 다중 패드는 모든 차원에 CW_USEDEFAULT 사용합니다.

MDICREATESTRUCT 구조체의 마지막 멤버는 창에 대한 스타일 비트를 포함할 수 있는 스타일 멤버입니다. 창 스타일을 조합할 수 있는 MDI 자식 창을 만들려면 MDIS_ALLCHILDSTYLES 창 스타일을 지정합니다. 이 스타일을 지정하지 않으면 MDI 자식 창에 기본 설정으로 WS_MINIMIZE, WS_MAXIMIZE, WS_HSCROLLWS_VSCROLL 스타일이 있습니다.

다중 패드는 로컬로 정의된 AddFile 함수(원본 파일 MPFILE에 있음)를 사용하여 MDI 자식 창을 만듭니다. C). AddFile 함수는 창의 MDICREATESTRUCT 구조체의 szTitle 멤버를 편집 중인 파일의 이름 또는 "제목 없음"에 할당하여 자식 창의 제목을 설정합니다. szClass 멤버는 Multipad의 InitializeApplication 함수에 등록된 MDI 자식 창 클래스의 이름으로 설정됩니다. hOwner 멤버는 애플리케이션의 instance 핸들로 설정됩니다.

다음 예제에서는 다중 패드의 AddFile 함수를 보여줍니다.

HWND APIENTRY AddFile(pName) 
TCHAR * pName; 
{ 
    HWND hwnd; 
    TCHAR sz[160]; 
    MDICREATESTRUCT mcs; 
 
    if (!pName) 
    { 
 
        // If the pName parameter is NULL, load the "Untitled" 
        // string from the STRINGTABLE resource and set the szTitle 
        // member of MDICREATESTRUCT. 
 
        LoadString(hInst, IDS_UNTITLED, sz, sizeof(sz)/sizeof(TCHAR)); 
        mcs.szTitle = (LPCTSTR) sz; 
    } 
    else 
 
        // Title the window with the full path and filename, 
        // obtained by calling the OpenFile function with the 
        // OF_PARSE flag, which is called before AddFile(). 
 
        mcs.szTitle = of.szPathName; 
 
    mcs.szClass = szChild; 
    mcs.hOwner  = hInst; 
 
    // Use the default size for the child window. 
 
    mcs.x = mcs.cx = CW_USEDEFAULT; 
    mcs.y = mcs.cy = CW_USEDEFAULT; 
 
    // Give the child window the default style. The styleDefault 
    // variable is defined in MULTIPAD.C. 
 
    mcs.style = styleDefault; 
 
    // Tell the MDI client window to create the child window. 
 
    hwnd = (HWND) SendMessage (hwndMDIClient, WM_MDICREATE, 0, 
        (LONG) (LPMDICREATESTRUCT) &mcs); 
 
    // If the file is found, read its contents into the child 
    // window's client area. 
 
    if (pName) 
    { 
        if (!LoadFile(hwnd, pName)) 
        { 
 
            // Cannot load the file; close the window. 
 
            SendMessage(hwndMDIClient, WM_MDIDESTROY, 
                (DWORD) hwnd, 0L); 
        } 
    } 
    return hwnd; 
} 

WM_MDICREATE 메시지의 lParam 매개 변수에 전달된 포인터는 CreateWindow 함수에 전달되고 CREATETRUCT 구조체의 첫 번째 멤버로 나타나며 WM_CREATE 메시지에 전달됩니다. 다중 패드에서 자식 창은 추가 데이터에서 문서 변수를 초기화하고 편집 컨트롤의 자식 창을 만들어 메시지 처리 에 WM_CREATE 동안 자체를 초기화합니다.