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

2018.06.04

[VC/VC++] Dialog without the Resource File

Filed under: Programming — Peter_KIM @ 08:55

Visual C 또는 C++ 언어에서 Windows GUI 응용 프로그램을 만들 때, 습관적으로 또는 편리함의 이유로 리소스라는 것을 이용하는 것이 일반적입니다. 코딩을 하면서 만들어진 리소스 파일은 리소스 컴파일러에 의하여, 바이너리 값으로 변환되고, 링커(Linker)의하여 출력 파일에 병합됩니다.
대부분 다이얼로그 기반의 GUI 창을 만들 때에 리소스 파일(RC ,텍스트 형식)을 사용하는데, C/C++ 코드에서는 GUI 창을 어떻게 만들 수 있을지 알아보겠습니다.

여가에는 두가지의 방법이 있습니다.
첫번째 방법은 우선 비어 있는 다이얼로그 창을 만들고, 그 창의 메시지 핸들러 가운데, WM_CREATE 이벤트에서 다이얼로그를 구성하는 컨트롤을 만드는 것입니다.

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_INITDIALOG:
		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;
}

이 코드에서는 윈도우의 다이얼로그 클래스의 이름 “#32770″에서 클래스의 기본 정보를 얻고, 사용자 다이얼로그의 클래스를 등록하여 새로운 윈도우를 생성합니다. 연결된 메시지 처리 함수에서는 다이얼로그의 자식 컨트롤을 만들고, 이벤트를 처리합니다.
사실 이런 방법은 너무 고전적이면서도 쉬운 것입니다. 이미 10여년 전에 제 블로그에도 이 내용이 있습니다. 아래의 주소에서 전체 코드를 얻을 수 있습니다.
https://1drv.ms/u/s!An5hHO7t37wbgWvZlOnZTyorAY7J

다른 하나의 방법도 고전적이지만 조금은 어렵습니다. 다만, 스릴을 느낄 수 있는 코드를 즐기는 분이라면 강력하게 추천합니다.
아래의 MSDN 문서에도 설명되어 있지만, 조금만 코드를 잘못 바꾸어도 실행되지 않는 매우 위험한 코드 입니다.
https://msdn.microsoft.com/ko-kr/library/windows/desktop/ms644996(v=vs.85).aspx#modeless_box
이 페이의 아래의 “Creating a Template in Memory” 부분을 보겠습다. 이 코드는 있는 그대로 실행됩니다. 그러나, 코드를 다음과 같이 고쳐쓰면 정상적으로 실행이 되지 않습니다.
Before: nchar = 1 + MultiByteToWideChar(CP_ACP, 0, “OK”, -1, lpwsz, 50);
After: nchar = 1 + MultiByteToWideChar(CP_ACP, 0, “OKAY!”, -1, lpwsz, 50);

Before: nchar = 1 + MultiByteToWideChar(CP_ACP, 0, “Help”, -1, lpwsz, 50);
After: nchar = 1 + MultiByteToWideChar(CP_ACP, 0, “Help!”, -1, lpwsz, 50);
코드를 실행하는 시스템의 환경에 따라서 다르겠지만, 컨트롤을 표시하는 글자의 수를 어느 정도 늘리면 코드는 실행되지 않습니다.

그래서, 아래와 같이 컨트롤의 텍스트를 WM_INITDIALOG 이벤트에서 처리하도록 고쳐보았습니다.

