# 如何发送击键

    Send "Hello, world{!}{Left}^+{Left}"

发送击键(或简称键) 是自动化程序最常见的方法, 因为它是最常用的方法. 更直接的方法往往只适用于特定类型的应用程序.

学习如何发送 key 大致有两个部分:

1. 如何编写代码, 以便程序知道要发送哪些 key.
2. 如何使用可用的模式和选项来获得正确的最终结果.

重要的是要理解, 发送一个键并不能完美地复制物理按下键的行为, 即使您将其降低到人类的速度. 但在深入研究之前, 我们先了解一些基础知识.

## 尝试这些例子

如果您运行 `SendText "Hi!"` 这样的示例, 文本将立即发送到活动(聚焦)窗口, 这可能没有多大用处, 这取决于您如何运行示例. 通常更好的方法是定义一个热键, 运行示例来加载它, 然后在您想要测试它的效果时按下热键. 下面的一些例子将使用有编号的热键 `^1::` (`Ctrl` 和一个数字, 所以如果没有重复的例子, 你可以一次尝试多个例子), 但你可以根据自己的需要进行更改.

要了解如何自定义热键或创建自己的热键, 请参阅如何编写热键.

如果您不确定如何尝试这些示例, 请参阅如何运行示例代码.

## 如何编写代码

当发送按键时, 您通常希望发送按键或键组合以获得其效果(如 `Ctrl`+`C` 复制到剪贴板), 或键入一些文本. 键入文本更简单, 因此我们将从那里开始: 只需调用 SendText 函数, 将您想要发送的确切文本传递给它.

    ^1::SendText "To Whom It May Concern"

从技术上讲, SendText 实际上发送的是 Unicode 字符包, 而不是击键, 这使得它对于通常使用组合键(如 `Shift`+`2` 或 `AltGr`+`a`) 键入的字符更加可靠.

### 带引号的字符串的规则

SendText 逐字发送文本, 但请记住语言规则. 例如, 原义文本必须括在引号中(双引号 `"` 或单引号 `'`), 并且引号本身不会被 SendText 函数 "看到". 若要发送原义引号, 可以将其括在相反类型的引号中. 例如:

    ^2::SendText 'Quote marks are also known as "quotes".'

或者, 使用转义序列. 在带引号的字符串中, ``"` 转换为原义的 `"`, 而 ``'` 转换为原义的 `'`. 例如:

    ^3::{
        SendText "Double quote (`")"
        SendText 'Single quote (`')'
    }

您还可以交替使用两种引号:

    ^4::SendText 'Double (") and' . " single (') quote"

这两个字符串在传递给 SendText 函数之前连接在一起(连接). 点(`.`) 可以省略, 但这会使一个结束和另一个开始的位置更难看到.

正如您在上面看到的, 转义字符 ```(被称为 backquote, backtick, grave accent 和其他名称) 具有特殊含义, 因此如果您想发送该原义字符(或发送相应的按键), 则需要将其加倍, 如发送 `Send "``"`. 其他常见的转义序列包括 ``n` 表示换行符(Enter) 和 ``t` 表示制表符. 有关详情, 请参阅转义序列.

### 发送键和组合键

SendText 最适合逐字发送文本, 但它不能发送不生成文本的键, 如 `Left` 或 `Home`. Send, SendInput, SendPlay, SendEvent 和 ControlSend 可以发送文本和组合键, 或者不产生文本的键. 为了完成所有这些操作, 它们为以下符号添加了特殊含义: `^!+#{}`

前四个符号对应于标准修键, Ctrl(`^`), Alt(`!`), Shift(`+`) 和 Win(`#`). 它们可以组合使用, 但仅影响下一个键.

要按名称发送按键, 或发送上述任一符号的原义文本, 请将其括在大括号中. 例如:

- `^+{Left}` 生成 `Ctrl`+`Shift`+`Left`
- `^{+}{Left}` 生成 `Ctrl`+`+` 后跟字母 `Left`
- `^+Left` 生成 `Ctrl`+`Shift`+`L` 后跟原义字母 `eft`

当按下 `Ctrl`+`Shift`+`"` 时, 下面的示例发送两个引号, 然后将插入点向左移动, 准备在引号内键入内容:

    ^+"::Send '""{Left}'

对于 `^!+#{}` 以外的任何单个字符, Send 将其转换为相应的组合键, 并按下并释放该组合键. 例如, `Send "aB"` 按下并释放 `A`, 然后按下并释放 `Shift`+`B`. 类似地, 在默认情况下, 用大括号括起来的任何键名都会被按下并释放. 例如, `Send "{Ctrl}a"` 会按下并释放 `Ctrl`, 然后按下并释放 `A`; 可能这并不是你想要的.

若要只按下(按住) 或释放某个键, 请将键名用大括号括起来, 后面跟着空格, 然后是单词 "down" 或 "up". 下面的例子使 `Ctrl`+`CapsLock` 切换 `Shift`:

    *^CapsLock::{
        if GetKeyState("Shift")
            Send "{Shift up}"
        else
            Send "{Shift down}"
    }

### 热键 vs. Send

警告: 热键和发送有一些您应该注意的差异.

尽管热键也使用符号 `^!+#` 和相同的按键名称, 但有几个重要的区别:

