AutoCAD ARX开发:使用CAdUiPaletteSet创建高效屏幕菜单
1. 项目概述为什么要在ARX里折腾屏幕菜单如果你在AutoCAD二次开发这个圈子里混过一段时间肯定会遇到一个经典难题如何优雅地管理那些功能繁多、但又不想一股脑全塞进Ribbon工具栏或传统对话框里的命令和工具尤其是在开发一些专业插件时比如一个复杂的参数化构件库、一个图层状态管理器或者一个需要频繁切换选项的绘图工具。传统的模态对话框会阻塞主界面用户体验割裂而把几十个按钮全堆在Ribbon上又会显得杂乱无章用户找起来也费劲。这时候一个可以停靠在屏幕侧边、随时展开收起、并且能承载丰富控件的“屏幕菜单”或者叫“选项板”、“工具选项板”就成了一个绝佳的解决方案。在AutoCAD的ObjectARXARX开发体系中CAdUiPaletteSet类就是官方为我们提供的、用来创建和管理这类屏幕菜单的核心武器库。简单来说这次我们要聊的就是如何利用CAdUiPaletteSet这个类在AutoCAD内部打造一个功能强大、交互流畅、用户体验媲美原生功能的侧边栏工具。这不仅仅是创建一个窗口那么简单它涉及到ARX框架下的消息机制、资源管理、界面布局以及与AutoCAD主程序的深度集成。很多新手在初次接触时往往卡在如何创建、如何添加内容、如何响应事件这些环节上。接下来我就结合自己踩过的坑和积累的经验把这个过程掰开揉碎了讲清楚。2. 核心思路与架构设计2.1 CAdUiPaletteSet 是什么它能解决什么问题在深入代码之前我们得先搞清楚CAdUiPaletteSet的定位。你可以把它想象成一个“标签页容器管理器”。它本身不是一个具体的工具面板而是一个可以容纳多个CAdUiPalette工具选项板的框架窗口。这个框架窗口具备以下关键特性可停靠性用户可以将其拖拽到AutoCAD主窗口的左侧、右侧或底部并自动吸附对齐也可以让其浮动。标签页管理一个CAdUiPaletteSet可以包含多个CAdUiPalette每个Palette作为一个标签页用户通过点击标签头进行切换。自动布局框架负责管理其内部所有Palette的显示、隐藏、尺寸以及滚动条。与AutoCAD主题集成其外观颜色、字体等会自动匹配当前AutoCAD的主题设置保证视觉统一。那么它解决了什么问题呢核心就是非模态、持久化、高效率的工具交互。相比于模态对话框它不打断用户当前的绘图操作用户可以一边看着选项板上的参数一边在图形窗口中进行绘制。相比于固定的工具栏它又能提供更丰富的控件如树形列表、属性网格、图像按钮等和更大的信息展示空间。2.2 整体实现方案选型在ARX中创建一个完整的屏幕菜单通常遵循“总-分”结构“总” - CAdUiPaletteSet作为顶层容器负责窗口的创建、显示、销毁以及管理下属的各个子面板。一个应用程序通常只需要一个PaletteSet实例。“分” - CAdUiPalette作为具体的内容载体。每个Palette代表一个功能模块比如“图库”、“属性”、“设置”等。我们需要从CAdUiPalette类派生自己的子类并在其中创建具体的对话框资源CDialog或自定义控件。为什么选择派生CAdUiPalette而不是直接使用CDialog这是因为CAdUiPalette类封装了与CAdUiPaletteSet容器协同工作所需的特定接口和消息处理机制。直接使用CDialog无法获得自动停靠、标签页切换、主题适配等特性。派生后我们可以在OnCreate或OnInitialUpdate等重载函数中创建并嵌入实际的对话框界面。方案流程概览在ARX模块初始化时如acrxEntryPoint中的kInitAppMsg消息处理中创建我们的自定义CAdUiPaletteSet实例。创建并初始化一个或多个自定义的CAdUiPalette派生类实例。将这些Palette实例添加到PaletteSet中。在适当的时机如响应某个命令调用PaletteSet的Display或SetVisible方法将其显示出来。在模块卸载时kUnloadAppMsg安全地销毁PaletteSet及其所有资源。注意CAdUiPaletteSet和CAdUiPalette的生命周期管理至关重要。它们必须与ARX模块的生命周期绑定确保在AutoCAD卸载你的程序前所有窗口资源都已正确释放否则可能导致内存泄漏或AutoCAD崩溃。3. 核心细节解析与实操要点3.1 自定义CAdUiPalette派生类的实现细节这是整个屏幕菜单的“血肉”部分。创建一个典型的自定义Palette类你需要做以下几件事1. 类声明与消息映射// MyToolPalette.h class CMyToolPalette : public CAdUiPalette { DECLARE_DYNAMIC(CMyToolPalette) // 支持运行时类型信息 public: CMyToolPalette(); virtual ~CMyToolPalette(); // 重写关键虚函数 virtual void OnCreate(CWnd* pParentWnd) override; virtual void OnDestroy() override; virtual BOOL OnSaveState(CAdUiState state) const override; virtual BOOL OnRestoreState(const CAdUiState state) override; // 自定义成员函数和数据 void UpdateToolList(); CListCtrl m_wndListCtrl; // 示例一个列表控件 CButton m_btnRefresh; // 示例一个按钮 protected: DECLARE_MESSAGE_MAP() afx_msg void OnSize(UINT nType, int cx, int cy); afx_msg void OnBtnRefreshClicked(); // ... 其他消息处理函数 private: CDialog* m_pDlg; // 指向内嵌对话框的指针 };2. 关键重载函数解析OnCreate: 这是Palette的“出生点”。在这里你需要创建实际的界面。通常有两种方式方式A嵌入一个CDialog资源。这是最常用、最直观的方式。你可以先在资源编辑器里设计好一个对话框记得设置Style为ChildBorder为None然后在这里用Create函数创建它。void CMyToolPalette::OnCreate(CWnd* pParentWnd) { CAdUiPalette::OnCreate(pParentWnd); // 先调用基类 m_pDlg new CMyEmbeddedDlg(this); // CMyEmbeddedDlg是你从CDialog派生的类 if (m_pDlg m_pDlg-Create(IDD_MY_EMBEDDED_DIALOG, this)) { m_pDlg-ShowWindow(SW_SHOW); } else { acutPrintf(_T(\nFailed to create embedded dialog!)); delete m_pDlg; m_pDlg nullptr; } }方式B动态创建控件。对于简单界面也可以直接创建控件并手动布局。void CMyToolPalette::OnCreate(CWnd* pParentWnd) { CAdUiPalette::OnCreate(pParentWnd); CRect rectClient; GetClientRect(rectClient); // 创建列表控件 if (!m_wndListCtrl.Create(WS_CHILD | WS_VISIBLE | LVS_REPORT, CRect(10, 10, rectClient.Width()-10, rectClient.Height()-40), this, IDC_LIST_TOOLS)) { // 处理创建失败 } // 创建按钮 if (!m_btnRefresh.Create(_T(刷新), WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, CRect(10, rectClient.Height()-30, 80, rectClient.Height()-10), this, IDC_BTN_REFRESH)) { // 处理创建失败 } }OnDestroy: 在这里进行资源清理特别是删除动态创建的对象如m_pDlg避免内存泄漏。OnSaveState/OnRestoreState: 这两个函数用于实现用户界面状态的持久化。这是提升用户体验的关键当用户调整了Palette的大小、位置或者折叠/展开了某个区域AutoCAD会调用OnSaveState让你保存这些状态。当Palette下次显示时会调用OnRestoreState来恢复。BOOL CMyToolPalette::OnSaveState(CAdUiState state) const { // 保存列表的列宽 for (int i 0; i m_wndListCtrl.GetHeaderCtrl()-GetItemCount(); i) { int width m_wndListCtrl.GetColumnWidth(i); CString key; key.Format(_T(ColumnWidth_%d), i); state.WriteInt(key, width); } // 保存某个复选框的勾选状态 state.WriteBool(_T(ShowPreview), m_bShowPreview); return CAdUiPalette::OnSaveState(state); // 别忘了调用基类 } BOOL CMyToolPalette::OnRestoreState(const CAdUiState state) { // 恢复列宽 for (int i 0; i m_wndListCtrl.GetHeaderCtrl()-GetItemCount(); i) { CString key; key.Format(_T(ColumnWidth_%d), i); int width 100; // 默认值 if (state.ReadInt(key, width)) { m_wndListCtrl.SetColumnWidth(i, width); } } // 恢复复选框状态 state.ReadBool(_T(ShowPreview), m_bShowPreview); UpdateUI(); // 根据状态更新界面 return CAdUiPalette::OnRestoreState(state); }3. 处理尺寸变化 (OnSize)当用户拖动Palette边框改变其大小时OnSize消息会被触发。你必须在这里重新计算和调整内部控件的位置和大小否则界面会混乱。void CMyToolPalette::OnSize(UINT nType, int cx, int cy) { CAdUiPalette::OnSize(nType, cx, cy); if (m_pDlg ::IsWindow(m_pDlg-GetSafeHwnd())) { // 让内嵌对话框充满整个Palette客户区 m_pDlg-MoveWindow(0, 0, cx, cy); } // 或者手动调整各个控件... // if (::IsWindow(m_wndListCtrl.GetSafeHwnd())) { ... } }3.2 创建与管理CAdUiPaletteSet有了Palette接下来就需要用PaletteSet把它们装起来。1. 创建PaletteSet实例通常在你的ARX应用类或一个全局管理类中将CAdUiPaletteSet作为成员变量。class CMyArxApp : public AcRxArxApp { // ... private: CAdUiPaletteSet* m_pMyPaletteSet; CMyToolPalette* m_pToolPalette; CMySettingsPalette* m_pSettingsPalette; };在初始化函数中创建它void CMyArxApp::createPaletteSet() { // 防止重复创建 if (m_pMyPaletteSet ! nullptr ::IsWindow(m_pMyPaletteSet-GetSafeHwnd())) return; // 1. 创建PaletteSet CString strPaletteSetTitle _T(我的工具箱); // 窗口标题 UINT nPaletteSetId 0x1234; // 一个唯一的ID用于状态持久化 m_pMyPaletteSet new CAdUiPaletteSet(strPaletteSetTitle, nPaletteSetId); // 2. 设置PaletteSet的属性可选但推荐 m_pMyPaletteSet-SetAutoDelete(true); // 设置窗口关闭时自动删除C对象 // 设置允许停靠的位置 m_pMyPaletteSet-SetDockableFloatingFlags( CAdUiPaletteSet::kDockableFloatingFlags_AllowDockLeft | CAdUiPaletteSet::kDockableFloatingFlags_AllowDockRight | CAdUiPaletteSet::kDockableFloatingFlags_AllowFloat ); // 设置初始位置和大小 m_pMyPaletteSet-SetMinimumSize(CSize(250, 300)); // 最小尺寸 // m_pMyPaletteSet-SetRect(...); // 设置初始矩形通常让系统决定或读取保存的状态 // 3. 创建并添加Palette m_pToolPalette new CMyToolPalette(); m_pSettingsPalette new CMySettingsPalette(); // 添加Palette并指定其在标签页上的显示名称 m_pMyPaletteSet-Add(_T(工具), m_pToolPalette); m_pMyPaletteSet-Add(_T(设置), m_pSettingsPalette); // 4. 可选设置默认激活的标签页 m_pMyPaletteSet-SetActivePalette(m_pToolPalette); }2. 显示与隐藏PaletteSet通常通过一个ARX命令来触发显示。static void MyShowToolboxCommand() { CMyArxApp* pApp getMyAppInstance(); // 获取你的应用实例 if (!pApp) return; pApp-ensurePaletteSetCreated(); // 确保PaletteSet已创建 CAdUiPaletteSet* pPalSet pApp-getPaletteSet(); if (pPalSet) { if (!pPalSet-IsVisible()) { pPalSet-SetVisible(TRUE); // 显示 // 或者使用Display方法它更强大可以指定停靠位置 // pPalSet-Display(TRUE, CAdUiPaletteSet::kDockRight, NULL, CAdUiPaletteSet::kInitSizeDefault); } else { // 如果已经显示可以将其激活到前台 pPalSet-SetFocus(); } } }3. 生命周期与销毁在ARX模块的kUnloadAppMsg或应用类的析构函数中必须安全地销毁PaletteSet。void CMyArxApp::destroyPaletteSet() { if (m_pMyPaletteSet) { // 重要先隐藏并销毁窗口 m_pMyPaletteSet-DestroyWindow(); // 如果SetAutoDelete(true)则窗口销毁时会自动删除对象此时应将指针置空。 // 如果未设置自动删除则需要手动delete。 // 这里假设设置了SetAutoDelete(true) m_pMyPaletteSet nullptr; } // Palette对象会被PaletteSet在内部管理销毁我们通常不需要手动delete它们。 m_pToolPalette nullptr; m_pSettingsPalette nullptr; }实操心得关于SetAutoDelete。我强烈建议设置为true。这能让CAdUiPaletteSet遵循MFC窗口对象的常见生命周期管理规则当用户点击窗口关闭按钮或你调用DestroyWindow()时C对象会自动被删除。这比手动管理delete要安全得多能有效避免“窗口已销毁但对象还在”或“重复删除”的经典错误。4. 高级功能与交互实现4.1 在Palette与AutoCAD图形空间之间通信屏幕菜单不是孤岛它需要与AutoCAD的主图形文档AcDbDatabase和编辑器AcEdEditor进行交互。例如点击Palette上的一个图块名将其插入到当前图中。核心机制使用ARX的全局函数、通知机制或自定义消息。方法一直接调用全局命令或函数。在你的Palette按钮点击事件处理函数中可以调用acedCommand或acDocManager-sendStringToExecute来执行AutoCAD命令。void CMyToolPalette::OnBtnInsertBlockClicked() { CString blockName GetSelectedBlockName(); // 假设这个函数获取选中的图块名 if (blockName.IsEmpty()) return; // 方式1使用acedCommand (适用于简单命令序列) struct resbuf *cmdList acutBuildList( RTLB, // 开始列表 RTSTR, _T(_-INSERT), // 命令名 RTSTR, blockName, // 图块名 RTPOINT, asDblArray(AcGePoint3d::kOrigin), // 插入点这里简化了 RTSTR, _T(), // X比例默认 RTSTR, _T(), // Y比例默认 RTSTR, _T(), // 旋转角默认 RTLE, // 结束列表 0); if (cmdList) { acedCommand(cmdList); acutRelRb(cmdList); } // 方式2使用sendStringToExecute (更灵活不阻塞当前调用栈) // CString cmdStr; // cmdStr.Format(_T((command \_-INSERT\ \%s\) ), blockName); // acDocManager-sendStringToExecute(acDocManager-curDocument(), cmdStr); }方法二使用自定义消息或事件机制。对于更复杂的交互比如Palette需要根据当前图形选择集的变化来更新自身内容建议使用观察者模式或ARX的通知器AcApDocManagerReactor,AcEditorReactor。创建一个全局的或应用级的事件管理器。让Palette类订阅相关事件如“选择集已更改”、“文档已切换”。当AutoCAD中发生相应事件时事件管理器通知所有订阅者包括你的Palette。Palette在收到通知后调用自己的UpdateToolList()等方法来刷新界面。这种方式解耦了UI和业务逻辑使代码更清晰、更易于维护。4.2 实现拖放功能 (Drag Drop)让用户可以直接从你的Palette中将一个项目如图标、图块名拖拽到AutoCAD图形区域这是一个极大的体验提升。实现起来稍复杂但思路清晰在Palette控件上启动拖放在你的列表或树形控件CListCtrl/CTreeCtrl中处理LVN_BEGINDRAG或TVN_BEGINDRAG通知消息。在这个消息处理函数中你需要获取被拖拽项的数据如唯一的图块ID或路径。创建一个COleDataSource对象。将你的数据按照某种格式如自定义剪贴板格式CF_TEXT或CF_HDROP放入COleDataSource。调用COleDataSource::DoDragDrop开始拖放操作。在AutoCAD图形区域接受拖放这需要你实现一个AcEdDragDrop反应器AcEdDragDropReactor。你需要派生一个类并重写其虚函数特别是onDragEnter,onDragOver,onDrop。在onDrop函数中你可以从数据对象COleDataObject中提取出在第一步中放入的数据。根据这些数据执行相应的操作比如调用acedCommand插入图块。注意事项拖放功能的实现涉及到COM和OLE的基础知识调试起来可能比较麻烦。务必确保数据格式在拖放两端保持一致并且在DoDragDrop和onDrop中处理好所有的错误情况。初次实现时可以先做一个最简单的文本拖放进行验证。4.3 样式与主题适配为了让你的屏幕菜单看起来和AutoCAD原生组件一样专业需要关注样式适配。颜色和字体CAdUiPalette和其内部的控件默认会继承AutoCAD的主题。但如果你自定义绘制了某些区域应该使用adui_get_system_color()等函数来获取当前主题的系统颜色而不是硬编码颜色值。高DPI支持随着4K显示器的普及高DPI适配变得重要。确保你的对话框资源在“属性”中设置了True Type字体并且考虑在OnCreate或OnSize中根据DPI缩放因子动态调整控件位置和字体大小。可以使用GetDeviceCaps获取DPI。图标使用与AutoCAD图标风格一致的图标通常为16x16, 24x24, 32x32 PNG格式带有透明通道。可以在Palette的标题栏或按钮上使用CAdUiBitmapButton等控件来获得更好的视觉效果。5. 常见问题、调试技巧与性能优化5.1 开发中常见的“坑”与解决方案问题现象可能原因排查与解决思路PaletteSet创建后不显示1. 未调用SetVisible(TRUE)或Display()。2. 创建PaletteSet的代码未在正确的时机执行如命令尚未注册。3. 窗口句柄创建失败。1. 在命令函数中单步调试确认Display或SetVisible被调用。2. 检查new CAdUiPaletteSet和Add方法是否成功检查指针是否为空。3. 在OnCreate中加入acutPrintf输出看是否被调用。内嵌对话框/控件显示为空白或错位1.OnCreate中创建控件失败。2. 未正确处理OnSize消息控件尺寸未更新。3. 对话框资源风格设置错误如不是Child风格。1. 检查Create函数的返回值检查资源ID是否正确。2. 在OnSize中设置断点检查cx, cy参数并确认控件MoveWindow被调用。3. 在资源编辑器中检查对话框属性。关闭AutoCAD时崩溃1.CAdUiPaletteSet或CAdUiPalette对象未正确销毁导致双重删除。2. 在析构函数或OnDestroy中访问了已销毁的MFC对象。1. 确保遵循“窗口销毁自动删除对象”的模式SetAutoDelete(true)并理清所有权关系。2. 在销毁前使用::IsWindow(hWnd)判断窗口句柄是否有效。3. 使用调试工具如Visual Studio的调试器查看崩溃时的调用栈。状态如窗口位置、列宽无法保存1. 未重写OnSaveState/OnRestoreState或重写但未正确读写数据。2. 保存的键名Key不唯一或格式错误。3. 读写的数据类型与函数不匹配。1. 确认这两个虚函数已被正确重写并调用。2. 在OnSaveState中使用acutPrintf输出你保存的值在OnRestoreState中输出读取的值对比是否一致。3. 检查CAdUiState的WriteInt,ReadInt等函数调用是否正确。与AutoCAD交互如执行命令无效1. 命令字符串格式错误。2. 在错误的文档上下文或事务中执行。3.acedCommand使用resbuf结构错误。1. 先在AutoCAD命令行手动输入命令确认语法正确。2. 使用acDocManager-curDocument()确保获取到当前活动文档。3. 使用acutBuildList辅助构建resbuf并仔细检查括号配对RTLB/RTLE。5.2 调试技巧使用acutPrintf进行日志输出这是ARX开发中最简单直接的调试手段。在关键函数入口、错误分支处输出信息可以在AutoCAD的文本窗口F2看到。利用Visual Studio调试器将AutoCAD设置为调试启动项目。你可以在自己的ARX代码中设置断点单步执行观察变量。这对于追踪消息流、检查指针状态至关重要。检查资源ID对话框、控件、字符串的ID冲突是MFC开发中常见的静默错误。确保你的资源ID在resource.h中定义唯一并且与代码中使用的ID完全一致。关注消息映射如果某个消息如按钮点击没有响应首先检查BEGIN_MESSAGE_MAP和END_MESSAGE_MAP是否包含了对应该消息的条目以及消息处理函数的声明和实现是否正确。5.3 性能优化建议惰性加载如果某个Palette内容非常复杂例如包含一个需要从网络加载大量数据的三维预览不要在OnCreate中就初始化所有内容。可以等到该Palette第一次被激活OnSetActive时再加载。虚拟列表/树控件如果CListCtrl或CTreeCtrl需要显示成千上万行数据务必使用虚拟模式LVS_OWNERDATA。在这种模式下控件只保存索引数据由你按需提供可以极大减少内存占用和初始化时间。避免频繁刷新不要在任何可能被频繁调用的函数如OnTimer或无节制的消息处理函数中进行全界面刷新。使用标志位m_bNeedUpdate和延迟更新机制PostMessage一个自定义的更新消息。图形资源管理图标、位图等资源在使用后应及时释放DeleteObject。对于频繁使用的资源可以设计一个缓存管理器避免重复加载。6. 一个完整的迷你示例简易图块选项板为了把上面的理论串联起来这里给出一个极度简化的、但可运行的示例框架实现一个只有一个标签页、一个列表显示图块名、点击插入的简易选项板。步骤1创建对话框资源在资源编辑器里创建一个ID为IDD_PALETTE_BLOCK的对话框风格为Child边框为None。上面放一个CListCtrlID设为IDC_LIST_BLOCKS。步骤2定义Palette派生类// BlockPalette.h #pragma once #include AdUiPalette.h #include AdUiPaletteSet.h #include afxtempl.h // 用于CArray class CBlockPalette : public CAdUiPalette { DECLARE_DYNAMIC(CBlockPalette) public: CBlockPalette(); virtual ~CBlockPalette(); virtual void OnCreate(CWnd* pParentWnd) override; virtual void OnDestroy() override; virtual BOOL OnSaveState(CAdUiState state) const override; virtual BOOL OnRestoreState(const CAdUiState state) override; void RefreshBlockList(); // 刷新列表内容 protected: DECLARE_MESSAGE_MAP() afx_msg void OnSize(UINT nType, int cx, int cy); afx_msg void OnNMDblclkListBlocks(NMHDR* pNMHDR, LRESULT* pResult); private: CDialog* m_pEmbeddedDlg; CListCtrl m_wndList; CArrayCString, CString m_blockNames; // 存储图块名 };步骤3实现Palette派生类// BlockPalette.cpp #include StdAfx.h #include BlockPalette.h #include resource.h // 包含对话框资源ID IMPLEMENT_DYNAMIC(CBlockPalette, CAdUiPalette) BEGIN_MESSAGE_MAP(CBlockPalette, CAdUiPalette) ON_WM_SIZE() ON_NOTIFY(NM_DBLCLK, IDC_LIST_BLOCKS, CBlockPalette::OnNMDblclkListBlocks) END_MESSAGE_MAP() CBlockPalette::CBlockPalette() : m_pEmbeddedDlg(nullptr) {} CBlockPalette::~CBlockPalette() {} void CBlockPalette::OnCreate(CWnd* pParentWnd) { CAdUiPalette::OnCreate(pParentWnd); // 创建内嵌对话框 m_pEmbeddedDlg new CDialog(); if (!m_pEmbeddedDlg-Create(IDD_PALETTE_BLOCK, this)) { acutPrintf(_T(\nFailed to create block palette dialog.)); delete m_pEmbeddedDlg; m_pEmbeddedDlg nullptr; return; } // 获取列表控件 m_wndList.SubclassDlgItem(IDC_LIST_BLOCKS, m_pEmbeddedDlg); m_wndList.InsertColumn(0, _T(图块名称), LVCFMT_LEFT, 200); // 初始刷新列表 RefreshBlockList(); } void CBlockPalette::OnDestroy() { if (m_pEmbeddedDlg) { m_pEmbeddedDlg-DestroyWindow(); delete m_pEmbeddedDlg; m_pEmbeddedDlg nullptr; } CAdUiPalette::OnDestroy(); } void CBlockPalette::OnSize(UINT nType, int cx, int cy) { CAdUiPalette::OnSize(nType, cx, cy); if (m_pEmbeddedDlg ::IsWindow(m_pEmbeddedDlg-GetSafeHwnd())) { m_pEmbeddedDlg-MoveWindow(0, 0, cx, cy); // 调整列表控件大小假设对话框客户区只有这个列表 if (::IsWindow(m_wndList.GetSafeHwnd())) { CRect rect; m_pEmbeddedDlg-GetClientRect(rect); rect.DeflateRect(2, 2, 2, 2); // 留点边距 m_wndList.MoveWindow(rect); m_wndList.SetColumnWidth(0, rect.Width() - 20); // 调整列宽 } } } void CBlockPalette::RefreshBlockList() { if (!::IsWindow(m_wndList.GetSafeHwnd())) return; m_wndList.DeleteAllItems(); m_blockNames.RemoveAll(); // 这里简化遍历当前数据库的块表获取所有块定义名 AcDbDatabase* pDb acdbHostApplicationServices()-workingDatabase(); if (!pDb) return; AcDbBlockTable* pBlockTable nullptr; if (pDb-getBlockTable(pBlockTable, AcDb::kForRead) Acad::eOk) { AcDbBlockTableIterator* pIter nullptr; if (pBlockTable-newIterator(pIter) Acad::eOk) { for (; !pIter-done(); pIter-step()) { AcDbBlockTableRecord* pRecord nullptr; if (pIter-getRecord(pRecord, AcDb::kForRead) Acad::eOk) { TCHAR* pName nullptr; if (pRecord-getName(pName) Acad::eOk) { // 过滤掉模型空间和图纸空间等特殊块 if (_tcscmp(pName, ACDB_MODEL_SPACE) ! 0 _tcscmp(pName, ACDB_PAPER_SPACE) ! 0) { int idx m_wndList.InsertItem(m_wndList.GetItemCount(), pName); m_blockNames.Add(pName); } acutDelString(pName); } pRecord-close(); } } delete pIter; } pBlockTable-close(); } } void CBlockPalette::OnNMDblclkListBlocks(NMHDR* pNMHDR, LRESULT* pResult) { LPNMITEMACTIVATE pNMItemActivate reinterpret_castLPNMITEMACTIVATE(pNMHDR); int nItem pNMItemActivate-iItem; if (nItem 0 nItem m_blockNames.GetSize()) { CString cmdStr; cmdStr.Format(_T(_-INSERT \%s\ ), m_blockNames[nItem]); acDocManager-sendStringToExecute(acDocManager-curDocument(), cmdStr); } *pResult 0; } BOOL CBlockPalette::OnSaveState(CAdUiState state) const { // 保存列表排序状态等此处略 return CAdUiPalette::OnSaveState(state); } BOOL CBlockPalette::OnRestoreState(const CAdUiState state) { // 恢复状态此处略 return CAdUiPalette::OnRestoreState(state); }步骤4创建PaletteSet并注册命令在你的主应用初始化代码中// 全局或应用类成员 CAdUiPaletteSet* g_pBlockPaletteSet nullptr; CBlockPalette* g_pBlockPalette nullptr; void InitMyPalette() { if (g_pBlockPaletteSet) return; g_pBlockPaletteSet new CAdUiPaletteSet(_T(我的图块库), 0xABCD); g_pBlockPaletteSet-SetAutoDelete(true); g_pBlockPalette new CBlockPalette(); g_pBlockPaletteSet-Add(_T(图块), g_pBlockPalette); } static void SHOWMYBLOCKSPALETTE() { InitMyPalette(); if (g_pBlockPaletteSet !g_pBlockPaletteSet-IsVisible()) { g_pBlockPaletteSet-Display(TRUE); } } // 在 acrxEntryPoint 的 kInitAppMsg 中注册命令 SHOWMYBLOCKSPALETTE这个示例虽然简单但涵盖了从创建、布局、内容填充到交互的完整链条。你可以在此基础上逐步添加状态保存、过滤搜索、图标显示、拖放支持等高级功能。开发ARX屏幕菜单是一个系统工程它考验的不仅是对CAdUiPaletteSet这个类的熟悉程度更是对MFC、Windows消息机制、ARX API以及良好软件设计理念的综合运用。从简单的工具面板到复杂的专业工作台这个框架都能提供坚实的支撑。关键在于多动手、多调试遇到问题时善用官方文档、ObjectARX SDK的样例代码以及开发社区的资源。当你亲手打造的第一个屏幕菜单在AutoCAD中流畅运行起来时那种成就感会让你觉得所有的折腾都是值得的。

