> **本文回答**：监听用 `Engine.eventManager.on(CadEvents.X, handler)`；取消监听必须保留 handler 引用 `off(...)`；一次性 `once`；批量场景用 `suspendEvents() / resumeEvents()` 提速；移除全部 `offAll(event?)`。注意 `CadEvents` 里约一半成员（`DocumentOpened/Saving/Closing…`、`EntityErasing`、`CommandCancelled` 等）当前版本没有派发点，订阅永不触发，清单见 [overview](overview.md) 第三节。

## 一、API 一览

```js
const em = Engine.eventManager;

em.on(CadEvents.EntityAdded, handler);       // 监听
em.once(CadEvents.DocumentCreated, handler); // 一次性
em.off(CadEvents.EntityAdded, handler);      // 取消
em.offAll(CadEvents.EntityAdded);            // 取消该事件全部
em.offAll();                                 // 取消所有事件全部

em.suspendEvents();    // 暂停（批量优化用）
em.resumeEvents();     // 恢复
em.isSuspended();      // 是否暂停

em.hasListeners(CadEvents.EntityAdded);
em.listenerCount(CadEvents.EntityAdded);
```

MCP 参考：`vjcad/doc/event/event-usage.md`。

| 方法 | 说明 |
|---|---|
| `on(event, handler)` | 监听事件 |
| `off(event, handler)` | 取消监听 |
| `once(event, handler)` | 一次性监听 |
| `offAll(event?)` | 移除全部监听 |
| `emit(event, args)` | 触发事件（内部使用） |
| `suspendEvents() / resumeEvents()` | 暂停 / 恢复 |
| `isSuspended()` | 是否暂停 |
| `hasListeners(event)` / `listenerCount(event)` | 监听器查询 |

## 二、最小示例

```js
import { Engine, CadEvents } from 'vjcad';

// 普通监听
Engine.eventManager.on(CadEvents.EntityAdded, (args) => {
  console.log(args.entity.type, args.entity.id);
});

// 一次性（DocumentOpened 当前版本不派发，别用它）
Engine.eventManager.once(CadEvents.DocumentCreated, (args) => {
  console.log('首个新文档:', args.document.name);
});
```

## 三、取消监听（保留 handler 引用）

```js
const handler = (args) => { /* ... */ };

Engine.eventManager.on(CadEvents.EntityAdded, handler);
// ...
Engine.eventManager.off(CadEvents.EntityAdded, handler);
```

> **关键**：`off` 必须传**同一个 handler 引用**，匿名函数 `(args)=>{}` 无法 off。

MCP 参考：`vjcad/doc/event/event-usage.md`。

## 四、批量操作时暂停事件（提速）

```js
Engine.eventManager.suspendEvents();
try {
  for (let i = 0; i < 1000; i++) {
    const line = new LineEnt([i, 0], [i, 100]);
    line.setDefaults();
    Engine.pcanvas.addEntity(line);
  }
} finally {
  Engine.eventManager.resumeEvents();
}
```

> **注意**：默认 `suspendEvents()`（`queueEvents=false`）暂停期间事件**直接被丢弃**；传 `suspendEvents(true)` 则排队暂存，`resumeEvents()` 时按序回放（`suspendEvents(queueEvents?: boolean)`；2026-09-21 运行时验证：丢弃/回放两模式均成立，`once` 精确一次、`offAll(event)` 只清指定事件）。

MCP 参考：`vjcad/doc/event/event-usage.md`。

## 五、两种访问方式（等价）

```js
// 方式一（推荐）：通过 Engine
import { Engine, CadEvents } from 'vjcad';
Engine.eventManager.on(CadEvents.EntityAdded, handler);

// 方式二：直接 CadEventManager
import { CadEventManager, CadEvents } from 'vjcad';
const em = CadEventManager.getInstance();
em.on(CadEvents.EntityAdded, handler);
```

`Engine.eventManager` 内部就是 `CadEventManager.getInstance()`。

MCP 参考：`vjcad/doc/event/event-usage.md`。

## 六、可取消事件（`-ing` 前置）

```js
// 阻止删除特定图层的实体（EntityErasing 不派发；删除只发批量版 EntitiesErasing）
Engine.eventManager.on(CadEvents.EntitiesErasing, (args) => {
  const protectedEnts = args.entities.filter(e => e.layer === '保护图层');
  if (protectedEnts.length) args.excludeEntities = protectedEnts;   // 部分排除；全拦用 args.cancel = true
});

// 阻止保存空文档（DocumentSaving 不派发，改在命令层拦截）
Engine.eventManager.on(CadEvents.CommandStarting, (args) => {
  if (['QSAVE', 'SAVEAS', 'SAVESERVER', 'SAVELOCAL'].includes(args.commandName)
      && Engine.getEntities(e => e.isAlive).length === 0) {
    args.cancel = true;
    alert('文档为空，无法保存');
  }
});

// 关闭前自定义校验（DocumentClosing 不派发；CLOSE 命令自带未保存确认框）
Engine.eventManager.on(CadEvents.CommandStarting, (args) => {
  if (args.commandName === 'CLOSE' && !myBusinessCheckPassed()) {
    args.cancel = true;
  }
});
```

> ⚠️ `Engine.eraseEntities` 在 `EntitiesErasing` 被取消/部分排除时**仍按原数组记录撤销**——重做会把受保护实体也删掉，详见 [entity-events](entity-events.md) 第三节。

MCP 参考：`vjcad/doc/event/event-usage.md`（原文的 `EntityErasing/DocumentSaving/DocumentClosing` 示例在当前版本不触发，已改写）。

## 七、事件参数类型（TS）

```ts
import type {
  EntityAddedEventArgs,
  DocumentSwitchedEventArgs,
  CommandStartedEventArgs,
  EntitiesErasingEventArgs
} from 'vjcad';

Engine.eventManager.on(CadEvents.EntityAdded, (args: EntityAddedEventArgs) => {
  args.entity;
  args.timestamp;
});

Engine.eventManager.on(CadEvents.DocumentSwitched, (args: DocumentSwitchedEventArgs) => {
  args.currentDocument;
});

Engine.eventManager.on(CadEvents.CommandStarted, (args: CommandStartedEventArgs) => {
  args.commandName;
});

// 可取消事件用对应的 *ing 类型（已继承 CancellableEventArgs，带 cancel 字段）
Engine.eventManager.on(
  CadEvents.EntitiesErasing,
  (args: EntitiesErasingEventArgs) => {
    args.cancel = true;
  }
);
```

> 事件参数是**逐事件类型**（`EntityAddedEventArgs`/`DocumentSwitchedEventArgs`/…），全表见 `EventArgsMap`（未派发事件的 `*EventArgs` 类型也照样导出，类型存在不代表会触发）；**没有** `EntityEventArgs`/`DocumentEventArgs`/`CommandEventArgs` 这种泛型名。

MCP 参考：`vjcad/doc/event/event-usage.md`。

## 反例

- `on(...,(a)=>{})` + `off(...,(a)=>{})`：两个匿名函数引用不同，**off 失败**。
- 在 `EntityAdded` 回调里 `Engine.addEntities(...)`：会触发新一轮 `EntityAdded`，可能**死循环**。
- 频繁 `suspendEvents`：影响其他依赖事件的功能（如 UI 状态栏 / 自动保存）。

## 参考（官方文档 MCP）

- 文档：`vjcad/doc/event/event-usage.md`
- 示例：`vjcad/example/event/01entityadded.js`
