Swarup Somanna Swarup Somanna - 6 months ago 30
Vb.net Question

Issue toggling caps lock with check box in vb .net

I've developed an application (with vb .net) that can toggle caps lock state by clicking on a check box. I've coded the program in such a way that when I click on the checkbox, if it gets checked the caps lock must be turned on and when unchecked it must turn off. Below are the codes.

Public Class Form1

Private Declare Sub keybd_event Lib "user32" (ByVal bVk As Byte, ByVal bScan As Byte, ByVal dwFlags As Integer, ByVal dwExtraInfo As Integer)

Private Const VK_CAPITAL As Integer = &H14
Private Const VK_SCROLL As Integer = &H91
Private Const VK_NUMLOCK As Integer = &H90

Private Const KEYEVENTF_EXTENDEDKEY As Integer = &H1
Private Const KEYEVENTF_KEYUP As Integer = &H2

Private Sub checkbutton_caps_CheckedChanged(sender As Object, e As EventArgs) Handles checkbutton_caps.CheckStateChanged

If checkbutton_caps.Checked = True Then
Call keybd_event(VK_CAPITAL, &H45, KEYEVENTF_EXTENDEDKEY Or 0, 0)
checkbutton_caps.Image = Image.FromFile("resources\btn_ico_caps_on.png")

ElseIf checkbutton_caps.Checked = False Then
Call keybd_event(VK_CAPITAL, &H45, KEYEVENTF_EXTENDEDKEY Or KEYEVENTF_KEYUP, 0)
checkbutton_caps.Image = Image.FromFile("resources\btn_ico_caps_off.png")

End If
End Sub
End Class


Now the problem is it's not working as expected. If I check the checkbox, only the image of the checkbox changes but not the caps lock status. The caps lock status changes only when I click on the check box twice. So I need to click on the check box twice to toggle the caps lock. I suspect there's a problem in the way I've used the conditional statements.

Answer

The problem is not the conditional logic per se (in other words, you've written the If statement correctly), but rather the way you are using the keybd_event function.

Think about the physical keys on a keyboard. When you "press" a key, two things actually happen: the key goes down, and then the key comes back up. After both of those things happen, a key press event is registered as having taken place.

So the proper way to trigger a key press using the keybd_event function is to inject a key-down event, followed by a key-up event. So you actually need a pair of calls:

 Call keybd_event(VK_CAPITAL, &H45, KEYEVENTF_EXTENDEDKEY Or 0, 0)
 Call keybd_event(VK_CAPITAL, &H45, KEYEVENTF_EXTENDEDKEY Or KEYEVENTF_KEYUP, 0)

Now that you understand that, look back at your code. The checkbox control starts out unchecked. The first time you click on it, it gets automatically checked and your CheckedChanged event handler fires. In response, you make the first call to keybd_event, which essentially "presses down" the caps-lock key. It is not until the second time you click the checkbox control, switching it back to unchecked and making the second call to keybd_event that you actually trigger a full press of the caps lock key.

Write the code like so:

Private Sub checkbutton_caps_CheckedChanged(sender As Object, e As EventArgs) Handles checkbutton_caps.CheckStateChanged

    If checkbutton_caps.Checked = True Then
        Call keybd_event(VK_CAPITAL, &H45, KEYEVENTF_EXTENDEDKEY Or 0, 0)
        Call keybd_event(VK_CAPITAL, &H45, KEYEVENTF_EXTENDEDKEY Or KEYEVENTF_KEYUP, 0)
        checkbutton_caps.Image = Image.FromFile("resources\btn_ico_caps_on.png")

    ElseIf checkbutton_caps.Checked = False Then
        Call keybd_event(VK_CAPITAL, &H45, KEYEVENTF_EXTENDEDKEY Or 0, 0)
        Call keybd_event(VK_CAPITAL, &H45, KEYEVENTF_EXTENDEDKEY Or KEYEVENTF_KEYUP, 0)
        checkbutton_caps.Image = Image.FromFile("resources\btn_ico_caps_off.png")

    End If

End Sub

Or, better yet, encapsulate the logic to trigger a key press in another function, reducing code duplication:

Private Sub SimulateKeyPress(ByVal bVKCode As Byte, ByVal bScanCode As Byte)
     keybd_event(VK_CAPITAL, &H45, KEYEVENTF_EXTENDEDKEY Or 0, 0)
     keybd_event(VK_CAPITAL, &H45, KEYEVENTF_EXTENDEDKEY Or KEYEVENTF_KEYUP, 0)
End Sub

Private Sub checkbutton_caps_CheckedChanged(sender As Object, e As EventArgs) Handles checkbutton_caps.CheckStateChanged

    If checkbutton_caps.Checked = True Then
        SimulateKeyPress(VK_CAPITAL, &H45)
        checkbutton_caps.Image = Image.FromFile("resources\btn_ico_caps_on.png")

    ElseIf checkbutton_caps.Checked = False Then
        SimulateKeyPress(VK_CAPITAL, &H45)
        checkbutton_caps.Image = Image.FromFile("resources\btn_ico_caps_off.png")

    End If

End Sub

Even better yet, use the SendInput function instead of the deprecated keybd_event function. This allows you to, among other things, check for and handle errors.

Note that you really don't need the Call syntax in VB.NET. That's an old VB 6 thing, and not an idiomatic way to write code in VB.NET.