# 热键(鼠标, 操纵杆和键盘快捷键)

## 目录

- 介绍和简单示例
- 热键修饰符
- 上下文相关的热键
- 自定义组合键
- 其他特性
- 鼠标滚轮热键
- 热键技巧和备注
- Alt-Tab 热键
- 命名的函数热键

## 介绍和简单示例

热键有时也称为快捷键, 因为它们能简便地触发动作(例如运行程序或键盘宏). 在下面的例子中, `Win`+`N` 被设置为启动记事本. 磅的符号 [#] 表示 `Win` 键, 它被称为 修饰符:

    #n::
    {
        Run "notepad"
    }

在上文中, 大括号的作用是为热键定义一个函数主体. 左大括号也可以与双冒号指定在同一行, 以支持 OTB(One True Brace) 样式. 然而, 如果热键只需要执行一行, 那么这一行可以列在双冒号的右边. 换句话说, 大括号是隐式的:

    #n::Run "notepad"

当热键被触发时, 热键的名称作为其第一个参数传递, 参数名为 ThisHotkey(不包括尾部的冒号). 例如:

    #n::MsgBox ThisHotkey  ; 报告 #n

除了少数例外, 这与内置变量 A_ThisHotkey 相似. 该参数的名称(首个参数的名称, 例如 ThisHotkey) 可以通过使用命名函数来改变.

要在热键中使用多个修饰键, 那么需要把它们连续地列出来(顺序无关紧要). 下面的例子中使用 `^!s` 来表示 `Ctrl`+`Alt`+`S`:

    ^!s::
    {
        Send "Sincerely,{enter}John Smith"  ; 此行发送键击到活动(最前面的) 窗口.
    }

## 热键修饰符

在定义热键时您可以使用下列修饰符:

符号 | 说明
--- | ---
# | `Win` (Windows 徽标键). 包含 `Win` 键的热键(例如 #a) 会等待 `Win` 键被释放后才发送任何包含 `L` 键击的文本. 这样避免了这种热键中的 Send 锁定 PC. 这种行为适用于除 SendPlay(这里不需要), 盲从模式和文本模式以外的所有发送模式. 注意: 按下包含 `Win` 键的热键可能会导致额外的模拟击键(默认为 `Ctrl` 键). 请参阅 A_MenuMaskKey.
! | `Alt` 注意: 按下包含 `Alt` 键的热键可能会导致额外的模拟击键(默认为 `Ctrl` 键). 请参阅 A_MenuMaskKey.
^ | `Ctrl`
+ | `Shift`
& | 和符号可以用来组合任意两个按键或鼠标按钮, 让它们成为自定义热键. 有关详情, 请参阅下文.
< | 成对按键左边的那个按键. 例如 <!a 相当于 !a, 但只有使用左边的 `Alt` 键才可以触发.
> | 成对按键右边的那个按键.
<^>! | `AltGr`(译者注: 美式标准键盘上没有该键). 如果您键盘布局中含 `AltGr` 键而非右 `Alt` 键, 那么这一系列符号一般可用于表示 `AltGr`. 例如:
    <^>!m::MsgBox "你按下的是 AltGr+m."
    <^<!m::MsgBox "你按下的是 LeftControl+LeftAlt+m."
或者, 让 `AltGr`(alternate graph 或 alternate graphic) 本身成为热键, 请使用下面的热键(不像上面出现的任何热键):
    LControl & RAlt::MsgBox "你按下的是 AltGr 本身."
* | 通配符: 即使额外的修饰键被按住也能触发热键. 这常与重映射按键或按钮组合使用. 例如:
    *#c::Run "calc.exe"  ; Win+C, Shift+Win+C, Ctrl+Win+C, 等都会触发此热键.
    *ScrollLock::Run "notepad"  ; 即使在按住其他修饰键时按下 ScrollLock 也会触发此热键.
通配符热键总是使用键盘钩子, 就像任何被通配符热键覆盖的热键一样. 例如, `*a::` 的存在会导致 `^a::` 总是使用钩子.
~ | 触发热键时, 热键中按键原有的功能不会被屏蔽(对操作系统隐藏). 在下面的两个例子中, 用户的鼠标按钮点击会被发送到活动窗口:
    ~RButton::MsgBox "You clicked the right mouse button."
    ~RButton & C::MsgBox "You pressed C while holding down the right mouse button."
与其他前缀符号不同, 可以在热键的某些变体中使用颚化符(波浪号) 前缀而其他的不使用. 然而, 如果应用于颚化符(波浪号) 前缀的任何自定义组合键没有被关闭或暂停, 它会影响那个前缀键在 所有 组合中的行为. 用于替代 alt-tab 的特殊按键总是忽略颚化符(波浪号) 前缀. 如果把颚化符(前缀键) 用在自定义修饰键中且此修饰键自身也作为热键, 则在按下此修饰键时会触发热键而不会被延迟到按键释放的时候. 例如, 上文中 ~RButton 热键在按下此按键时就会触发. 如果颚化符前缀只应用于自定义组合键, 而不应用于非组合热键, 按键的原生功能将持续被屏蔽. 例如, 下面的脚本, 按住 `Menu` 键将会一直显示一个工具提示而不会触发上下文菜单:
    AppsKey::ToolTip "Press < or > to cycle through windows."
    AppsKey Up::ToolTip
    ~AppsKey & <::Send "!+{Esc}"
    ~AppsKey & >::Send "!{Esc}"
如果键盘热键的一个变体具有颚化修饰符, 那么热键总是使用键盘钩子.
$ | 通常只在脚本使用 Send 函数发送包含了热键自身的按键时才需要使用此符号, 此时可以避免触发它自己. $ 前缀强制使用键盘钩子来实现此热键, 作为副作用这样阻止了 Send 函数触发它自己. $ 前缀相当于在此热键定义之前的某个位置指定了 `#UseHook`. $ 前缀对于鼠标热键不起作用, 因为它们始终使用鼠标钩子. 它对于已经启用了键盘钩子的热键同样无效, 包括任何带有颚化符(~) 或通配符(*) 修饰的热键, key-up 热键以及自定义组合. 要确定特定热键是否使用键盘钩子, 请使用 ListHotkeys 查看. #InputLevel 和 SendLevel 对 Send 函数是否触发热键和热字串提供了额外的控制功能.
UP | 单词 UP 可以跟在热键名后面使得在释放按键时触发热键, 而不是按下时. 下面的例子把左 `Win` 重映射为左 `Ctrl`:
    *LWin::Send "{LControl down}"
    *LWin Up::Send "{LControl up}"
"Up" 也可以用于普通热键中, 例如: `^!r Up::MsgBox "You pressed and released Ctrl+Alt+R"`. 它还可以用在组合键(例如 `F1 & e Up::`) 限制: 1) "Up" 不能和游戏杆按钮一起使用; 2) 在没有普通/按下热键副本的 "Up" 热键会完全接管那个按键以防被卡住. 避免这种情况的一种方法是添加颚化符(波浪号) 前缀(例如 `~LControl up::`) "Up" 热键和其 key-down 副本(如果有的话) 总是使用键盘钩子. 相关提示, 类似于上面的一种技术是让热键成为前缀键. 好处是尽管热键会在按键松开时激发, 但仅在您之前按住此前缀键时没有按其他任何键的情况下才会如此. 例如:
    LControl & F1::return  ; 至少在 "&" 前面使用左 control 键一次, 来让它成为前缀键.
    LControl::MsgBox "You released LControl without having used it to modify any other key."

