Tutorial 10: Dialog Box as Main Window
이제 겨우 GUI 프로그램의 실제적인 것인 다이얼로그 박스의 설명을 할 수 있게 되었다. 이 번장과 다음장에 걸쳐서, 다이얼로그 박스의 사용법을 익힐수 있게 된다.
소스 1 | 리소스 스크립트 1 | 실행 결과 1 |
소스 2 | 리소스 스크립트 2 | 실행 결과 2 |
|
눈치빠른 독자라면 이전 장들의 예제들이, TAB 키로 포커스를 변경할 수 없다는 것을 알 수 있을 것이다. 입력하고 싶은 컨트롤로 포커스를 변경하려면 , 해당 컨트롤을 클릭해야만 한다. 이는 약간 번거롭다. 또한,부모윈도우의 배경색을 보통때와는 다르게 회색으로 변경한것을 눈치 챘는지도 모르겠다. 이는, 컨트롤과 부모윈도우의 작업영역과의 조화때문에 행한 것이다. 이 문제를 피하는 방법도 있지만, 간단하지 않고, 모든 컨트롤을 서브클래싱해야만 한다. (나중에 설명된다)
이렇게 불편한 이유는, 컨트롤은 원래 보통 윈도우용이 아니고, 다이얼로그 박스 등에서 사용하도록 설계되어 있기 때문이다. 다이얼로그 박스의 작업영역은 보통회색이므로, 버튼과 같은 컨트롤의 기본컬러가 회색인 것이다.
세부적인 설명에 들어가기 전에, 다이얼로그 박스가 어떤 것인지를 설명한다. 다이얼로그 박스라는 것은, 컨트롤과 유기적으로 동작되도록 설계되어있는 윈도우이다. Windows는 내부적으로 "다이얼로그 박스 매니저" 를 제공하고 있고, 다이얼로그 박스 매니저는 유저가 탭을 누른다거나 포커스를 이동한다거나 엔터키를 누르고, 디폴트 푸쉬 버튼을 누르는, 등의 행동을 할때 해당 처리를 수행하게 되어 있어서 프로그래머의 번거로움을 줄여주는 역활을 담당하고 있다.(자체처리)
다이얼로그 박스는 주로 유저로부터의 입력을 받거나 유저에게 어떠한 정보를 통지하기 위해서 사용된다. 그런 이유로, 다이얼로그 박스는 입출력의 「블랙 박스」라고 생각할 수도 있다. 즉, 다이얼로그 박스를 사용하기 위해 내부적으로 다이얼로그 박스가 어떻게 처리되는지를 알 필요가 없고,단지 다이얼로그 박스와의 정보를 교환하는 방법만 알면 된다. 이것은 객체 지향 방식에서, 「은폐」라고 불리고 있는 기술이다.
만약,완전히블랙 박스로서 설계되었다면, 어떤 지식도 없이 그것을 사용할 수가 있다. 문제는, 블랙박스가 완전히 블랙박스화 되어야만 가능한 것으로, 실제로 매우 어려운 것이다. Win32API도 블랙 박스와 같이 설계되어 있다. (최대한)
다시 다이얼로그 박스로 돌아와서, 다이얼로그 박스는 프로그래머의 부담을 줄여주는 기능이 있다. 보통, 표준 윈도우에 컨트롤을 직접 생성하려면, 서브 클래싱하지 않으면 안 되고, 키보드 처리도 스스로 해야만 한다. 그러나, 다이얼로그 박스에서 컨트롤을 사용할 때는, 다이얼로그 박스 매니져가 프로그래머를 대신해서 키보드와 같은 각종 기본적인 처리를 수행해 준다. 그러므로 프로그래머는, 다이얼로그 박스에서 입력값을 구하는법과 다이얼로그 박스에 어떻게 명령을 전송하는지만 알면 되는 것이다.
다이얼로그 박스는 메뉴와 비슷한 방법으로 사용되며 리소스로서 정의한다. 다이얼로그 박스와 그 컨트롤의 특징을 기록한 다이얼로그 박스 템플릿을 사용해서, 리소스 스크립트를 컴파일 한다.
모든 리소스는 같은 리소스파일에 기록 되어야만 한다. 텍스트 편집기에서도 다이얼로그 박스 템플릿을 작성할 수 있지만, 추천하는 방법은 아니다. 리소스 편집기를 사용해서 비주얼방식으로 작성하는 것이 좋다. 다이얼로그 박스상의 컨트롤을 손으로 편집하는 것은 매우 힘든 작업이기 때문이다. 몇가지의 뛰어난 리소스 에디터가 있고, 대부분의 통합환경(IDE)에는 이미 포함되어 있다. 이러한 리소스 에디터로 리소스 스크립트를 작성했다면, MFC에서 사용하는 함수 부분 등, 불필요한 곳은 삭제하고 사용하기 바란다.(MSVC를 사용하는 경우)
다이얼로그 박스에는 두가지의 형태가 있다. 모달 과 모달리스다. 모달리스는 다른 윈도우로 포커스를 옮기는 것을 허가하는 것이다. MS-Word(나 IE)의 찾기창을 생각하면 이해하기 쉬울 것이다. 모달에는 또다시 두가지 형태가 있다. 어플리케이션 모달 과 시스템 모달이다. 어플리케이션 모달은, 같은 어플리케이션으로 표시되는 윈도우로서, 포커스 이동을 할 수 없다. 그러나, 다른 어플리케이션의 윈도우로는 포커스를 이동할 수 있다. 시스템 모달은 다이얼로그 박스에서 처리를 완료하지 않으면 다른 윈도우로 절대로 포커스를 이동할 수 없도록 된 것 을 의미한다.
모달리스는 CreateDialogParam 함수를 호출해서 생성할 수 있다. 이에 비해, 모달방식은 DialogBoxParam 함수를 호출 하게 되어 있다. 어플리케이션 모달 다이얼로그와 시스템 모달 다이얼로그박스의 유일한 차이점은, DS_SYSMODAL 스타일의 설정 유무이다. DS_SYSMODAL가 지정되면, 시스템 모달이 된다. (다른윈도우로 포커스를 이동하지 못한다)
SendDlgItemMessage 함수를 사용해서, 다이얼로그 박스의 컨트롤과 정보 교환이 가능하게 된다. 이 함수 다음과 같이 선언 되어 있다.
SendDlgItemMessage proto hwndDlg : DWORD,\ idControl: DWORD,\ uMsg : DWORD,\ wParam : DWORD,\ lParam : DWORD이 API 함수는 컨트롤과 사용할 때 아주 유용하다. 예를 들어, 에디트 컨트롤로부터 문자열을 얻고 싶다면 다음과 같이 할 수 있다.
call SendDlgItemMessage, hDlg, ID_EDITBOX, WM_GETTEXT, 256, ADDR text_bufferWindows는 get 이나 set 이라는 제어 방법을 제공하고 해당API 함수도 준비해 주고 있다. 예를 들어, GetDlgItemText나 SetDlgItemText라는 함수이다. 이러한 함수는 프로그래머에게 아주 편리한 것으로, 메세지마다 의미가 다른 wParam나 lParam를 일부러 조사하지 않아도 되게 된다. 그래서, 코드의 유지보수를 쉽게하므로, 될 수 있는 한 이러한 제어 API 함수를 사용하도록 하자.
Windows 다이얼로그 박스 매니저는, 다이얼로그 박스 프로시저라고 불리는 특수한 콜백 함수로 메세지를 전송하게 되어 있다. 다이얼로그 박스 프로시저의 형식은 아래와 같이 되어 있다.
DlgProc proto hDlg : DWORD,\ iMsg : DWORD,\ wParam: DWORD,\ lParam: DWORD다이얼로그 박스 프로시저는 윈도우 프로시저와 매우 비슷한 것으로, 윈도우 프로시저의 반환값이 LRESULT였는것에 비해 , 다이얼로그 박스 프로시저는 TRUE/FALSE로 되어 있다. Windows 내부에서 처리를 수행하는 다이얼로그 박스 매니저가, 다이얼로그 박스에서의 진정한 윈도우프로시저이다. 다이얼로그 박스 매니저가, 다이얼로그 박스 프로시저를 메세지를 추가해서 호출 하는 것이다.
일반적인 방법을 설명하면 다음과 같이 된다.
생성하는 다이얼로그 박스 프로시저가 메세지를 처리한다면, eax 레지스터에 TRUE를 설정 한 후 리턴 한다. 만약, 메세지를 처리하지 않는다면, eax 레지스터에 FALSE를 설정한다. 다이얼로그 박스 프로시저에서는 처리하지 않는 메세지에 대해서 DefWindowProc 함수와 같이 처리해서는 안 된다. 이런 이유는, 다이얼로그 박스 프로시저는 진정한 윈도우 프로시저가 아니기 때문이다.다이얼로그 박스의 주된 두가지 사용법을 설명한다. 어플리케이션의 메인 윈도우로서 사용할 수도 있고, 유저와의 정보교환용으로서 사용할 수도 있다. 이 번장에서는, 전자(메인 윈도우로 사용)의 프로그램을 설명하지만, 이것도 2가지의 해석 방법이 존재한다.
- RegisterClassEx 함수로 등록한 클래스 템플릿으로 다이얼로그 박스를 사용할 수 있다. 이 경우, 다이얼로그 박스는 단순한 표준의 윈도우와 같이 동작한다. 즉, 윈도우클래스의 멤버 lpfnWndProc 로 지정되는 윈도우 프로시저로 메세지가 보내진다. 다이얼로그 박스 프로시저가 아닌 경우이다. 이 방법이 편리한 경우는, 컨트롤을 사용하지 않는 경우로서, Windows가 생성해 주는 것이다. 게다가, TAB 키등의 키보드 처리도 Windows가 처리해 준다. 게다가 윈도우 클래스 구조체에서 윈도우의 아이콘이나 커서를 지정할 수도 있다.
- 부모윈도우를 만들지 않고 , 다이얼로그 박스만을 생성한다. 이 방법은 메시지 루프를 작성 하지 않아도 된다. 왜냐면, 메세지가 직접 다이얼로그 박스 프로시저에 보내지기 때문이다. 게다가, 윈도우 클래스를 등록할 필요도 없다.
이 번장의 내용은 꽤 많으므로 처음방법을 설명하고 다음방법도 설명할 것이다.
|
|
|
|
그럼, 먼저 처음방식을 설명한다.(메인윈도우로 사용)
예제에서는, 어떻게 다이얼로그 템플릿을 윈도우 클래스로 등록하고, 클래스로부터 「윈도우」를 어떻게 생성하는지를 설명한다. 컨트롤을 생성할 필요가 없기 때문에, 단순한 프로그램 구조이다.
MyDialog DIALOG 10, 10, 205, 60여기에서는, 「DIALOG」키워드의 이전에 있는 「MyDialog」라는 다이얼로그 이름을 선언하고 있다. 이어지는 인수는 다이얼로그 박스의 크기등을 나타내는 수치지만, 순서대로, X좌표, Y좌표, 폭, 높이, 값이다(단위가 반드시 픽셀은 아니다. DLU : Dialog Logical Unit = 시스템폰트에 맞춰서 크기가 자동으로 변화되는 단위.).
STYLE 0x0004 | DS_CENTER | WS_CAPTION | WS_MINIMIZEBOX | WS_SYSMENU | WS_VISIBLE | WS_OVERLAPPED | DS_MODALFRAME | DS_3DLOOK다이얼로그 박스의 스타일을 선언하고 있다.
CAPTION "Our First Dialog Box"이것은 다이얼로그 박스의 타이틀 바에 표시되는 문자열이다.
CLASS "DLGCLASS"이 행은 아주 중요하다.「CLASS」키워드에 의해서 , 다이얼로그 박스 템플릿을 윈도우 클래스로 사용할 수 있게 된다. 「CLASS」키워드뒤에 있는 문자열이 「윈도우 클래스명」이 된다.
BEGIN EDITTEXT IDC_EDIT, 15,17,111,13, ES_AUTOHSCROLL | ES_LEFT DEFPUSHBUTTON "Say Hello", IDC_BUTTON, 141,10,52,13 PUSHBUTTON "E&xit", IDC_EXIT, 141,26,52,13 END위의 BEGIN-END 블록으로, 다이얼로그 박스의 컨트롤을 정의하고 있다. 일반적으로 다음과 같이 사용한다.
control-type "text" , controlID, x, y, width, height [, styles]control-type은 컴파일러가 사용하는 정수므로, 각각의 컴파일러에 포함되어 있는 매뉴얼을 참고하기 바란다.
그럼, 소스 코드로 돌아와서, 재미있는 부분은 윈도우 클래스 구조체다.
mov wc.cbWndExtra, DLGWINDOWEXTRA mov wc.lpszClassName, OFFSET ClassName보통, 이 멤버는 NULL로 설정했지만, 다이얼로그 박스 템플릿을 윈도우 클래스로 등록할 때는, 이 멤버를 반드시 DLGWINDOWEXTRA에 설정해야 한다. 이 클래스명은 리소스 스크립트에서 사용하는 「CLASS」키워드의 문자열과 같은것을 사용해야하는 것도 잊지 말기 바란다. 나머지의 멤버는 보통때 처럼 설정하면 된다. 윈도우 클래스 구조체 설정이 끝나면, RegisterClassEx 함수로 해당 클래스를 등록한다. 이미 사용해 봤을 것이다. 바로, 보통의 윈도우 클래스를 등록하는 절차와 같다.
invoke CreateDialogParam, hInstance, ADDR DlgName, NULL, NULL, NULL「윈도우 클래스」 등록을 마친 뒤에는, 다이얼로그 박스를 생성한다. 이 예제에서는, CreateDialogParam 함수를 사용하므로, 모달리스형태로 생성한다. 이 함수는 5개의 인수를 취하지만, 1번째와 2번째만 설정하고 나머지는 자주 사용하지 않는다, 두가지의 인수는 인스턴스 핸들과 다이얼로그 박스 템플릿이름에 대한 포인터이다. 클래스이름의 포인터가 아님을 주의하기 바란다.
이 시점에서, Windows는 다이얼로그 박스와 컨트롤을 생성한다. 이상태에서, WM_CREATE 메세지를 윈도우 프로시저가 받게 될 것이다.
invoke GetDlgItem, hDlg, IDC_EDIT invoke SetFocus, eax다이얼로그 박스가 생성되면, 에디트 컨트롤로 포커스를 옮기기 위해서,포커스를 이동하는 코드를 WM_CREATE 섹션에서 사용하고 싶지만, 아직 컨트롤이 만들어지지 않았기 때문에, GetDlgItem 함수를 호출 해도 실패하게 된다. 그래서, GetDlgItem 함수를 호출하는 것은, 다이얼로그 박스와 그 컨트롤이 생성된 이후므로, UpdateWindow 함수를 호출 한 후에 위의 2행을 입력하기로 한다. GetDlgItem 함수는 컨트롤 ID를 인수로 취하기 때문에,해당 컨트롤 윈도우의 핸들을 반환하는 함수로, 컨트롤 ID는 알고 있지만, 윈도우 핸들을 모르는경우에 사용한다.
invoke IsDialogMessage, hDlg, ADDR msg .IF eax ==FALSE invoke TranslateMessage, ADDR msg invoke DispatchMessage, ADDR msg .ENDIF프로그램이 메시지 루프에 들어가서 , Windows로부터 오는 메세지를 처리하거나 해석하기 전에, IsDialogMessage 함수를 호출 해서 , 다이얼로그 박스 매니저가 생성한 다이얼로그 박스의 키보드 처리를 할 수 있도록 설정한다. 이 함수가 TRUE를 반환하게 되면, 그 때의 메세지는 다이얼로그 박스 매니저가 대신 처리해 주게 된다. 이전 장과는 다른것을 주의하기 바란다. 윈도우 프로시저가 에디트 컨트롤로부터 문자열을 얻을때, GetWindowText 함수 대신에 GetDlgItemText 함수를 호출 한다. GetDlgItemText 함수는 윈도우 핸들이 아니고 컨트롤 ID를 인수로 받게 되어 있기 때문에 다이얼로그 박스를 사용하는 경우에 아주 간단하게 처리 할 수 있게된다.
|
이젠, 메인 윈도우로 사용되는 다이얼로그 박스의 두번째의 설명을 하겠다. 예제에서는, 모달형식의 어플리케이션을 생성한다. 메시지 루프는 전혀 필요하지 않다!
|
|
|
DlgProc proto :DWORD, :DWORD, :DWORD, :DWORDDlgProc 함수의 프로토타입을 선언하고 있으므로, DlgProc 함수의 포인터를 다음과 같이 사용할 수 있다.
invoke DialogBoxParam, hInstance, ADDR DlgName, NULL, addr DlgProc, NULLDialogBoxParam 함수는 5개의 인수를 취하며, 다음과 같다
1 인스턴스 핸들
2 다이얼로그 박스 템플릿이름
3 부모윈도우 핸들
4 다이얼로그 박스 프로시저의 포인터
5 다이얼로그 박스 고유의 데이터
DialogBoxParam 함수는 모달 다이얼로그 박스를 생성하고, 다이얼로그 박스가 닫혀질 때까지 제어를 반환하지 않는다.
.IF uMsg==WM_INITDIALOG invoke GetDlgItem, hWnd, IDC_EDIT invoke SetFocus, eax .ELSEIF uMsg==WM_CLOSE invoke SendMessage, hWnd, WM_COMMAND, IDM_EXIT, 0다이얼로그 박스 프로시저는 윈도우 프로시저와 매우 비슷하기는 하지만, WM_CREATE 메세지를 받아들이지 않는 것이 가장 다르고 처음으로 받는 메세지는 WM_INITDIALOG 라는 것이다. 보통, WM_INITDIALOG 섹션에서 초기화 코드를 생성하면 되고, 만약에 어떤 처리를 하고자 한다면, TRUE를 eax 레지스터에 설정해서 함수를 리턴 해야 한다.(룰이다)
내부적으로 다이얼로그 박스 매니저는, 생성한 다이얼로그 박스가 WM_CLOSE 메세지를 받을때, 기본적으로는 WM_DESTROY 메세지를 생성한 다이얼로그 박스 프로시저에 보내지 않는다. 그래서, 만약 유저가 닫기 버튼을 눌렀을 때, 어떤 처리를 하고 싶다면, WM_CLOSE 메세지에 대한 처리를 해 주어야한다. 이 예제에서는, wParam에 IDM_EXIT를 설정하고, WM_COMMAND 메세지를 전송하고 있다. 이것에 의해서 , 유저가 메뉴의 「Exit」를 선택한 것이라고 생각하게 된다. EndDialog 함수는 IDM_EXIT에 대한 응답으로 호출 된다.
그외 다른 부분의 WM_COMMAND 메세지에 대한 처리는 이전과 같아서, 생략 한다.
다이얼로그 박스를 종료하고 싶다면, EndDialog 함수를 호출 하는 것이 유일한 방법이다. 절대로 DestroyWindow 함수를 호출 해서는 안 된다. EndDialog 함수는 곧바로 윈도우를 닫지 않고, 다이얼로그 박스 매니저가 관리하는 플래그를 설정함으로서, 다음의 실행 코드로 계속되는 것이다.
다음으로, 리소스 파일을 보자. 주목해야 할 부분은, 메뉴명으로 문자열을 사용하는 것이 아니라, IDR_MENU1 이라는 값을 사용하고 있다는 점이다. DialogBoxParam 함수로 생성한 다이얼로그 박스에 메뉴를 붙이고 싶은 경우에는, 이런 방법이 사용된다. 다이얼로그 박스 템플릿에서, 메뉴 리소스 ID의 뒤에 「MENU」키워드(IDR_MENU1 MENU)를 잊어서는 안된다.
이 번장에서 설명한 두가지 방법중, 후자의 예에서는 아이콘이 없다는 것을 눈치챌 것이다. 그렇지만, WM_INITDIALOG 섹션에서, WM_SECTION 메세지를 보내는 것으로, 아이콘을 설정할 수도 있다.