唯杰地图 logo唯杰地图
VJMAP
EN

输入与事件系统

vjcad/references/quickref/input-events.md 12.8 KB408 行
原文

用户输入交互和事件监听。

输入函数

getPoint - 获取点

const { PointInputOptions, InputStatusEnum, getPoint, Point2D } = vjcad;

// 基本用法
const options = new PointInputOptions("指定点位置:");
const result = await getPoint(options);

if (result.status === InputStatusEnum.OK) {
    const point = result.value;  // Point2D {x, y}
    console.log(`点: (${point.x}, ${point.y})`);
}

带橡皮筋线:

const opt = new PointInputOptions("指定终点:");
opt.useBasePoint = true;      // 启用橡皮筋线
opt.basePoint = startPoint;   // 设置基点
const result = await getPoint(opt);

隐藏默认橡皮筋线,使用自定义预览:

const opt = new PointInputOptions("指定终点:");
opt.useBasePoint = true;
opt.basePoint = startPoint;
opt.hideRubberBand = true;    // 隐藏默认橡皮筋线

opt.callback = (canvasPt) => {
    const worldPt = Engine.canvasToWcs(canvasPt);
    // 创建自定义预览
    const preview = new LineEnt([startPoint.x, startPoint.y], [worldPt.x, worldPt.y]);
    preview.setDefaults();
    preview.color = 1;
    Engine.clearPreview();
    Engine.drawPreviewEntities([preview]);
};

const result = await getPoint(opt);
Engine.clearPreview();

带关键字选项:

const opt = new PointInputOptions("指定点 [直线(L)/圆(C)/退出(E)]:");
opt.keywords = ["L", "C", "E"];

const result = await getPoint(opt);

switch (result.status) {
    case InputStatusEnum.OK:
        // 用户点击了坐标点
        const point = result.value;
        break;
    case InputStatusEnum.Keyword:
        // 用户输入了关键字
        const keyword = result.stringResult.toUpperCase();
        if (keyword === "L") { /* 处理直线 */ }
        else if (keyword === "C") { /* 处理圆 */ }
        else if (keyword === "E") { /* 退出 */ }
        break;
    case InputStatusEnum.Cancel:
        // 用户取消(ESC)
        break;
}

getSelections - 获取选择集

const { SelectionInputOptions, InputStatusEnum, getSelections } = vjcad;

const options = new SelectionInputOptions();   // 无参构造:提示语不在这里传
const result = await getSelections(options);

if (result.status === InputStatusEnum.OK) {
    const entities = result.value;  // 实体数组
    console.log(`选中了 ${entities.length} 个实体`);
    
    entities.forEach((ent, i) => {
        console.log(`${i + 1}. ${ent.type} (ID: ${ent.id}, 颜色: ${ent.color})`);
    });
}

特点:

  • 支持框选和点选
  • 用户按回车确认选择
  • 返回实体数组

getReal - 获取实数

const { RealInputOptions, InputStatusEnum, getReal } = vjcad;

const options = new RealInputOptions("输入圆的半径:");
options.defaultValue = 30;  // 默认值(构造时默认就是 0)

const result = await getReal(options);

if (result.status === InputStatusEnum.OK) {
    // 直接回车/空格时 getReal 返回 OK + defaultValue(getPoint/getInteger/getString 则返回 None)
    const radius = result.value;
} else if (result.status === InputStatusEnum.None) {
    // 仅当 defaultValue 为 null/undefined 时,直接回车才返回 None
}

getInteger - 获取整数

const { IntegerInputOptions, InputStatusEnum, getInteger } = vjcad;

const options = new IntegerInputOptions("输入多边形边数:");
options.defaultValue = 6;   // getInteger 不会自动套用 defaultValue

const result = await getInteger(options);

if (result.status === InputStatusEnum.OK) {
    const sides = result.value;
} else if (result.status === InputStatusEnum.None) {
    // 直接回车/空格(或输入非数字)返回 None,需自己回退到默认值
    const sides = options.defaultValue;
}

getString - 获取字符串

const { StringInputOptions, InputStatusEnum, getString } = vjcad;

const options = new StringInputOptions("输入图层名称:");
// StringInputOptions 没有 defaultValue 字段(只有 keywords/allowEmpty/defaultKeyWord)

const result = await getString(options);

