MEAICAD预约演示
技术博客/二次开发
二次开发

🛠️ CATIA 二次开发:CAA 对话框与状态命令联动,5 分钟做出带参数面板的交互命令

2026-08-22#CATIA#二次开发

一、痛点引入

上回讲到 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 DI0PANV2

CATApplicationFrame 提供 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 字_

本文为基础实战分享。如需完整源码包、配套课程或企业内训,欢迎进一步了解付费体系。

咨询深入资源 →