相关新闻

Valhalla 静态工程审阅 #017|DeepSeek-V3 源码证据驱动评测【大厂开源基础设施特辑】

Valhalla 静态工程审阅 #017|DeepSeek-V3 源码证据驱动评测【大厂开源基础设施特辑】

Valhalla 静态工程审阅 #017|DeepSeek-V3 源码证据驱动评测【大厂开源基础设施特辑】硬核工业风技术文章,建议搭配封面图阅读。 本文基于固定 Commit 快照开展只读静态工程审阅,不代表动态安全结论;所有观测均以可复查源码证据为边…

2026/8/4 15:26:49 阅读更多 →
AI赋能笔记工具:智能标注与语义检索实践

AI赋能笔记工具:智能标注与语义检索实践

1. 项目概述:当传统笔记遇上AI技术 这个标题让我想起自己十年前刚入行时,还在用纸质笔记本记录工作要点。后来经历了Evernote、OneNote等数字笔记工具的洗礼,直到现在开始尝试将AI能力整合进笔记工作流。从Notes到AI再到Notes的闭环&#xff…

2026/8/4 15:26:49 阅读更多 →
SQL 白名单漏了 DELETE 子查询?我在 Taotoken 上拦截了 3 种高危 MCP 调用

SQL 白名单漏了 DELETE 子查询?我在 Taotoken 上拦截了 3 种高危 MCP 调用