if (result.status === InputStatusEnum.OK) {
    const layerName = result.value;
} else if (result.status === InputStatusEnum.None) {
    // 直接回车/空格(空输入),默认值自己兜底
    const layerName = "新图层";
}

注意:getString 中空格也会结束输入(与回车相同),因此输入值不能含空格。


InputStatusEnum 状态

状态 说明 常见场景
OK 成功获取输入 用户点击/输入有效值;直接回车时 getReal 返回 OK + defaultValue、getKeyword(设了 defaultKeyword)返回 OK + 默认关键字
Cancel 用户取消 按 ESC
None 直接回车/空格(空输入) getPoint / getInteger / getString(getInteger 输入非数字也返回 None)
EnterOrSpace 回车/空格 仅 getEntity 返回
Keyword 用户输入关键字 带 keywords 选项时(getPoint 设了 defaultKeyword 时空回车返回该关键字)

事件系统

CadEventManager 基本用法

const { CadEventManager, CadEvents, Engine } = vjcad;

// 获取事件管理器
const events = Engine.eventManager;
// 或: const events = CadEventManager.getInstance();

// 注册监听器
events.on(CadEvents.EntityAdded, (args) => {
    console.log('实体已添加:', args.entity.type);
});

// 链式调用
events
    .on(CadEvents.EntityAdded, handler1)
    .on(CadEvents.EntityModified, handler2);

// 一次性监听器(DocumentOpened 在 vjcad@1.0.16 从不派发,打开/切换文档用 DocumentSwitched)
events.once(CadEvents.DocumentSwitched, (args) => {
    console.log('当前文档:', args.currentDocument.name);   // 另有 args.previousDocument(可能为 null)
});

取消订阅

// 保存处理函数引用,用 off 精确移除
const handler = (args) => { console.log(args); };
events.on(CadEvents.EntityAdded, handler);
events.off(CadEvents.EntityAdded, handler);

⚠️ 不要用 offAll() / offAll(event) 做清理:Engine.eventManager 是全局单例,内核自己也在上面订阅(标注关联反应器桥 ReactorEventBridge 订阅 EntityAdded/EntityModified、RibbonBar 订阅 Plugin*/RibbonConfigChanged、MainView 订阅 DocumentCreated),且只订阅一次——offAll 会把它们一并移除,关联标注、Ribbon 刷新等从此失效。只用 off(event, handler)。

事件管理器方法

方法 说明
on(event, handler) 注册监听器
once(event, handler) 注册一次性监听器
off(event, handler) 移除指定监听器(清理只用它)
offAll(event?) 移除所有监听器——含内核内部监听,勿用
hasListeners(event) 检查是否有监听器
listenerCount(event) 获取监听器数量
suspendEvents(queueEvents = false) 暂停事件触发(默认丢弃期间的事件;传 true 则排队,resume 时补发)
resumeEvents() 恢复事件触发

常用事件类型

实体事件

// 实体添加
events.on(CadEvents.EntityAdded, (args) => {
    console.log('实体:', args.entity);
    console.log('文档:', args.document);
});

// 批量添加——只由 Engine.currentSpace.addEntities([...]) 派发(期间逐个 EntityAdded 被抑制);
// Engine.addEntities 走逐个 pcanvas.addEntity,只派发逐个 EntityAdding/EntityAdded
events.on(CadEvents.EntitiesAdded, (args) => {
    console.log('添加数量:', args.entities.length);
});

// 实体修改
events.on(CadEvents.EntityModified, (args) => {
    console.log('修改的实体:', args.entity);
});

// 实体删除
events.on(CadEvents.EntitiesErased, (args) => {
    args.entities.forEach(ent => {
        console.log('已删除:', ent.type);
    });
});

选择事件

events.on(CadEvents.SelectionChanged, (args) => {
    const selected = Engine.ssGetFirst();
    console.log('当前选中数量:', selected.length);
});

命令事件

events.on(CadEvents.CommandStarted, (args) => {
    console.log('命令开始:', args.commandName);
});

// Esc 取消的命令同样触发 CommandEnded(CommandCancelled 在 vjcad@1.0.16 从不派发);
// success 只表示 main() 没抛异常,duration 为耗时毫秒
events.on(CadEvents.CommandEnded, (args) => {
    console.log('命令结束:', args.commandName, args.success, args.duration);
});

