将 MapVirtualKeyA 与 Shift 和 Ctrl Alt 一起使用

Use MapVirtualKeyA with Shift and Ctrl Alt

我发现 this page 有一个函数可以根据系统语言和键盘布局将给定的键码转换为相应的字符。我测试了它(在 PowerPoint 中使用 VBA)并且它有效,除了我不知道如何告诉函数 ShiftCtrl + Alt 正在被按下,因此函数应该然后 return 不同的结果。比方说:keycode 51 对应数字 3,当我将它传递给函数时,它 returns 3。但是如果用户按下 Shift 键呢?它应该 return #(或不同的字符,取决于键盘布局等)

所以,我知道如何检查 ShiftCtrl + Alt 是否被按下,但我不知道如何告诉函数按键被按下。

我将以下代码放在一个模块中:

Public Declare PtrSafe Function MapVirtualKeyA Lib "user32" (ByVal uCode As Long, ByVal uMapType As Long) As Integer

然后我在幻灯片的代码中输入:

Sub test()

    MsgBox Chr(MapVirtualKeyA(vbKey3, 2)) ' always returns 3, even when pressing shift or ctrl + alt

End Sub

我想知道我必须在我的代码中更改什么,以便函数知道正在按下 ShiftCtrl + Alt

提前致谢。

这应该会有所帮助(但如果您使用的是 64 位版本的 Office,则需要对其进行修改以包含 64 位兼容声明)。

(In declarations)
Public Const VK_SHIFT = &H10
Public Const VK_CONTROL = &H11
Public Const VK_MENU = &H12
Private Declare Function GetAsyncKeyState Lib "user32" (ByVal vKey As Long) As Integer

Public Function GetAltState() As Boolean
    Call GetAsyncKeyState(VK_MENU)
    If GetAsyncKeyState(VK_MENU) <> 0 Then GetAltState = True
End Function

Public Function GetCtrlState() As Boolean
    Call GetAsyncKeyState(VK_CONTROL)
    If GetAsyncKeyState(VK_CONTROL) <> 0 Then GetCtrlState = True
End Function

Public Function GetShiftState() As Boolean
    Call GetAsyncKeyState(VK_SHIFT)
    If GetAsyncKeyState(VK_SHIFT) <> 0 Then GetShiftState = True
End Function

您可以使用 ToAscii API.

Option Explicit

Private Declare PtrSafe Function ToAscii Lib "user32" (ByVal uVirtKey As Long, ByVal uScanCode As Long, lpbKeyState As Byte, lpChar As LongPtr, ByVal uFlags As Long) As Long
Private Declare PtrSafe Function MapVirtualKeyA Lib "user32" (ByVal uCode As Long, ByVal uMapType As Long) As Long

Sub Test()
    Debug.Print GetCharacterFromVK(uVirtKey:=vbKey3, shiftDown:=True)
End Sub

Function GetCharacterFromVK(uVirtKey As Long _
    , Optional shiftDown As Boolean = False _
    , Optional ctrlDown As Boolean = False _
    , Optional altDown As Boolean = False _
) As String
    'https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes
    Const VK_SHIFT = &H10   'SHIFT key
    Const VK_CONTROL = &H11 'CTRL key
    Const VK_MENU = &H12    'ALT key
    '
    Dim keyState(0 To 255) As Byte
    Dim buffer As String: buffer = Space$(2)
    '
    'Set Key States by setting the high-order bit of the byte
    If shiftDown Then keyState(VK_SHIFT) = &H80
    If ctrlDown Then keyState(VK_CONTROL) = &H80
    If altDown Then keyState(VK_MENU) = &H80
    '
    'Populate buffer
    Select Case ToAscii(uVirtKey, MapVirtualKeyA(uVirtKey, 0), keyState(0), ByVal StrPtr(buffer), 0)
    Case 0
        'The specified virtual key has no translation for the current state of the keyboard
        GetCharacterFromVK = vbNullString
    Case 1
        'One character was copied to the buffer
        GetCharacterFromVK = Left$(buffer, 1)
    Case 2
        'Two characters were copied to the buffer
        GetCharacterFromVK = buffer
    End Select
End Function

要理解您问题的答案,我们需要先了解一下键盘输入的工作原理。

Microsoft 对此有一个完整的部分:

当按下某个键时,会生成 keyboard, a scan code。扫描码取决于设备。

下一个:

The keyboard device driver receives scan codes from the keyboard

