Windows Forms MDI Best Practice

안녕하세요. Windows Azure MVP 남정현입니다.


Windows 8 Metro Style App에 대한 이야기가 요즈음 많이 회자되고 있습니다만, 그럼에도 불구하고 전문적인 도구나 프로그램에서의 Windows Forms에 대한 수요는 끊이지 않습니다. 오늘 살펴보려고 하는 내용은 Windows Forms 환경에서 MDI를 구현할 때 어떻게 시작을 하는 것이 가장 바람직한 모양을 만들 수 있는지 간단하게 정리한 예제 코드와 함께 살펴볼까 합니다.


예제 다운로드: https://skydrive.live.com/redir?resid=318484C5AAD6B73D!2684&authkey=!AO5fx9Fp4Xr0fog


둘러보기


아래 세 장의 스크린 샷과 같은 기능을 제공하는 MDI UI를 제공하는 Desktop App이 가장 이상적일 것입니다. 이 예제는 처음 시작하자마자 몇 개의 창이 자식 창으로 등록된 상태에서 시작됩니다.



자식 창을 전체 화면으로 바꾸었을 때, 아래와 같이 MenuStrip 제일 앞에 자식 창의 아이콘이, 제일 뒤에 최소화, 최대화, 닫기 버튼이 자동으로 병합됩니다.



창 메뉴를 클릭하면 현재 이 창 안에 등록된 모든 자식 창들이 열거되고, 선택 상태가 자동으로 관리됨은 물론, 메뉴를 선택하면 창 전환까지 가능합니다.



그리고 왼쪽편에는 트리 뷰 컨트롤을 도킹시켜 MDI 창과 조화롭게 레이아웃이 구성됩니다. 보이지는 않지만, 트리뷰 바로 다음에는 Splitter 컨트롤을 도킹시켜 창의 크기도 자유자재로 조절이 가능하게 만들었습니다. 제일 하단에는 상태 표시줄까지 추가하였습니다.


깔끔한 구성이지만 사실 이렇게 만드는 것이 기본 제공되는 사항만으로는 알아내기 다소 어려운 점이 있습니다. 위와 같이 만들기 위해서 무엇을 어떻게 해야하는지 단계별로 살펴보도록 하겠습니다.


MDI 부모로 창을 만들기


MDI는 부모 창과 자식 창으로 구성되며 부모 창 1개에 자식 창 여러개가 들어갈 수 있습니다. 이렇게 하기 위해서 부모 창으로 사용할 Form을 열고 디자인 타임에서 아래와 같이 설정하거나 IsMdiContainer 속성을 코드에서 적당한 시점 (생성자나 Load 이벤트 실행 시점)에서 True를 설정합니다.



위와 같이 설정하면 디자인 타임이나 런타임 상의 폼 화면의 클라이언트 영역이 회색 배경에 움푹 파인 모양으로 바뀌는 것을 볼 수 있습니다.


MenuStrip, ToolStrip, StatusStrip 붙이기


MDI 인터페이스에서 일상적으로 쓰이는 메뉴, 도구 모음, 상태 표시줄을 차례대로 가져다 높습니다. 아래 스크린 샷에서 빨간색 사각형으로 강조한 세 가지 항목을 폼에 가져다 놓습니다.



폼에 다른 메뉴 스트립 컨트롤들이 없다면, 폼의 속성 창을 다시 살펴보았을 때 아래와 같이 자동으로 MainMenuStrip에 방금 추가한 메뉴가 포함됩니다. MDI에 관련된 다른 여러 가지 설정들은 지금 이 속성에 연결된 컨트롤 앞으로 전달되거나 응용 동작에 연결되므로 이 설정이 중요합니다.



이렇게 해서 MDI 부모 창의 기본 구성이 완료되었습니다.


자식 창 관리 기능을 만들기


방금 추가한 메뉴 스트립 컨트롤에 MDI 창의 목록을 자동으로 보이고 현재 활성화된 MDI 창 항목을 강조 표시하며 메뉴 항목을 클릭하면 자동으로 창이 앞에 나타나는 기능을 구현해보도록 하겠습니다. 직접 코드를 작성해도 상관은 없지만, 빠르고 간편하게 완성도 높은 기능을 구현하기 위함이라면 지금 소개하는 방법이 가장 편리할 것입니다.


앞서 추가한 메뉴 스트립 컨트롤을 선택하고 아래 그림과 같이 MDI 목록을 표시할 대 메뉴 항목을 하나 추가합니다.



이번엔 메뉴 스트립 컨트롤을 선택한 상태에서 속성을 살펴봅니다. 속성 중에 MdiWindowListItem 속성이 비어있는 상태로 되어있을 것인데, 이것을 방금 추가한 대 메뉴 항목의 것으로 바꿔넣습니다.



좀 더 기능을 넣을 수 있는데, 이렇게 설정된 창 메뉴에 여러분이 임의로 새로운 메뉴를 더 넣을 수 있습니다. 개발자가 추가한 메뉴보다 뒤에 이러한 기능들이 오도록 구현되므로, 계단식 정렬이나 그리드 정렬 등의 창 관리 기능, 창 모두 닫기, 현재 활성화된 창 이외에 전부 닫기 같은 기능을 마음껏 구현하실 수 있습니다. 그리고 이러한 커스텀 메뉴들 제일 끝에 구분선 메뉴를 하나 더 넣어주시면 깔끔하게 구현됩니다.