文档事件

// 打开/新建/切换文档后都会触发(DocumentOpened/DocumentSaved 等在 vjcad@1.0.16 从不派发)
events.on(CadEvents.DocumentSwitched, (args) => {
    console.log('当前文档:', args.currentDocument.name, '之前:', args.previousDocument?.name);
});

// 只在文档从"未修改"变为"已修改"时触发一次;保存清除修改标记前不会再触发
events.on(CadEvents.DocumentModified, (args) => {
    console.log('文档已修改:', args.document.name);
});

图层事件

events.on(CadEvents.LayerAdded, (args) => {
    console.log('新图层:', args.layer.name);
});

events.on(CadEvents.CurrentLayerChanged, (args) => {
    // 参数是图层**名**字符串,不是 Layer 对象:previousLayerName / currentLayerName
    console.log('当前图层:', args.currentLayerName);
});

可取消事件

设 args.cancel = true 即取消(只对下方列表里真正派发且检查 cancel 的事件有效)。

// 阻止删除保护图层上的实体(EntityErasing 从不派发,用批量的 EntitiesErasing)
events.on(CadEvents.EntitiesErasing, (args) => {
    const protectedEnts = args.entities.filter(ent => ent.layer === "保护图层");
    if (protectedEnts.length === args.entities.length) {
        args.cancel = true;                       // 整批取消
        args.cancelReason = "保护图层上的实体不允许删除";
    } else if (protectedEnts.length > 0) {
        args.excludeEntities = protectedEnts;     // 只把这些排除,其余照删
    }
});

// 阻止命令执行
events.on(CadEvents.CommandStarting, (args) => {
    if (args.commandName === 'ERASE' && !hasPermission()) {
        args.cancel = true;
        args.cancelReason = "没有删除权限";
    }
});

可取消事件列表(vjcad@1.0.16 实际派发并检查 cancel 的):

  • DocumentCreating(取消后 Engine.view.newDocument() / openDbDoc 不判空会直接抛错,慎用)
  • EntityAdding / EntitiesAdding / EntitiesErasing(后者另支持 args.excludeEntities 部分排除)
  • CommandStarting
  • LayerAdding / LayerDeleting
  • ContextMenuOpening

DocumentOpening / DocumentClosing / DocumentSaving / EntityErasing / BlockAdding / BlockDeleting 虽在 CadEvents 枚举里,但从不派发,监听它们取消不了任何操作。


事件类型完整列表

⚠️ 标 ✗ 的事件只存在于 CadEvents 枚举,vjcad@1.0.16 从不派发(监听永远不触发)。

文档事件

事件 说明
DocumentCreating / DocumentCreated 新建文档
DocumentOpening / DocumentOpened ✗ 打开文档——改用 DocumentSwitched
DocumentClosing / DocumentClosed ✗ 关闭文档
DocumentSaving / DocumentSaved ✗ 保存文档
DocumentModified 文档由未修改变为已修改(仅首次)
DocumentSwitched 打开/新建/切换文档(previousDocument / currentDocument)

实体事件

事件 说明
EntityAdding / EntityAdded 添加实体(Engine.addEntities 逐个派发)
EntitiesAdding / EntitiesAdded 批量添加(仅 currentSpace.addEntities)
EntityModified 实体修改
EntityErasing / EntityErased ✗ 删除实体——删除只派发批量版
EntitiesErasing / EntitiesErased 删除实体(单个删除也走这对)

命令事件

事件 说明
CommandStarting / CommandStarted 命令开始
CommandEnded 命令结束(Esc 取消也触发)
CommandCancelled ✗ 命令取消——看 CommandEnded

选择事件

事件 说明
SelectionChanged 选择集变化
SelectionCleared 选择集清空

图层事件

事件 说明
LayerAdding / LayerAdded 添加图层
LayerDeleting / LayerDeleted 删除图层
LayerModified ✗ 图层修改
CurrentLayerChanged 当前图层切换

视图事件

事件 说明
ViewZoomed 视图缩放
ViewPanned ✗ 视图平移
ViewRegenerated 视图重绘

块事件

事件 说明
BlockAdding / BlockAdded ✗ 添加块定义
BlockDeleting / BlockDeleted ✗ 删除块定义
BlockModified ✗ 块定义修改