注意: 请参阅按键列表来了解键盘按键和鼠标/操纵杆按钮的完整列表.

多个热键可以垂直地叠放来让它们执行相同的动作. 例如:

    ^Numpad0::
    ^Numpad1::
    {
        MsgBox "Pressing either Control+Numpad0 or Control+Numpad1 will display this message."
    }

通过不为按键或按键组合的热键指定任何操作可以在整个系统中完全禁用它们. 下面的例子中禁用了右 `Win` 键:

    RWin::return

## 上下文相关的热键

#HotIf 指令可以用来让热键根据不同的条件执行不同的动作(或什么都不做). 例如:

    #HotIf WinActive("ahk_class Notepad")
    ^a::MsgBox "你在记事本中按下了 Ctrl-A. 而在其他窗口中按下 Ctrl-A 将原样发送."
    #c::MsgBox "你在记事本中按下了 Win-C."

    #HotIf
    #c::MsgBox "你在非记事本程序中按下了 Win-C."

    #HotIf MouseIsOver("ahk_class Shell_TrayWnd") ; 有关 MouseIsOver, 请参阅 #HotIf 的示例 1.
    WheelUp::Send "{Volume_Up}"     ; 在任务栏上滚动滚轮: 增加/减小音量.
    WheelDown::Send "{Volume_Down}" ;

## 自定义组合键

通过在两个按键(不包括操纵杆按钮) 间使用 " & " 可以定义自定义的组合键. 在下面的例子中, 您在按住 Numpad0 接着按下第二个按键时会触发此热键:

    Numpad0 & Numpad1::MsgBox "You pressed Numpad1 while holding down Numpad0."
    Numpad0 & Numpad2::Run "Notepad"

