一、痛点引入
上回讲到 CATPathElementAgent 时,你已经能让用户"点一下鼠标 命令自动识别对象"。但项目现场的真实场景往往是另一种——
需求来了:研发主管让你做个小工具,把当前模型里所有
thickness参数统一改成 2.0mm,工程师点开工具,在面板里输入参数名 + 新值,点"应用"就改完一个,循环直到点"关闭"。
这种"先开窗口填字段按钮触发保留窗口继续"的交互,仅靠前两篇的 CATIndicationAgent / CATPathElementAgent 是做不出来的。你需要把对话框(CATDlgDialog)接入到状态机里。今天用"批量参数修改"这个高频需求,把对话框、对话代理、状态机三者贯通一次讲透。
二、核心思路
CAA 的对话框和状态机,通过一个通用对话代理(CATDialogAgent)对接:对话框的"按钮事件"被代理接收,代理被估值就触发状态机的转移。整个数据流是:
用户在对话框输入参数名 + 新值
点 "应用" 按钮 对话框发出 GetDiaAPPLYNotification
CATDialogAgent 通过 AcceptOnNotify 订阅该通知
代理被估值 触发状态机自转移 执行参数修改
窗口保留 状态回到自身 等待下一次输入
点 "关闭" 按钮 另一条转移 状态机走到 NULL 命令结束你需要准备:
- 编译通过的 CAA 模块(参考 08-15 环境配置)
- 一个继承
CATStateCommand的状态命令类(参考 08-17 状态机) - 一个自定义对话框类(继承
CATDlgDialog)
三、环境搭建(2分钟)
在已有 CAA 模块的 Imakefile.mk 中补一个依赖:
LINK_WITH = JS0GROUP CATApplicationFrame CATDialogEngine \
CATMathematics JS0FM DI0PANV2CATApplicationFrame 提供 CATApplicationFrame::GetFrame()->GetMainWindow(),作为对话框的父窗口。验证方式:编译能过即可。
四、核心代码
实现一个完整的"批量参数修改"命令:用户在面板里填参数名 + 新值,点 Apply 就改一次,可以连续改多次,直到点 Close 退出。
4.1 对话框类 MyParamDialog
MyParamDialog.h:
#include "CATDlgDialog.h"
#include "CATDlgEditor.h"
#include "CATDlgPushButton.h"
#include "CATDlgFrame.h"
#include "CATDlgLabel.h"
class MyParamDialog : public CATDlgDialog
{
// 资源文件(NLS 提示)
DeclareResource(MyParamDialog, CATDlgDialog)
public:
MyParamDialog(CATDialog* iParent);
virtual ~MyParamDialog();
void Build();
// 对外提供当前输入
void GetInput(CATUnicodeString& oName, double& oValue);
private:
CATDlgEditor* _pNameEditor; // 参数名输入框
CATDlgEditor* _pValueEditor; // 参数新值输入框
CATDlgPushButton* _pApplyBtn; // Apply 按钮
CATDlgPushButton* _pCloseBtn; // Close 按钮
// 回调方法:Apply 被点,弹出消息框显示当前输入
void OnApplyPressed(CATCommand* iPublisher,
CATNotification* iNotif,
CATCommandClientData iData);
};MyParamDialog.cpp:
#include "MyParamDialog.h"
#include "CATDlgNotify.h" // 用于弹消息框的小对话框
MyParamDialog::MyParamDialog(CATDialog* iParent)
: CATDlgDialog(iParent, "ParamDlgId",
CATDlgWndNoButton | CATDlgWndModal)
{
_pNameEditor = NULL;
_pValueEditor = NULL;
_pApplyBtn = NULL;
_pCloseBtn = NULL;
Build();
}
MyParamDialog::~MyParamDialog() {}
void MyParamDialog::Build()
{
// ---- 第1步:用 CATDlgFrame 装一组标签+输入框 ----
CATDlgFrame* pFrame = new CATDlgFrame(this, "InputFrame", CATDlgGridLayout);
new CATDlgLabel(pFrame, "NameLab", CATDlgLabLeft); // 标签
_pNameEditor = new CATDlgEditor(pFrame, "NameEditor"); // 单行输入框
new CATDlgLabel(pFrame, "ValLab", CATDlgLabLeft);
_pValueEditor = new CATDlgEditor(pFrame, "ValueEditor");
// ---- 第2步:放两个按钮在对话框底部 ----
_pApplyBtn = new CATDlgPushButton(this, "ApplyBtn");
_pCloseBtn = new CATDlgPushButton(this, "CloseBtn");
// ---- 第3步:用 AddAnalyseNotificationCB 注册按钮回调 ----
AddAnalyseNotificationCB(
_pApplyBtn,
_pApplyBtn->GetPushButtonActivateNotification(),
(CATCommandMethod)&MyParamDialog::OnApplyPressed,
NULL);
}
void MyParamDialog::OnApplyPressed(CATCommand*, CATNotification*,
CATCommandClientData)
{
// 演示用:弹个 Notify 把当前输入显示出来
CATUnicodeString txt;
txt.BuildString("已填入参数名:",
_pNameEditor->GetText().ConvertToChar(),
" 新值:", _pValueEditor->GetText().ConvertToChar());
CATDlgNotify* pNotify = new CATDlgNotify(this, "Confirm");
pNotify->DisplayBlocked(txt, "已应用");
}
void MyParamDialog::GetInput(CATUnicodeString& oName, double& oValue)
{
oName = _pNameEditor->GetText();
oValue = atof(_pValueEditor->GetText().ConvertToChar());
}4.2 状态命令类 MyParamCmd
MyParamCmd.h:
#include "CATStateCommand.h"
class MyParamDialog;
class CATDialogAgent;
class MyParamCmd : public CATStateCommand
{
DeclareResource(MyParamCmd, CATStateCommand)
public:
MyParamCmd();
virtual ~MyParamCmd();
void BuildGraph();
private:
MyParamDialog* _pDialog; // 对话框实例
CATDialogAgent* _pApplyAgent; // 监听 Apply 按钮的代理
CATDialogAgent* _pCloseAgent; // 监听 Close 按钮的代理
CATBoolean ActionApply(void* iUsefulData);
CATBoolean ActionClose(void* iUsefulData);
};MyParamCmd.cpp:
#include "MyParamCmd.h"
#include "MyParamDialog.h"
#include "CATDialogAgent.h"
#include "CATApplicationFrame.h"
CATImplementClass(MyParamCmd,
Implementation, CATStateCommand, CATNull);
#include "CATCreateExternalObject.h"
CATCreateClass(MyParamCmd);
MyParamCmd::MyParamCmd() : CATStateCommand("MyParamCmd") {}
MyParamCmd::~MyParamCmd()
{
if (_pApplyAgent) { _pApplyAgent->RequestDelayedDestruction(); _pApplyAgent = NULL; }
if (_pCloseAgent) { _pCloseAgent->RequestDelayedDestruction(); _pCloseAgent = NULL; }
if (_pDialog) { _pDialog->RequestDelayedDestruction(); _pDialog = NULL; }
}
void MyParamCmd::BuildGraph()
{
// ========== 第1步:创建两个对话代理,分别订阅 Apply / Close 通知 ==========
_pApplyAgent = new CATDialogAgent("ApplyAgentId");
_pApplyAgent->SetBehavior(CATDlgEngRepeat); // 关键:可被估值多次
_pCloseAgent = new CATDialogAgent("CloseAgentId");
// ========== 第2步:创建对话框,父窗口取主框架 ==========
CATApplicationFrame* pFrame = CATApplicationFrame::GetFrame();
_pDialog = new MyParamDialog(pFrame ? pFrame->GetMainWindow() : NULL);
// 把对话框的通知"喂"给代理:点 Apply 代理估值;点 Close 代理估值
_pApplyAgent->AcceptOnNotify(_pDialog,
_pDialog->GetDiaAPPLYNotification());
_pCloseAgent->AcceptOnNotify(_pDialog,
_pDialog->GetDiaCLOSENotification());
// ========== 第3步:创建状态,把代理加入状态 ==========
CATDialogState* pApplyState = AddDialogState("ApplyState");
pApplyState->AddDialogAgent(_pApplyAgent); // 等待 Apply
pApplyState->AddDialogAgent(_pCloseAgent); // 同时等 Close
// ========== 第4步:添加转移 ==========
// 转移 A:Apply 触发 留在原状态(自转移),执行参数修改
AddTransition(
pApplyState, pApplyState,
IsLastModifiedAgentCondition(_pApplyAgent),
Action((ActionMethod)&MyParamCmd::ActionApply));
// 转移 B:Close 触发 走到 NULL,命令结束
AddTransition(
pApplyState, NULL,
IsLastModifiedAgentCondition(_pCloseAgent),
Action((ActionMethod)&MyParamCmd::ActionClose));
}
CATBoolean MyParamCmd::ActionApply(void*)
{
CATUnicodeString name;
double val = 0.0;
_pDialog->GetInput(name, val);
// 此处加 Part.Parameters 查找与修改逻辑(参考 pywin32 系列 07-29 文章)
// CATISpecObject_var spPart = ...;
// 遍历 spPart->GetParameters(),找到名字匹配的 Parameter,把 Value 设为 val
// 实际工程中需包含 CATIPrtContainer.h / CATIPrtPart.h / CATICkeParm.h
printf("[MyParamCmd] Apply -> param=%s value=%.3f\n",
name.ConvertToChar(), val);
return TRUE;
}
CATBoolean MyParamCmd::ActionClose(void*)
{
printf("[MyParamCmd] Close -> 命令结束\n");
return TRUE;
}代码解析:
CATDialogAgent:最基础的对话代理,本身不解析坐标或选对象,纯粹靠"通知"被估值。AcceptOnNotify(dialog, notif):把"哪个对话框发出的哪个通知"作为触发条件接到代理上。IsLastModifiedAgentCondition+ 自转移:让 Apply 可以反复触发同一个状态,比上篇的IsOutputSetCondition更适合长会话。GetDiaAPPLYNotification()/GetDiaCLOSENotification():CATDlgDialog 内置按钮的标准事件名。
五、效果对比
| 对比项 | CATScript/VBA 录宏 | CAA 对话框+状态机 |
|---|---|---|
| UI 一致性 | 弹窗风格脱节 | 沿用 CATIA 原生风格 |
| 多状态联动 | 不支持 | 多个代理组合+条件分支天然支持 |
| 复用程度 | 每次重新录 | 编译成模块,菜单 / 工具栏可重复挂载 |
| 撤销支持 | 脚本无 Undo | 状态机 + DialogAgent 自带全局 Undo 集成 |
| 学习门槛 | 低 | 中(CAA 状态机是绕不开的概念) |
六、进阶用法
技巧 1:在 Apply 回调里更新预览
把 ActionApply 里的 printf 替换成实际调用 Part.Parameters 修改参数;调用 _pDialog->SetVisibility(CATDlgShow) 配合 SetText 在对话框里追加"已修改 N 条"日志,给用户实时反馈。
技巧 2:用 PowerInputLabel + 双击编辑框触发计算
CATDlgEditor 支持键盘回车事件 GetEditModifyNotification,把它接一个"回车即 Apply"的代理,省去点击按钮:
_pApplyAgent->AcceptOnNotify(_pNameEditor,
_pNameEditor->GetEditModifyNotification());按回车或点 Apply 都能触发状态转移,更贴合工程师习惯。
关注回复「CAA对话框」获取完整源码
下一篇:CAA C++ 第5篇——单步撤销与命令持久化,把工具改得能 Undo / Redo
_栏目:开发实战 | 发布日期:2026-08-22 | 字数:约 1180 字_