简介:本资源面向AutoCAD二次开发初学者与ObjectArx工程师,聚焦CAD2010环境下屏幕菜单(DockControlBar)的实现方法,帮助开发者掌握可浮动、可停靠自定义界面的构建流程。包内共16个文件,以6个cpp源文件与5个h头文件为核心,配合rc资源脚本、sln解决方案与vcproj工程文件,完整呈现从CAcUiDockControlBar派生自定义类、资源定义、初始化注册到事件处理与命令响应的代码结构,另有suo配置记录开发环境状态。压缩包约31KB,体积轻巧但结构完整,可直接编译调试。目前已有1236人学习下载。通过研读这套工程,读者能够理解MFC框架与ObjectArx接口的协作方式,掌握DockControlBar的显示隐藏控制、布局样式调整及自定义命令实现思路,为扩展AutoCAD个性化工作界面打下实践基础。
1. 从一次“菜单栏消失”的排查说起:ARX 屏幕菜单到底解决什么问题
有个做 CAD 二次开发的朋友遇到过一件挺玄学的事:客户现场反馈,软件启动后左侧那块停靠面板偶尔不显示,重启又好了,日志里干干净净。排查了半天才发现,是 DockControlBar 的创建时机和主框架窗口的初始化顺序没对齐。这件事让我意识到,屏幕菜单(DockControlBar)看着只是“加个面板”,实际上牵扯到 ARX 模块加载、MFC 框架接管、资源切换、停靠状态持久化一整条链路。
这份资源围绕的就是这件事:在 ObjectARX 2010 环境下,用 DockControlBar 给 CAD 主界面挂一个可停靠的屏幕菜单。它适合两类人——一类是刚接触 ObjectArx 2010 屏幕菜单、想把面板挂进 CAD 但不知道从哪下手的开发者;另一类是已经能挂上面板,但被停靠位置、显示时机、多文档切换这些问题反复折腾的人。下面我按“能干什么 → 怎么跑起来 → 怎么改”的顺序,把这份代码拆开讲。
2. 架构拆解:ARX 模块、MFC 框架与 DockControlBar 是怎么串起来的
2.1 三个层次:ARX 入口、UI 宿主、停靠面板
先把这个项目的结构理清楚,不然后面看代码会晕。整个屏幕菜单功能其实分三层:
第一层是 ARX 模块入口。ObjectARX 程序本质是个 DLL,CAD 通过acrxEntryPoint回调来加载和卸载它。所有初始化动作——注册命令、创建面板、绑定消息——都得挂在AcRx::kInitAppMsg这个时机上。
第二层是 UI 宿主。DockControlBar 不是凭空存在的,它必须依附在一个CFrameWnd派生类上。CAD 主窗口本身是 MFC 框架窗口,ARX 通过acedGetAcadFrame()拿到这个主框架指针,才能把控制条挂上去。
第三层才是停靠面板本体。它通常是一个CDialogBar或CControlBar派生类,内部可以放对话框资源、树控件、列表控件,甚至嵌一个CAdUiDockControlBar。
这三层的关系是:ARX 入口负责“什么时候建”,主框架负责“挂到哪里”,面板类负责“长什么样、点了干什么”。很多新手翻车就翻在把这三件事混在一个函数里写,结果时机一乱全乱。
2.2 目录与文件职责
这类 ARX 工程的文件结构一般长这样,我按常见做法列一下,方便你对照自己的工程:
| 文件 | 职责 |
|---|---|
acrxEntryPoint.cpp | ARX 入口,处理 initApp/unloadApp,注册命令 |
XXXApp.cpp / .h | 应用类,管理面板的创建与销毁 |
DockBar.cpp / .h | 停靠面板类,继承 CDialogBar 或 CAdUiDockControlBar |
Resource.h / .rc | 对话框资源、控件 ID、字符串表 |
StdAfx.h | 预编译头,包含 ObjectARX 与 MFC 头文件 |
这里有个容易忽略的点:ARX 工程同时链接 MFC 和 ObjectARX 两套库,资源 ID 段必须错开。CAD 自己占用了大量资源 ID,如果你把对话框 ID 定义成1000这种小数字,很可能和 CAD 内部资源撞车,表现就是面板能创建但控件显示错乱。常见做法是从10000以上开始定义自己的资源 ID。
2.3 初始化链路:从 kInitAppMsg 到面板显示
把链路串起来看,一次完整的加载流程是这样的:
// acrxEntryPoint.cpp —— ARX 入口的典型写法 extern "C" AcRx::AppRetCode acrxEntryPoint(AcRx::AppMsgCode msg, void* pkt) { switch (msg) { case AcRx::kInitAppMsg: // 允许 ARX 模块被卸载,否则 CAD 关闭时可能残留 acrxDynamicLinker->unlockApplication(pkt); acrxRegisterAppMDIAware(pkt); // 声明支持多文档 initApp(); // 自定义初始化:注册命令、建面板 break; case AcRx::kUnloadAppMsg: unloadApp(); // 清理面板、注销命令 break; default: break; } return AcRx::kRetOK; }逻辑说明:kInitAppMsg是 CAD 加载 ARX 后发的第一条消息,此时主框架窗口已经存在,可以安全调用acedGetAcadFrame()。unlockApplication让模块可卸载,acrxRegisterAppMDIAware告诉 CAD 这个模块支持多文档环境——不加这句,切换图纸时面板可能跟着出问题。
参数说明:pkt是模块句柄,卸载和资源切换都要用到它,别丢。kUnloadAppMsg里必须把创建的面板销毁掉,否则 CAD 退出时会报内存泄漏或者直接崩。
2.4 面板创建:拿到主框架再挂控制条
初始化里最关键的一步是创建 DockControlBar:
void initApp() { CFrameWnd* pFrame = acedGetAcadFrame(); // 拿 CAD 主框架 if (pFrame == nullptr) return; // 创建停靠面板,IDD_DOCKBAR 是对话框资源 ID g_pDockBar = new CDockBar; if (!g_pDockBar->Create(pFrame, IDD_DOCKBAR, WS_CHILD | WS_VISIBLE | CBRS_LEFT)) { delete g_pDockBar; g_pDockBar = nullptr; return; } // 设置停靠样式:允许左侧停靠、可浮动、可调整大小 g_pDockBar->SetBarStyle(g_pDockBar->GetBarStyle() | CBRS_SIZE_DYNAMIC | CBRS_TOOLTIPS); // 挂到主框架左侧 g_pDockBar->EnableDocking(CBRS_ALIGN_LEFT); pFrame->DockControlBar(g_pDockBar, AFX_IDW_DOCKBAR_LEFT); pFrame->ShowControlBar(g_pDockBar, TRUE, FALSE); }逻辑说明:Create的第一个参数是父窗口,必须是主框架;第二个是对话框模板 ID;第三个是控制条样式。CBRS_LEFT表示初始停靠方向。EnableDocking和DockControlBar是两步——先声明这个控制条允许停在哪,再真正把它停过去。ShowControlBar负责显示,第三个参数FALSE表示不延迟。
参数说明:AFX_IDW_DOCKBAR_LEFT是 MFC 预定义的左侧停靠区 ID,右侧是AFX_IDW_DOCKBAR_RIGHT,顶部底部同理。如果你想让面板默认浮动,就跳过DockControlBar,直接ShowControlBar加SetWindowPos。
2.5 多文档环境下的一个关键处理
CAD 是多文档的,切换图纸时主框架不变,但文档上下文会变。面板如果持有文档相关数据,必须在文档切换时刷新。常见做法是重写面板类的OnUpdate,或者在 ARX 里监听文档事件:
// 面板类里响应文档切换 void CDockBar::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint) { // 文档变了,刷新面板里跟当前图纸相关的数据 if (lHint == DOC_CHANGED) { RefreshTreeContent(); } }逻辑说明:OnUpdate是 MFC 文档/视图架构里的标准回调,lHint用来区分是什么变化。这里用自定义的DOC_CHANGED标记文档切换。参数pHint可以携带额外数据,一般传当前文档指针。
这一步不做的话,表现就是:切了图纸,面板里还显示上一张图的数据,用户点了就报错。这是多文档 ARX 里最常见的坑之一。
3. 跑起来:从零编译到面板出现在 CAD 左侧
3.1 环境准备与工程配置
ObjectArx 2010 对编译环境有硬性要求,版本对不上直接编译不过。我按常见配置列一下:
| 项目 | 要求 |
|---|---|
| Visual Studio | VS2008(ObjectARX 2010 官方配套) |
| ObjectARX SDK | 2010 对应版本 |
| CAD 版本 | 2010 系列 |
| 平台 | Win32(2010 时代基本是 32 位) |
| MFC | 动态链接 MFC |
配置工程时,头文件目录要加 ObjectARX 的inc和inc-win32,库目录加lib-win32。链接器里要带上acad.lib、acrx15.lib、acdb15.lib、acui15.lib这几个核心库。少一个就是一堆 LNK2019。
提示:ObjectARX 2010 的库文件名带版本号(如 acrx15),不同版本号不一样,别照抄别的版本的配置。
3.2 资源切换:ARX 里最容易翻车的一步
ARX 模块有自己的资源(对话框、字符串),但 CAD 主程序也有资源。加载 ARX 时如果不做资源切换,Create对话框会失败,因为 MFC 在 CAD 的资源里找不到你的对话框模板。
标准做法是用CAcExtensionModule或者手动切换模块状态:
// 在创建面板前切换到本模块资源 AFX_MANAGE_STATE(AfxGetStaticModuleState()); // 此时再 Create,MFC 才能找到 IDD_DOCKBAR if (!g_pDockBar->Create(pFrame, IDD_DOCKBAR, ...)) { // 创建失败,多半是资源没切过来 }逻辑说明:AFX_MANAGE_STATE是个宏,作用域内把 MFC 的模块状态切到当前 DLL。它必须在任何使用本模块资源的函数开头调用。参数AfxGetStaticModuleState()返回本模块的静态状态。
这个坑我踩过:面板死活创建不出来,Create返回 FALSE,查了半天资源 ID 没问题,最后发现是漏了AFX_MANAGE_STATE。加上就好了。血泪经验,别省这一行。
3.3 编译、加载与验证
编译通过后得到.arx文件(本质是 DLL 改后缀)。加载方式有两种:
第一种是手动加载,在 CAD 命令行输入APPLOAD,选你的 arx 文件。这种方式适合调试,改一次加载一次。
第二种是自动加载,把 arx 路径写进注册表或者 CAD 的启动组。注册表位置在 CAD 配置对应的Applications键下,加一个项,值指向 arx 路径。这种方式适合交付。
加载成功后,CAD 左侧应该出现你的面板。如果没出现,按这个顺序查:命令行有没有报错 →APPLOAD里模块是否显示已加载 → 面板是不是被其他停靠条挤到看不见了 → 资源 ID 是否和 CAD 冲突。
3.4 停靠状态持久化
用户把面板拖到右边、调了宽度,下次启动 CAD 应该保持这个状态。MFC 框架自带这个能力,但需要你显式保存和恢复:
// 保存停靠状态到配置文件 void SaveDockState() { CFrameWnd* pFrame = acedGetAcadFrame(); if (pFrame && g_pDockBar) { // 把控制条布局写进注册表或 ini pFrame->SaveBarState(_T("MyAppDockState")); } } // 启动时恢复 void LoadDockState() { CFrameWnd* pFrame = acedGetAcadFrame(); if (pFrame) { pFrame->LoadBarState(_T("MyAppDockState")); } }逻辑说明:SaveBarState把当前所有控制条的位置、大小、可见性写进注册表(键名就是传入的字符串)。LoadBarState反过来读。注意这两个函数操作的是主框架下所有控制条,如果你的应用和别的 ARX 共存,键名要唯一,别用默认的。
参数说明:字符串参数是注册表子键名,建议用你的模块名加前缀,避免冲突。调用时机上,SaveBarState放在kUnloadAppMsg里,LoadBarState放在面板创建之后。
4. 避坑与排查:屏幕菜单开发里最常见的 5 个问题
4.1 面板创建返回 FALSE,但没有任何报错
现象:Create调用后返回 FALSE,命令行无提示,面板不出现。
原因:九成是资源没切换。MFC 在 CAD 主程序的资源里找IDD_DOCKBAR,找不到就静默失败。
解决:在Create之前加AFX_MANAGE_STATE(AfxGetStaticModuleState()),确认对话框资源 ID 在.rc文件里真实存在,且 ID 值大于 10000。
4.2 面板显示了,但上面的控件是空的或者错位
现象:面板框架出来了,里面的按钮、树控件不显示,或者显示成别的样子。
原因:资源 ID 和 CAD 内部资源冲突。CAD 自己用了大量低段 ID,你的对话框或控件 ID 撞上了,MFC 加载到了错误的资源。
解决:把所有自定义资源 ID 统一挪到 10000 以上,重新编译。检查.rc里有没有手写的低 ID。
4.3 切换图纸后面板数据不刷新
现象:打开第二张图纸,面板里还是第一张图的数据,点击报错。
原因:面板没有响应文档切换事件,或者响应了但没拿到当前文档。
解决:重写OnUpdate,在文档切换时刷新数据。取当前文档用acDocManager->curDocument(),别缓存文档指针。
4.4 CAD 退出时崩溃或报内存泄漏
现象:关闭 CAD 时弹异常,或者调试输出里一堆泄漏报告。
原因:kUnloadAppMsg里没销毁面板,或者销毁顺序不对。面板是new出来的,必须delete;而且要在主框架还活着的时候销毁。
解决:在unloadApp里先DestroyWindow再delete,并且把全局指针置空。顺序反了会访问已释放的窗口。
4.5 面板能停靠但拖拽后位置错乱
现象:把面板拖到右边,松手后它跳回左边,或者宽度变得很怪。
原因:EnableDocking时声明的对齐方式和DockControlBar传入的停靠区不一致,或者样式里少了CBRS_SIZE_DYNAMIC。
解决:EnableDocking里把允许的方向都加上(CBRS_ALIGN_ANY),样式里确保有CBRS_SIZE_DYNAMIC,DockControlBar的第二个参数用对应的AFX_IDW_DOCKBAR_XXX。
5. 二次开发改造:把面板换成你自己的业务界面
5.1 替换面板内容:从对话框到自定义控件
这份代码给的是一个基础停靠面板,实际项目里你肯定要换成自己的界面。改造路径很直接:把IDD_DOCKBAR对应的对话框模板重新画,放上你的树控件、列表控件、按钮。然后在面板类里给这些控件绑定消息处理。
如果界面复杂,建议不要在对话框模板里堆控件,而是嵌一个自定义CWnd派生类。做法是在对话框上留一个占位区域,OnInitDialog里创建你的自定义窗口并MoveWindow到占位区。这样界面逻辑和停靠逻辑解耦,后面改界面不用动停靠代码。
BOOL CDockBar::OnInitDialog() { CDialogBar::OnInitDialog(); // 在占位区创建自定义业务窗口 CRect rcPlaceholder; GetDlgItem(IDC_PLACEHOLDER)->GetWindowRect(&rcPlaceholder); ScreenToClient(&rcPlaceholder); m_pBizWnd = new CBizTreeWnd; m_pBizWnd->Create(nullptr, nullptr, WS_CHILD | WS_VISIBLE, rcPlaceholder, this, 0x1234); return TRUE; }逻辑说明:IDC_PLACEHOLDER是对话框上一个静态框或空区域,用来定位。Create的父窗口传this,让自定义窗口成为面板的子窗口。控件 ID0x1234自己定,别和别的撞。
参数说明:rcPlaceholder是客户区坐标,所以要先ScreenToClient。自定义窗口的Create第一个参数是窗口类名,传nullptr用默认。
5.2 与 CAD 数据库交互:面板里操作图形
屏幕菜单的价值在于能操作当前图纸。面板里的按钮点击后,通常要读写 CAD 数据库。这里有个必须遵守的规则:任何数据库操作都要加文档锁。
void CDockBar::OnBtnAddEntity() { // 拿当前文档 AcApDocument* pDoc = acDocManager->curDocument(); if (!pDoc) return; // 加锁,防止多文档并发修改 acDocManager->lockDocument(pDoc); // 在模型空间加一条直线 AcDbBlockTable* pBlockTable = nullptr; acdbHostApplicationServices()->workingDatabase() ->getBlockTable(pBlockTable, AcDb::kForRead); AcDbBlockTableRecord* pModelSpace = nullptr; pBlockTable->getAt(ACDB_MODEL_SPACE, pModelSpace, AcDb::kForWrite); AcDbLine* pLine = new AcDbLine(AcGePoint3d(0,0,0), AcGePoint3d(100,100,0)); pModelSpace->appendAcDbEntity(pLine); pLine->close(); pModelSpace->close(); pBlockTable->close(); // 解锁 acDocManager->unlockDocument(pDoc); }逻辑说明:lockDocument和unlockDocument必须成对出现,中间任何提前 return 都要先解锁,否则文档死锁,CAD 卡住。getBlockTable用kForRead,getAt拿模型空间用kForWrite,实体 append 后要close。
参数说明:ACDB_MODEL_SPACE是模型空间的固定标识。AcGePoint3d三个参数是 XYZ 坐标。实体close不调用会导致数据库对象泄漏。
5.3 验证改造是否成功
改完之后怎么确认没问题?我一般走这几步:加载 arx,看面板是否正常出现;拖动面板到四个方向,看停靠是否正常;切换图纸,看面板数据是否刷新;点面板按钮,看图形是否按预期变化;关闭 CAD,看是否有崩溃或泄漏。这五步走完,基本能覆盖 90% 的问题。
注意:调试 ARX 时,CAD 进程会锁住 arx 文件,重新编译前必须先关 CAD,否则链接报错“无法写入文件”。这个坑几乎每个新手都踩过。
5.4 一个我自己的习惯
从那以后,我每次写 ARX 面板相关的代码,都会强制走一遍“资源切换 → 创建 → 停靠 → 持久化 → 销毁”这条链路,每一步都单独验证,绝不把创建和停靠写在一个函数里图省事。因为一旦出问题,混在一起你根本不知道是哪一步挂的。希望这份拆解能帮你少走点弯路,把屏幕菜单稳稳挂进你的 CAD 里。
本文还有配套的精品资源,点击获取