모델리스형은 대화상자를 열어놓은 채로 메인 윈도우를 조작할 수 있기 때문에 모달형 대화상자보다는 더 복잡하며 사용하기도 어렵다. 골치 아픈 문제가 발생할 수도 있으며 게다가 대화상자를 닫지 않은 채로 값을 변경하고 메인 윈도우가 변경된 값을 즉각 인지할 수 있도록 해 주어야 하는 부담까지 지니고 있다. 다음 단계를 따라 InfoDlg 프로젝트를 모델리스형으로 변경해 보자.
1일단 Mless라는 이름으로 프로젝트를 만들고 이 프로젝트를 InfoDlg 프로젝트와 완전히 동일한 기능을 가지도록 만들어 보자. Mless 프로젝트를 만든 후 Mless 디렉토리로 InfoDlg.cpp 소스 파일과 InfoDlg.rc, resource.h 파일을 복사한다. 그리고 InfoDlg.*를 Mless.*로 이름을 변경한 후 프로젝트에 포함시킨다. Mless.cpp의 선두에 있는 다음 한줄만 변경하고 나머지는 그대로 둔다.
LPSTR lpszClass="Mless";
이 프로젝트를 컴파일하여 실행시키면 InfoDlg.exe와 완전히 동일한(타이틀 바만 다르다) 예제가 만들어졌을 것이다. 이 예제를 변형하여 모델리스형 대화상자를 만들어 볼 것이다.
2모델리스형 대화상자를 만들기 위해서는 우선 대화상자를 호출하는 DialogBox 함수를 없애야 한다. 이 함수는 모달형의 대화상자를 만들고 실행시키는 모든 일을 다 하며 대화상자를 종료하기 전에는 리턴하지 않는 특성을 가지고 있으므로 이 함수를 사용해서는 모델리스형 대화상자를 만들 수 없다. 대화상자를 호출하는 WM_LBUTTONDOWN 메시지를 다음과 같이 수정한다.
case WM_LBUTTONDOWN:
if (!IsWindow(hDlg)) {
hDlg=CreateDialog(g_hInst,MAKEINTRESOURCE(IDD_DIALOG1),
hWnd,MlessDlgProc);
ShowWindow(hDlg,SW_SHOW);
}
return 0;
DialogBox 함수 대신 CreateDialog 함수를 사용하여 대화상자를 만들기만 하였다. CreateDialog 함수는 DialogBox 함수와 동일한 인수를 사용하되 대화상자를 만들기만 하며 만든 즉시 대화상자의 핸들값을 리턴한다. 이에 비해 DialogBox 함수는 대화상자를 만드는 것은 물론 대화상자를 운용하는 모든 일을 주관하며 대화상자가 닫힐 때까지 리턴하지 않는다. CreateDialog 함수가 대화상자를 만든 직후 바로 종료하기 때문에 모델리스형 대화상자가 되는 것이다. 대화상자 프로시저의 이름은 MlessDlgProc으로 변경하였다.
대화상자를 만든 후 곧바로 ShowWindow 함수를 호출하여 대화상자가 화면으로 나타나도록 하였다. 대화상자에 WS_VISIBLE 스타일을 주었다면 ShowWindow 함수는 호출해주지 않아도 상관없지만 디폴트로 대화상자는 WS_VISIBLE 스타일을 가지지 않으므로 ShowWindow 함수를 호출해 주어야 한다. 이 함수 호출을 생략해 버리면 대화상자가 만들어지기만 할 뿐 화면으로는 보이지 않게 된다.
모델리스형 대화상자를 만들 때 특히 주의해야 할 점은 같은 대화상자가 두번 만들어지지 않도록 해야 한다는 것이다. 모달형은 대화상자를 닫기 전에는 메인 윈도우를 조작할 수 없으므로 그런 문제가 발생하지 않지만 모델리스형은 대화상자가 열린채로 메인 윈도우에 명령을 내릴 수 있다. 대화상자가 열려있는 상태에서 또 마우스 왼쪽 버튼을 누르면 대화상자가 두 개 만들어지게 되는 기현상이 발생하게 되며 이는 골치 아픈 문제의 원인이 된다. 그래서 WM_LBUTTONDOWN 메시지에서는 대화상자를 만들기 전에 hDlg, 즉 대화상자의 윈도우 핸들을 조사해 보고 이 핸들이 유효한 윈도우 핸들인지를 먼저 조사해 본다.
BOOL IsWindow( HWND hWnd );
IsWindow 함수는 hWnd가 유효한 윈도우 핸들인지 조사해 보고 윈도우가 존재하면 TRUE를 리턴하고 그렇지 않으면 FALSE를 리턴한다. 그래서 hDlg가 존재하지 않은 경우에 한해서만 대화상자를 만들도록 하여 대화상자를 두번 만들지 않도록 한다. 대화상자의 존재 여부를 핸들의 유효성으로 판별하므로 hDlg 변수는 전역으로 선언해야 한다.
LRESULT CALLBACK WndProc(HWND,UINT,WPARAM,LPARAM);
HINSTANCE g_hInst;
LPSTR lpszClass="Mless";
int x;
int y;
char str[128];
HWND hDlg;
3모달형의 대화상자는 OK, Cancel 버튼 두 개가 주로 사용된다. OK는 입력한 값들을 받아들인다는 뜻이며 Cancel은 입력값을 버리라는 명령이되 두 버튼 모두 대화상자를 종료시키는 것은 동일하다. 그런데 모델리스형 대화상자에는 항상 열려있기 때문에 OK, Cancel 버튼이 논리적으로 잘 어울리지 않는다. 이 버튼보다는 대화상자를 열어놓은 채로 값을 실제로 변경시키는 Change 버튼과 대화상자를 닫는 Close 버튼이 모델리스형에 더 적합하다. 대화상자 편집기를 열어 두 버튼의 ID와 캡션을 다음과 같이 변경한다.
버튼 |
ID |
Caption |
OK |
ID_CHANGE |
Change |
Cancel |
ID_CLOSE |
Close |
4CreateDialog나 DialogBox 등의 함수들처럼 첫번째 인수로 인스턴스 핸들을 요구하는 함수들이 많이 있다. 그런데 프로그램의 인스턴스 핸들은 WinMain의 인수로 전달되는 지역변수이기 때문에 WinMain 외부의 다른 함수에서는 사용할 수 없다. 그래서 보통 g_hInst 나 glohInstance 등의 전역 변수를 선언하고 WinMain에서 이 전역변수에 인스턴스 핸들값을 대입해 주는 방법을 많이 사용한다. InfoDlg 예제에서도 이 방법을 사용하였다.
이와 마찬가지로 메인 윈도우의 핸들도 여러 곳에서 사용되는데 WinMain이나 WndProc 이외의 함수에서는 이 핸들값을 알 수 없다. 그래서 메인 윈도우의 핸들도 인스턴스 핸들과 마찬가지로 별도의 전역 변수에 대입해 두는 방법을 많이 사용한다. hMainWnd라는 이름의 전역 변수를 만들고 WinMain에서 윈도우를 만든 직후에 이 변수에 메인 윈도우의 핸들을 대입해 두도록 하자.
HWND hMainWnd;
int APIENTRY WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance
,LPSTR lpszCmdParam,int nCmdShow)
{
HWND hWnd;
MSG Message;
WNDCLASS WndClass;
g_hInst=hInstance;
................
hWnd=CreateWindow(lpszClass,lpszClass,WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,
NULL,(HMENU)NULL,hInstance,NULL);
ShowWindow(hWnd,nCmdShow);
hMainWnd=hWnd;
이 핸들값은 잠시 후 MlessDlgProc에서 사용된다.
5마지막으로 InfoDlgProc 함수를 MlessDlgProc으로 이름을 바꾼 후 코드를 약간 수정한다. 변경한 후의 코드는 다음과 같다. 어디가 어떻게 변경되었는지 InfoDlgProc의 경우와 비교해 보도록 하자.
BOOL CALLBACK MlessDlgProc(HWND hDlg,UINT iMessage,WPARAM wParam,LPARAM lParam)
{
switch(iMessage) {
case WM_INITDIALOG:
SetDlgItemText(hDlg,IDC_STR,str);
SetDlgItemInt(hDlg,IDC_X,x,FALSE);
SetDlgItemInt(hDlg,IDC_Y,y,FALSE);
return TRUE;
case WM_COMMAND:
switch (wParam) {
case ID_CHANGE:
GetDlgItemText(hDlg,IDC_STR,str,128);
x=GetDlgItemInt(hDlg,IDC_X,NULL,FALSE);
y=GetDlgItemInt(hDlg,IDC_Y,NULL,FALSE);
InvalidateRect(hMainWnd,NULL,TRUE);
return TRUE;
case ID_CLOSE:
DestroyWindow(hDlg);
hDlg=NULL;
return TRUE;
}
break;
}
return FALSE;
}
|
WM_INITDIALOG 메시지 처리 부분은 전혀 변경되지 않았으며 WM_COMMAND 메시지 처리 부분이 많이 변경되었다. 우선 ID_CHANGE 버튼이 눌러진 경우를 보면 대화상자 컨트롤에서 값을 읽어 전역 변수에 대입해주는 부분은 동일하되 대화상자를 종료하는 EndDialog 함수가 사라졌다. 모델리스형이므로 값만 변경할 뿐 대화상자를 종료할 필요는 없기 때문이다. 대신 변경된 값을 메인 윈도우에 즉각 반영하기 위해 InvalidateRect 함수를 호출하여 메인 윈도우 전체를 무효화시켰다.
ID_CLOSE가 눌러진 경우에는 대화상자를 파괴한다. 대화상자를 만들 때 CreateDialog 함수를 사용했으므로 대화상자를 닫을 때는 DestroyWindow 함수를 사용해야 한다. 대화상자를 파괴한 후 대화상자의 윈도우 핸들인 hDlg에 NULL을 대입하여 다시 대화상자를 만들수 있도록 해 주어야 한다.
그럼 이제 프로그램을 컴파일하여 실행시켜 보자. 실행 중의 모습은 다음과 같다.

대화상자를 열어놓고 메인 윈도우의 크기를 변경하거나 위치를 옮길 수 있다. 만약 메인 윈도우에 메뉴나 툴바 등이 있다면 선택할 수도 있을 것이다.