오빠두엑셀 `2026 무료 챌린지` 오픈! 완주하고 수료증 받아가세요! 5년 연속 IT분야 베스트셀러! 「 진짜쓰는 실무엑셀 」로 2026년 공부 끝내기 엑셀이 막히셨나요? Q&A 게시판에서 바로 해결하세요.
메뉴
VBA 프로젝트 강의

[엑셀마리오게임] 키보드 입력 인식

오빠두엑셀 by 오빠두엑셀
  • 학습시간 21분
  • 난이도 전문가
  • 작성일 2018.11.11

엑셀에 입력된 키보드 값을 받아오는 방법! (KeyPressAPI)

이 강의에서는 윈도우의 User32.dll 라이브러리를 사용해 엑셀 VBA에서 키보드 입력을 실시간으로 인식하는 KeyPressAPI 클래스 모듈을 작성하는 방법을 다룹니다. 64비트 호환성 처리, 클래스 이벤트 선언, 시트 모듈에서의 모듈 호출까지 정리해 VBA로 인터랙티브한 매크로를 구현할 때 바로 활용할 수 있는 패턴을 알아봅니다.

[엑셀마리오게임] 키보드 입력 인식
실습 가이드

실습파일 무료E-Book 강의 PPT 파일 완성파일
첨부파일에 문제가 발생한 경우 1:1 문의하기로 연락주시면 신속히 해결해 드립니다.

1. User32.dll 라이브러리 사용하기

User32.dll 라이브러리는 마이크로소프트 운영체제인 윈도우에 기본적으로 설치되는 라이브러리 파일입니다. 라이브러리 파일은 ‘단독’으로 실행할 수 없는 파일이며, 다른 프로그램에서 해당 라이브러리를 참조해 그 안에 정의된 함수를 호출하는 방식으로 동작합니다.

그중에서도 User32.dll 라이브러리는 실행 중인 윈도우의 GUI(Graphic User Interface)와 밀접한 연관이 있는 라이브러리로, 실행 중인 프로그램과의 상호작용(예: 키보드·마우스 클릭 이벤트, 알림창 출력 등)을 담당합니다. 윈도우 시스템 전반에 큰 영향을 끼치지 않는 안전한 라이브러리이며, 윈도우 운영체제 기반의 프로그램을 제작할 때 반드시 숙지해야 하는 핵심 라이브러리입니다.

[링크] 마이크로소프트 User32.dll 라이브러리 설명 바로가기
https://docs.microsoft.com/en-us/windows/desktop/api/winuser/

이번 강의에서는 아래의 3가지 함수를 활용합니다.

FindWindowA 함수

User32 FindWindowA 함수

PeekMessageA 함수

User32 PeekMesageA 함수

PostMessageA 함수

User32 PostMesageA 함수

2. 64비트 VBA 호환성 문제 해결

User32.dll 라이브러리를 사용할 때는 64비트 엑셀 VBA와의 호환성 문제를 함께 고려해야 합니다. User32.dll에는 수많은 함수가 포함되어 있으며, 각 함수마다 인수 구성이 다르기 때문에 "이렇게 처리하면 모든 호환성 문제가 해결된다"라는 식의 단일 정답은 존재하지 않습니다.
마이크로소프트가 제시하는 호환성 문제 해결 방안은 크게 두 가지로 정리할 수 있습니다.

  • User32.dll 라이브러리를 참조하여 함수를 선언할 때는 PtrSafe 키워드를 사용해 Private Declare PtrSafe Function 형태로 선언합니다.
    VBA 64bit PtrSafe Function
  • Long 데이터 타입 대신 64비트 환경에서도 호환되는 LongPtr 데이터 타입을 사용합니다.
    VBA 64bit LongPtr 데이터타입

앞서 설명한 것처럼 함수마다 인수 구성이 달라지므로, 상황에 맞춰 LongPtr 데이터 타입을 적용해야 안정적인 동작을 보장할 수 있습니다.

3. KeyPressAPI 클래스 모듈 작성

