ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

AutoCAD ARX 可停靠屏幕菜单开发:DockControlBar 原理、实现与避坑

AutoCAD ARX 可停靠屏幕菜单开发:DockControlBar 原理、实现与避坑 简介这份资源面向具备一定 C 与 MFC 基础的 AutoCAD 二次开发者聚焦 ObjectArx 2010 环境下屏幕菜单DockControlBar的实现方法帮助解决自定义停靠面板的创建、注册与交互问题。压缩包共 16 个文件约 31KB以 6 个 cpp 与 5 个 h 源码文件为核心另含 rc 资源脚本、vcproj 工程文件与 sln 解决方案便于直接编译调试。内容围绕 CAcUiDockControlBar 派生类展开涵盖资源定义、初始化注册、OnCmdMsg 事件处理、自定义命令及显示隐藏控制等关键环节并给出布局样式调整与测试调试思路。已有 1235 人学习下载适合希望掌握 ARX 界面扩展机制、打造个性化工作环境的开发者参考借鉴。1. 从一条“贴边不靠边”的菜单说起ARX 里 DockControlBar 到底解决什么问题做过 AutoCAD 二次开发的人大概率遇到过这种需求客户想要一个像特性面板那样能吸附在绘图区边缘、能拖动、能停靠、能自动隐藏的屏幕菜单而不是那种用acedRegCmds注册个命令弹个模态对话框就完事的老式交互。用 ObjectARX下称 ARX做这件事绕不开的核心类就是CDockControlBar——它是 MFC 的CControlBar体系在 ARX 里的落地形态配合CAdUiDockControlBar一起把“屏幕菜单”这种常驻 UI 挂到 AutoCAD 主框架上。标题里的“屏幕菜单”不是 AutoCAD 老版本那个右侧SCREENMENU文本菜单而是泛指停靠在主窗口四周、随图纸状态联动的自定义面板。它要解决三件事一是窗口生命周期归 AutoCAD 管不能自己Create一个顶层窗口乱飘二是布局要参与主框架的停靠算法能被用户拖拽、浮动、自动隐藏三是消息循环要和 ARX 的文档环境对齐切换图纸时面板内容得跟着刷新。适合谁看已经能用 ARX 写出acrxEntryPoint入口、能编译出.arx并APPLOAD加载但一碰 UI 停靠就翻车的同学。下面按“先立住原理、再动手复现、最后讲坑”的顺序拆开。2. DockControlBar 的停靠机制与 ARX 消息链为什么不能直接 new 一个 CWnd2.1 CControlBar 家族在 ARX 里的继承关系MFC 原生的停靠体系是CFrameWndCControlBarCDockBar三件套CControlBar负责“条状”控件的绘制和尺寸协商CDockBar是主框架里预留的四个停靠槽位。AutoCAD 没有直接用CFrameWnd而是自己实现了一套CAdUiMainFrame所以原生CDockControlBar不能直接拿来用必须走 ARX 提供的CAdUiDockControlBar。它的继承链大致是CAdUiDockControlBar : CControlBar └── 你的 CScreenMenuBar : CAdUiDockControlBar关键点在于CAdUiDockControlBar重写了CalcDynamicLayout、OnPaint、OnSize这些和停靠布局强相关的方法并且把“我是谁、我停在哪、我多宽”这套信息注册进了CAdUiDockBarManager。你如果绕过它直接new CWnd然后Create窗口能出来但拖到边缘不会吸附主框架 resize 时也不会通知你这就是最常见的“菜单飘着不靠边”现象。2.2 停靠注册的三步消息链一个能正常停靠的屏幕菜单启动过程分三步缺一不可第一步构造CAdUiDockControlBar派生类实例在构造函数里把m_bAutoHide、m_nInitialDock这些成员设好。第二步调用Create时传入父窗口——注意父窗口必须是adsw_acadMainWnd()返回的主框架句柄不能是acedGetAcadFrame()之外的任何窗口。第三步调用EnableDocking和CAdUiDockBarManager::AddDockBar把这条 bar 注册进管理器管理器才会在布局计算时把它算进去。消息链是这样的用户拖动菜单 → 主框架收到WM_NCHITTEST→CAdUiDockBarManager判断落点是否在停靠区 → 命中则调用你的CalcDynamicLayout询问期望尺寸 → 主框架调整CDockBar槽位 → 你的OnSize被调用完成重绘。任何一环断了表现就是“能拖但松手弹回”或者“停靠后尺寸不对”。2.3 为什么屏幕菜单要用 DockControlBar 而不是 PaletteAutoCAD 自己有CAdUiPaletteSet调色板体系很多人会问为什么不直接用调色板。区别在于调色板是独立浮窗默认不参与主框架停靠虽然也能设m_bDockable但它的停靠是“伪停靠”——吸附后仍占独立 Z 序和绘图区抢焦点。而DockControlBar是真正嵌进主框架布局的resize 时和命令行、状态栏一起参与尺寸分配适合做那种“常驻、窄条、随图纸联动”的屏幕菜单。选型判断很简单需要和绘图区共享焦点、需要参与主框架布局计算就用DockControlBar只是弹个工具面板用调色板更省事。3. 从零写一个可停靠屏幕菜单最小可编译代码与注册流程3.1 头文件与派生类骨架先定义一个继承自CAdUiDockControlBar的类头文件里声明必要的重写方法和成员。注意DECLARE_DYNAMIC和IMPLEMENT_DYNAMIC不能省ARX 的运行时类型识别依赖它。// ScreenMenuBar.h #pragma once #include AdUiDockControlBar.h class CScreenMenuBar : public CAdUiDockControlBar { DECLARE_DYNAMIC(CScreenMenuBar) public: CScreenMenuBar(); virtual ~CScreenMenuBar(); // 创建并注册到主框架 BOOL CreateBar(CWnd* pParent); protected: // 布局协商返回期望尺寸 virtual CSize CalcDynamicLayout(int nLength, DWORD dwMode); virtual void OnSize(UINT nType, int cx, int cy); virtual void OnPaint(); // 面板内容控件 CListCtrl m_wndList; DECLARE_MESSAGE_MAP() };CalcDynamicLayout是停靠体系问你“你想要多宽多高”的入口dwMode里带LM_HORZ、LM_VERTDOCK等标志必须根据停靠方向返回不同尺寸否则横向停靠和纵向停靠会共用一套尺寸导致显示错乱。3.2 实现文件Create 与注册实现里最关键的是CreateBar它把窗口创建和停靠注册串起来。// ScreenMenuBar.cpp #include stdafx.h #include ScreenMenuBar.h IMPLEMENT_DYNAMIC(CScreenMenuBar, CAdUiDockControlBar) BEGIN_MESSAGE_MAP(CScreenMenuBar, CAdUiDockControlBar) ON_WM_SIZE() ON_WM_PAINT() END_MESSAGE_MAP() CScreenMenuBar::CScreenMenuBar() { m_bAutoHide TRUE; // 允许自动隐藏 m_nInitialDock CBRS_ALIGN_LEFT; // 初始停靠左侧 } BOOL CScreenMenuBar::CreateBar(CWnd* pParent) { // 1. 创建窗口父窗口必须是 AutoCAD 主框架 if (!Create(_T(屏幕菜单), pParent, WS_CHILD | WS_VISIBLE | CBRS_LEFT)) return FALSE; // 2. 允许停靠到四个方向 EnableDocking(CBRS_ALIGN_ANY); // 3. 注册进停靠管理器 CAdUiDockBarManager* pMgr CAdUiDockBarManager::GetDockBarManager(); if (pMgr) pMgr-AddDockBar(this); return TRUE; } CSize CScreenMenuBar::CalcDynamicLayout(int nLength, DWORD dwMode) { // 纵向停靠时固定宽度 180横向停靠时固定高度 120 if (dwMode LM_HORZ) return CSize(0, 120); return CSize(180, 0); } void CScreenMenuBar::OnSize(UINT nType, int cx, int cy) { CAdUiDockControlBar::OnSize(nType, cx, cy); if (m_wndList.GetSafeHwnd()) m_wndList.MoveWindow(0, 0, cx, cy); }Create的第三个参数CBRS_LEFT只是初始建议真正决定停靠位置的是m_nInitialDock和用户拖拽。EnableDocking(CBRS_ALIGN_ANY)必须调用否则拖到边缘不会吸附。AddDockBar是 ARX 特有的注册步骤MFC 原生体系里没有这一步漏掉它菜单能显示但管理器不认resize 时会被忽略。3.3 在 ARX 入口里挂载与卸载屏幕菜单的创建时机放在kInitAppMsg之后、kLoadDwgMsg里比较稳因为此时主框架已经就绪。卸载时要在kUnloadAppMsg里销毁否则重复加载会残留。// 入口文件片段 static CScreenMenuBar* g_pMenuBar nullptr; void InitScreenMenu() { CWnd* pMain adsw_acadMainWnd(); if (!pMain) return; g_pMenuBar new CScreenMenuBar(); if (!g_pMenuBar-CreateBar(pMain)) { delete g_pMenuBar; g_pMenuBar nullptr; } } void UnloadScreenMenu() { if (g_pMenuBar) { g_pMenuBar-DestroyWindow(); delete g_pMenuBar; g_pMenuBar nullptr; } }adsw_acadMainWnd()返回的是CWnd*不要用AfxGetMainWnd()后者在 ARX 环境下可能拿到错误的窗口。创建失败要立刻delete否则下次加载会重复 new 导致内存泄漏和多个菜单实例。3.4 面板内容与图纸联动屏幕菜单里通常放列表、树或按钮内容要随当前图纸变化。监听文档切换用AcApDocManager的反应器在documentActivated回调里刷新m_wndList。注意回调发生在文档上下文里操作 UI 前要确认m_wndList句柄有效并且不要在回调里做耗时操作否则切换图纸会卡顿。常见做法是把数据准备放到AcApDocument的OnDocumentLock之后UI 刷新用PostMessage异步投递到主线程。4. 避坑与排查DockControlBar 屏幕菜单的 5 个血泪现场4.1 菜单能显示但拖到边缘不吸附现象窗口正常浮着拖到绘图区左边松手后弹回原位不吸附。原因EnableDocking没调用或者调用时传了0也可能是AddDockBar没执行管理器不知道这条 bar 的存在。解决确认CreateBar里EnableDocking(CBRS_ALIGN_ANY)在Create之后、AddDockBar之前调用并且CAdUiDockBarManager::GetDockBarManager()返回值非空。如果用的是自定义主框架还要检查主框架是否调用了EnableDocking。4.2 停靠后尺寸错乱横向停靠变成一条细线现象纵向停靠宽度正常拖到顶部横向停靠后高度只有几像素。原因CalcDynamicLayout里没有区分LM_HORZ横向和纵向返回了同一套尺寸。解决按dwMode LM_HORZ分支返回横向返回CSize(0, 固定高度)纵向返回CSize(固定宽度, 0)。宽度或高度为 0 的那一维交给主框架按可用空间分配。4.3 切换图纸后面板内容不刷新或崩溃现象打开第二张图列表还是第一张图的数据或者切换时直接崩在OnPaint。原因没有监听文档切换或者监听回调里直接操作了已失效的控件句柄。解决用AcApDocManager反应器监听documentActivated回调里先IsWindow(m_wndList.GetSafeHwnd())判断再用PostMessage投递刷新消息避免在文档切换的临界区里同步操作 UI。4.4 卸载 ARX 后菜单残留重新加载出现两个现象APPLOAD卸载后再加载屏幕上出现两个一样的菜单。原因kUnloadAppMsg里没有销毁窗口或者销毁了但没置空全局指针下次加载又 new 了一个。解决卸载时严格按DestroyWindow→delete→ 指针置nullptr的顺序执行并且把这段逻辑放在kUnloadAppMsg分支里不要依赖析构函数自动清理。4.5 自动隐藏后鼠标划过不弹出现象设了m_bAutoHide TRUE菜单收起后鼠标移到边缘没有反应。原因自动隐藏依赖主框架的WM_MOUSEMOVE命中检测如果菜单窗口的WS_VISIBLE被误关或者CalcDynamicLayout在收起状态下返回了非零尺寸命中区就错位了。解决收起状态由主框架管理不要手动ShowWindow(SW_HIDE)CalcDynamicLayout在LM_HORZ且收起时返回CSize(0, 0)让主框架自己决定展开尺寸。5. 进阶让屏幕菜单记住用户布局并做状态持久化5.1 停靠状态存到注册表还是配置文件用户把菜单拖到右边、调了宽度、设了自动隐藏下次启动应该还原。ARX 里没有现成的布局持久化 API常见做法是自己存。存哪里注册表HKEY_CURRENT_USER\Software\你的产品名\Layout适合单机固定配置配置文件适合需要随项目分发的场景。我一般用注册表因为读写快、不依赖文件路径权限。要存的字段就四个停靠方向、浮动时的窗口矩形、是否自动隐藏、面板宽度。// 保存布局 void SaveLayout(CScreenMenuBar* pBar) { CWinApp* pApp AfxGetApp(); if (!pApp) return; UINT nDock pBar-m_nInitialDock; CRect rcFloat; pBar-GetWindowRect(rcFloat); pApp-WriteProfileInt(_T(ScreenMenu), _T(Dock), nDock); pApp-WriteProfileInt(_T(ScreenMenu), _T(AutoHide), pBar-m_bAutoHide); pApp-WriteProfileInt(_T(ScreenMenu), _T(Width), pBar-m_nWidth); pApp-WriteProfileInt(_T(ScreenMenu), _T(FloatLeft), rcFloat.left); pApp-WriteProfileInt(_T(ScreenMenu), _T(FloatTop), rcFloat.top); }WriteProfileInt底层走的是CWinApp的注册表路径键名用产品名区分避免和其他 ARX 插件冲突。读取时在CreateBar之前把值取出来赋给m_nInitialDock和m_bAutoHide浮动矩形在Create之后用MoveWindow还原。5.2 用 WM_CLOSE 拦截做优雅退出用户点菜单上的关闭按钮时默认行为是销毁窗口但停靠体系里这条 bar 还在管理器注册表里下次加载会冲突。正确做法是拦截WM_CLOSE只做隐藏不做销毁或者通知管理器先RemoveDockBar再销毁。void CScreenMenuBar::OnClose() { // 不真正销毁只隐藏保留注册状态 ShowWindow(SW_HIDE); // 如果确实要销毁先反注册 // CAdUiDockBarManager::GetDockBarManager()-RemoveDockBar(this); // CAdUiDockControlBar::OnClose(); }隐藏和销毁的区别在于隐藏后 bar 仍在管理器里用户可以通过菜单命令重新ShowWindow(SW_SHOW)销毁则要重新走一遍CreateBar。屏幕菜单这种常驻 UI我倾向隐藏避免反复创建销毁带来的句柄泄漏风险。5.3 验证布局是否生效的三个检查点改完持久化逻辑别急着交付按这三个点验一遍第一拖到四个方向各停靠一次重启后方向是否还原第二浮动状态调好矩形重启后位置和大小是否一致第三自动隐藏开关切换后重启状态是否保持。任何一条不通过先查注册表里对应键值有没有写进去再查读取时机是不是在Create之前。我自己的习惯是每次改完布局代码先在注册表里手动删掉整个键从默认状态跑一遍全流程确认没有依赖旧值的隐藏逻辑。这套东西没有后悔药状态错一次用户就会觉得菜单“不听话”所以宁可多验两遍。希望帮到你。本文还有配套的精品资源点击获取
返回列表