大模型时代数据库操作安全实战:从SQL注入防御到MCP最佳实践 事故复盘:17条订单消失的背后 2023年12月15日凌晨2:17,我们的订单数据库发生了一起离奇事件。在常规的压测过程中,监控系统突然发出警报——orders表中有17条记录被异常…

2026/8/4 15:26:49 阅读更多 →

最新新闻

HRP DSL 酶标曼陀罗凝集素在糖蛋白分布、细胞糖组表征领域科研应用汇总

HRP DSL 酶标曼陀罗凝集素在糖蛋白分布、细胞糖组表征领域科研应用汇总

基本性质 英文名称:HRP-Datura Stramonium Lectin (DSL), HRP-DSL 中文名称:HRP 标记的曼陀罗凝集素 来源物种:曼陀罗 (Datura stramonium) 种子 外观状态:固体粉末或溶液 储存条件:-20℃ 避光干燥保存(严禁…

2026/8/4 16:12:18 阅读更多 →
3分钟学会B站视频永久保存:m4s-converter跨平台无损转换工具完整指南

3分钟学会B站视频永久保存:m4s-converter跨平台无损转换工具完整指南

3分钟学会B站视频永久保存:m4s-converter跨平台无损转换工具完整指南 【免费下载链接】m4s-converter 一个跨平台小工具,将bilibili缓存的m4s格式音视频文件合并成mp4 项目地址: https://gitcode.com/gh_mirrors/m4/m4s-converter 你是否曾为B站缓…

2026/8/4 16:12:18 阅读更多 →
OpenHands手势识别:从入门到工程优化

OpenHands手势识别:从入门到工程优化

1. OpenHands项目概述OpenHands是一个基于计算机视觉的手势识别开源项目,它能够通过普通摄像头实时检测和识别用户的手部动作。这个项目最初由卡内基梅隆大学的研究团队开发,旨在为开发者提供一套简单易用的手势交互解决方案。在实际应用中,我…

2026/8/4 16:12:18 阅读更多 →
国产楼宇自控系统哪家靠谱?

国产楼宇自控系统哪家靠谱?

一、为什么做智能化项目,业内都重点推荐裕乾?当下智慧建筑、园区节能、老旧楼宇改造市场需求持续上涨,很多工程总包、物业、政企业主在筛选系统集成供应商时,都会优先锁定裕乾。对比外资品牌与小型贴牌厂商,裕乾凭借自研技术、完…

2026/8/4 16:12:17 阅读更多 →
热-光耦合分析原理与工程实践指南

热-光耦合分析原理与工程实践指南

1. 热-光耦合分析的本质与工程价值 多物理场耦合仿真中的热-光耦合分析,本质上研究的是温度场与光场之间的相互作用机制。这种耦合效应在工程实践中极为常见——当光学元件工作时,其温度分布会改变材料折射率;而折射率变化又会反过来影响光路…

2026/8/4 16:12:17 阅读更多 →
Cursor Pro破解工具技术架构深度解析:从机器标识重置到多平台兼容的完整解决方案

Cursor Pro破解工具技术架构深度解析:从机器标识重置到多平台兼容的完整解决方案

Cursor Pro破解工具技术架构深度解析:从机器标识重置到多平台兼容的完整解决方案 【免费下载链接】cursor-free-vip [Support 0.45](Multi Language 多语言)自动注册 Cursor Ai ,自动重置机器ID , 免费升级使用Pro 功能…

2026/8/4 16:11:17 阅读更多 →

日新闻

AI Agent白手起家26: 使用标准事件驱动大模型实践

AI Agent白手起家26: 使用标准事件驱动大模型实践

纲要 练习目标:掌握大模型标准事件的调用回顾 LangChain 中的核心标准事件 invokestreambatchastream_eventswith_structured_output 环境准备实战代码:多种事件调用对比 同步调用与流式输出批量处理异步事件流监听结构化输出 运行说明与预期结果总结与扩…

2026/8/4 0:00:40 阅读更多 →
dealsea是什么?跨境卖家必知的美国deal站入门指南

dealsea是什么?跨境卖家必知的美国deal站入门指南

说实话,第一次听说美国这个老牌折扣网站的跨境卖家,十个有八个会问同一个问题:这个平台到底是干嘛的?我见过一个做家居出口的朋友,他在亚马逊上月销二十万美金,却从来没用过它。我给他看了首页——一屏一屏…

2026/8/4 0:01:40 阅读更多 →
清华大学重磅EST:植物自导电闪蒸焦耳热600°C/2600°C两步法!稀土超积累植物秒级转化为CeO₂-石墨烯电催化剂!

清华大学重磅EST:植物自导电闪蒸焦耳热600°C/2600°C两步法!稀土超积累植物秒级转化为CeO₂-石墨烯电催化剂!

通讯作者:邓兵、刘建国通讯单位:清华大学DOI:https://doi.org/10.1021/acs.est.6c00603研究背景稀土元素(REEs)是清洁能源技术与电子器件不可或缺的核心原料,然而传统提取方式依赖能耗高、排放大的采矿与强…

2026/8/4 0:01:40 阅读更多 →

周新闻

最大流算法详解:从水管网络到Ford-Fulkerson与Dinic实战

最大流算法详解:从水管网络到Ford-Fulkerson与Dinic实战

1. 从水管网络到最大流:一个核心问题的诞生想象一下,你是一个城市供水系统的总工程师。你的城市有多个水源(水库),需要通过一个复杂的地下管道网络,将水输送到各个居民区。每条管道都有其最大通水能力&…

2026/8/4 13:24:41 阅读更多 →
基于Springboot的企业门户网站(源码+LW+调试文档+讲解)

基于Springboot的企业门户网站(源码+LW+调试文档+讲解)

温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台…

2026/8/4 11:41:39 阅读更多 →
MATLAB xcorr函数详解:从互相关原理到四大实战应用

MATLAB xcorr函数详解:从互相关原理到四大实战应用

1. 从一次信号“找茬”说起:为什么我们需要互相关几年前,我在处理一组声学传感器数据时遇到了一个棘手的问题。我有两个麦克风记录了一段相同的音频信号,理论上它们接收到的声音波形应该非常相似,只是由于麦克风位置不同&#xff…

2026/8/4 5:26:40 阅读更多 →

月新闻

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南 【免费下载链接】BaiduNetdiskPlugin-macOS For macOS.百度网盘 破解SVIP、下载速度限制~ 项目地址: https://gitcode.com/gh_mirrors/ba/BaiduNetdiskPlugin-macOS 还在为百度网盘macOS版的龟速下…

2026/8/4 13:38:24 阅读更多 →
终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换 【免费下载链接】ncmdump 项目地址: https://gitcode.com/gh_mirrors/ncmd/ncmdump 还在为网易云音乐下载的NCM格式文件无法在其他播放器播放而烦恼吗?ncmdump解密工具帮你轻松解决这个困…

2026/8/4 11:09:16 阅读更多 →
HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

AgentCard 智能体卡片:为英语学习 App 打造桌面级学习助手适用平台:HarmonyOS 7.0 (API 26 Beta)一、引言 HarmonyOS 7.0(API 26 Beta)新增了 AgentCard 智能体卡片能力,这是继 HMAF(鸿蒙智能体框架&#x…

2026/8/4 13:38:40 阅读更多 →