#4. 메시지 루프 - 대화상자
Modeless
대화상자를 지원하기 위해서는 대화상자에서 직접 처리하는 키보드 메시지(TAB
, ESC
, ENTER
, ALT+니모닉
등)를 중복해서 처리하지 않도록 다음과 같이 메시지 루프를 사용해야 한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
BOOL PumpMessage(LPMSG lpMsg, HWND hWnd, UINT wMsgFilterMin, UINT wMsgFilterMax) {
BOOL result = ::GetMessage(&msg, NULL, NULL, NULL);
if (result == 0) {
return FALSE; // WM_QUIT
}
else if (result == -1) {
// error 발생. msg 포인터가 유효하지 않거나, hWnd가 유효하지 않은 윈도우 핸들인 경우
DWORD error = GetLastError();
}
// 대화상자 메시지가 아니고, 단축키도 아니면 메시지 처리
else if (!IsDialogMessage(hwndDlgModeless, &msg) &&
!TranslateAccelerator(hwndMain, haccel, &msg)) {
::TranslateMessage(&msg);
::DispatchMessage(&msg);
}
return TRUE;
}
일반적으로 DialogBoxIndirect()
로 생성한 Modal
대화상자는 내부적으로 사용하는 메세지 루프에서 위의 과정을 처리해 주어 IsDialogMessage()
를 따로 호출할 필요가 없다.
하지만, MFC에서는 CDialog::PreTranslateMessage()
에서 IsDialogMessage()
함수를 호출하여 Modal
대화상자이건, Modeless
대화상자이건 상관없이 대화상자 키보드 메시지를 무시하도록 되어있다.
이는 MFC 에서의 Modal
대화상자는 사실 CreateDialogIndirect()
를 호출하여 생성된 Modeless
대화상자이며, 메시지 루프에 의해서 Modal
대화상자처럼 구현되어 있기 때문이다.
MFC처럼 대화상자의 Modal
을 구현하기 위해서는 대화상자의 종료상태를 검색하는 과정이 필요하다.
다음은 대략적인 DoModal
의 의사 구현 코드다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
void Dialog::DoModal() {
// MainFrame 아니면, 최상위 Parent Window
HWND owner = GetOwner();
// 대화상자 생성...
CreateDialogIndirect();
// Owner 비활성
EnableWindow(owner, FALSE);
while (1) {
if (!PumpMessage(&msg, NULL, NULL, NULL)) {
::PostQuitMessage(0); // WM_QUIT 를 주 쓰레드로 전송
break;
}
// EndDialog()가 호출될 때 아마도 이 함수가 FALSE 가 리턴되도록 플래그 설정을 해야 한다.
if (!IsContinue()) {
break;
}
}
// Owner 활성
EnableWindow(owner, FALSE);
SetActiveWindow(owner, TRUE);
// 대화상자 종료
DestroyWindow();
}
대화상자의 종료는 버튼 클릭등의 이벤트에 의해서 처리되는 것이므로, DispatchMessage()
문 다음에 IsContinue()
를 배치하여 실행하더라도 무리가 없다.
idle
처리를 위해 다음과 같이 수정할 수도 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
BOOL idle = TRUE;
while (1) {
// OnIdle 처리 블록
// OnIdle을 호출할 필요가 있고, 메시지가 없다면 OnIdle()을 호출한다.
while (idle && !::PeekMessage(pMsg, NULL, NULL, NULL, PM_NOREMOVE)) {
// owner에 WM_ENTERIDLE 을 전송하여 idle 처리를 할 수 있게 한다.
::SendMessage(owner, WM_ENTERIDLE, MSGF_DIALOGBOX, (LPARAM)m_hWnd);
idle = FALSE;
}
// 메시지 처리 블록 - 메시지 큐에 있는 모든 메시지를 처리한다.
do {
if (!PumpMessage(&msg, NULL, NULL, NULL)) {
::PostQuitMessage(0); // WM_QUIT 를 주 쓰레드로 전송
goto ExitLoop; // 중첩된 루프 탈출
}
// EndDialog()가 호출될 때 아마도 이 함수가 FALSE 가 리턴되도록 플래그 설정을 해야 한다.
if (!IsContinue()) {
goto ExitLoop; // 중첩된 루프 탈출
}
idle = TRUE; // 어떤 메시지가 처리되었다면 다음번에 OnIdle이 호출될 수 있게 한다.
} while (::PeekMessage(&msg, NULL, NULL, NULL, PM_NOREMOVE));
}
ExitLoop:
대화상자의 idle
타임에는 owner
윈도우에 WM_ENTERIDLE
을 전송하여 owner
윈도우가 idle
타임을 활용할 수 있게 한다.
댓글남기기