如果我使用无限循环,我应该把 VBA.DoEvents 和 Application.OnKey 放在哪里?

Where should I put VBA.DoEvents and Application.OnKey if I use an infinite loop?

我正在 VBA 中创建游戏,但我不知道如何让 Application.OnKey 工作。为什么我需要它?正如您所猜到的,我希望游戏中的对象在玩家按下某个键时移动。

游戏基本上是子程序 RunGame() 中的无限循环。如果我将 Application.OnKey 作为没有 运行ning RunGame() 的工作簿事件,那么它就可以工作,所以我的键盘和 Application.OnKey 可以相互理解。但是,当我 运行 RunGame() 时,关键侦听器不起作用。我敢肯定这是因为我把它放错了地方。关键是我到处都试过了,结果 运行 没主意了!你能帮我吗?我在这里粘贴了一个可重现的例子。

模块“主要”:

Option Explicit

Sub RunGame()

Call Application.OnKey("{Down}", "OnDownKey")

  Do While True
    '
    '
    'Some game's stuff here
    '
    '

    Call WasteTime(1)
  Loop

End Sub

模块“实用程序”:

Option Explicit

Sub WasteTime(TimeToWaste As Single)

  Dim i As Long
  
  For i = 1 To TimeToWaste * 1000
    VBA.DoEvents
    i = i
  Next
End Sub

Sub OnDownKey()
  MsgBox "You pressed down key"
End Sub

顺便说一句,如果这个小示例的逻辑有任何需要更改的地方,我将不胜感激。

为什么要使用无限循环?嘎.

改为使用 VBA 的 WithEvents 模型来等待输入。

并使用Application.Ontime调用一个宏,它会在设定的时间间隔内再次调用自己。如果你无限循环,你永远不会破坏那个子中的对象,并且 VBA 将 运行 内存不足,使游戏崩溃。当你甚至有一个侧滚动条,它移动框架或其他东西,并且你使用 AOT,然后你得到 1 帧,然后是 2 帧,而不是一个流畅的大子,你可以改变 AOT 调用的轨迹,通过使用 WithEvents 更改 AOT 调用中的逻辑,使其分支到不同的 AOT 宏。

我必须找到解决方案,所以我会回答我自己的问题。来自@Peyter 的提示帮助我找到了方法。

首先,无限循环对我来说绝对没问题,我觉得它比Application.OnTime更稳定。还有Chip Pearson的方法,但是没找到用它代替无限循环的原因(结果好像一样)。

其次,我也离开了Aplication.OnKey。我使用了 this site 的解决方案。这是一些 Windows 功能,效果很好。但是,有一个问题。从现在开始,按下的键将意味着 Excel 额外的 activity 和正常的 activity。例如,当我按下向下箭头时,我选择了我所在单元格下方的单元格。我想关闭箭头和字母的常规行为,但我没有找到任何漂亮的方法来做到这一点。所以我用了一个丑陋的;)我所有的功能如下所示。

主要模块:

Option Explicit

Sub RunGame()

  Do While True
    '
    '
    'Some game's stuff here
    '
    '

    Call WasteTime(1)
    IsKeyPressed
  Loop

End Sub

模块实用程序:

Option Explicit

Declare Function GetAsyncKeyState Lib "User32.dll" (ByVal vKey As Long) As Long

Private Const VK_DOWN = &H28 'DOWN ARROW key

Sub WasteTime(TimeToWaste As Single)

  Dim i As Long
  
  For i = 1 To TimeToWaste * 1000
    VBA.DoEvents
  Next
End Sub

Sub IsKeyPressed()

  If GetAsyncKeyState(VK_DOWN) <> 0 Then
    OnDownKey
  End If
End Sub

Sub OnDownKey()
  MsgBox "You pressed down key"
End Sub

有效。我仍然不知道将 VBA.DoEvents 放在哪里,但在循环中它运行良好。 Declare Function GetAsyncKeyState Lib "User32.dll" (ByVal vKey As Long) As Long 行向 Windows 函数打招呼,现在可以为我们工作了。 Private Const VK_DOWN = &H28 'DOWN ARROW key 是向下箭头键的定义,现在将被称为 VK_DOWN。 IsKeyPressed() 检查用户是否按下了某个键。 If GetAsyncKeyState(VK_DOWN) <> 0 检查是否 VK_DOWN 被按下。令我惊讶的是,这个 Windows 函数与 VBA 一起工作得很好,没有滞后!

现在,我做了什么来阻止按箭头或字母的常规行为?我选择了一个我想留在其中的单元格——在我的例子中它是 AG1。然后我使用了 worksheet 事件(在 VBA 编辑器中双击左侧的 sheet,您将打开它)。我在那里使用了两个功能:

Option Explicit

Private Sub Worksheet_SelectionChange(ByVal Target As Range)
  [AG1].Select
End Sub

Private Sub Worksheet_Change(ByVal Target As Range)
  If Target.Address = "$AG" Then
    Application.EnableEvents = False
    [AG1].Clear
    [AG1].Select
    Application.EnableEvents = True
    RunGame
  End If
End Sub

第一个让AG1逃不掉,因为每次你换select离子,VBAselects AG1给你

第二个在您尝试更改单元格的值时触发。子例程将清除单元格,select 它然后启动 RunGame()。为什么需要最后一件事?因为当用户开始输入内容时,无限循环会暂停,而当用户按下 Enter 时,无限循环不会继续。我真的不知道它是暂停了还是结束了。但希望我不用字母键也能凑合,这样我就不用担心了。