Link to home
Start Free TrialLog in
Avatar of Zack
ZackFlag for Canada

asked on

Capturing keypress in Excel Worksheet with VBA

Hi - Is there a way to capture keypresses in an excel worksheet?  I can't use _change.  I need to capture it immediately as it would in a userform.  I found a page where it can happen but I'm not sure where to put the various code.  Here is the code.
Option Explicit

Declare Function SetWindowsHookEx Lib _
"user32" Alias "SetWindowsHookExA" (ByVal idHook As Long, ByVal lpfn As Long, _
ByVal hmod As Long, ByVal dwThreadId As Long) As Long

Declare Function CallNextHookEx Lib "user32" _
(ByVal hHook As Long, ByVal nCode As Long, ByVal wParam As Long, lParam As Any) As Long

Declare Function UnhookWindowsHookEx Lib "user32" (ByVal hHook As Long) As Long

Declare Function GetActiveWindow Lib "user32" () As Long

Declare Function FindWindow Lib "user32" Alias "FindWindowA" _
(ByVal lpClassName As String, ByVal lpWindowName As String) As Long

Const HC_ACTION = 0
Const WM_KEYDOWN = &H100
Const WH_KEYBOARD_LL = 13
Dim hhkLowLevelKybd As Long
Dim blnHookEnabled As Boolean
Dim enumAllowedValues As AllowedValues
Dim objTargetRange As Range
Dim objValidationRange As Range
Dim vAns As Variant

Type KBDLLHOOKSTRUCT
    vkCode As Long
    scanCode As Long
    flags As Long
    time As Long
    dwExtraInfo As Long
End Type

Enum AllowedValues
    alpha
    numeric
End Enum




Function LowLevelKeyboardProc _
(ByVal nCode As Long, ByVal wParam As Long, lParam As KBDLLHOOKSTRUCT) As Long

    '\hook keyboard only if XL is the active window
    If GetActiveWindow = FindWindow("XLMAIN", Application.Caption) Then
        If (nCode = HC_ACTION) Then
            '\check if a key is pushed
            If wParam = WM_KEYDOWN Then
            '\if so, check if the active cell is within the target range
                If Union(ActiveCell, objTargetRange).Address = objTargetRange.Address Then
                '\if only numeric values should be allowed then
                    If enumAllowedValues = 1 Then
                    '\check if the pushed key is a numeric key or a navigation key
                    '\by checking the vkCode stored in the laparm parameter
                        If Chr(lParam.vkCode) Like "#" Or _
                            lParam.vkCode = 37 Or lParam.vkCode = 38 Or lParam.vkCode = 39 Or _
                            lParam.vkCode = 40 Or lParam.vkCode = 9 Or lParam.vkCode = 13 Then
                            '\if so allow the input
                            LowLevelKeyboardProc = 0
                        Else
                            '\else filter out this Key_Down message from message qeue
                            Beep
                            LowLevelKeyboardProc = -1
                            Exit Function
                        End If
                        '\if onle alpha values should be allowed then
                    ElseIf enumAllowedValues = 0 Then
                        '\check the laparam parameter
                        If Chr(lParam.vkCode) Like "#" Then
                            '\if numeric prevent the input
                            Beep
                            LowLevelKeyboardProc = -1
                            Exit Function
                        Else
                            '\otherwise allow the input
                            LowLevelKeyboardProc = 0
                    End If
                    End If
                End If
            End If
        End If
    End If
    '\pass function to next hook if there is one
    LowLevelKeyboardProc = CallNextHookEx(0, nCode, wParam, ByVal lParam)

End Function


Public Sub Unhook_KeyBoard()

    If hhkLowLevelKybd <> 0 Then UnhookWindowsHookEx hhkLowLevelKybd
    blnHookEnabled = False
    Cells.Clear

End Sub


Sub ValidateRange(r As Range, ByVal v As AllowedValues)

    '\store these in global variables for they will be
    '\needed later in the filter function
    enumAllowedValues = v
    Set objTargetRange = r
    '\don't hook the keyboard twice !!
    If blnHookEnabled = False Then
        hhkLowLevelKybd = SetWindowsHookEx _
        (WH_KEYBOARD_LL, AddressOf LowLevelKeyboardProc, Application.Hinstance, 0)
        blnHookEnabled = True
    End If

End Sub


