在所有 PowerPoint 幻灯片中使用 CommandButton1_KeyDown
Utilising CommandButton1_KeyDown across all PowerPoint Slides
我有 4 张幻灯片,每张幻灯片都有一个 ActiveX 标签。第一张幻灯片包含一个 ActiveX 命令按钮。
Private Sub CommandButton1_KeyDown(ByVal KeyCode As MSForms.ReturnInteger, ByVal Shift As Integer)
Set shpPoint = ActivePresentation.SlideShowWindow.View.Slide.Shapes("TextBox 1")
If (KeyCode = vbKeyA) Then
Point = Point + 1
shpPoint.TextFrame.TextRange = Point
End If
End Sub
此代码允许我捕获按键和 运行 相应的宏。要开始捕获按键,我必须先单击幻灯片 1 中的命令按钮。之后,代码就可以很好地完成它的工作。但是,如果我转到另一张幻灯片,代码将不起作用。按键捕获仅发生在幻灯片 1 中。我认为它与 Slide1
中存在的 Private Sub
有关
我在浏览幻灯片 1 到 4 时一直在复制相同的内容。我不想在每张幻灯片上都放置一个命令按钮。我希望在所有幻灯片和 运行 相应的宏中捕获 vbKeyA。
请告知最佳的处理方法。
您的方法有效,因为当您第一次单击按钮时,它会获得焦点。只要按钮有焦点,就会触发 KeyDown
事件。一旦按钮失去焦点,事件将不再触发。更改幻灯片后,第一张幻灯片上的按钮将失去焦点。
@DanielDušek 提供的评论是明智的。使用这种方法,您需要一个控件来公开 KeyDown
事件以捕获它,不幸的是,您需要这样一个控件来始终获得焦点,因此每张幻灯片上都有一个。可以是 Frame
、TextBox
、CommandButton
等
初步方法 - 你可以跳过这部分
我最初的方法是尝试改进您的方法。主要步骤:
- 在单击初始按钮时,我使用
Slide.Shapes.AddOLEObject ClassName:="Forms.CommandButton.1"
以编程方式在每张幻灯片上添加了一个透明按钮
- 我已经使用
AddOLEObject
返回的形状检索了每个按钮:Shape.OLEFormat.Object
- 我在包装器中添加了每个按钮 class 这样我就可以捕获
KeyDown
事件
- 我编辑了每个按钮(比如让它透明)
- 然后我将所有包装按钮添加到一个全局集合中,以便稍后删除它们。
我遇到了 2 个问题:
- 我无法以编程方式将焦点设置在透明按钮上(顺便说一句,幻灯片应用程序事件似乎没有正确触发 - 是的,我也有一个包装器
WithEvents
应用程序 class ).即使这有效,控件仍然会失去焦点,因此它不会太可靠
- 包裹的按钮似乎失去了状态(尽管全局集合有对每个按钮的引用),我以后无法删除它们
总体而言,上述方法很糟糕且不可靠。
实解
我没有依赖事件控件,而是连接到键盘本身。以下解决方案仅适用于 Windows(不适用于 Mac)。据我测试,它运行良好。
将以下代码放入标准模块中。称之为 KeyboardHook
:
Option Explicit
'API declarations
#If Mac Then
'No Mac functionality implemented
#Else 'Windows API functionality
#If VBA7 Then
Private Declare PtrSafe Function CallNextHookEx Lib "user32" (ByVal hHook As LongPtr, ByVal ncode As Long, ByVal wParam As LongPtr, lParam As Any) As LongPtr
Private Declare PtrSafe Function GetCurrentThreadId Lib "kernel32" () As Long
Private Declare PtrSafe Function GetKeyState Lib "user32" (ByVal nVirtKey As Long) As Integer
Private Declare PtrSafe Function GetActiveWindow Lib "user32" () As LongPtr
Private Declare PtrSafe Function GetWindowText Lib "user32" Alias "GetWindowTextA" (ByVal hwnd As LongPtr, ByVal lpString As String, ByVal cch As Long) As Long
Private Declare PtrSafe Function GetWindowTextLength Lib "user32" Alias "GetWindowTextLengthA" (ByVal hwnd As LongPtr) As Long
Private Declare PtrSafe Function SetWindowsHookEx Lib "user32" Alias "SetWindowsHookExA" (ByVal idHook As Long, ByVal lpfn As LongPtr, ByVal hmod As LongPtr, ByVal dwThreadId As Long) As LongPtr
Private Declare PtrSafe Function UnhookWindowsHookEx Lib "user32" (ByVal hHook As LongPtr) As Long
#Else
Private Declare Function CallNextHookEx Lib "user32" (ByVal hHook As Long, ByVal ncode As Long, ByVal wParam As Long, lParam As Any) As Long
Private Declare Function GetCurrentThreadId Lib "kernel32" () As Long
Private Declare Function GetKeyState Lib "user32" (ByVal nVirtKey As Long) As Integer
Private Declare Function GetActiveWindow Lib "user32" () As Long
Private Declare Function GetWindowText Lib "user32" Alias "GetWindowTextA" (ByVal hwnd As Long, ByVal lpString As String, ByVal cch As Long) As Long
Private Declare Function GetWindowTextLength Lib "user32" Alias "GetWindowTextLengthA" (ByVal hwnd As Long) As Long
Private 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
Private Declare Function UnhookWindowsHookEx Lib "user32" (ByVal hHook As Long) As Long
#End If
#End If
'Id of the hook procedure to be installed with SetWindowsHookExA for KeyboardProc
Private Const WH_KEYBOARD As Long = 2
'Hook handle returned by SetWindowsHookEx. Used later in UnhookWindowsHookEx
#If VBA7 Then
Private m_hHookKeyboard As LongPtr
#Else
Private m_hHookKeyboard As Long
#End If
'Stored to check if presentation is still running via 'IsPresentationActive'
Private m_presentation As Presentation
Private Const REG_APP As String = "PP"
Private Const REG_SECTION As String = "KeyHook"
Private Const REG_KEY As String = "hHook"
Private Function IsPresentationActive() As Boolean
On Error Resume Next
IsPresentationActive = ActivePresentation.SlideShowWindow.Active
IsPresentationActive = (Err.Number = 0)
On Error GoTo 0
End Function
'*******************************************************************************
'Hooks Keyboard messages
'*******************************************************************************
Public Sub HookKeyboard()
UnHookKeyboard 'Remove previous hook
'
Set m_presentation = ActivePresentation
If Not IsPresentationActive Then Exit Sub
'
Dim isHookSuccessful As Boolean
'
#If Mac Then
#Else
m_hHookKeyboard = SetWindowsHookEx(idHook:=WH_KEYBOARD _
, lpfn:=AddressOf KeyboardProc _
, hmod:=0 _
, dwThreadId:=GetCurrentThreadId())
#End If
If m_hHookKeyboard <> 0 Then
SaveSetting REG_APP, REG_SECTION, REG_KEY, m_hHookKeyboard
Debug.Print "Keyboard hooked " & Now
End If
End Sub
'*******************************************************************************
'UnHooks Keyboard
'*******************************************************************************
Public Sub UnHookKeyboard()
If m_hHookKeyboard = 0 Then 'Try to restore if state was lost
Dim savedHook As String
'
savedHook = GetSetting(REG_APP, REG_SECTION, REG_KEY)
If savedHook <> vbNullString Then
#If VBA7 Then
m_hHookKeyboard = CLngPtr(savedHook)
#Else
m_hHookKeyboard = CLng(savedHook)
#End If
End If
End If
'
If m_hHookKeyboard <> 0 Then
#If Mac Then
#Else
UnhookWindowsHookEx m_hHookKeyboard
#End If
m_hHookKeyboard = 0
DeleteSetting REG_APP, REG_SECTION, REG_KEY
Debug.Print "Keyboard unhooked " & Now
End If
End Sub
'*******************************************************************************
'Callback hook function - monitors keyboard messages
'https://docs.microsoft.com/en-us/previous-versions/windows/desktop/legacy/ms644984(v=vs.85)
'*******************************************************************************
#If Mac Then
#Else
#If VBA7 Then
Private Function KeyboardProc(ByVal ncode As Long _
, ByVal wParam As Long _
, ByVal lParam As Long) As LongPtr
#Else
Private Function KeyboardProc(ByVal ncode As Long _
, ByVal wParam As Long _
, ByVal lParam As Long) As Long
#End If
'nCode
Const HC_ACTION As Long = 0
Const HC_NOREMOVE As Long = 3
'
'WM_KEYUP/DOWN/CHAR HIWORD(lParam) flags
Const KF_EXTENDED = &H100
Const KF_DLGMODE = &H800
Const KF_MENUMODE = &H1000
Const KF_ALTDOWN = &H2000
Const KF_REPEAT = &H4000
Const KF_UP = &H8000
'
If IsVBEActive Then GoTo Unhook 'Unhook if a VBE window is active (to avoid crashes)
If Not IsPresentationActive Then GoTo Unhook
'
If ncode = HC_ACTION Then
If wParam = vbKeyA And (lParam And KF_UP) > 0 Then
Debug.Print "A " & Now
Debug.Print "Shift is down: " & IsShiftKeyDown()
Debug.Print "Ctrl is down: " & IsControlKeyDown()
Debug.Print
'
KeyboardProc = -1
Exit Function
End If
End If
'
NextHook:
KeyboardProc = CallNextHookEx(0, ncode, wParam, ByVal lParam)
Exit Function
Unhook:
UnHookKeyboard
GoTo NextHook
End Function
#End If
'*******************************************************************************
'Get Shift/Control Key State
'https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-getkeystate
'https://docs.microsoft.com/en-us/windows/desktop/inputdev/virtual-key-codes
'*******************************************************************************
Private Function IsShiftKeyDown() As Boolean
Const VK_SHIFT As Long = &H10
'
IsShiftKeyDown = CBool(GetKeyState(VK_SHIFT) And &H8000) 'hi-order bit only
End Function
Private Function IsControlKeyDown() As Boolean
Const VK_CONTROL As Long = &H11
'
IsControlKeyDown = CBool(GetKeyState(VK_CONTROL) And &H8000)
End Function
'*******************************************************************************
'Returns the String Caption of a Window identified by a handle
'*******************************************************************************
#If VBA7 Then
Private Function GetWindowCaption(ByVal hwnd As LongPtr) As String
#Else
Private Function GetWindowCaption(ByVal hwnd As Long) As String
#End If
Dim bufferLength As Long: bufferLength = GetWindowTextLength(hwnd)
GetWindowCaption = VBA.Space$(bufferLength)
GetWindowText hwnd, GetWindowCaption, bufferLength + 1
End Function
'*******************************************************************************
'Checks if the ActiveWindow is a VBE Window
'*******************************************************************************
Private Function IsVBEActive() As Boolean
#If Mac Then
#Else
IsVBEActive = VBA.InStr(1, GetWindowCaption(GetActiveWindow()) _
, "Microsoft Visual Basic", vbTextCompare) <> 0
#End If
End Function
要开始跟踪按键,您需要做的就是在演示开始后调用 HookKeyboard
方法。您可以通过几种方式做到这一点。这里有 2 个:
- 按Alt+F8(Macro对话框)然后运行直接Macro
- 在第一张幻灯片上使用 ActiveX 按钮:
Private Sub CommandButton1_Click()
HookKeyboard
End Sub
重要! 我编写代码的方式是,只有在您调用它时演示文稿已经开始时它才会挂钩。此外,它会在演示结束时(按任意键)自动脱钩。如果您想在演示结束前停止挂钩,只需调用 UnHookKeyboard
方法即可。
目前,上面的代码只会在您按下 A 键时立即 Window 中显示一些信息:
您需要做的就是转到 KeyboardProc
方法并更改这些行:
Debug.Print "A " & Now
Debug.Print "Shift is down: " & IsShiftKeyDown()
Debug.Print "Ctrl is down: " & IsControlKeyDown()
Debug.Print
随心所欲。我想你会简单地称呼你想要的宏。
我有 4 张幻灯片,每张幻灯片都有一个 ActiveX 标签。第一张幻灯片包含一个 ActiveX 命令按钮。
Private Sub CommandButton1_KeyDown(ByVal KeyCode As MSForms.ReturnInteger, ByVal Shift As Integer)
Set shpPoint = ActivePresentation.SlideShowWindow.View.Slide.Shapes("TextBox 1")
If (KeyCode = vbKeyA) Then
Point = Point + 1
shpPoint.TextFrame.TextRange = Point
End If
End Sub
此代码允许我捕获按键和 运行 相应的宏。要开始捕获按键,我必须先单击幻灯片 1 中的命令按钮。之后,代码就可以很好地完成它的工作。但是,如果我转到另一张幻灯片,代码将不起作用。按键捕获仅发生在幻灯片 1 中。我认为它与 Slide1
Private Sub
有关
我在浏览幻灯片 1 到 4 时一直在复制相同的内容。我不想在每张幻灯片上都放置一个命令按钮。我希望在所有幻灯片和 运行 相应的宏中捕获 vbKeyA。
请告知最佳的处理方法。
您的方法有效,因为当您第一次单击按钮时,它会获得焦点。只要按钮有焦点,就会触发 KeyDown
事件。一旦按钮失去焦点,事件将不再触发。更改幻灯片后,第一张幻灯片上的按钮将失去焦点。
@DanielDušek 提供的评论是明智的。使用这种方法,您需要一个控件来公开 KeyDown
事件以捕获它,不幸的是,您需要这样一个控件来始终获得焦点,因此每张幻灯片上都有一个。可以是 Frame
、TextBox
、CommandButton
等
初步方法 - 你可以跳过这部分
我最初的方法是尝试改进您的方法。主要步骤:
- 在单击初始按钮时,我使用
Slide.Shapes.AddOLEObject ClassName:="Forms.CommandButton.1"
以编程方式在每张幻灯片上添加了一个透明按钮
- 我已经使用
AddOLEObject
返回的形状检索了每个按钮:Shape.OLEFormat.Object
- 我在包装器中添加了每个按钮 class 这样我就可以捕获
KeyDown
事件 - 我编辑了每个按钮(比如让它透明)
- 然后我将所有包装按钮添加到一个全局集合中,以便稍后删除它们。
我遇到了 2 个问题:
- 我无法以编程方式将焦点设置在透明按钮上(顺便说一句,幻灯片应用程序事件似乎没有正确触发 - 是的,我也有一个包装器
WithEvents
应用程序 class ).即使这有效,控件仍然会失去焦点,因此它不会太可靠 - 包裹的按钮似乎失去了状态(尽管全局集合有对每个按钮的引用),我以后无法删除它们
总体而言,上述方法很糟糕且不可靠。
实解
我没有依赖事件控件,而是连接到键盘本身。以下解决方案仅适用于 Windows(不适用于 Mac)。据我测试,它运行良好。
将以下代码放入标准模块中。称之为 KeyboardHook
:
Option Explicit
'API declarations
#If Mac Then
'No Mac functionality implemented
#Else 'Windows API functionality
#If VBA7 Then
Private Declare PtrSafe Function CallNextHookEx Lib "user32" (ByVal hHook As LongPtr, ByVal ncode As Long, ByVal wParam As LongPtr, lParam As Any) As LongPtr
Private Declare PtrSafe Function GetCurrentThreadId Lib "kernel32" () As Long
Private Declare PtrSafe Function GetKeyState Lib "user32" (ByVal nVirtKey As Long) As Integer
Private Declare PtrSafe Function GetActiveWindow Lib "user32" () As LongPtr
Private Declare PtrSafe Function GetWindowText Lib "user32" Alias "GetWindowTextA" (ByVal hwnd As LongPtr, ByVal lpString As String, ByVal cch As Long) As Long
Private Declare PtrSafe Function GetWindowTextLength Lib "user32" Alias "GetWindowTextLengthA" (ByVal hwnd As LongPtr) As Long
Private Declare PtrSafe Function SetWindowsHookEx Lib "user32" Alias "SetWindowsHookExA" (ByVal idHook As Long, ByVal lpfn As LongPtr, ByVal hmod As LongPtr, ByVal dwThreadId As Long) As LongPtr
Private Declare PtrSafe Function UnhookWindowsHookEx Lib "user32" (ByVal hHook As LongPtr) As Long
#Else
Private Declare Function CallNextHookEx Lib "user32" (ByVal hHook As Long, ByVal ncode As Long, ByVal wParam As Long, lParam As Any) As Long
Private Declare Function GetCurrentThreadId Lib "kernel32" () As Long
Private Declare Function GetKeyState Lib "user32" (ByVal nVirtKey As Long) As Integer
Private Declare Function GetActiveWindow Lib "user32" () As Long
Private Declare Function GetWindowText Lib "user32" Alias "GetWindowTextA" (ByVal hwnd As Long, ByVal lpString As String, ByVal cch As Long) As Long
Private Declare Function GetWindowTextLength Lib "user32" Alias "GetWindowTextLengthA" (ByVal hwnd As Long) As Long
Private 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
Private Declare Function UnhookWindowsHookEx Lib "user32" (ByVal hHook As Long) As Long
#End If
#End If
'Id of the hook procedure to be installed with SetWindowsHookExA for KeyboardProc
Private Const WH_KEYBOARD As Long = 2
'Hook handle returned by SetWindowsHookEx. Used later in UnhookWindowsHookEx
#If VBA7 Then
Private m_hHookKeyboard As LongPtr
#Else
Private m_hHookKeyboard As Long
#End If
'Stored to check if presentation is still running via 'IsPresentationActive'
Private m_presentation As Presentation
Private Const REG_APP As String = "PP"
Private Const REG_SECTION As String = "KeyHook"
Private Const REG_KEY As String = "hHook"
Private Function IsPresentationActive() As Boolean
On Error Resume Next
IsPresentationActive = ActivePresentation.SlideShowWindow.Active
IsPresentationActive = (Err.Number = 0)
On Error GoTo 0
End Function
'*******************************************************************************
'Hooks Keyboard messages
'*******************************************************************************
Public Sub HookKeyboard()
UnHookKeyboard 'Remove previous hook
'
Set m_presentation = ActivePresentation
If Not IsPresentationActive Then Exit Sub
'
Dim isHookSuccessful As Boolean
'
#If Mac Then
#Else
m_hHookKeyboard = SetWindowsHookEx(idHook:=WH_KEYBOARD _
, lpfn:=AddressOf KeyboardProc _
, hmod:=0 _
, dwThreadId:=GetCurrentThreadId())
#End If
If m_hHookKeyboard <> 0 Then
SaveSetting REG_APP, REG_SECTION, REG_KEY, m_hHookKeyboard
Debug.Print "Keyboard hooked " & Now
End If
End Sub
'*******************************************************************************
'UnHooks Keyboard
'*******************************************************************************
Public Sub UnHookKeyboard()
If m_hHookKeyboard = 0 Then 'Try to restore if state was lost
Dim savedHook As String
'
savedHook = GetSetting(REG_APP, REG_SECTION, REG_KEY)
If savedHook <> vbNullString Then
#If VBA7 Then
m_hHookKeyboard = CLngPtr(savedHook)
#Else
m_hHookKeyboard = CLng(savedHook)
#End If
End If
End If
'
If m_hHookKeyboard <> 0 Then
#If Mac Then
#Else
UnhookWindowsHookEx m_hHookKeyboard
#End If
m_hHookKeyboard = 0
DeleteSetting REG_APP, REG_SECTION, REG_KEY
Debug.Print "Keyboard unhooked " & Now
End If
End Sub
'*******************************************************************************
'Callback hook function - monitors keyboard messages
'https://docs.microsoft.com/en-us/previous-versions/windows/desktop/legacy/ms644984(v=vs.85)
'*******************************************************************************
#If Mac Then
#Else
#If VBA7 Then
Private Function KeyboardProc(ByVal ncode As Long _
, ByVal wParam As Long _
, ByVal lParam As Long) As LongPtr
#Else
Private Function KeyboardProc(ByVal ncode As Long _
, ByVal wParam As Long _
, ByVal lParam As Long) As Long
#End If
'nCode
Const HC_ACTION As Long = 0
Const HC_NOREMOVE As Long = 3
'
'WM_KEYUP/DOWN/CHAR HIWORD(lParam) flags
Const KF_EXTENDED = &H100
Const KF_DLGMODE = &H800
Const KF_MENUMODE = &H1000
Const KF_ALTDOWN = &H2000
Const KF_REPEAT = &H4000
Const KF_UP = &H8000
'
If IsVBEActive Then GoTo Unhook 'Unhook if a VBE window is active (to avoid crashes)
If Not IsPresentationActive Then GoTo Unhook
'
If ncode = HC_ACTION Then
If wParam = vbKeyA And (lParam And KF_UP) > 0 Then
Debug.Print "A " & Now
Debug.Print "Shift is down: " & IsShiftKeyDown()
Debug.Print "Ctrl is down: " & IsControlKeyDown()
Debug.Print
'
KeyboardProc = -1
Exit Function
End If
End If
'
NextHook:
KeyboardProc = CallNextHookEx(0, ncode, wParam, ByVal lParam)
Exit Function
Unhook:
UnHookKeyboard
GoTo NextHook
End Function
#End If
'*******************************************************************************
'Get Shift/Control Key State
'https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-getkeystate
'https://docs.microsoft.com/en-us/windows/desktop/inputdev/virtual-key-codes
'*******************************************************************************
Private Function IsShiftKeyDown() As Boolean
Const VK_SHIFT As Long = &H10
'
IsShiftKeyDown = CBool(GetKeyState(VK_SHIFT) And &H8000) 'hi-order bit only
End Function
Private Function IsControlKeyDown() As Boolean
Const VK_CONTROL As Long = &H11
'
IsControlKeyDown = CBool(GetKeyState(VK_CONTROL) And &H8000)
End Function
'*******************************************************************************
'Returns the String Caption of a Window identified by a handle
'*******************************************************************************
#If VBA7 Then
Private Function GetWindowCaption(ByVal hwnd As LongPtr) As String
#Else
Private Function GetWindowCaption(ByVal hwnd As Long) As String
#End If
Dim bufferLength As Long: bufferLength = GetWindowTextLength(hwnd)
GetWindowCaption = VBA.Space$(bufferLength)
GetWindowText hwnd, GetWindowCaption, bufferLength + 1
End Function
'*******************************************************************************
'Checks if the ActiveWindow is a VBE Window
'*******************************************************************************
Private Function IsVBEActive() As Boolean
#If Mac Then
#Else
IsVBEActive = VBA.InStr(1, GetWindowCaption(GetActiveWindow()) _
, "Microsoft Visual Basic", vbTextCompare) <> 0
#End If
End Function
要开始跟踪按键,您需要做的就是在演示开始后调用 HookKeyboard
方法。您可以通过几种方式做到这一点。这里有 2 个:
- 按Alt+F8(Macro对话框)然后运行直接Macro
- 在第一张幻灯片上使用 ActiveX 按钮:
Private Sub CommandButton1_Click()
HookKeyboard
End Sub
重要! 我编写代码的方式是,只有在您调用它时演示文稿已经开始时它才会挂钩。此外,它会在演示结束时(按任意键)自动脱钩。如果您想在演示结束前停止挂钩,只需调用 UnHookKeyboard
方法即可。
目前,上面的代码只会在您按下 A 键时立即 Window 中显示一些信息:
您需要做的就是转到 KeyboardProc
方法并更改这些行:
Debug.Print "A " & Now
Debug.Print "Shift is down: " & IsShiftKeyDown()
Debug.Print "Ctrl is down: " & IsControlKeyDown()
Debug.Print
随心所欲。我想你会简单地称呼你想要的宏。