22

您在工作表中的特定单元格中按下一个键(进行编辑)时,是否可以以任何方式捕获事件?

最接近的是已知Change事件,但只有在取消选择已编辑的单元格后才能激活它。我想在编辑单元格时捕获事件。

4

3 回答 3

24

这是答案,我已经测试过了,它对我来说工作正常。

在 Excel 中跟踪按键

有趣的问题:Worksheet_Change当您完成更改并离开单元格时,MS Excel 的事件总是被触发。捕获Key Press事件。使用 Excel 标准或内置函数无法跟踪 Keypress 事件。

这可以通过使用API.

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

Sub TrackKeyPressInit()

    Dim msgMessage As MSG
    Dim bCancel As Boolean
    Dim iKeyCode As Integer
    Dim lXLhwnd As Long

    On Error GoTo errHandler:
        Application.EnableCancelKey = xlErrorHandler
        'initialize this boolean flag.
        bExitLoop = False
        'get the app hwnd.
        lXLhwnd = FindWindow("XLMAIN", Application.Caption)
    Do
        WaitMessage
        'check for a key press and remove it from the msg queue.
        If PeekMessage _
            (msgMessage, lXLhwnd, WM_KEYDOWN, WM_KEYDOWN, PM_REMOVE) Then
            'strore the virtual key code for later use.
            iKeyCode = msgMessage.wParam
           'translate the virtual key code into a char msg.
            TranslateMessage msgMessage
            PeekMessage msgMessage, lXLhwnd, WM_CHAR, _
            WM_CHAR, PM_REMOVE
           'for some obscure reason, the following
          'keys are not trapped inside the event handler
            'so we handle them here.
            If iKeyCode = vbKeyBack Then SendKeys "{BS}"
            If iKeyCode = vbKeyReturn Then SendKeys "{ENTER}"
           'assume the cancel argument is False.
            bCancel = False
            'the VBA RaiseEvent statement does not seem to return ByRef arguments
            'so we call a KeyPress routine rather than a propper event handler.
            Sheet_KeyPress _
            ByVal msgMessage.wParam, ByVal iKeyCode, ByVal Selection, bCancel
            'if the key pressed is allowed post it to the application.
            If bCancel = False Then
                PostMessage _
                lXLhwnd, msgMessage.Message, msgMessage.wParam, 0
            End If
        End If
errHandler:
        'allow the processing of other msgs.
        DoEvents
    Loop Until bExitLoop

End Sub

Sub StopKeyWatch()

    'set this boolean flag to exit the above loop.
    bExitLoop = True

End Sub