前缀键会失去它原有的功能: 在上面的例子中, Numpad0 为 前缀键; 但是这也让 Numpad0 在被按下时失去了它原有/自带的功能. 为了避免此问题, 脚本中可以配置 Numpad0 执行新的动作, 例如下列热键的其中一个:

    Numpad0::WinMaximize "A"   ; 最大化活动/前端窗口.
    Numpad0::Send "{Numpad0}"  ; 让 Numpad0 释放 时产生 Numpad0 键击. 请参阅下面的注释.

释放时触发: 使用上面的其中一个自定义组合热键可以在 释放 Numpad0 时执行指定的动作, 但不包括按住 Numpad0 时您又按下了其他按键的情况. 可以在其中某个热键前使用颚化符(波浪号) 前缀来避免这种行为.

修饰: 与普通热键不同的是, 自定义组合在默认情况下表现得就像拥有一个通配符(*) 修饰一样. 例如, `1 & 2::` 当按下 `1` 和 `2` 时, 无论 `Ctrl` 或 `Alt` 是否被按住都会触发, 然而 `^1::` 仅在 `Ctrl`+`1` 组合键时触发, 而 `Ctrl`+`Alt`+`1` 不会触发.

对于普通的修饰键, 一般的热键会比 "自定义" 组合工作得更好. 例如, `<+s::` 就是比 `LShift & s::` 更好的用法.

不支持组合三个或更多的键. 一般来说组合键支持你键盘上物理存在的所有按键, 都可以用 #HotIf 和 GetKeyState 探测状态, 但是也可能出现不一致的情况. 例如:

    ; 以任何顺序按下 AppsKey 和 Alt, 然后按下反斜杠键(/).
    #HotIf GetKeyState("AppsKey", "P")
    Alt & /::MsgBox "Hotkey activated."

    ; 如果对调 AppsKey 和 Alt, 则必须先按下 Alt(一次按一个):
    #HotIf GetKeyState("Alt", "P")
    AppsKey & /::MsgBox "Hotkey activated."

    ; 同时按下这三个键: [ & ] & \::
    #HotIf GetKeyState("[") && GetKeyState("]")
    \::MsgBox

键盘钩子: 涉及键盘键的自定义的组合键总是使用键盘钩子, 使用前缀键作为后缀的任何热键也是如此. 例如, `a & b::` 导致 `^a::` 总是使用键盘钩子.

## 其他特性

NumLock, CapsLock 和 ScrollLock: 这些键可以被强制设置为 "AlwaysOn" 或 "AlwaysOff". 例如: `SetNumLockState "AlwaysOn"`.

