I'm on your side, when times get rough.

2009-02-24

Win32 Control: “CreateWindow”

Filed under: Programming — Peter_KIM @ 07:23

잡담:

현재 서버 또는 개인용 컴퓨터에서 우리가 주로 사용하는 GUI 운영 체제는 크게 Microsoft Windows, SUN Solaris, Apple Mc OS, LINUX X-Window 등이 있습니다. 이것들 외에도 역사의 뒤안길로 사라져 이제는 거의 잊혀진 GUI 운영 체제들도 상당히 많습니다. 운영 체제의 변화와 발전에 대한 이야기하다 보면, 과거의 역사적 사건들이 떠오르곤 합니다. 개인적으로는, IBM “OS/2”라는 운영 체제가 사라진 것에 대해 상당히 마음이 아파집니다.

 

컨트롤에 대한 이야기를 하려고 했는데…… 본론으로 들어가겠습니다.

Win32 환경에서 모든 윈도우(컨트롤)CreateWindow 함수를 이용하여 생성됩니다. 간단한 C 코드를 작성해 보겠습니다. 

(Visual Studio C++ Win32 프로젝트에서 마법사가 만들어준 프로그램도 좋습니다.)

……

HINSTANCE hInst;

HWND hWnd;

int CALLBACK WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow)

{

        WNDCLASS wc;

        MSG msg;

        BOOL bRet;

 

        if(!hPrevInstance) {

               wc.lpszClassName = "TestWin32Class";

               wc.lpfnWndProc = MainWndProc;

               wc.style = CS_OWNDC | CS_VREDRAW | CS_HREDRAW;

               wc.hInstance = hInstance;

               wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);

               wc.hCursor = LoadCursor(NULL, IDC_ARROW);

               wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);

               wc.lpszMenuName = "Test32Menu";

               wc.cbClsExtra = 0;

               wc.cbWndExtra = 0;

               RegisterClass(&wc);

        }

        hInst = hInstance;

        hWnd = CreateWindow("TestWin32Class", "TestWin32", WS_OVERLAPPEDWINDOW,

               0, 0, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL);

        ShowWindow(hWnd, nCmdShow);

        while((bRet = GetMessage(&msg, NULL, 0, 0)) != 0)

        {

               TranslateMessage(&msg);

               DispatchMessage(&msg);

        }

        return (int)msg.wParam;

}

 

LRESULT CALLBACK MainWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)

{

        PAINTSTRUCT ps;

        HDC hDC;

        switch(msg)

        {

        case WM_PAINT:

               hDC = BeginPaint(hWnd, &ps);

               EndPaint(hWnd, &ps);

               break;

        case WM_COMMAND:

               switch(wParam)

               {

               case IDM_MSG:

                       MessageBox(hWnd, "TestText", "TestCaption", MB_ICONINFORMATION);

                       break;

               case IDM_ABOUT:

                       DialogBox(hInst, "ABOUTDLG", hWnd, AboutDlgProc);

                       break;

               }

               break;

        case WM_DESTROY:

               PostQuitMessage(0);

               break;

        default:

               return(DefWindowProc(hWnd, msg, wParam, lParam));

        }

        return 0;

}

 

INT_PTR CALLBACK AboutDlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)

{

        switch (message)

        {

        case WM_COMMAND:

               if (LOWORD(wParam) == IDOK)

               {

                       EndDialog(hDlg, LOWORD(wParam));

                       return (INT_PTR)TRUE;

               }

               break;

        }

        return (INT_PTR)FALSE;

}

C/C++ 언어를 이용하여, Windows GUI 프로그램을 작성하다 보면, 많이 사용하는 함수가 CreateWindow 입니다. 무수히 많은 함수들과, Wrapper 클래스에서 함수를 처리하기 때문에 직접 사용하는 일이 없을 뿐입니다.

위의 소스 코드에서는 모두 3개의 윈도우를 생성합니다. 3개의 윈도우 모두 CreateWindow 함수를 이용하여 생성됩니다. DialogBox, MessageBox 함수 역시 내부적으로는 CreateWindow 함수를 호출하게 됩니다. 단지, 생성 코드가 운영체제 내부에 있을 뿐입니다.

작성된 프로그램을 실행하고, Visual Studio 도구에 있는 Spy++ 프로그램을 이용하여, 살펴보겠습니다. Spy++ 프로그램에서 창의 속성을 조사해보면, 그림과 같은 결과를 얻을 수 있습니다.


<MessageBox>

<DialogBox>

DialogBox, MessageBox 함수에서 만든 대화 상자의 클래스가 동일한 것을 알 수 있습니다. 조금 복잡하기는 하지만, CreateWindow 함수를 이용하여, DialogBox, MessageBox 함수에서 만든 대화 상자를 구현할 수도 있습니다. 모든 윈도우(컨트롤)는 메시지 처리 방식으로 작성되어 있고, 런타임에 발생하는 이벤트를 처리하기 위한 프로시저를 가지고 있어야 합니다. MFC, WTL 등의 Wrapper 클래스들은 메시지 맵이라는 매크로를 가지고 있습니다. 메시지 맵은 Windows 메시지를 해당 프로시저에 연결합니다.

