ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [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를 다루는 것이 이번이 처음이라, 구조적인 부분에서 이해도가 많이 부족합니다.

    그렇지만 제가 겪은 이슈에 대하여 이후에 참고하거나, 공유하고자 이 글을 작성합니다.

    이 글이 지금 겪고계신 이슈와 관련이 있고, 도움이 되길 바랍니다.

     

    감사합니다.

    댓글

Designed by Tistory.