覆盖资源管理器热键: 系统的内置热键(如 `Win`+`E`(#e) 和 `Win`+`R`(#r)) 可以通过将它们指定给脚本中的一个操作单独替换. 有关详情, 请参阅覆盖页面.

替换 Alt-Tab: 热键可以提供 Alt-Tab 的可选方法. 例如, 下面的两个热键可以让您用右手进行 alt-tab:

    RControl & RShift::AltTab  ; 按住右 control 后接着反复下右 shift 来向前移动.
    RControl & Enter::ShiftAltTab  ; 甚至不需要释放右 control, 直接按 Enter 来反向移动.

有关详情, 请参阅 Alt-Tab.

## 鼠标滚轮热键

通过键名 WheelDown 和 WheelUp 可以支持转动滚轮来激发热键. 这里是一些鼠标滚轮热键的例子:

    MButton & WheelDown::MsgBox "You turned the mouse wheel down while holding down the middle button."
    ^!WheelUp::MsgBox "You rotated the wheel up while holding down Control+Alt."

如果鼠标支持它, 可以通过键名 WheelLeft 和 WheelRight 来检测水平滚动. 有些鼠标只有一个滚轮, 可以向上或向下滚动或向左或向右倾斜. 通常, 在这些情况下, 将滚轮固定在一侧时会重复发送 WheelLeft 或 WheelRight 信号, 以模拟连续滚动. 这通常会导致热键重复执行.

内置变量 A_EventInfo 包含了滚轮转动的格数, 通常为 120. 但是, A_EventInfo 在以下情况下可以大于或小于 120:

- 如果鼠标硬件报告小于一格的距离, 那么 A_EventInfo 可能小于 120;
- 如果快速滚动滚轮(取决于鼠标的类型), 那么 A_EventInfo 可能大于 120. 像后面这样的热键可以帮助您分析鼠标: `~WheelDown::ToolTip A_EventInfo`

鼠标滚轮的一些最有用的热键包括滚动窗口文本的可选模式. 例如, 使用下面的一对热键可以在您按住左 `Ctrl` 键并转动滚轮时进行水平滚动而不是垂直滚动:

    ~LControl & WheelUp::  ; 向左滚动.
    {
        Loop 2  ; <-- 增加这个值来加快滚动速度.
            SendMessage 0x0114, 0, 0, ControlGetFocus("A")  ; 0x0114 是 WM_HSCROLL, 它后面的 0 是 SB_LINELEFT.
    }

    ~LControl & WheelDown::  ; 向右滚动.
    {
        Loop 2  ; <-- 增加这个值来加快滚动速度.
            SendMessage 0x0114, 1, 0, ControlGetFocus("A")  ; 0x0114 是 WM_HSCROLL, 它后面的 1 是 SB_LINERIGHT.
    }

最后, 由于鼠标滚轮热键只产生按下事件(从没有弹起事件), 所以它们无法用作弹起热键.

## 热键技巧和备注

根据 `NumLock` 的状态, 每个小键盘上的按键可以运行两个不同的热键子程序. 或者, 小键盘上的按键也可以运行相同的热键子程序, 而不管 NumLock 的状态如何. 例如:

    NumpadEnd::
    Numpad1::
    {
        MsgBox "This hotkey is launched regardless of whether NumLock is on."
    }

如果波浪号(~) 与前缀键一起使用, 即使只是一次, 它会更改所有组合键的前缀键的行为. 例如, 在下面的两个热键中, 活动窗口都会接收到右键点击, 尽管只是其中一个热键使用了波浪号:

    ~RButton & LButton::MsgBox "You pressed the left mouse button while holding down the right."
    RButton & WheelUp::MsgBox "You turned the mouse wheel up while holding down the right button."

Suspend 函数可以临时禁用所有的热键, 但不包括您要排除的那些. 要获得更高的选择性, 请使用 #HotIf.

通过使用 Hotkey 函数, 可以在脚本运行时动态创建热键. Hotkey 函数还可以单独修改, 禁用或启用脚本的现有热键.

操纵杆热键当前不支持修饰符前缀(如 ^(Ctrl) 和 #(Win)). 但是, 您可以使用 GetKeyState 来模拟此效果, 如下例所示:

    Joy2::
    {
        if not GetKeyState("Control")  ; 左边和右边的 Control 键都没有按下.
            return  ; 即什么都不做.
        MsgBox "You pressed the first joystick's second button while holding down the Control key."
    }

热键在继续之前可能需要等待自己的修饰键被释放. 请参考下面的例子:

    ^!s::Send "{Delete}"

按下 `Ctrl`+`Alt`+`S` 会让系统以为您按下了 `Ctrl`+`Alt`+`Del`(由于系统对此热键的侵略性检测). 要解决此问题, 请使用 KeyWait 来等待按键释放; 例如:

    ^!s::
    {
        KeyWait "Control"
        KeyWait "Alt"
        Send "{Delete}"
    }

如果形如 `#z::` 这样的热键产生了类似 "Invalid Hotkey(无效热键)" 的错误, 那么您的系统键盘布局/语言可能不包含指定的字符(本例中为 "Z"). 尝试使用您键盘布局中您知道存在的其他字符.

只有在函数被命名的情况下, 脚本才能明确地调用热键的函数. 请参阅命名的函数热键.

热键的一个常见用途是启动和停止重复的动作, 例如一系列的键击或鼠标点击. 关于这方面的例子, 请参阅这个 FAQ 主题.

最后, 每个脚本都是准多线程的, 这样可以在之前的热键子程序还在运行时启动新的热键. 例如, 即使在当前热键显示消息框时也能启动新的热键.

## Alt-Tab 热键

Alt-Tab 热键简化了新组合键到系统 Alt-Tab 热键的映射, 它被用来调用切换任务(激活窗口) 的菜单.

每个 Alt-Tab 热键必须是一个键或两个键的组合, 通常通过和符号(&) 实现. 在下面的例子中, 按住右 `Alt` 然后按下 `J` 或 `K` 来浏览 alt-tab 菜单:

    RAlt & j::AltTab
    RAlt & k::ShiftAltTab

AltTab 和 ShiftAltTab 是两个特殊的命令, 只有在与热键在同一行上时才能被识别. 这里是完整的列表:

AltTab: 如果 alt-tab 菜单可见, 那么在菜单中前移. 否则, 显示菜单(仅当热键为两个按键的组合键时; 否则, 它什么都不做).

ShiftAltTab: 与上面相同, 只是在菜单中向后移动.

AltTabAndMenu: 如果 alt-tab 菜单可见, 则向前移动. 否则, 显示菜单.

AltTabMenu: 显示或隐藏 alt-tab 菜单.

AltTabMenuDismiss: 关闭 Alt-tab 菜单.

为了说明上述内容, 可以用鼠标滚轮完全代替 Alt-tab. 使用下面的热键, 点击鼠标中键显示菜单, 转动滚轮在菜单中导航:

    MButton::AltTabMenu
    WheelDown::AltTab
    WheelUp::ShiftAltTab

要取消 Alt-Tab 菜单而不激活选择的窗口, 按下或发送 `Esc`. 在下面的例子中, 按住左 `Ctrl` 并按下 `CapsLock` 来显示菜单并在其中向前移动. 然后释放左 `Ctrl` 激活所选窗口, 或按下鼠标滚轮取消. 在运行这个例子之前, 定义 AltTabWindow 窗口组, 如下所示.

    LCtrl & CapsLock::AltTab
    #HotIf WinExist("ahk_group AltTabWindow")  ; 表示 alt-tab 菜单出现在屏幕上.
    *MButton::Send "{Blind}{Escape}"  ; * 前缀允许它被触发, 无论 Alt 是否被按住.
    #HotIf

如果脚本发送 `{Alt Down}`(例如调用 Alt-Tab 菜单), 那么也可能需要发送 `{Alt Up}`, 如下面的例子所示.

### 一般说明

当前, 所有特殊的 Alt-tab 动作必须像上面的例子那样直接指定到热键上(即它们无法像函数那样使用). 它们不受 #HotIf 的影响.

无论是否使用 `up` 关键字, alt-tab 动作都可以在 key-down 和/或 key-up 生效, 并且不能与同一键上的其他动作相结合. 例如, 同时使用 `F1::AltTabMenu` 和 `F1 up::OtherAction()` 是不支持的.

自定义的 alt-tab 动作还可以通过热键创建. 由于不同版本的操作系统对 alt-tab 菜单的标识不同, 使用如下所示的窗口组可能会有帮助. 在上面和下面例子中使用 `ahk_group AltTabWindow`, 这个窗口组是在脚本启动时定义的. 另外, `ahk_group AltTabWindow` 也可以用适合你的系统的 `ahk_class` 来代替.

    GroupAdd "AltTabWindow", "ahk_class MultitaskingViewFrame"  ; Windows 10
    GroupAdd "AltTabWindow", "ahk_class TaskSwitcherWnd"  ; Windows Vista, 7, 8.1
    GroupAdd "AltTabWindow", "ahk_class #32771"  ; 更早的系统, 或启用了经典的 alt-tab

在下面的例子中, 按下 `F1` 来显示菜单并向前移动. 然后可以按下 `F2` 来激活选择的窗口, 或按下 `Esc` 取消:

    *F1::Send "{Alt down}{tab}" ; 在这种情况下需要星号.
    !F2::Send "{Alt up}"  ; 释放 Alt 键, 激活选择的窗口.
    #HotIf WinExist("ahk_group AltTabWindow")
    ~*Esc::Send "{Alt up}"  ; 取消菜单时, 自动释放 Alt 键.
    ;*Esc::Send "{Esc}{Alt up}"  ; 如果不使用(~), 则需要发送 Escape 键.
    #HotIf

## 命名的函数热键

如果需要在不触发热键本身的情况下调用热键的函数, 则可以为一个或多个热键分配一个命名的函数, 只需在热键的双冒号之后简单地定义该函数即可, 例如:

    ; Ctrl+Shift+O 在资源管理器中打开包含的文件夹.
    ; Ctrl+Shift+E 打开文件夹并选中当前编辑的文件.
    ; 支持 SciTE 和 Notepad++.
    ^+o::
    ^+e::
        editor_open_folder()
        {
            path := WinGetTitle("A")
            if RegExMatch(path, "\*?\K(.*)\\[^\\]+(?= [-*] )", &path)
                if (FileExist(path[0]) && ThisHotkey = "^+e")
                    Run Format('explorer.exe /select,"{1}"', path[0])
                else
                    Run Format('explorer.exe "{1}"', path[1])
        }

热字串也可以这样定义. 可以将多个热键或热字串堆叠在一起以调用同一个函数.

在热键/热字符串和函数之间只能有空格或注释.

命名函数还支持自我描述热键, 就像上面的代码其中函数名称描述了热键那样.

Hotkey 函数也可以关联函数或函数对象到一个热键.

dftg