[MFC] Modeless Dialog 생성하기

반응형

이번 포스팅에서는 MFC 환경에서 Modeless Dialog를 생성하고 사용하는 법을 알아보겠습니다.

Dialog에는 Modal과 Modeless가 있는데 Modal은 부모 윈도를 제어할 수 없지만 Modeless는 각각 독립적으로 동작하는 특징이 있어서 저는 Modeless를 선호합니다.

Modeless Dialog의 Base Class는 CDialog입니다.

 

1. Dialog 비교

Modeless Dialog는 CDialog 기본 생성자를 호출하여 Dialg object를 생성하지만 DoModal() 대신 CDialog:Create 멤버 함수 호출이 필요합니다. Create()는 리소스 ID를 매개 변수로 사용하고 Dialog가 화면에 계속 표시된 상태로 즉시 반환됩니다.

  Modal Dialog Modeless Dialog
생성자 호출방식 생성자 + RESOURCE ID 기본 생성자 호출
윈도우 생성 함수 DoModal() Create() + RESOURCE ID

2. 사용자 정의 메시지 사용

Dialog에서 Windows 메시지를 View로 보내서 이벤트 처리. SendMessage() 또는 PostMessage() 사용합니다.


3. Modeless Dialog 생성 예제 코드

실제 동작하는 예제 코드를 처음부터 작성해 보겠습니다.

MFC SDI 프로젝트 기준입니다.

3.1. Dialog Resouce 생성

- 리소스 편집기에 가서 IDD_EBEST_SETTINGS라는 Dialog를 하나 생성하고 아래와 같이 컨트롤을 배치했습니다.

Dialog Properties의 Behavior-Visible 기본값이 False인데 True로 해주세요. 안 그러면 Create() 호출해도 안보입니다.

다이얼로그 컨트롤 배치

각 컨트롤 아이디는 아래와 같이 정했습니다.

Resource.h

3.2. Dialog Class 생성 및 Member Variable 추가

위 리소스편집기에서 IDD_EBEST_SETTINGS Dialog를 우클릭 후 "Add Class" 메뉴를 클릭하여 클래스를 생성합니다.

"CDlgEbestSettings" 이름으로 클래스를 생성했습니다.

그리고 Class Wizard를 열어서 아래와 같이 위에서 만든 각 컨트롤에 대응되는 멤버변수를 CDlgEbestSettings 클래스에 만들어줍니다. 저는 Control 타입의 변수를 선호해서 아래와 같이 추가했습니다.

Class Wizard

3.3. 코드 작성

Dialog 클래스에서는 Dialog UI 관련 처리만 하고 로직은 View 클래스를 통해서 다른 클래스와 상호작용하는 방식으로 합니다. 

CDlgEbestSettings Dialog는 프로그램 메뉴에서 버튼을 클릭하면 그 이벤트 핸들러를 CView클래스에 두고 거기서 CDlgEbestSettings Dialog를 생성할 겁니다. 

그래서 아래와 같이 CDlgEbestSettings.h에 CView* 를 파라미터로 받는 생성자를 하나 선언해 주고, Create() 함수도 선언해 줍니다.

Private 변수로 CView* _m_pNoyecubeView; 도 하나 정의했습니다.

헤더파일 내 컨트롤 변수

cpp파일에는 기본 생성자에 _m_pNoyecubeView를 NULL로 초기화 해주고

선언한 두 개의 함수를 작성하는데 생성자에는 넘겨받은 CView 포인터를 멤버변수에 저장하고 Create함수에서는 Dialog Resouce ID를 넘겨서 Dialog를 생성합니다. 여기에서의 Create()가 호출되려면 선행조건으로 기본생성자가 호출되어 이 Dialog의 객체가 생성되어 있어야 합니다. 그건 CView 클래스에서 합니다. CView 클래스는 SDI프로젝트 생성 시 <프로젝트명>+"View"로 자동 생성됩니다.

포인터 변수 초기화 코드

프로젝트명이 Noyecube라서 NoyecubeView.cpp가 있습니다.

추가 작성한 부분은 OnMenuFuncEbestSettings()와 OnInitialUpdate()입니다.

OnMenuFuncEbestSettings()는 메뉴에서 Dialog 생성버튼을 눌렀을 때 호출되는 핸들러입니다.

OnInitialUpdate()는 CView 클래스가 생성되고 나서 호출되는 버추얼함수입니다. 여기에서 CDlgEbestSettings Dialog의 객체를 생성하면서 자기 자신(CView)의 객체 포인터를 인자로 넘겨줍니다. 그럼 나중에 CDlgEbestSettings Dialog 객체에서 이 포인터를 갖고 있다가 이벤트 메시지와 파라미터를 View로 전달하여 View에서 처리할 수 있습니다.

