엑셀로 키보드 입력 인식받기 (KeyPressAPI 모듈) :: 엑셀 VBA 강의 3-5
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에는 수많은 종류의 함수가 있습니다. 각 함수마다 사용되는 인수가 모두 다르기 때문에, “이렇게 하면 모든 호환성 문제가 해결된다”라는 명확한 정답은 없습니다.
마이크로소프트에서 제시하는 호환성문제 해결방안는 크게 2가지입니다.
- User32.dll 라이브러리를 참조하여 함수를 선언할 경우, PtrSafe를 사용하여 Private Declare PtrSafe Function 으로 선언합니다.
- Long 데이터 타입 대신, 64비트에서도 호환 가능한 LongPtr 데이터타입을 사용합니다.
하지만 앞서 말씀드린 바와 같이, 함수마다 사용되는 인수는 각각 다르기 때문에 상황에 따라 LongPtr 데이터타입을 적용할 필요가 있습니다.
3. KeyPressAPI 클래스 모듈 작성
엑셀에서 키보드 입력을 인식받기위해 새로운 클래스모듈 개체를 생성합니다. 이번 강의에서는 64bit 의 VBA를 기준으로 작성하였습니다. 삽입에가서 클래스 모듈을 생성한 뒤, 속성창에서 클래스모듈의 이름을 KeyPressAPI로 변경해줍니다.

우선 첫번째로 아래 코드를 복사하여 붙여넣기 해줍니다. (타입과 변수, 사용자 지정함수를 선언합니다.)
자세한 내용은 영상강의를 참고해주세요.
내용추가 [2018년 11월 17일] :: 엑셀 2010 이전 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)
두번째로 클래스모듈의 2개의 프로시져 명령문을 작성합니다. 마찬가지로 복사해서 생성한 클래스모듈에 붙여넣기 해주세요. (*위에서 붙여넣기 한 명령문 아래로 이어서 붙여넣기 해주어야 합니다.)
'######################################################################################################################## '# 해당 명령문에 대한 저작권은 오빠두엑셀(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을 보시면 2개의 버튼이 있습니다. 각 버튼을 클릭한 뒤 [매크로지정]에 들어갑니다. [키입력받기]버튼에는 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)