참고로, 위의 예제와 같이 자동으로 메인 메뉴 항목을 완성하기를 원한다면 메뉴 스트립 컨트롤을 디자인 타임에서 오른쪽 버튼으로 클릭하고, 나타나는 메뉴 중에서 표준 항목 삽입이라는 메뉴를 아래 그림과 같이 클릭하면 간편하게 Mock Up UI 구성을 위한 항목이 자동으로 채워집니다. 표준 Windows UI 가이드 라인에 따라 필요한 항목들을 얼추 완성해주므로 이걸 활용하면 작업이 좀 더 빨라질 수 있습니다.



메인 화면의 상/하/좌/우 활용하기


Windows 탐색기, Microsoft Office 등 우리가 잘 아는 소프트웨어들은 공통적으로 이러한 MDI 화면의 상/하/좌/우에 적당한 패널을 배치하여 사용자의 작업을 돕는 대시보드를 넣어 프로그램을 돋보이게 만듭니다. 다행스럽게도 지금 만드는 MDI 부모 창도 이러한 구성이 가능합니다. 이 예제에서는 트리 뷰를 배치하려고 합니다. 트리뷰를 부모 창의 아무 곳에서 가져다 놓고, 컨트롤을 선택한 다음 아래와 같이 Dock 속성을 원하는 위치로 설정합니다. 예제에서는 Left로 설정했습니다.




적당한 크기를 Size 속성이나 디자인 타임에서 시각적으로 설정한 다음, Splitter (SplitContainer가 아닙니다.)를 추가하고 Splitter의 Dock 속성도 위와 동일한 값으로 설정합니다. 이렇게 하면 크기를 조절하려고 하는 컨트롤이 먼저 Stack에 올라가고, 그 다음에 크기 조절을 위한 Splitter 컨트롤이 올라가는 모양이 됩니다.


자식 창을 만들고 추가하기


이제 자식 창을 만들고 추가하는 코드를 넣을 차례입니다. 자식 창의 경우에는 임의로 새로운 폼을 몇 종류 만듭니다. 다른 어떠한 설정도 필요 없으며, 구분을 위해서 간단하게 라벨을 추가하여 서로 구분될 수 있게만 꾸며주기 바랍니다. 이 과정을 거쳐 예제에서는 ChildForm1, ChildForm2, ChildForm3라는 세 개의 클래스를 프로젝트에 아래 그림과 같이 추가하였습니다.



그 후, Parent Form의 디자인 타임 영역 상의 제목 표시줄을 더블 클릭하면 Load 이벤트에 대한 이벤트 처리기가 자동으로 만들어집니다. 그리고 아래와 같이 코드를 작성합니다.


C#


    private void MainForm_Load(object sender, EventArgs e) {
        new ChildForm1() { MdiParent = this, Visible = true };
        new ChildForm2() { MdiParent = this, Visible = true };
        new ChildForm3() { MdiParent = this, Visible = true };
    }


VB.NET


    Private Sub MainForm_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        Dim Window1 As New ChildForm1()
        Window1.MdiParent = Me
        Window1.Visible = True


        Dim Window2 As New ChildForm2()
        Window2.MdiParent = Me
        Window2.Visible = True


        Dim Window3 As New ChildForm3()
        Window3.MdiParent = Me
        Window3.Visible = True
    End Sub


그리고 앞서 추가한 ToolStrip 컨트롤에 버튼을 세 개 추가하고 각각 세 종류의 창을 띄우도록 위의 코드를 응용하여 만들어 봅니다.


자식 창을 최대화했을 때의 문제점


이제 거의 다 되었습니다. 프로그램을 실행해보고 이것저것 테스트해보면 잘 됩니다. 그런데 한 가지 눈에 거슬리는 점이 보이네요. 자식 창을 최대화했더니 아래처럼 메뉴 스트립과 자식 창의 아이콘이 서로 다른 줄에 그려집니다. 왜 그럴까요?



이 문제를 해결하기 위해 엄청나게 복잡한 코드를 쓰는 경우가 있습니다. 자식 창이 최대화 되면 자식 창의 ShowIcon 속성을 끈다거나 제목 표시줄을 없앤다거나 최대화를 흉내내도록 상태를 바꾸지 않고 창 크기만 업데이트한다던가 여러가지가 있지요. 그러나 이런 방법들은 거의 열 중 아홉 이상이 사소한 문제들을 많이 일으킵니다.


위의 문제를 해결하기 위해서는 아래 사항을 확인해야 합니다.



  • 앞서 이야기한 폼의 속성 중 MainMenuStrip 속성이 정확히 메인 메뉴를 가리키고 있는지 확인해야 합니다.

  • 필요한 경우 MainMenuStrip의 RenderMode 속성을 ManagedRenderMode가 아닌 System이나 Professional로 변경해 봅니다.

올바르게 적용되었다면 앞의 그림과 같은 단순하지만 튼튼한 토대를 갖춘 MDI UI를 가진 프로그램이 완성됩니다.





사족


이러한 특성을 극복할 수 없어서 다시 Windows Forms 초기에 제공되던 MainMenu 같은 컨트롤로 롤백하시는 경우도 종종 있을 수 있습니다. 그러나 최신 버전의 .NET Framework로 업그레이드하거나 개발 도구를 변경하게 될 경우에는 가능한 스트립 계열 컨트롤로 업그레이드하시는 것이 좋습니다. 지금 소개한 아티클을 참고하여 다시 시도해보시면 좋은 결과가 있을 것입니다. 🙂

댓글 남기기