OnInitialUpdate() 함수 추가는 Class View에 가서 View클래스를 선택한 상태로 Properties-Overrides 목록에서 OnInitialUpdate를 클릭하면 생성됩니다.

생성 코드

생성자, 소멸자에서는 아래와 같이 m_pDlgEbestSettings 변수를 초기화 및 메모리 해제 해줍니다.

생성자 및 소멸자 코드

3.4. Menu 바 Resource 

위에서 만든 Dialog를 열 수 있게 프로그램 메뉴에 버튼을 추가합니다. "Ebest Settings"를 우 클릭 후 "Add Event Handler"를 클릭하여 CView 클래스에 핸들러를 추가해 줍니다. 그럼 위 3.3. 과 같이 OnMenuFuncEbestSettings()가 생깁니다.

메뉴바에 버튼 추가

여기까지 하고 빌드 후 실행 해보면 위에서 만든 Dialog가 열립니다.

실행된 Modeless 다이얼로그

여기에서 "닫기" 버튼을 눌러도 안 닫히고 우측상단 "X"를 눌러서 창을 닫으면 다시 안 열립니다. DestroyWindow()가 호출되지 않았기 때문입니다. 각 버튼에 대한 이벤트핸들러를 추가하고, "X"에 대한 핸들러도 추가하여 Dialog가 닫혔을 때 DestroyWindow() 호출하여 다시 열리도록 합니다. "X"를 누르면 호출되는 OnClose() 함수를 Override 하여 "닫기" 버튼을 눌렀을 때와 동일하게 처리하겠습니다.

 

3.5. Close 처리

3.5.1. 핸들러 추가

- "닫기" 버튼의 이벤트 핸들러를 CDlgEbestSettings 클래스에 아래와 같이 추가합니다.

- CDlgEbestSettings Class 속성의 Messages 메뉴에서  WM_CLOSE 메시지에 대한 핸들러 OnClose()를 추가합니다.

Close 핸들러 등록
OnClose 등록

3.5.2. Dialog 핸들러에서 View Class에 이벤트 전달할 Message 정의

App.h 파일에 CDlgEbestSettings Dialog에서 CView로 던질 버튼클릭이벤트 윈도 메시지를 정의합니다. 

WM_USER 또는 WM_APP을 윈도메시지 Offset으로 잡으면 되는데 WM_USER는 다른 DLL에서 하드코딩값으로 사용하고 있어서 WM_APP을 기준으로 잡았습니다.

메시지 등록

MSG_ID_CDLGEBESTSETTINGS_EVENT 메시지를 던질 때 파라미터로 이벤트값을 넘겨줘야 하는데 Resource.h에 정의되어 있는 ID값으로 어떤 컨트롤에 이벤트가 발생했는지 알려줘도 되지만 저는 switch문의 점프테이블이 0번부터 깔끔하게 다시 정렬되는 것을 원하므로 아래와 같이 Resource.h의 ID에 대응되는 이벤트를 별도 정의하였습니다.

이벤트 정의

3.5.3. View Class에 3.5.1. 에서 정의한 user-defined message 핸들러 작성

3.5.1. 에서 정의한 MSG_ID_CDLGEBESTSETTINGS_EVENT 메시지는 Class Wizard에서 지원하지 않기 때문에 직접 작성합니다. 

View.h에 핸들러원형 선언,

핸들러 원형

View.cpp의 MESSAGE_MAP에 message ID와 핸들러 맵핑, 핸들러 코드 작성합니다.

메시지맵 등록
분기 코드

다시 Dialog.cpp의 핸들러로 가서 아래와 같이 코드 작성합니다. View로 사용자정의 메시지와 이벤트 파라미터를 PostMessage로 보냅니다.

PostMessage 호출

여기까지 하면 "닫기", "x" 버튼으로 다이얼로그 종료 시 DestoryWindow()가 호출되어 다시 Create 호출이 됩니다.

추가로 Esc, Enter 키에 대한 예외처리도 해둬야 합니다. Esc는 OnCancel(), Enter는 OnOK() 함수를 오버라이딩 후 코드내용을 삭제하면 됩니다. 또는 PreTranslateMessage에서 VK_ESCAPE, VK_RETURN을 무시해도 됩니다.

이벤트 핸들러 오버라이드
이벤트 핸들러에서 해당 이벤트 무시 처리

 

이상으로 MFC 환경에서 Modeless Dialog 생성방법을 실제 예제코드 기반으로 알아보았습니다.

반응형