엑셀에서 키보드 입력을 인식하기 위해 새로운 클래스 모듈 객체를 생성합니다. 이번 강의에서는 64비트 VBA를 기준으로 코드를 작성합니다. VBA 편집기의 [삽입] 메뉴에서 클래스 모듈을 추가한 뒤, 속성 창에서 클래스 모듈의 이름을 KeyPressAPI로 변경합니다.

VAB 클래스모듈 이름변경

먼저 아래 코드를 복사해 클래스 모듈에 붙여넣습니다. (타입 정의, 변수, 사용자 정의 함수를 선언하는 부분입니다.)
자세한 내용은 영상 강의를 참고해 주세요.

내용추가 [2018년 11월 17일] :: 엑셀 2010 이전의 32비트 버전을 사용 중일 때 오류가 발생한다면, 본 포스트 마지막 부분에 첨부한 32비트 전용 명령문을 사용하시기 바랍니다.

'########################################################################################################################
'# 해당 명령문에 대한 저작권은 오빠두엑셀(https://www.oppadu.com)에 있습니다.                                                #
'# 모든 정보는 Cretive Commns License에 의해 저작권을 보호받습니다.                                                     #
'# 영리를 목적으로 하지 않는 사용 및 공유는 허용됩니다. 반드시 저작자, 오빠두엑셀(https://www.oppadu.com)을 명시해야합니다.  #
'# This VBA Code is protected by Creative Commons License.                                                              #
'# All information can be posted, uploaded, shared at online for NON-commercial use only.                               #
'# The Author, '오빠두엑셀(https://www.oppadu.com)' have to be mentioned when you post this code.                            #
'########################################################################################################################
 
Option Explicit
 
'// 마우스 포인트 위치 (x,y)를 받아옵니다.
Private Type POINTAPI
    x As Long
    y As Long
End Type
 
'// TOP LEVEL WINDOW 의 키정보, 마우스위치정보를 받아올 메세지 Type을 설정합니다. (윈도우 Default 값)
Private Type msgKeyPress
    hwnd As LongPtr
    Message As LongPtr
    wParam As LongPtr
    lParam As LongPtr
    time As Long
    pt As POINTAPI
End Type
 
'// TOP LEVEL 윈도우 정보를 받습니다.
'// 더 자세한 내용은 아래 링크를 참고하세요
'// For more details, please refer to below links at MSDN
'// https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-findwindowa
Private Declare PtrSafe Function FindWindow Lib "user32" Alias "FindWindowA" _
    (ByVal lpClassName As String, _
     ByVal lpWindowName As String) As LongPtr
 
'// 실행중인 Thread에서 키입력을 받기위해 대기합니다.
'// 더 자세한 내용은 아래 링크를 참고하세요
'// For more details, please refer to below links at MSDN
'// https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-waitmessage
Private Declare PtrSafe Function WaitMessage Lib "user32" () As LongPtr
 
'// 입력된 키정보를 받습니다.
'// https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-peekmessagea
Private Declare PtrSafe Function PeekMessage Lib "user32" Alias "PeekMessageA" _
    (ByRef lpMsg As msgKeyPress, ByVal hwnd As LongPtr, _
     ByVal wMsgFilterMin As LongPtr, _
     ByVal wMsgFilterMax As LongPtr, _
     ByVal wRemoveMsg As LongPtr) As LongPtr
 
'// 키 값을 TOP LEVEL WINDOW에 Return 합니다.
'// https://msdn.microsoft.com/en-us/library/ms910658.aspx
Private Declare PtrSafe Function PostMessage Lib "user32" Alias "PostMessageA" _
    (ByVal hwnd As LongPtr, _
     ByVal wMsg As LongPtr, _
     ByVal wParam As LongPtr, _
     lParam As Any) As LongPtr
 
'// 받아온 키 정보를 처리가능한 데이터로 변환합니다.
'// https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-translatemessage
Private Declare PtrSafe Function TranslateMessage Lib "user32" _
    (ByRef lpMsg As msgKeyPress) As LongPtr
 
Private Const wmKeyDown As LongPtr = &H100
Private Const PM_REMOVE  As LongPtr = &H1
Private Const wmChar    As LongPtr = &H102
Private blnExit As Boolean
 
'// 키입력 이벤트가 일어났을때 발생시킵니다.
Public Event KeyPressed(ByVal KeyAscii As LongPtr, _
     ByVal KeyCode As LongPtr, _
     ByVal Target As Range, _
     ByRef Cancel As Boolean)

다음으로 클래스 모듈에 두 개의 프로시저 명령문을 작성합니다. 마찬가지로 아래 코드를 복사해 동일한 클래스 모듈에 붙여넣습니다. (위에서 붙여넣은 명령문 아래로 이어서 붙여넣어야 정상적으로 동작합니다.)

'########################################################################################################################
'# 해당 명령문에 대한 저작권은 오빠두엑셀(https://www.oppadu.com)에 있습니다.                                                #
'# 모든 정보는 Cretive Commns License에 의해 저작권을 보호받습니다.                                                     #
'# 영리를 목적으로 하지 않는 사용 및 공유는 허용됩니다. 반드시 저작자, 오빠두엑셀(https://www.oppadu.com)을 명시해야합니다.  #
'# This VBA Code is protected by Creative Commons License.                                                              #
'# All information can be posted, uploaded, shared at online for NON-commercial use only.                               #
'# The Author, '오빠두엑셀(https://www.oppadu.com)' have to be mentioned when you post this code.                            #
'########################################################################################################################
Public Sub StartKeyPress()
    Dim msgMessage As msgKeyPress
    Dim blnCancel As Boolean
    Dim iMessage As LongPtr
    Dim iKeyCode As LongPtr
    Dim lXLhwnd As LongPtr
 
    On Error GoTo errHandler
    Application.EnableCancelKey = xlErrorHandler
 
    '// blnExit이 TRUE 가 되면 키입력인식을 종료합니다.
    blnExit = False
 
    '// 현재 실행중인 TOP LEVEL WINDOW (프로그램 창)을 인식하여 longptr (64비트 호환)로 받아옵니다.
    lXLhwnd = FindWindow("XLMAIN", Application.Caption)
 
    Do
        '// 실행중인 Thread에서 키입력을 받기위해 대기합니다.
        WaitMessage
 
        '// 오류 방지를 위해 blnExit를 Cross-Check 합니다.
        If blnExit Then Exit Do
 
        '// 키 입력을 받아옵니다.
        '// PeekMessage 값이 존재하는지(<>0) Cross-Check 합니다.
        If PeekMessage(msgMessage, lXLhwnd, wmKeyDown, wmKeyDown, PM_REMOVE) Then
            'Store the virtual key code for later use.
            iMessage = msgMessage.Message
            iKeyCode = msgMessage.wParam   '// <- 입력된 키보드 Input입니다.
 
            '받아온 msgKeyPress(=Virtual Message)를 실제 처리가능한 정보로 변환합니다.
            TranslateMessage msgMessage
            PeekMessage msgMessage, lXLhwnd, wmChar, wmChar, PM_REMOVE
 
            '// 오류 방지를 위해 blnCancel를 Cross-Check 합니다.
            blnCancel = False
 
            'KeyPressed 이벤트를 실행합니다.
            RaiseEvent KeyPressed(msgMessage.wParam, iKeyCode, Selection, blnCancel)
 
            'TOP LEVEL WINDOW에 입력값 Return합니다.
            If Not blnCancel Then
                PostMessage lXLhwnd, iMessage, iKeyCode, 0
            End If
        End If
errHandler:
        'StopKeyPress까지 DoEvents로 Loop합니다.
        DoEvents
    Loop Until blnExit
End Sub
 
Public Sub StopKeyPress()
    'Set this boolean flag to exit the above loop.
    blnExit = True
End Sub

4. 원하는 시트에 KeyPressAPI 클래스 모듈 적용하기

이제 원하는 시트에 키보드 입력이 들어왔을 때, 앞서 작성한 KeyPressAPI 클래스 모듈을 사용해 시트에서 입력된 키 정보를 받아오는 방법을 살펴봅니다.

이번 강의에서는 예제 파일의 "Sheet1"에 키보드 입력이 들어오면, 해당 키의 아스키(Ascii) 코드를 읽어 메시지 박스로 출력하도록 명령문을 구성했습니다. VBA 편집창에서 Sheet1을 더블클릭한 뒤, 아래 명령문을 복사해 붙여넣습니다.

'########################################################################################################################
'# 해당 명령문에 대한 저작권은 오빠두엑셀(https://www.oppadu.com)에 있습니다.                                                #
'# 모든 정보는 Cretive Commns License에 의해 저작권을 보호받습니다.                                                     #
'# 영리를 목적으로 하지 않는 사용 및 공유는 허용됩니다. 반드시 저작자, 오빠두엑셀(https://www.oppadu.com)을 명시해야합니다.  #
'# This VBA Code is protected by Creative Commons License.                                                              #
'# All information can be posted, uploaded, shared at online for NON-commercial use only.                               #
'# The Author, '오빠두엑셀(https://www.oppadu.com)' have to be mentioned when you post this code.                            #
'########################################################################################################################
Option Explicit
 
Dim WithEvents KeyPressWatcher As KeyPressAPI
 
Sub Start_KeyPress()
 
If KeyPressWatcher Is Nothing Then
    Set KeyPressWatcher = New KeyPressAPI
End If
 
KeyPressWatcher.StartKeyPress
 
End Sub
 
Sub End_KeyPress()
 
If KeyPressWatcher Is Nothing Then Exit Sub
KeyPressWatcher.StopKeyPress
 
End Sub
 
Private Sub KeyPressWatcher_KeyPressed(ByVal KeyAscii As LongPtr, ByVal KeyCode As LongPtr, ByVal Target As Range, Cancel As Boolean)
 
MsgBox "[[알림]]" & vbNewLine & _
        "현재 입력한 키는 " & Chr(CLng(KeyAscii)) & " 입니다." & vbNewLine & _
        "키의 아스키코드는 " & CLng(KeyAscii) & " 입니다."
 
End Sub

5. 해당시트에 매크로 실행을 위한 버튼 생성

이제 모든 모듈과 명령문 작성이 끝났습니다. VBA 편집창을 닫고 엑셀 시트로 돌아옵니다. Sheet1에는 두 개의 버튼이 미리 배치되어 있습니다. 각 버튼을 우클릭한 뒤 [매크로 지정] 메뉴로 이동합니다. [키입력받기] 버튼에는 Start_KeyPress 명령문을, [키입력중단] 버튼에는 End_KeyPress 명령문을 연결합니다.

VBA 매크로 지정하기 GIF

이제 모든 단계가 완료되었습니다. [키입력받기] 버튼을 클릭한 뒤 임의의 키를 입력하면, 실행 중인 엑셀 프로그램이 키보드 정보를 인식해 아래와 같이 메시지 박스로 결과를 출력합니다.

6. 32비트용 KeyPressAPI 클래스모듈

이전 버전의 32비트 엑셀을 사용하는 환경에서는 클래스 모듈의 함수 선언 단계에서 오류가 발생할 수 있습니다. 이때는 클래스 모듈의 함수·변수 선언부를 아래 명령문으로 교체해 사용하면 됩니다.

'########################################################################################################################
'# 해당 명령문에 대한 저작권은 오빠두엑셀(https://www.oppadu.com)에 있습니다.                                                #
'# 모든 정보는 Cretive Commns License에 의해 저작권을 보호받습니다.                                                     #
'# 영리를 목적으로 하지 않는 사용 및 공유는 허용됩니다. 반드시 저작자, 오빠두엑셀(https://www.oppadu.com)을 명시해야합니다.  #
'# This VBA Code is protected by Creative Commons License.                                                              #
'# All information can be posted, uploaded, shared at online for NON-commercial use only.                               #
'# The Author, '오빠두엑셀(https://www.oppadu.com)' have to be mentioned when you post this code.                            #
'########################################################################################################################
 
Option Explicit
 
Private Type POINTAPI
    x As Long
    y As Long
End Type
 
Private Type MSG
    hwnd As Long
    Message As Long
    wParam As Long
    lParam As Long
    time As Long
    pt As POINTAPI
End Type
 
Private Declare Function WaitMessage Lib "user32" () As Long
 
Private Declare Function PeekMessage Lib "user32" Alias "PeekMessageA" _
    (ByRef lpMsg As MSG, ByVal hwnd As Long, _
     ByVal wMsgFilterMin As Long, _
     ByVal wMsgFilterMax As Long, _
     ByVal wRemoveMsg As Long) As Long
 
Private Declare Function TranslateMessage Lib "user32" _
    (ByRef lpMsg As MSG) As Long
 
Private Declare Function PostMessage Lib "user32" Alias "PostMessageA" _
    (ByVal hwnd As Long, _
     ByVal wMsg As Long, _
     ByVal wParam As Long, _
     lParam As Any) As Long
 
Private Declare Function FindWindow Lib "user32" Alias "FindWindowA" _
    (ByVal lpClassName As String, _
     ByVal lpWindowName As String) As Long
 
Private Const WM_KEYDOWN As Long = &H100
Private Const PM_REMOVE  As Long = &H1
Private Const WM_CHAR    As Long = &H102
Private bExitLoop As Boolean
 
Public Event KeyPressed(ByVal KeyAscii As Integer, _
     ByVal KeyCode As Integer, _
     ByVal Target As Range, _
     ByRef Cancel As Boolean)
댓글 5
5 (2개 평가)
이석호
이석호 2020.08.19 20:51
요즘 마리오 게임을 한창 배우고 있는 직장인 입니다.
영상 언제나 감사합니다.

질문 하나 드리고 싶어서요

rivate Sub KeyPressWatcher_KeyPressed(ByVal KeyAscii As LongPtr, ByVal KeyCode As LongPtr, ByVal Target As Range, cancle As Boolean)
이 함수가 실행되는 원리를 잘 모르겠어요 ㅠㅠ

Sub Start_KeyPress() 여기서 클래스모듈의 StartKeyPress를 실행시키는건 알겠는데 그 안에서 Private Sub KeyPressWatcher_KeyPressed 이 함수가 실행되는 원리를 잘 모르겠어요..
오빠두엑셀
오빠두엑셀 작성자 2020.08.20 00:03
안녕하세요?^^

KeyPressWatcher.StartKeyPress
를 실행하면 엑셀에 입력되는 모든 키 입력을 받아오게 됩니다.
StarKeyPress 명령문 중간에 보시면,

RaiseEvent KeyPressed(msgMessage.wParam, iKeyCode, Selection, blnCancel)
이 보이실겁니다. 이 명령문을 통해 KeyPressWatcher 라는 개체의 KeyPressd 이벤트를 발생시키면서 명령문이 동작하게 됩니다.
답변이 도움이 되셨길 바랍니다.
김진국
김진국 2021.07.22 21:58
안녕하세요. 정말 많은 도움을 받고 있습니다.
저도 질문을 드리고 싶어요.
앞에 분과 같은 질문에 대한 추가 질문입니다.

뭐랄까.. 이제까지 알고 있는 함수의 연결고리가 없는데
RaiseEvent KeyPressed로
KeyPressWatcher_KeyPressed 함수가 실행된다는게 이해가 되지 않습니다.
쉽게 말해 VBA에서 함수를 콜 하기 위해서는 정확히 그 함수의 이름을 적어야 하지 않습니까?
그런데 여기는 그게 없어요.
RaiseEvent 에서는 KeyPressed를 Call 했지,
KeyPressWatcher_KeyPressed를 Call하지는 않았잖습니까?
근데 실행이 된다는게...
쓰레드에서는 KeyPressWatcher_KeyPressed 처럼 함수명에서
_ 문자 앞뒤로 연결이 되는 건가요?
아님... 매개변수인가요?
매개변수도...개수만 같지, 이름은 같지 않은데....
다른 모든 건 이해가 되는데... 여기가 끊어 지네요...
오빠두엑셀
오빠두엑셀 작성자 2021.07.23 10:14
안녕하세요.
언더바 뒤에 있는 KeyPressed는 '키가 눌렸을 때 실행해라~'라는 이벤트를 지정하는 코드입니다.
비슷한 예로는, WorkSheet_SelectionChage, WorkSheet_Change 도 동일하게 시트가 변경되었을 때 명령문을 실행해라~ 라는 이벤트 명령문입니다.
강민준🤗
강민준🤗 2024.08.09 11:13
좋은 강의 감사합니다🙇‍♂️