LRESULT DisplayMyMessage(HINSTANCE hinst, HWND hwndOwner, LPSTR lpszMessage)
{
	HGLOBAL hgbl;
	LPDLGTEMPLATE lpdt;
	LPDLGITEMTEMPLATE lpdit;
	LPWORD lpw;
	LPWSTR lpwsz;
	LRESULT ret;
	int nchar;
 
	hgbl = GlobalAlloc(GMEM_ZEROINIT, 1024);
	if (!hgbl)
		return -1;
	lpdt = (LPDLGTEMPLATE)GlobalLock(hgbl);
 
	// Define a dialog box.
	lpdt->style = WS_POPUP | WS_BORDER | WS_SYSMENU | DS_MODALFRAME | WS_CAPTION;
	lpdt->cdit = 3;         // Number of controls
	lpdt->x = 10;  lpdt->y = 10;
	lpdt->cx = 100; lpdt->cy = 100;
 
	lpw = (LPWORD)(lpdt + 1);
	*lpw++ = 0;             // No menu
	*lpw++ = 0;             // Predefined dialog box class (by default)
 
	lpwsz = (LPWSTR)lpw;
	nchar = 1 + MultiByteToWideChar(CP_ACP, 0, "My Dialog", -1, lpwsz, 50);
	lpw += nchar;
 
	//-----------------------
	// Define an OK button.
	//-----------------------
	lpw = lpwAlign(lpw);    // Align DLGITEMTEMPLATE on DWORD boundary
	lpdit = (LPDLGITEMTEMPLATE)lpw;
	lpdit->x = 10; lpdit->y = 70;
	lpdit->cx = 80; lpdit->cy = 20;
	lpdit->id = IDOK;       // OK button identifier
	lpdit->style = WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON;
 
	lpw = (LPWORD)(lpdit + 1);
	*lpw++ = 0xFFFF;
	*lpw++ = 0x0080;        // Button class
	lpw += 2;
	*lpw++ = 0;             // No creation data
 
	//-----------------------
	// Define a Help button.
	//-----------------------
	lpw = lpwAlign(lpw);    // Align DLGITEMTEMPLATE on DWORD boundary
	lpdit = (LPDLGITEMTEMPLATE)lpw;
	lpdit->x = 55; lpdit->y = 10;
	lpdit->cx = 40; lpdit->cy = 20;
	lpdit->id = ID_HELP;    // Help button identifier
	lpdit->style = WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON;
 
	lpw = (LPWORD)(lpdit + 1);
	*lpw++ = 0xFFFF;
	*lpw++ = 0x0080;        // Button class atom
	lpw += 2;
	*lpw++ = 0;             // No creation data
 
	//-----------------------
	// Define a static text control.
	//-----------------------
	lpw = lpwAlign(lpw);    // Align DLGITEMTEMPLATE on DWORD boundary
	lpdit = (LPDLGITEMTEMPLATE)lpw;
	lpdit->x = 10; lpdit->y = 10;
	lpdit->cx = 40; lpdit->cy = 20;
	lpdit->id = ID_TEXT;    // Text identifier
	lpdit->style = WS_CHILD | WS_VISIBLE | SS_LEFT;
 
	lpw = (LPWORD)(lpdit + 1);
	*lpw++ = 0xFFFF;
	*lpw++ = 0x0082;        // Static class
 
	for (lpwsz = (LPWSTR)lpw; *lpwsz++ = (WCHAR)*lpszMessage++;);
	lpw = (LPWORD)lpwsz;
	*lpw++ = 0;             // No creation data
 
	GlobalUnlock(hgbl);
	ret = DialogBoxIndirect(hinst,
		(LPDLGTEMPLATE)hgbl,
		hwndOwner,
		(DLGPROC)DialogProc);
	GlobalFree(hgbl);
	return ret;
}
 
INT_PTR CALLBACK DialogProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	UNREFERENCED_PARAMETER(lParam);
 	switch (uMsg)
	{
	case WM_INITDIALOG:
		SetWindowText(GetDlgItem(hDlg, IDOK), _T("OKAY!"));
		SetWindowText(GetDlgItem(hDlg, ID_HELP), _T("HELP!"));
		return (INT_PTR)TRUE;
	case WM_COMMAND:
		{
			switch (LOWORD(wParam))
			{
			case IDOK:
			case IDCANCEL:
				EndDialog(hDlg, LOWORD(wParam));
				return (INT_PTR)TRUE;
			case ID_HELP:
				EnableWindow(GetDlgItem(hDlg, IDOK), FALSE);
				break;
				break;
			}
			break;
		}
	}
	return (INT_PTR)FALSE;
}

한가지 더, X64 모드에서는 포인터가 8바이트이므로, lpwAlign 함수의 코드도 아래와 같이 바꾸어 주어야 합니다.

#ifdef _M_IX86
typedef ULONG UINTEGER;
#else
typedef ULONGLONG UINTEGER;
#endif
 
LPWORD lpwAlign(LPWORD lpIn)
{
	UINTEGER ul = 0;
	ul = (UINTEGER)lpIn;
	ul ++;
	ul >>=1;
	ul <<=1;
	return (LPWORD)ul;
}

사용된 예제 코드는 아래의 주소에서 내려받을 수 있습니다.
https://1drv.ms/u/s!An5hHO7t37wbhiDBz3I_VpeWqgV9

Advertisements

Leave a Comment »

No comments yet.

RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

Blog at WordPress.com.

%d bloggers like this: