Excel VBA:确定鼠标点击单元格时的位置

Excel VBA: Determine mouse location when clicking on a cell

我试图做的是编写一个 Excel SelectionChange 处理程序,这样当用户使用鼠标 选择单个单元格时 我可以 (a) 知道它是鼠标而不是键盘,并且 (b) 找出 单元格 中发生鼠标单击的位置。 (我会排除用户选择多个单元格的情况)。

假设一个单元格中有几行文本。这个想法是根据单击单元格的哪个部分来做一些不同的事情。 (对于我的目的来说,拥有多个单元格是行不通的。)

[更新:我认为 (b) 没有希望并放弃了它。但我仍然想知道什么时候选择因鼠标左键单击而改变。]

感谢任何想法。


编辑:我发现以下资源使我找到了一个似乎可行的解决方案。我还包括我的解决方案的代码。

来源:

Both answers here

This more general explanation

VBA keycode constants

EngJon 注意到他们的解决方案存在问题,因为它拾取了鼠标点击 起源于别处。例如,单击功能区后,使用箭头键更改选择仍会在选择更改处理程序中显示左键单击。

我玩了一会儿并决定(虽然我可能是错的)GetAsyncKeyState()returns True or False 对于最近的鼠标点击 and 最近的按键。它处理像 SHIFT-TAB 这样的组合键按下的方式是 return True for both shift 和 tab 键。 [将 0 视为 False,将所有非零值视为 True。]

(您会注意到,键码常量对于像后退标签(SHIFT-TAB)这样的组合没有任何价值。同样,我敢打赌,虽然我没有测试它,但您会发现像 SHIFT 这样的组合-RIGHTCLICK 通过测试 GetAsyncKeyState(vbKeyShift) AND GetAsyncKeyState(vbKeyRButton).)

所以,在一个模块(例如模块 1)中,我将...

选项显式

#If VBA7 Then
    'declare virtual key event listener
    Public Declare PtrSafe Function GetAsyncKeyState Lib "user32" _
            (ByVal vKey As Long) As Integer
#Else
    'declare virtual key event listener
    Public Declare Function GetAsyncKeyState Lib "user32" _
            (ByVal vKey As Long) As Integer
#End If

在 sheet 我想捕获击键和鼠标点击的代码中,我输入了 ...

Private Sub Worksheet_SelectionChange(ByVal Target As Range)
    
    'Check whether the left mouse key was recently pressed.
    
    Select Case True

     Case GetAsyncKeyState(vbKeyUp) Or GetAsyncKeyState(vbKeyRight) _
            Or GetAsyncKeyState(vbKeyDown) Or GetAsyncKeyState(vbKeyLeft) _
            Or GetAsyncKeyState(vbKeyTab) Or GetAsyncKeyState(vbKeyLeft) _
            Or GetAsyncKeyState(vbKeyHome) Or GetAsyncKeyState(vbKeyReturn)

        'One of the cell selection keys was pressed, so do nothing.
        ' For some reason, PageUp, PageDown, and End don't seem
        ' to be needed here.
     
     Case GetAsyncKeyState(vbKeyLButton)

        'Left mouse key was pressed
        
        <your code here>
     
    End Select

End Sub

您可能会问为什么第一个 Case 语句在那里。正如 EngJon 所说,“如果您执行的操作以单击结束(例如 MsgBox),则下一次使用箭头键进行的选择更改也将触发该事件并重新执行您的操作。”我发现在检查鼠标单击之前 检查击键可以解决这个问题。

对我来说,到目前为止,这对我在 <your code here> 部分中检测到的单元格进行单独左键单击非常有效。

[也包含在我对上述原始问题的编辑中。]

我发现以下资源使我找到了一个似乎有效的解决方案。我还包括我的解决方案的代码。

来源:

Both answers here

This more general explanation

VBA keycode constants

EngJon 注意到他们的解决方案存在问题,因为它拾取了鼠标点击 起源于别处。例如,单击功能区后,使用箭头键更改选择仍会在选择更改处理程序中显示左键单击。

我玩了一会儿并决定(虽然我可能是错的)GetAsyncKeyState()return对于最近的鼠标点击 和 [=85] 是真还是假=] 最近的按键。它处理像 SHIFT-TAB 这样的组合键按下的方式是 return True for both shift 和 tab 键。 [将 0 视为 False,将所有非零值视为 True。]

(您会注意到键码常量对于像后退标签这样的组合没有价值,它是 SHIFT-TAB。同样,我敢打赌,虽然我没有测试它,但您会找到像 SHIFT 这样的组合-RIGHTCLICK 通过测试 GetAsyncKeyState(vbKeyShift) AND GetAsyncKeyState(vbKeyRButton).)

所以,在一个模块(例如,Module1)中,我把...

选项显式

#If VBA7 Then
    'declare virtual key event listener
    Public Declare PtrSafe Function GetAsyncKeyState Lib "user32" _
            (ByVal vKey As Long) As Integer
