更改 window 焦点时 AutoHotKey WinActive returns 错误值

AutoHotKey WinActive returns wrong value when changing window focus

在我的 AutoHotKey 脚本中,我使用 #IfWinActive 来检测 Roblox window 是否处于焦点,然后每当左键单击鼠标时按下数字 1 按钮,例如这个:

#IfWinActive, Roblox
    LButton::
        MouseClick, Left
        SendInput, {1}
    return
#IfWinActive

它工作得很好,除了当我从 Roblox window 点击回到另一个 window 时。它仍然在第一次点击时触发此代码,导致它在记事本中输入数字 1(或我将焦点切换到的任何 window)。

我发现当我点击记事本时,焦点仍然在 Roblox window 上,这就是代码仍然触发的原因。所以我尝试将代码更改为:

#IfWinActive, Roblox
    LButton::
        Sleep, 100
        if WinActive("Roblox")
        {
            MouseClick, Left
            SendInput, {1}
        }
    return
#IfWinActive

假设在 Sleep 完成时焦点会转移到记事本 window 并且 If WinActive("Roblox") 会 return 错误,但它仍然 returns true 并在记事本中输入 1

我也尝试使用 StartTimer 和标签,认为 Sleep 可能不是异步的,但也有同样的问题。

有人知道如何解决这个问题吗?提前致谢!

这种情况下的主要问题是按下 LButton 后立即触发热键并且 Roblox window 仍处于活动状态。

我看到的唯一解决方案是使用 tilde prefix (~) 在释放 LButton 时触发热键,以防止 AHK 阻止 key-down/up 事件:

#IfWinActive, Roblox

    ~LButton Up:: SendInput, 1

#IfWinActive

我们可以通过多种方式实现这一目标。 TL;DR 解决方案,检查此 post.
的黄色部分 首先,我将解决您代码中的问题

  1. MouseClick over Click 的用法。技术上没有错,但据说 Click 在某些情况下更可靠且更易于使用。看起来也更干净了。
  2. 不需要将 1 包裹在 {} 中,这里对您没有任何帮助。在某些情况下,这样做甚至可能会产生不需要的行为。在发送命令中,{} 用于转义具有特殊含义的键,或定义不能直接输入的键。有关此内容的更多信息,请参见 documentation.
  3. 你的匹配对象 不好 WinTitle。同样,技术上没有错,但现在你匹配任何以单词 Roblox 开头的 window。不应该太用力一不小心匹配错了window.
    一个快速且非常有效的解决方案是匹配您的 Roblox window.
    的进程名称 所以 #IfWinActive, ahk_exe Roblox.exe 或在 if 语句中 if (WinActive("ahk_exe Roblox.exe")) (假设这是进程的名称,我不知道)
    对于绝对简单的方法可以与 Roblox window 的 hwnd 相匹配。但是,这可能有点矫枉过正,您也不能 真的 将它与 #IfWinActive 一起使用。不过,我将在下面编写的示例将使用它。

但是,问题 1 和 2 可以通过这种重新映射键的巧妙方法完全避免(重新映射几乎就是您在这里所做的)。
~LButton::1
好的,那为什么有效?
key::key 只是简单地进行基本重新映射的语法,并且使用 ~ 我们指定热键在触发时不被消耗。


很酷,但现在进入您遇到的实际问题。
那么睡觉的东西出了什么问题呢?既然您正在使用热键,那么您实际上所做的就是按下热键,等待 100 毫秒,然后检查 Roblox 是否处于活动状态。嗯,是的,它仍将处于活动状态,因为没有采取任何措施将焦点从它移开。
如果您不使用左键单击操作,它会起作用,但是这绝对不是一个好主意。你不想睡在热键语句中。 AHK 没有真正的多线程,除非你为你的热键指定了一个更高的 #MaxThreadsPerHotkey,否则所有后续的热键按下都会在这 100 毫秒内被完全忽略。
所以是的,通过为该热键指定更多的线程可以 运行 ,它会使这个解决方案起作用,但它仍然是不好的做法。我们可以想出更好的办法。

使用定时器可以避免在热键语句中休眠。听起来你已经尝试过定时器了,但我不能确定它是否正确,因为没有提供代码所以我会检查一下:

#IfWinActive, ahk_exe Roblox.exe
~LButton::SetTimer, OurTimersCallbackLabel, -100 ;-100 specifies that it runs ONCE after 100ms has passed
#IfWinActive

OurTimersCallbackLabel:
    if (WinActive("ahk_exe Roblox.exe"))
        SendInput, 1
return

And now onto the real solution, to which @user3419297 seems to have beat me to, just as I'm writing this line of text.

使用 LButton 按下的向上事件作为热键。

#IfWinActive, ahk_exe Roblox.exe
~LButton Up::SendInput, 1
#IfWinActive

这样按下事件已经切换了 window 的焦点,我们的热键甚至不会触发。
请注意,不幸的是,在这里我们不能使用我上面描述的 key::key 重映射方式。


奖金:

如果我们的按键的向上事件不受欢迎,或者活动 window 的 window 切换被延迟,可以使用这里的东西。

RobloxHwnd := WinExist("ahk_exe Roblox.exe")

#If, RobloxUnderMouse()
~LButton::1
#If

RobloxUnderMouse()
{
    global RobloxHwnd ;specify that we're using the variable defined outside of this function scope
    ;could've also ran the code to get Roblox's hwnd here every time, but that would be wasteful

    MouseGetPos, , , HwndUnderMouse ;we don't need the first two parameters
    return RobloxHwnd == HwndUnderMouse ;are they the same hwnd? (return true or false)
}

这里我们首先将 Roblox 的 hwnd 存储到变量 RobloxHwnd
请注意,在我们 运行 这个脚本之前,Roblox 需要 运行ning,如果你重新启动 robox,脚本也需要重新启动。
所以添加一些方法来动态更新这个变量的值会很好,也许在一些热键下。

然后通过使用 #If 我们正在评估一个表达式(在我们的例子中,运行 一个函数并评估它的 return 值)每次我们将要尝试触发热键。如果表达式的计算结果为真,我们将触发热键。
#If 实际上 不推荐 的用法,最好尽可能避免使用。但是,在这么小的脚本中你不会遇到任何问题,所以在这里使用 #If 会非常方便。
如果你有一个更大的脚本,其中经常有很多代码 运行ning,你很可能 运行 会遇到问题。