并将它们变成虚拟键:

a device-independent value defined by the system that identifies the purpose of a key.

虚拟键表示键盘上的键,而不是它们可以表示的字符。因此,例如 3 是一个虚拟键,但它是移位值(在我的例子中是 £),不是,并且取决于所使用的键盘。

这些存储在设备相关的 KeyboardLayout 中。 MapVirualKey 等基本函数调用使用默认键盘布局(即您现在使用的键盘布局)。如果你想使用不同的布局,你可以通过调用获得列表:

GetKeyboardLayoutList

所以一个虚拟键和任何修饰符被发送到键盘布局,这个 return 是适当的字符,然后呈现到屏幕等...

所以你的问题变成了,'How can we find the Unicode/ASCII value of myKey + shift?'。没有任何特别简单的答案,例如在我的计算机上 'shift 3 (£)' 的 ASCII 值为 163,但 'shift 4 ($)' 的 ASCII 值为 36!

这些示例使用 Excel,而不是 Powerpoint。

我们先来看MapVirtualKey`。我没有使用过一般的键盘布局。

在模块中,添加以下声明:

' https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-mapvirtualkeya
Public Declare PtrSafe Function MapVirtualKeyA Lib "user32" ( _
        ByVal uCode As Long, _
        ByVal uMapType As Long) As Integer

并将以下子例程添加到 Button1:

Sub Button1_Click()

Cells.Clear

' MapVirtualKeyA
Cells(1, 1) = "MapVirtualKeyA"
Cells(1, 1).Font.Bold = 1

char = "3"
Cells(2, 1) = "char: " & char
Cells(2, 2) = MapVirtualKeyA(Asc(char), 2)
Cells(2, 3) = Chr(MapVirtualKeyA(Asc(char), 2))

char = "£"
Cells(3, 1) = "char: " & char
Cells(3, 2) = MapVirtualKeyA(Asc(char), 2)
Cells(3, 3) = Chr(MapVirtualKeyA(Asc(char), 2)) ' doesn't exist

第一个调用工作正常,第二个调用不能将 £ 识别为虚拟键(它不是),因此 returns 0.

所以这在确定 'shift 3' 应该 return 时没有多大用处。

我们可以使用 VkKeyScan API 函数从 £ 检索 'shift' 状态。

在模块中声明函数:

' https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-vkkeyscana
Public Declare Function VkKeyScan Lib "user32" Alias "VkKeyScanA" ( _
        ByVal cChar As Byte) As Integer

并将此代码添加到按钮:

'VkKeyScan
Cells(5, 1) = "VkKeyScan"
Cells(5, 1).Font.Bold = 1

char = "3"
keyScan = VkKeyScan(Asc(char))
shift = keyScan And &H100
ctrl = keyScan And &H200
vkKey = keyScan And &HFF

Cells(6, 1) = "char: " & char
Cells(6, 2) = "shift: " & shift
Cells(6, 3) = "ctrl: " & ctrl
Cells(6, 4) = keyScan
Cells(6, 5) = ChrW(keyScan)
Cells(6, 6) = vkKey
Cells(6, 7) = Chr(vkKey)

char = "£"
keyScan = VkKeyScan(Asc(char))
shift = keyScan And &H100
ctrl = keyScan And &H200
vkKey = keyScan And &HFF

Cells(7, 1) = "char: " & char
Cells(7, 2) = "shift: " & shift
Cells(7, 3) = "ctrl: " & ctrl
Cells(7, 4) = keyScan
Cells(7, 5) = ChrW(keyScan) ' wrong character
Cells(7, 6) = vkKey
Cells(7, 7) = Chr(vkKey)

这提供了更多信息,但仍然没有给我们答案。

这里有两个解决方案,两者几乎相同:

SendInput 是官方的 Win32API 做事方式,但涉及相当长的代码,并且被 VBA SendKeys 函数巧妙地包裹起来。

SendKeys 将文本输出发送到当前活动的光标。

Cells(10, 1).Select
SendKeys ("3+3~")
While Cells(10, 1) = ""
DoEvents
Wend
Cells(10, 2) = Asc(Mid(Cells(10, 1), 1, 1))
Cells(10, 3) = Asc(Mid(Cells(10, 1), 2, 1))
End Sub

Cells(10, 1).Select 将光标放在我们想要的地方。

+用来模拟'shift',~模拟'enter'.

等待处理器赶上..,我们有“£”的 ASCII 值!