-
[WinAPI] Edit Control에서 Enter키 입력 받기C++/미분류 2022. 6. 22. 12:39
WinAPI의 Edit control은 사용자의 입력을 받는 컨트롤 중 하나입니다.
때때로는 설계 방식에 따라, 이 입력 도중 Enter키를 통해 특정 상호작용을 해야 할 때가 있습니다.
이번 글에서는 그에 대해 다루어 보도록 하겠습니다.
1. UI 설정
더보기글에서 목표하는 바는 Edit control에서 Enter키가 개행 이외의 방법으로 작동 되게 하는 것 입니다.
이를 위해 간단하게 Edit Control이 있는 UI를 만들어 보도록 하겠습니다.
코드 및 형태는 아래와 같습니다.
#include <stdlib.h> #include <string.h> #include <tchar.h> #include <string> #include <Windows.h> #define ID_EDIT 100 static TCHAR szWindowClass[] = _T("DesktopApp"); static TCHAR szTitle[] = _T("Simple APP"); LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); HWND hEdit; int WINAPI WinMain( _In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nCmdShow ) { WNDCLASSEX wcex; wcex.cbSize = sizeof(WNDCLASSEX); wcex.style = CS_HREDRAW | CS_VREDRAW; wcex.lpfnWndProc = WndProc; wcex.cbClsExtra = 0; wcex.cbWndExtra = 0; wcex.hInstance = hInstance; wcex.hIcon = LoadIcon(wcex.hInstance, IDI_APPLICATION); wcex.hCursor = LoadCursor(NULL, IDC_ARROW); wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); wcex.lpszMenuName = NULL; wcex.lpszClassName = szWindowClass; wcex.hIconSm = LoadIcon(wcex.hInstance, IDI_APPLICATION); if (!RegisterClassEx(&wcex)) { MessageBox(NULL, _T("Call to RegisterClassEx failed!"), _T("Simple APP"), NULL); return 1; } HWND hWnd = CreateWindowEx( WS_EX_OVERLAPPEDWINDOW, szWindowClass, szTitle, WS_OVERLAPPEDWINDOW, 10, 10, 310, 510, NULL, NULL, hInstance, NULL ); if (!hWnd) { MessageBox(NULL, _T("Call to CreateWindow failed!"), _T("Windows Desktop Guided Tour"), NULL); return 1; } ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); MSG msg; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return (int)msg.wParam; } LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { case WM_CREATE: hEdit = CreateWindowEx( 0, L"EDIT", NULL, WS_CHILD | WS_VISIBLE | WS_BORDER | ES_LEFT | ES_MULTILINE | ES_AUTOVSCROLL | ES_WANTRETURN, 10, 410, 270, 50, hWnd, (HMENU)ID_EDIT, (HINSTANCE)GetWindowLongPtr(hWnd, GWLP_HINSTANCE), NULL); break; case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProc(hWnd, uMsg, wParam, lParam); break; } return DefWindowProc(hWnd, uMsg, wParam, lParam); }
위 실행 화면 하단부의 Edit control에서 사용자의 입력을 받고, Enter키가 입력되었을 경우 특정 행동을 하도록 (이 글에서는 화면 상단에 기록이 남도록)해 보도록 하겠습니다.
2. Enter 키 반응
더보기Edit control에 포커스가 있는 동안에는 특정 키 입력이 되지 않습니다.
이는 Edit control에 추가적인 Procedure를 설정하는 것으로 해결할 수 있습니다.
기본 UI 코드가 긴 관계로, 추가되는 코드만 작성하도록 하겠습니다.
WNDPROC DefEditProc; LRESULT CALLBACK SubEditProc(HWND, UINT, WPARAM, LPARAM); LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { case WM_CREATE: DefEditProc = (WNDPROC)SetWindowLongPtr(hEdit, GWLP_WNDPROC, (LONG_PTR)SubEditProc); ... } ... } LRESULT CALLBACK SubEditProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { HDC hdc; PAINTSTRUCT ps; RECT rt = { 10, 10, 100, 100 }; switch (uMsg) { case WM_KEYDOWN: switch (wParam) { case VK_RETURN: // // Code // break; } break; default: return CallWindowProc(DefEditProc, hWnd, uMsg, wParam, lParam); } return FALSE; }
Edit control을 생성하고, Edit control에 대하여 적용되는 추가적인 Procedure를 정의한 모습입니다.
Enter키에 해당하는 파라미터인 VK_RETURN에 대하여 행동을 정의하는 것으로, Enter키 입력을 받을 수 있습니다.
3. Not Ctrl + Enter
더보기Enter키를 입력으로 받는 몇몇 프로그램의 경우, Enter키를 조합하여 사용하는 키(Alt, Ctrl 등)에 대하여는 Enter키와 다른 기능을 하는 경우가 대부분입니다.
이 경우는, Enter키 입력을 처리하기 전에 다른 키가 중복되어 입력되어 있는지 확인하는 것으로 구현할 수 있습니다.
switch (wParam) { case VK_RETURN: if (!GetAsyncKeyState(VK_CONTROL)) { // // Code // } break; }
GetAsyncKeyState는 인자로 받은 키의 입력 여부를 반환합니다.
위 코드는 부정 연산자를 사용함으로써, Ctrl이 눌리지 않았을 경우의 Enter 입력에 대하여 작동하는 코드입니다.
위 문단에서 정의한 Edit control에 ES_MULTILINE옵션은 Edit control에 대한 여러 줄 입력을 허용하는 옵션입니다.
위와 같은 코드를 작성함으로써, Enter키에 대하여는 특정 동작을 수행하고, Ctrl + Enter에 대하여는 개행을 할 수 있도록 구현할 수 있습니다.
4. Write text
더보기위 문단의 내용을 통해 Enter키에 대해 특정 행동을 할 수 있는 조건문을 만들었습니다.
이제 이 조건문 내부에 특정 행동을 정의하면 됩니다.
이 글에서는 첫 문단의 내용대로 입력한 텍스트를 지우고, 상단에 출력하도록 하는 코드를 작성하도록 하겠습니다.
HDC hdc; RECT rt; if (!GetAsyncKeyState(VK_CONTROL)) { hdc = GetDC(GetParent(hEdit)); GetClientRect(GetParent(hEdit), &rt); TCHAR buff[1024]; GetWindowText(hEdit, buff, 1024); SetWindowText(hEdit, 0); DrawText(hdc, buff, -1, &rt, DT_CENTER); ReleaseDC(GetParent(hEdit), hdc); }
구현하는 방법은 여러가지가 있겠지만, 위와 같은 방식으로 Edit control의 텍스트를 지우고 이용할 수 있습니다.
5. Issue : CRLF
더보기위 문단의 코드에는 아쉬운 문제점이 하나 있습니다.
Enter키를 입력해서 텍스트를 지우게 될 때, 개행문자는 지워지지 않는 문제가 있습니다.
이 문제의 원인은 개행 문자의 입력과 조건문 사이의 서순이 원인입니다.
깔끔한 해답은 찾지 못 했지만, 제가 찾은 두 가지 해결책을 공유하도록 하겠습니다.
첫 번째 방법은 서순을 변경하는 것 입니다.
이것은 Procedure의 Message를 변경하는 것으로 간단하게 해결 가능합니다.
// Before switch (uMsg) { case WM_KEYDOWN: ... break; ... } // After switch (uMsg) { case WM_KEYUP: ... break; ... }
위 문단의 WM_KEYDOWN을 WM_KEYUP으로 변경하면 입력 이후 개행 문자가 남아있는 문제를 해결할 수 있습니다.
하지만, KEYUP 이전에 개행 문자가 삽입되고, 입력 기능이 동작하기 전에 추가적인 키보드 입력이 발생할 수 있다는 점에서 시각적, 기능적으로 깔끔하지 못 한 경향이 있습니다.
두 번째 방법은 Edit control을 새로 생성하는 것 입니다.
#define MY_CLEAR (WM_USER + 1) LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { ... case MY_CLEAR: //RemovePropW(hEdit, 0); SendMessage(hEdit, WM_CLOSE, 0, 0); hEdit = CreateWindowEx( 0, L"EDIT", // predefined class NULL, // no window title WS_CHILD | WS_VISIBLE | WS_BORDER | ES_LEFT | ES_MULTILINE | ES_AUTOVSCROLL, 10, 410, 270, 50, // set size in WM_SIZE message hWnd, // parent window (HMENU)ID_EDIT, // edit control ID (HINSTANCE)GetWindowLongPtr(hWnd, GWLP_HINSTANCE), NULL); // pointer not needed DefEditProc = (WNDPROC)SetWindowLongPtr(hEdit, GWLP_WNDPROC, (LONG_PTR)SubEditProc); SetFocus(hEdit); break; ... } } LRESULT CALLBACK SubEditProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { ... case WM_KEYDOWN: switch (wParam) { case VK_RETURN: if (!GetAsyncKeyState(VK_CONTROL)) { ... ... SendMessage(GetParent(hEdit), MY_CLEAR, 0, 0); } } ... } }
Edit control을 새로 생성하는 메시지를 위한 커스텀 메시지를 정의합니다.
WndProc에 커스텀 메시지를 수신할 경우 Edit control을 제거하고, 새 Edit control을 생성하는 코드를 작성합니다.
이 때, 입력 커서가 사라질 경우 자연스럽지 못 하므로 SetFocus를 통해 커서를 유지시킵니다.
위 기능을 Enter키 입력 동작의 마지막에 호출하는 것으로 개행 문자를 제거할 수 있습니다.
이 구현은 시각적으로는 깔끔할 수 있으나, 사용자가 입력할 때 마다 Control을 새로 생성해야 하므로 성능적인 이슈가 발생할 수 있습니다.
두 방법 모두 명확한 단점이 존재하기 때문에 사용하는 것에 부담감이 있습니다.
이후 더 나은 구현법을 찾을 경우 추가로 작성하도록 하겠습니다.
Win32를 다루는 것이 이번이 처음이라, 구조적인 부분에서 이해도가 많이 부족합니다.
그렇지만 제가 겪은 이슈에 대하여 이후에 참고하거나, 공유하고자 이 글을 작성합니다.
이 글이 지금 겪고계신 이슈와 관련이 있고, 도움이 되길 바랍니다.
감사합니다.
'C++ > 미분류' 카테고리의 다른 글
[C++] 랜덤 이벤트 (확률) 구현하기 (0) 2022.10.06 [Visual Studio] Code style 변경하기 (BSD, K&R) (0) 2022.09.14 [Visual Studio] LNK2019, LNK1120 (0) 2022.06.13 [Visual Studio] Solution & Project (0) 2022.06.07 [C++] Boost 라이브러리 설치 & Visual Studio 설정 (0) 2022.03.10