헤더 파일에 포함할 수 있으며 리소스 파일이 필요하지 않습니다. 프로그램 내에서 언제나 서브루틴처럼 호출할 수 있습니다.
실행 효과는 다음과 같습니다:
관련 프로그램:
다이얼로그 호출:
호출 시 입력 다이얼로그의 프롬프트와 기본 문자열을 전달하고 입력된 문자열 포인터를 반환합니다. 사용이 끝나면 메모리를 해제해야 합니다. 문자열 길이가 가변적이므로 함수 내에서 입력에 따라 메모리가 할당되기 때문입니다.
wchar_t* p = CustomDialog::InputStringW(m_hInst, hWnd, _T("비밀번호"), _T("기본비밀번호")); // 기본값 전달
PRINT(_T("\r\n 입력값= %s"), p);
delete p; // 이전에 delete 실패한 이유는 문자열 끝에 null이 없어 new 공간이 부족했기 때문입니다
다이얼로그 구현:
주의할 점:
1. 매개변수 전달 시 주소 전달의 다양한 방법
2. 정적 변수 사용을 피하기 위해 창의 사용자 데이터에 포인터 저장
3. 문자열에 메모리를 할당할 때 반드시 문자열 끝에 null을 추가해야 합니다. 이 문제는 자신의 프로그램에서는 디버깅이 비교적 쉽지만, 다른 완전히 이해되지 않는 API 함수와 함께 사용할 때는 번거로워집니다.
// dialog.h 헤더 파일<br></br>class CustomDialog
{
public:
CustomDialog(void);
// 기본 문자열을 전달하고 문자열 포인터를 반환, 호출 프로그램은 이 포인터를 사용 후 delete 해야 합니다
static wchar_t* InputStringW(HINSTANCE hInst, HWND hWndOwner, LPWSTR lpszTitle, LPWSTR lpszDefault);
// 문자열 공간 할당 시 끝에 null을 추가해야 하며, 그렇지 않으면 delete가 실패합니다
static LRESULT CALLBACK InputStringWProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
// 컨트롤이 파괴되는지 테스트용
static LONG OriginalEditProc;
static LRESULT CALLBACK ModifiedEditProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
};
<br></br>// dialog.cpp 파일
// 콜백 함수에서 문자열에 할당된 메모리 공간은 반드시 문자열 끝에 null을 포함해야 합니다
wchar_t* CustomDialog::InputStringW(HINSTANCE hInst, HWND hWndOwner, LPWSTR lpszTitle, LPWSTR lpszDefault)
{
LRESULT result;
int charCount;
HGLOBAL globalHandle;
LPDLGTEMPLATE dialogTemplate;
LPWORD wordPtr;
globalHandle = GlobalAlloc(GMEM_ZEROINIT, 1024);
dialogTemplate = (LPDLGTEMPLATE)GlobalLock(globalHandle);
dialogTemplate = (LPDLGTEMPLATE)globalHandle;
dialogTemplate->style = WS_POPUP | WS_BORDER | WS_THICKFRAME | WS_CAPTION | WS_SYSMENU; //DS_MODALFRAME
dialogTemplate->cdit = 0; // 컨트롤 수
dialogTemplate->x = 100; dialogTemplate->y = 50;
dialogTemplate->cx = 150; dialogTemplate->cy = 60;
wordPtr = (LPWORD)(dialogTemplate + 1);
*wordPtr++ = 0; // 메뉴 없음
*wordPtr++ = 0; // 미리 정의된 다이얼로그 박스 클래스 (기본값)
wcscpy((wchar_t*)wordPtr, lpszTitle); // 다이얼로그 제목
charCount = wcslen((wchar_t*)wordPtr) + 1;
wordPtr += charCount;
GlobalUnlock(globalHandle);
// 이렇게 주소를 전달하는 것은 합리적입니다. InputStringW 함수에 전달된 포인터는 실제로 포인터의 복사본이며, 포인터의 포인터와는 다릅니다.
// 여기서 프로그램에서 복사된 포인터의 주소를 DialogBoxIndirectParam에 전달하면, 하위 프로그램에서 수정하고 새로운 공간을 반환할 수 있습니다.
// 원래 공간은 메인 프로그램에서 할당되며, 여기서는 처리할 필요가 없습니다
result = DialogBoxIndirectParam(hInst, (LPDLGTEMPLATE)globalHandle, hWndOwner, (DLGPROC)InputStringWProc, (LPARAM)&lpszDefault);
// 이해를 돕기 위해 다음과 같이 작성할 수도 있습니다. 실제로는 같습니다.
// wchar_t* ptr = NULL;
// ptr = (wchar_t*)lpszDefault;
// result = DialogBoxIndirectParam(hInst, (LPDLGTEMPLATE)globalHandle, hWndOwner, (DLGPROC)InputStringWProc, (LPARAM)&ptr); //0으로 다른 포인터 전달
// return (wchar_t*)ptr;
GlobalFree(globalHandle);
if (result == IDOK)
{
return (wchar_t*)lpszDefault;
}
else
{
return NULL;
}
}
LRESULT CALLBACK CustomDialog::InputStringWProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
// ID의 범위는 창이며, 읽기 함수는 모두 HWND 매개변수를 가져야 합니다. 다이얼로그에서만 사용되며, 여기서 정의하면 충분합니다
#define IDC_EDIT_INPUT 1000
#define BTN_OK 1001
#define BTN_CANCEL 1002
RECT rect;
// 초기화할 필요가 없습니다. 할당부터 사용하기 시작하면 되기 때문입니다
wchar_t** stringPtr; // 문자열 포인터 주소를 가리키는 포인터, 가변 길이 문자열을 반환하기 위해 사용
HINSTANCE instanceHandle;
static HWND inputWindow;
static HWND okButton;
static HWND cancelButton;
if (message == WM_INITDIALOG)
{
int testValue = 100;
testValue = GetWindowLong(hWnd, GWL_USERDATA); // 테스트: testValue==0, 원래는 사용되지 않았습니다
if ((wchar_t **)lParam != NULL) // 내용이 유효한 포인터여야 하며, 새 문자열을 반환하는 데 사용됩니다
{
stringPtr = (wchar_t**)lParam; //*stringPtr는 NULL이 될 수 없지만, **stringPtr는 NULL이 될 수 있습니다
}
else
{
EndDialog(hWnd, IDCANCEL); // 취소하고 cancel 전달
return TRUE;
}
// WM_INITDIALOG 메시지는 매개변수를 전달하며, 이 매개변수를 창 사용자 데이터에 저장합니다. 이 공간은 호출 함수에서 이미 할당되었습니다
SetWindowLong(hWnd, GWL_USERDATA, reinterpret_cast<LPARAM>(stringPtr));
instanceHandle = (HINSTANCE)GetWindowLong(hWnd, GWL_HINSTANCE);
GetClientRect(hWnd, &rect); // 메인 창
inputWindow = CreateWindow(_T("edit"), NULL,
WS_CHILD | WS_BORDER | WS_VISIBLE | ES_MULTILINE | WS_VSCROLL,// | ES_READONLY
rect.left + 10, rect.top + 10, rect.right - rect.left - 20, rect.bottom - rect.top - 70,
hWnd, (HMENU)IDC_EDIT_INPUT, instanceHandle, NULL);
// 이 부분은 다이얼로그가 파괴될 때 컨트롤이 파괴되는지 테스트하기 위함입니다
OriginalEditProc = SetWindowLong(inputWindow, GWL_WNDPROC, (LONG)ModifiedEditProc);
<br></br> okButton = CreateWindow(_T("button"), _T("확인"),
WS_CHILD | WS_VISIBLE | BS_FLAT,
rect.right - 220, rect.bottom - 40, 80, 25,
hWnd, (HMENU)BTN_OK, instanceHandle, NULL);
cancelButton = CreateWindow(_T("button"), _T("취소"),
WS_CHILD | WS_VISIBLE | BS_FLAT,
rect.right - 120, rect.bottom - 40, 80, 25,
hWnd, (HMENU)BTN_CANCEL, instanceHandle, NULL);
SetDlgItemText(hWnd, IDC_EDIT_INPUT, (LPWSTR)((wchar_t*)(*stringPtr))); // 여기서 기본 문자열
return TRUE;
}
else
{
// 다른 메시지에서는 이전에 저장된 매개변수를 가져옵니다
stringPtr = reinterpret_cast<wchar_t **>(GetWindowLong(hWnd, GWL_USERDATA));
switch (message)
{
case WM_INITDIALOG:
// 이미 외부로 이동했으며, else 때문에 이미 true를 반환했습니다
return TRUE;
case WM_SIZE:
GetClientRect(hWnd, &rect); // 메인 창
MoveWindow(inputWindow, rect.left + 10, rect.top + 10, rect.right - rect.left - 20, rect.bottom - rect.top - 70, TRUE);
MoveWindow(okButton, rect.right - 220, rect.bottom - 40, 80, 25, TRUE);
MoveWindow(cancelButton, rect.right - 120, rect.bottom - 40, 80, 25, TRUE);
break;
case WM_PAINT:
break;
case WM_COMMAND:
if (LOWORD(wParam) == BTN_OK || LOWORD(wParam) == BTN_CANCEL) // 여기서는 IDCANCEL을 분리해야 하지만, 나중에 수정하겠습니다
{
// 여기에 끝에 null을 추가해야 합니다. 그렇지 않으면 delete 포인터가 실패합니다
int length= GetWindowTextLength(GetDlgItem(hWnd, IDC_EDIT_INPUT))+1;
wchar_t* newString = new wchar_t[length]; // 호출 함수에서 해제해야 합니다
*stringPtr = newString; // 새로 할당된 주소 공간을 반환합니다. 실제로는 전달하는 것이 아니라, 이 포인터가 가리키는 내용을 수정합니다
GetDlgItemText(hWnd, IDC_EDIT_INPUT, newString, length);
EndDialog(hWnd, LOWORD(wParam)); // ID 전달
return TRUE;
}
break;
}
return FALSE;
}
}
long CustomDialog::OriginalEditProc = 0;
// 테스트용
LRESULT CALLBACK CustomDialog::ModifiedEditProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static int counter = 0;
switch (message)
{
case WM_CREATE: // 테스트: 실행되지 않음, 이 메시지는 자체 생성의 전제 조건입니다
counter++;
break;
case WM_DESTROY: // 테스트: 실행됨, 부모 창이 파괴될 때 컨트롤도 함께 파괴됨을 나타냅니다
counter++;
break;
case WM_KEYDOWN:
counter++;
break;
default:
// SendMessage(GetParent(hWnd), message, wParam, lParam); // 테스트: 임의로 보내지 마세요. Notify로 변경하고 자신의 HWND를 전달하는 것이 좋습니다
break;
}
return CallWindowProc((WNDPROC)OriginalEditProc, hWnd, message, wParam, lParam);
}