'\\This example illustrates how to catch worksheet
'\\Key strokes in order to prevent entering numeric
'\\characters in the Range "A1:D10" .
Private Sub Sheet_KeyPress(ByVal KeyAscii As Integer, _
                           ByVal KeyCode As Integer, _
                           ByVal Target As Range, _
                           Cancel As Boolean)

    Const MSG As String = _
    "Numeric Characters are not allowed in" & _
    vbNewLine & "the Range:  """
    Const TITLE As String = "Invalid Entry !"

    If Not Intersect(Target, Range("A1:D10")) Is Nothing Then
        If Chr(KeyAscii) Like "[0-9]" Then
            MsgBox MSG & Range("A1:D10").Address(False, False) _
            & """ .", vbCritical, TITLE
            Cancel = True
        End If
    End If

End Sub
于 2012-06-22T10:16:10.273 回答
4

我知道这是一个老问题,但我最近需要类似的功能,并且提供的答案有一些限制,我必须解决它如何处理(或不处理)Del、Backspace、功能键等。

解决方法是发回原始消息而不是翻译的消息。

也更改为使用带有事件的类模块,因为它在 Excel 2010 中工作正常,我不想将相同的代码复制到多个工作表:

类模块(将其命名为 KeyPressApi)

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)

Public Sub StartKeyPressInit()
    Dim msgMessage As MSG
    Dim bCancel As Boolean
    Dim iMessage As Integer
    Dim iKeyCode As Integer
    Dim lXLhwnd As Long

    On Error GoTo errHandler
    Application.EnableCancelKey = xlErrorHandler
    'Initialize this boolean flag.
    bExitLoop = False
    'Get the app hwnd.
    lXLhwnd = FindWindow("XLMAIN", Application.Caption)
    
    Do
        WaitMessage
        
        'Exit the loop if we were aborted
        If bExitLoop Then Exit Do
        
        'Check for a key press and remove it from the msg queue.
        If PeekMessage(msgMessage, lXLhwnd, WM_KEYDOWN, WM_KEYDOWN, PM_REMOVE) Then
            'Store the virtual key code for later use.
            iMessage = msgMessage.Message
            iKeyCode = msgMessage.wParam

            'Translate the virtual key code into a char msg.
            TranslateMessage msgMessage
            PeekMessage msgMessage, lXLhwnd, WM_CHAR, WM_CHAR, PM_REMOVE
            
            bCancel = False
            RaiseEvent KeyPressed(msgMessage.wParam, iKeyCode, Selection, bCancel)
            
            'If not handled, post back to the window using the original values
            If Not bCancel Then
                PostMessage lXLhwnd, iMessage, iKeyCode, 0
            End If
        End If
errHandler:
        'Allow the processing of other msgs.
        DoEvents
    Loop Until bExitLoop
End Sub

Public Sub StopKeyPressWatch()
    'Set this boolean flag to exit the above loop.
    bExitLoop = True
End Sub

用法

Option Explicit

Dim WithEvents CKeyWatcher As KeyPressApi

Private Sub Worksheet_Activate()
    If CKeyWatcher Is Nothing Then
        Set CKeyWatcher = New KeyPressApi
    End If
    CKeyWatcher.StartKeyPressInit
End Sub

Private Sub Worksheet_Deactivate()
    CKeyWatcher.StopKeyPressWatch
End Sub

'\\This example illustrates how to catch worksheet
'\\Key strokes in order to prevent entering numeric
'\\characters in the Range "A1:D10" .
Private Sub CKeyWatcher_KeyPressed(ByVal KeyAscii As Integer, _
                                   ByVal KeyCode As Integer, _
                                   ByVal Target As Range, _
                                   Cancel As Boolean)

    Const MSG As String = _
    "Numeric Characters are not allowed in" & _
    vbNewLine & "the Range:  """
    Const TITLE As String = "Invalid Entry !"

    If Not Intersect(Target, Range("A1:D10")) Is Nothing Then
        If Chr(KeyAscii) Like "[0-9]" Then
            MsgBox MSG & Range("A1:D10").Address(False, False) _
            & """ .", vbCritical, TITLE
            Cancel = True
        End If
    End If

End Sub
于 2014-10-02T15:12:38.030 回答
3

我有同样的问题,并通过在单元格上放置一个文本框来解决它。我设置了属性,使文本框看起来像 Excel 单元格,然后使用 Top 和 Left 属性将其放置在单元格上,使用单元格中的相同属性,并将 Width 和 Height 设置为比细胞。然后我让它可见。我使用 KeyDown 事件来处理击键。在我的代码中,我在单元格下方放置了一个列表框,以显示另一张纸上列表中的匹配项。注意:此代码在工作表中,单元格变量在模块中声明:全局单元格为范围。这比组合框好得多。tb1 是一个文本框,而 lb1 是一个列表框。您将需要一张名为 Fruit 的工作表,其中第一列中包含数据。仅当所选单元格在列 = 2 中并且为空时,运行此代码的工作表才会运行。

Option Explicit

Private Sub lb1_Click()
  Cell.Value2 = lb1.Value
  tb1.Visible = False
  lb1.Visible = False
  Cell.Activate
End Sub

Private Sub tb1_KeyDown(ByVal KeyCode As MSForms.ReturnInteger, ByVal Shift As Integer)
Dim Row As Long
Dim Temp As String
  Select Case KeyCode
  Case vbKeyBack
    If Len(tb1.Value) > 0 Then tb1.Value = Left(tb1.Value, Len(tb1.Value) - 1)
  Case vbKeySpace, vbKeyA To vbKeyZ
    tb1.Value = WorksheetFunction.Proper(tb1.Value & Chr(KeyCode))
  Case vbKeyReturn
    If lb1.ListCount > 0 Then
      Cell.Value2 = lb1.List(0)
    Else
      Cell.Value2 = tb1.Value
      With Sheets("Fruit")
        .Cells(.UsedRange.Rows.Count + 1, 1) = tb1.Value
        .UsedRange.Sort Key1:=.Cells(1, 1), Header:=xlYes
      End With
      MsgBox tb1.Value & " has been added to the List"
    End If
    tb1.Visible = False
    lb1.Visible = False
    Cell.Activate
  Case vbKeyEscape
    tb1.Visible = False
    lb1.Visible = False
    Cell.Activate
  End Select
  lb1.Clear
  Temp = LCase(tb1.Value) & "*"
  With Sheets("Fruit")
    For Row = 2 To .UsedRange.Rows.Count
      If LCase(.Cells(Row, 1)) Like Temp Then
        lb1.AddItem .Cells(Row, 1)
      End If
    Next Row
  End With
KeyCode = 0
End Sub

Private Sub Worksheet_SelectionChange(ByVal Target As Range)

  If Target.Column = 2 And Target.Cells.Count = 1 Then
    If Target.Value2 = Empty Then
      Set Cell = Target
      With Cell
        tb1.Top = .Top
        tb1.Left = .Left
        tb1.Height = .Height + 1
        tb1.Width = .Width + 1
      End With
      tb1.Value = Empty
      tb1.Visible = True
      tb1.Activate
      With Cell.Offset(1, 0)
        lb1.Top = .Top
        lb1.Left = .Left
        lb1.Width = .Width + 1
        lb1.Clear
        lb1.Visible = True
      End With
    Else
      tb1.Visible = False
      lb1.Visible = False
    End If
  Else
    tb1.Visible = False
    lb1.Visible = False
  End If
End Sub
于 2017-05-26T02:05:41.827 回答