#Else
    'declare virtual key event listener
    Public Declare Function GetAsyncKeyState Lib "user32" _
            (ByVal vKey As Long) As Integer
#End If

在 sheet 我想捕获击键和鼠标点击的代码中,我输入了 ...

Private Sub Worksheet_SelectionChange(ByVal Target As Range)
    
    'Check whether the left mouse key was recently pressed.
    
    Select Case True

     Case GetAsyncKeyState(vbKeyUp) Or GetAsyncKeyState(vbKeyRight) _
            Or GetAsyncKeyState(vbKeyDown) Or GetAsyncKeyState(vbKeyLeft) _
            Or GetAsyncKeyState(vbKeyTab) Or GetAsyncKeyState(vbKeyLeft) _
            Or GetAsyncKeyState(vbKeyHome) Or GetAsyncKeyState(vbKeyReturn)

        'One of the cell selection keys was pressed, so do nothing.
        ' For some reason, PageUp, PageDown, and End don't seem
        ' to be needed here.
     
     Case GetAsyncKeyState(vbKeyLButton)

        'Left mouse key was pressed
        
        <your code here>
     
    End Select

End Sub

您可能会问为什么第一个 Case 语句在那里。正如 EngJon 所说,“如果您执行的操作以单击结束(例如 MsgBox),则下一次使用箭头键进行的选择更改也将触发该事件并重新执行您的操作。”我发现在检查鼠标单击之前 检查击键可以解决这个问题。

对我来说,到目前为止,这可以完美地拾取我在 <your code here> 部分检测到的单元格上的单独左键单击。


更新:查看我的新问题 here。某处似乎有一个错误(Excel或Windows)使得GetAsyncKeyState(vbLKeyButton)总是returns 0粘贴到作品后sheet 单元格。幸运的是,它似乎对按键工作正常,所以如果你涵盖了所有可能涉及更改选择的键,并且这些都是 0,那么你真的不必确认它是通过鼠标更改的。


补充说明: 在阅读了大家最喜欢的 MSDN documentation 之后,我想知道使用 GetKeyState()GetAsyncKeyState() 之间的 实用 区别是什么。经过大量测试后,我决定就 Excel 消息队列而言,仅查看 Worksheet_SelectionChange 事件中的值 return,GetKeyState() 似乎给出了一个有趣的按键历史记录。在整数值中,对于给定的键,您将获得:

  • -127 = 这是按下的最后一个键,此键的先前值为 0
  • -128 = 这是按下的最后一个键,该键的先前值为 1
  • 0 = 发生了其他一些键或鼠标事件并且该键最近按下的值为 -128
  • 1 = 发生了其他一些键或鼠标事件并且该键最近按下的值为 -127

在再次按下该键之前,0 或 1 值一直存在。

如果你点击其他地方——比如进入不同的工作sheet——然后按一些导航键,然后用事件处理程序点击返回工作sheet,我不是当然可以,但我认为按键的状态将取决于中间的按键操作。另一方面,如果您只对触发事件的按键感兴趣,您可能只需检查负键值即可。

麻烦的是 GetKeyState() 而不是 可靠地告诉您鼠标何时被点击。左键单击会产生 0 和 1 的交替值。如果您单击其他地方,例如功能区或工作簿 window 边缘,每次单击都会导致值切换,因此当您最终单击工作sheet 并触发 Worksheet_SelectionChange 事件,你不知道你会得到 0 还是 1。

我对一系列操作的 GetKeyState() 结果的观察:

尽管我原来的问题和答案中有所有详细的注释,但我发现 没有实用的方法 来确定是否使用鼠标选择了 cell/range而不是击键,或者更重要的是,通过查找操作。

无法使用 GetAsyncKeyState() 了解选择何时因鼠标而改变,因为:

  1. 如果您单击功能区或 window 边框等位置,然后更改 使用键盘的单元格,函数显示鼠标被点击, 这是正确的但没有用。
  2. 如果先检查导航键是否被按下,逻辑 如果单元格是通过“查找”对话框输入的,则会失败——也就是说,用户单击“查找下一个”,因此该函数显示没有按下任何键而鼠标按下,这是正确的但没有用。
  3. 如果可以克服这些问题,则存在导致该功能的错误 到 return 0 鼠标左键 每次 之后 copy/paste 在工作表中(参见我的相关问题 here)。

无法使用 GetKeyState() 知道选择何时因鼠标而改变,因为对于鼠标左键,该函数总是在值 0 和 1 之间交替(参见我的插图较早的回答)。

虽然我不是特别喜欢,但我的解决方法是要求用户 double-click 以触发我的代码。 Excel before-double-click 事件是唯一捕获鼠标左键单击的事件。这实际上简化了逻辑,但对于我预期的用户交互来说不太理想。

我真正需要 Excel 的是 before-left-click 活动。