[엑셀마리오게임] 키보드 입력 인식
엑셀에 입력된 키보드 값을 받아오는 방법! (KeyPressAPI)
이 강의에서는 윈도우의 User32.dll 라이브러리를 사용해 엑셀 VBA에서 키보드 입력을 실시간으로 인식하는 KeyPressAPI 클래스 모듈을 작성하는 방법을 다룹니다. 64비트 호환성 처리, 클래스 이벤트 선언, 시트 모듈에서의 모듈 호출까지 정리해 VBA로 인터랙티브한 매크로를 구현할 때 바로 활용할 수 있는 패턴을 알아봅니다.
실습 가이드
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 함수

PeekMessageA 함수

PostMessageA 함수

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

- Long 데이터 타입 대신 64비트 환경에서도 호환되는 LongPtr 데이터 타입을 사용합니다.

앞서 설명한 것처럼 함수마다 인수 구성이 달라지므로, 상황에 맞춰 LongPtr 데이터 타입을 적용해야 안정적인 동작을 보장할 수 있습니다.
3. KeyPressAPI 클래스 모듈 작성
엑셀에서 키보드 입력을 인식하기 위해 새로운 클래스 모듈 객체를 생성합니다. 이번 강의에서는 64비트 VBA를 기준으로 코드를 작성합니다. VBA 편집기의 [삽입] 메뉴에서 클래스 모듈을 추가한 뒤, 속성 창에서 클래스 모듈의 이름을 KeyPressAPI로 변경합니다.

먼저 아래 코드를 복사해 클래스 모듈에 붙여넣습니다. (타입 정의, 변수, 사용자 정의 함수를 선언하는 부분입니다.)
자세한 내용은 영상 강의를 참고해 주세요.
내용추가 [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 명령문을 연결합니다.

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

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)



영상 언제나 감사합니다.
질문 하나 드리고 싶어서요
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 이 함수가 실행되는 원리를 잘 모르겠어요..
를 실행하면 엑셀에 입력되는 모든 키 입력을 받아오게 됩니다.
StarKeyPress 명령문 중간에 보시면,
이 보이실겁니다. 이 명령문을 통해 KeyPressWatcher 라는 개체의 KeyPressd 이벤트를 발생시키면서 명령문이 동작하게 됩니다.
답변이 도움이 되셨길 바랍니다.
저도 질문을 드리고 싶어요.
앞에 분과 같은 질문에 대한 추가 질문입니다.
뭐랄까.. 이제까지 알고 있는 함수의 연결고리가 없는데
RaiseEvent KeyPressed로
KeyPressWatcher_KeyPressed 함수가 실행된다는게 이해가 되지 않습니다.
쉽게 말해 VBA에서 함수를 콜 하기 위해서는 정확히 그 함수의 이름을 적어야 하지 않습니까?
그런데 여기는 그게 없어요.
RaiseEvent 에서는 KeyPressed를 Call 했지,
KeyPressWatcher_KeyPressed를 Call하지는 않았잖습니까?
근데 실행이 된다는게...
쓰레드에서는 KeyPressWatcher_KeyPressed 처럼 함수명에서
_ 문자 앞뒤로 연결이 되는 건가요?
아님... 매개변수인가요?
매개변수도...개수만 같지, 이름은 같지 않은데....
다른 모든 건 이해가 되는데... 여기가 끊어 지네요...
언더바 뒤에 있는 KeyPressed는 '키가 눌렸을 때 실행해라~'라는 이벤트를 지정하는 코드입니다.
비슷한 예로는, WorkSheet_SelectionChage, WorkSheet_Change 도 동일하게 시트가 변경되었을 때 명령문을 실행해라~ 라는 이벤트 명령문입니다.