Sub test()

    '\ignore any mishandling of the following
    '\input boxes by the user
    On Error Resume Next
    Cells.Clear
    Set objValidationRange = Application.InputBox _
    ("Selet one or more Cells ", "Custom Data Validation...", Type:=8)
    If objValidationRange Is Nothing Then GoTo errHdlr
        objValidationRange.Interior.Color = vbGreen
        vAns = InputBox("To allow only alpha values in the selected range enter 1 " _
        & vbCrLf & vbCrLf & "To allow only numeric values in the selected range enter 2 ")
        If vAns = 1 Then
            ValidateRange objValidationRange, AllowedValues.alpha
        ElseIf vAns = 2 Then
            ValidateRange objValidationRange, AllowedValues.numeric
        Else
        GoTo errHdlr
    End If
    objValidationRange.Cells(1).Select
    Set objValidationRange = Nothing
    Exit Sub
errHdlr:
    MsgBox "criteria error- Try again !", vbCritical
    Unhook_KeyBoard

End Sub

Open in new window

Avatar of dlmille
dlmille
Flag of United States of America image

You want to capture all keystrokes, or just a control sequence or function keypress?

Dave
Better yet, can you explain briefly what you're trying to do?  Just in case there's something that comes to mind as an alternative in addition to what you're trying, which I'll attempt to explain with the code you have, now.

Cheers

Dave
Avatar of byundt
What exactly are you trying to accomplish?

Are you trying to make a data validation dropdown list shrink as the user types data? Microsoft Excel MVP Debra Dalgleish shows how here:http://www.contextures.on.ca/xlDataVal10.html
Exactly.  There are also KeyDown, etc., events for controls on a userform, so not sure you need the sophistication you've posted to get what you want.

Tell us your story and we'll help.

Dave
Avatar of Zack

ASKER

I'm trying to get a _keypress type event with a worksheet.  As soon as a user presses any alpha or numeric key on the worksheet it captures it and runs a procedure or possibly ignores it.  I have application.onkey for function keys so I don't want it to interfere with alpha nums.

Thanks! Zack
Is this a worksheet control in a userform, or the base Excel worksheet?

Dave
Avatar of Zack

ASKER

base excel worksheet
Ok.  There's an excellent primer on the code you're trying to use.  Give me a few as I think I had a solution that did this, otherwise I'll build...

http://www.codeguru.com/columns/vb/article.php/c4829

Dave
ASKER CERTIFIED SOLUTION
Avatar of dlmille
dlmille
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of Zack

ASKER

Thanks that works!!  Keep monitoring this thread.  I will open up another one for extra assistance.  Thanks for your help!
Zack
Avatar of Zack

ASKER

Wow excel 2003 is being a real piece of crap!  Everytime I change the hook and it errors out Excel crashes arg...  I guess i will have to come up with something totally different. :(

Thanks again for the help tho!
What do you mean "change the hook"?  Ok.  If you want to modify the code, ensure you've UNHOOKED.  Unplanned crashes can result from modifying code while its trying to run it...
Avatar of Zack

ASKER

oooooops.  Do I feel dumb...Yep - I forgot to unhook.  Sorry man. That works awesome now.  I feel really dumb. :( :( :(

I'm used to changing stuff on the fly when it crashes.  I forgot you said to unhook first...

Thanks again!
Zack
Hey - I've been there, too.  Crashed many times on things like this, addins, timers, etc., before I thought, "whoa", let's get the cart behind the horse :)

Dave
Avatar of Zack

ASKER

Hehe. Actually I do have a quick question about select cases. Is there a way to have like a to z instead of doing "a", "b", "c", etc. Thanks!
you mean a test for alpha characters?
You could use the ascii codes, or just test for the text.  See code (commented is looking at it from an ascii code basis, which could be useful if you're trying to trap a function key or something):

 
Sub processKeyPress(keyPressed As String)

    'Select Case Asc(keyPressed)
    Select Case keyPressed
        'case 65 to 90, 97 to 122 'for Asc(keyPressed)
        Case "a" To "z", "A" To "Z":
            Application.StatusBar = "key Pressed was: " & keyPressed
            'put code here if capital alpha key was pressed
        Case "*":
            Call Unhook_KeyBoard
            Application.StatusBar = False
        Case Else
            'do whatever
    End Select
        
End Sub

Open in new window


Dave

PS - ask a related question by clicking it at the top right hand corner where you add a new post.
Note an asterisk now unhooks.  Or you could have a button to run the Unhook, etc.

Dave
Avatar of Zack

ASKER

dlmille - go here so i can award you points for this additional question and respond. https://www.experts-exchange.com/index.jsp?qid=27142619

Thanks!
Zack
You didnt' need to do that - I meant for future follow-ups where others might help (unless its bug related)....

But, I posted this in the post you created.  It will benefit the knowledgebase.

Dave
Avatar of Zack

ASKER

That's cool it's appreciated.  I know the more points you get you can get your monthly membership waived as I have had in the past, so it's all good. :)
Avatar of Sach44
Sach44

Hi guys. Can anyone be kind enough to convert this for Win64 ? My attemps are not functional and not worth posting!