메시지 맵과 연결된 프로시저를 잘 이용하면, 기본 컨트롤의 동작을 변경하거나, 또 다른 이벤트를 추가 할 수 있습니다. MFC 프로젝트에서 보았듯이 Sub-Classing 기법을 이용하여, 기본 컨트롤을 확장한 컨트롤을 만들 수 있습니다.

아래의 코드는 CreateWindow 함수를 이용하여, 사용자가 정의한 다이얼로그를 만들어 본 것입니다.

 

int CreateMyDialog()

{

        HWND hDlg;

        MSG msg;

 

        WNDCLASSEX MyDialogClass;

 

        if (GetClassInfoEx( NULL, "#32770", &MyDialogClass ))

        {

               MyDialogClass.cbSize = sizeof(WNDCLASSEX);

               MyDialogClass.lpfnWndProc = DialogProc;

               MyDialogClass.lpszClassName = "MyDialogClass";

 

               RegisterClassEx( &MyDialogClass );

        }

 

        hDlg = CreateWindowEx(WS_EX_DLGMODALFRAME | WS_EX_TOPMOST, "MyDialogClass",

                (LPCSTR)"Dialog From CreateWindowEx",

                WS_VISIBLE | WS_SYSMENU | WS_CAPTION, 0, 0, 200, 100,

                hWnd, NULL, hInst, NULL);

 

        ShowWindow(hDlg, SW_SHOW);

        UpdateWindow(hDlg);

 

        while (GetMessage(&msg, NULL, 0, 0) != 0)

        {

               if(!IsWindow(hDlg) || !IsDialogMessage(hDlg, &msg))

               {

                       TranslateMessage(&msg);

                       DispatchMessage(&msg);

               }

        }

        return msg.wParam;

}

 

LRESULT CALLBACK DialogProc(HWND hWndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)

{

        INT_PTR nRetVal= (INT_PTR)FALSE;

 

        switch(uMsg)

        {

        case WM_CREATE:

               CreateWindowEx(0, (LPCSTR)"STATIC", (LPCSTR)"This is Win32 Sample",

                WS_CHILD | WS_VISIBLE, 10, 10, 150, 25, hWndDlg, (HMENU)-1, hInst, NULL);

 

               CreateWindowEx(0, (LPCSTR)"BUTTON", (LPCSTR)"OK", WS_CHILD | WS_VISIBLE,

                       10, 40, 100, 25, hWndDlg, (HMENU)IDOK, hInst, NULL);

               break;

         case WM_COMMAND:

               if (LOWORD(wParam) == IDOK) {

                       nRetVal= (INT_PTR)TRUE;

                       EndDialog(hWndDlg, LOWORD(wParam));

                       DestroyWindow(hWndDlg);

               }

               break;

         case WM_CLOSE:

               DestroyWindow(hWndDlg);

               break;

        case WM_DESTROY:

               PostQuitMessage(0);

               break;

        default:

               return DefDlgProc(hWndDlg, uMsg, wParam, lParam);

               break;

        }

        return nRetVal;}

우선 CreateWindow 함수를 이용하여 윈도우를 생성하기 전에, 알아야 할 것이 있습니다. Spy++ 프로그램을 이용하여 알아낸 "#32770" 클래스에 대한 것입니다. 이 클래스와 연결된 메시지 처리 프로시저는 코드에서 본 것과 같이 운영체제 내부(MessageBox)에 구현되어 있거나, DialogBox 함수의 마지막 인자로 전달되어야 합니다. 그러므로, CreateWindow 함수에서 "#32770" 클래스를 직접 사용하여, 대화 상자를 만든다면, 해당 대화 상자의 메시지 처리 프로시저를 할당할 수 없게 됩니다.

WNDCLASSEX 구조체의 변수에 "#32770" 클래스의 내용을 복사하고, 메시지 처리 프로시저를 코드에 있는 DialogProc 함수에 연결합니다. 그리고 CreateWindow 함수를 이용하여, 대화 상자를 만듭니다. 이로서, 모든 작업이 끝났습니다. 앞에서 만들어본 대화 상자가 Modal 상태인 반면에 지금 만든 대화 상자는 Modeless 상태를 갖습니다. Modal 대화 상자는 좀더 복잡하므로, 여기서는 생략합니다.(^^;)

1. 모든 윈도우는 CreateWindow 함수로 만들어지고,

2. 메시지 처리 프로시저를 가지고 있으며,

3. Win32 윈도우(컨트롤)의 기본메시지 처리 프로시저를 조작하여 확장할 수 있다.

어떻게 보면, 아무도 이렇게 만들지 않을 수 있는 코드를 가지고 이야기 했습니다. 그러나, 이런 기본적인 동작을 알고 있어야만, MFC 또는 쉽다고 하는 C#, VB.NET 환경에서도 여러 가지 응용이 가능할 것입니다. 기본적으로 .NET Framework 환경에서 제공되는 기본 컨트롤은 Win32 리소스를 이용하여 만들어 진 것임을 안다면, 그것들 또한 이런 방식으로 동작할 것이 분명합니다.

Create a free website or blog at WordPress.com.