- Send 不支持其他热键修饰符号. 例如, `>^a::` 对应于 `RCtrl`+`A`, 但要发送该组合, 您需要完整拼写出键名, 如 `Send "{RCtrl down}a{RCtrl up}"`.
- 按键名称从不括在热键中的大括号中, 但在 Send 中必须始终括在大括号中(如果长度超过一个字符).
- Send 区分大小写. 例如, `Send "^A"` 发送 `Ctrl` 和 大写 "a" 的组合, 因此 `Ctrl`+`Shift`+`A`. 相比之下, `^a::` 和 `^A::` 是等价的.

这是因为 Send 有多种用途, 而热键针对组合键进行了优化.

在相关说明中, 热字符串专门用于检测文本输入, 因此符号 `^!+#{}` 在热字符串触发文本中没有特殊含义. 但是, 热字符串的 replacement 文本使用与 Send 相同的语法(使用 T 选项 时除外). 每当在以下热字符串处于活动状态的情况下键入 "{" 时, 它都会发送 "}", 然后发送 `Left`, 以将插入点 移回括号之间:

    :*?B0:{::{}}{Left}

### Blind 模式

通常情况下, Send 假定您物理按住的任何修饰键都不应该与您要求它发送的键组合在一起. 例如, 如果你按住 `Ctrl` 并调用 `Send "Hi"`, Send 会在发送 "Hi" 之前自动释放 `Ctrl`, 然后再按下它.

有时你想要的是将一些键与之前按下或发送的其他修饰符一起发送. 为此, 您可以使用 `{Blind}` 前缀. 在运行以下示例时, 尝试聚焦一个非空文本编辑器或输入字段, 并按住 `Ctrl` 或 `Ctrl`+`Shift` 的同时按住 `1` 或 `2`:

    *^1::Send "{Blind}{Home}"
    *^2::Send "{Blind}{End}"

For more about `{Blind}`, see Blind mode.

### 其他

Send 支持其他一些特殊的结构, 如:

- `{U+00B5}` 按其序号值(字符代码)发送一个 Unicode 字符.
- `{ASC 0181}` 发送 Alt+Numpad 序列.
- `{Click Options}` 单击或移动鼠标.

有关完整列表, 请参阅按键名称.

## 模式和选项

发送一个键并不能完全复制物理按下键的行为. 操作系统提供了几种不同的发送按键的方法, 每种方法都有不同的注意事项. 有时候为了得到你想要的结果, 你不仅需要尝试不同的方法, 还需要调整时间.

主要方法有 SendInput, SendEvent 和 SendPlay. SendInput 通常是最可靠的, 因此默认情况下, Send 等同于 SendInput. SendMode 可以用来使 Send 与 SendEvent 或 SendPlay 同义. 文档详细描述了 SendInput 和 SendPlay 的其他优点和缺点, 但我建议当您使用 SendInput 时, 只尝试 SendEvent 或 SendPlay.

警告: SendPlay 不能在现代系统上工作, 除非你使用 UI 访问.

另一个值得尝试的选项是 ControlSend. 这并不使用发送击键的正式方法, 而是直接将消息发送到您指定的窗口.主要的优点是窗口通常不需要处于活动状态来接收这些消息. 但由于它绕过了系统对键盘输入的正常处理, 有时它无法工作.

### 时间和延迟

有时你可以比人类更快地发送大量的击键, 有时你不能. 通常有两种情况可能需要延迟:

- 按键应该触发目标应用中的某些更改(例如显示新控件或窗口), 并且在发生这种情况之前发送另一个按键会产生错误的效果.
- 该应用程序无法跟上快速的击键流, 您需要将它们全部放慢.

对于第一种情况, 您可以简单地调用 Send, 接着 Sleep, 继续 Send, 依此类推.

对于第二种情况, 使用 SetKeyDelay. 此函数可以设置每次击键之间的延迟和击键的持续时间(即按下和松开键之间的延迟).

    ^1::{
        SetKeyDelay 75, 25  ; 75ms between keys, 25ms between down/up.
        SendEvent "You should see the keys{bs 4}text appear gradually."
    }

警告: SendInput 不支持键延迟, 默认情况下 Send 也不支持.

为了使 SetKeyDelay 有效, 你通常必须使用 `SendMode "Event"` 或调用 SendEvent, SendPlay 或 ControlSend 代替 Send 或 SendText.

## 发送大量文本

发送多行文本的一种方法是使用延续片段:

    SendText "
    (
        Leading indentation is stripped out,
        based on the first line.
        Line breaks are kept
        unless you use the "Join" option.
    )"

虽然它通常非常快, 但 SendText 仍然必须每次发送一个字符, 而 Send 通常需要发送至少两倍的消息(按下 和 松开键). 当发送大量文本时, 这会造成明显的延迟. 它也可能变得不可靠, 因为较长的延迟意味着与用户输入冲突, 键盘焦点转移或其他条件变化的风险更高.

一般来说, 将文本放在剪贴板上并 粘贴 会更快, 更可靠. 例如:

    ^1::{
        old_clip := ClipboardAll()  ; Save all clipboard content
        A_Clipboard := "
        (Join`s
            This text is placed on the clipboard,
            and will be pasted below by sending Ctrl+V.
        )"
        Send "^v"
        Sleep 500  ; Wait a bit for Ctrl+V to be processed
        A_Clipboard := old_clip  ; Restore previous clipboard content
    }

dftg