AutoCAD C# 二次开发:如何精确监听工作空间切换事件

AutoCAD C# 二次开发:如何精确监听工作空间切换事件

在 AutoCAD 插件开发中,经常需要根据用户切换"工作空间"(Workspace)来动态调整界面、工具栏或者程序逻辑。然而 AutoCAD .NET API 并没有提供名为 WorkspaceChanged 的专用事件。本篇文章将深入剖析原理,并给出一个可直接运行的解决方案。

一、理解工作空间与系统变量 WSCURRENT

AutoCAD 的工作空间(如"草图与注释"、"三维建模"、"AutoCAD 经典")本质上是一组界面配置(功能区、工具栏、菜单、选项板等)的集合。当用户在工作空间下拉菜单或"选项"对话框中切换工作空间时,AutoCAD 内部会修改一个名叫 WSCURRENT 的系统变量,其值就是当前工作空间的名称(字符串)。

因此,如果我们能捕获系统变量变更的事件,并筛选出变量名为 "WSCURRENT" 的情形,就等于捕获了工作空间的切换动作。

二、核心技术:SystemVariableChanged 事件

Autodesk.AutoCAD.ApplicationServices.Application 类提供了一个全局静态事件 SystemVariableChanged。只要 AutoCAD 中任意系统变量发生变化(包括用户主动修改、程序修改、切换工作空间等),该事件就会被触发。

事件参数 SystemVariableChangedEventArgs 包含两个重要属性:

  • Name:发生变化的系统变量名称。
  • Value:新值(注意此处的 Value 可能为 null 或者类型不准确,建议在事件回调中重新通过 GetSystemVariable 获取)。

三、完整实现代码

创建一个类库项目(.NET Framework 4.7.2 或更高,与 AutoCAD 版本匹配),添加对 AcMgd.dllAcDbMgd.dll 的引用。下面是完整的实现。

csharp 复制代码
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.Runtime;

namespace WorkspaceMonitorDemo
{
    public class WorkspaceWatcher
    {
        // 标志位,防止重复注册
        private static bool _isWatching = false;

        /// <summary>
        /// 启动监听工作空间切换
        /// </summary>
        [CommandMethod("START_WORKSPACE_WATCH")]
        public void StartWatch()
        {
            if (_isWatching)
            {
                ShowAlert("工作空间监听已在运行中");
                return;
            }

            Application.SystemVariableChanged += OnSystemVariableChanged;
            _isWatching = true;
            ShowMessage("✓ 工作空间监听已启动。切换工作空间时将在命令行输出信息。");
        }

        /// <summary>
        /// 停止监听工作空间切换
        /// </summary>
        [CommandMethod("STOP_WORKSPACE_WATCH")]
        public void StopWatch()
        {
            if (!_isWatching) return;

            Application.SystemVariableChanged -= OnSystemVariableChanged;
            _isWatching = false;
            ShowMessage("✗ 工作空间监听已停止。");
        }

        private void OnSystemVariableChanged(object sender, SystemVariableChangedEventArgs e)
        {
            // 只关心 WSCURRENT 变量
            if (e.Name == "WSCURRENT")
            {
                // 获取新的工作空间名称(建议重新读取,保证数据准确)
                string newWorkspace = (string)Application.GetSystemVariable("WSCURRENT");
                
                // 获取当前活动文档的编辑器,输出信息
                Document doc = Application.DocumentManager.MdiActiveDocument;
                if (doc != null)
                {
                    doc.Editor.WriteMessage("\n=== 工作空间已切换为: {0} ===\n", newWorkspace);
                }
                else
                {
                    // 无活动文档时(极少见),用对话框提示
                    ShowAlert($"工作空间已切换为: {newWorkspace}");
                }

                // 你可以在这里添加自己的业务逻辑,例如:
                // - 根据工作空间加载/卸载特定的自定义面板
                // - 更新插件界面的状态
                // - 记录用户操作日志等
            }
        }

        private void ShowMessage(string msg)
        {
            var doc = Application.DocumentManager.MdiActiveDocument;
            if (doc != null)
                doc.Editor.WriteMessage("\n" + msg + "\n");
            else
                ShowAlert(msg);
        }

        private void ShowAlert(string msg)
        {
            Application.ShowAlertDialog(msg);
        }
    }
}
csharp 复制代码
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.Runtime;

namespace YourWorkspaceMonitor
{
    public class WorkspaceChangeMonitor
    {
        /// <summary>
        /// 用于启动监听的命令
        /// </summary>
        [CommandMethod("MonitorWorkspaceChange")]
        public void MonitorWorkspaceChange()
        {
            // 将自定义的事件处理方法注册到 Application 对象的 SystemVariableChanged 事件上
            Application.SystemVariableChanged += new 
                SystemVariableChangedEventHandler(SystemVariableChanged);

            // 可选:在命令行输出提示,告知用户监听已启动
            Application.DocumentManager.MdiActiveDocument?.Editor.WriteMessage(
                "工作空间变化监听已启动。\n");
        }

        /// <summary>
        /// 系统变量改变时触发的事件处理方法
        /// </summary>
        void SystemVariableChanged(object sender, 
            SystemVariableChangedEventArgs e)
        {
            // 检查被改变的系统变量是否是 WSCURRENT
            if (e.Name == "WSCURRENT")
            {
                // 获取当前 WSCURRENT 变量的新值,即新工作空间的名称
                string currentWorkspaceName = 
                    (string)Application.GetSystemVariable(e.Name);

                // 获取当前活动文档,在其命令行输出新的工作空间信息
                Document doc = Application.DocumentManager.MdiActiveDocument;
                if (doc != null)
                {
                    doc.Editor.WriteMessage(
                        "工作空间已切换为: {0}\n", currentWorkspaceName);
                }
                else
                {
                    // 如果出于某种原因当前无活动文档,也可以使用 Application 的警报窗口
                    Application.ShowAlertDialog(
                        $"工作空间已切换为: {currentWorkspaceName}");
                }
            }
        }

        /// <summary>
        /// 用于停止监听的命令
        /// </summary>
        [CommandMethod("StopMonitorWorkspaceChange")]
        public void StopMonitorWorkspaceChange()
        {
            // 将之前注册的事件处理方法取消注册,以避免资源泄露
            Application.SystemVariableChanged -= new 
                SystemVariableChangedEventHandler(SystemVariableChanged);

            // 输出提示信息
            Application.DocumentManager.MdiActiveDocument?.Editor.WriteMessage(
                "工作空间变化监听已停止。\n");
        }
    }
}


using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.Runtime;

public class DocumentActivationMonitor
{
    [CommandMethod("MonitorDocActivation")]
    public void MonitorDocActivation()
    {
        // 为 DocumentManager 的 DocumentActivated 事件添加处理方法
        Application.DocumentManager.DocumentActivated += 
            new DocumentCollectionEventHandler(OnDocumentActivated);
    }

    public void OnDocumentActivated(object sender, 
        DocumentCollectionEventArgs e)
    {
        // 当用户在多个CAD图纸间切换时,此方法会被调用
        // e.Document 就是新激活的那个文档对象
        Application.ShowAlertDialog($"您现在正在编辑的图纸是:{e.Document.Name}");
    }
}

四、如何测试

  1. 使用 NETLOAD 命令加载编译好的 DLL。
  2. 输入命令 START_WORKSPACE_WATCH,启动监听。
  3. 在 AutoCAD 右下角或顶部快速访问工具栏切换工作空间(例如从"草图与注释"切换到"三维建模")。
  4. 观察命令行输出:"工作空间已切换为: 三维建模"。
  5. 输入命令 STOP_WORKSPACE_WATCH 停止监听。

五、进阶话题

5.1 避免重复注册

由于 SystemVariableChanged 是静态事件,如果多次调用 StartWatch 而忘记取消注册,会导致事件处理器被重复添加,同一事件触发多次相同逻辑。解决方案:使用一个静态标志位(如上述代码中的 _isWatching),并在命令开始时判断。

5.2 获取切换前的工作空间名称

事件参数 SystemVariableChangedEventArgs 不直接提供旧值。如果需要切换前的旧工作空间名称,可以在事件回调外部缓存一个静态字段,每次 WSCURRENT 变更时,先读取旧值,再更新缓存。示例:

csharp 复制代码
private static string _lastWorkspace = (string)Application.GetSystemVariable("WSCURRENT");

private void OnSystemVariableChanged(object sender, SystemVariableChangedEventArgs e)
{
    if (e.Name == "WSCURRENT")
    {
        string oldWorkspace = _lastWorkspace;
        string newWorkspace = (string)Application.GetSystemVariable("WSCURRENT");
        // 执行业务逻辑...
        _lastWorkspace = newWorkspace;
    }
}

5.3 与文档激活事件的区别

有时开发者会混淆"工作空间切换"与"文档(图纸)激活切换"。后者应监听 Application.DocumentManager.DocumentActivated 事件。两者的应用场景完全不同:

  • 工作空间切换 → 界面布局、功能区、菜单发生变化 → 监听 SystemVariableChanged + WSCURRENT
  • 文档激活 → 用户在不同打开的 .dwg 文件间切换 → 监听 DocumentActivated

六、注意事项

  1. 线程安全SystemVariableChanged 事件回调运行在 AutoCAD 主线程,可以安全调用 AutoCAD API,无需额外 Invoke
  2. 性能 :系统变量变化非常频繁(例如 LASTANGLEORTHOMODE 等),在回调中仅通过 if (e.Name == "WSCURRENT") 进行早期过滤,开销极小。
  3. 与 UI 联动 :如果需要在切换工作空间时自动显示/隐藏自己开发的 PaletteSetRibbon 选项卡,建议设置一个短暂的延迟(Application.DoEvents 或定时器),因为工作空间切换时界面重建并非瞬时完成。
  4. AutoCAD 版本兼容性WSCURRENT 系统变量在 AutoCAD 2009 之后引入(对应功能区界面的工作空间概念)。更早的版本(如 AutoCAD 2007)没有工作空间概念,此方法不适用。

七、总结

通过监听 Application.SystemVariableChanged 事件并筛选 WSCURRENT 变量,我们就能可靠地捕获 AutoCAD 工作空间的每一次切换。这个方法简单、高效,无需调用任何未文档化的 COM API 或发送 Windows 消息。

扩展建议 :你可以将此功能封装成一个单例服务类,供插件中其他模块订阅自定义的 WorkspaceChanged 事件,实现解耦。例如:

csharp 复制代码
public static class WorkspaceService
{
    public static event Action<string> WorkspaceChanged;
    static WorkspaceService() { /* 注册系统变量事件 */ }
    private static void OnSysVarChanged(...) 
    {
        if (e.Name == "WSCURRENT")
            WorkspaceChanged?.Invoke(newWorkspace);
    }
}

监听Application.SystemVariableChanged事件确实没有预设的变量白名单------AutoCAD中的所有系统变量发生变化时(部分除外)都会触发该事件。下面列出开发中需要重点关注的系统变量,以及各自的触发场景和典型值范围。


一、绘图控制类(最常用,几乎每个操作都会涉及)

变量名 数据类型 触发场景 典型值范围
CLAYER 字符串 切换当前图层、新建/删除图层影响当前层时 图层名称
OSMODE 整数(位码) 改变对象捕捉设置时 0--32767(位码组合)
ORTHOMODE 整数 切换正交模式(F8)时 0(关闭)/1(开启)
SNAPMODE 整数 切换捕捉模式(F9)时 0/1
GRIDMODE 整数 切换栅格显示(F7)时 0/1
POLARMODE 整数 修改极轴追踪设置时 0--7(位码)
TRIMODE 整数 修改修剪/延伸模式时 0/1
PICKFIRST 整数 切换"先选择后执行"模式时 0/1
PICKAUTO 整数 修改选择集自动检测方式时 0/1/2/3
PICKBOX 整数 修改拾取框大小(设置 → 显示)时 0--50(像素)
APERTURE 整数 修改靶框大小时 1--50(像素)
VIEWSIZE 只读·实数 视图缩放/平移时 当前视图高度
VIEWCTR 二维点 视图平移时 X,Y坐标
TARGET 三维点 改变视图目标点时 X,Y,Z坐标
UCSNAME 字符串 切换用户坐标系时 UCS名称或空
UCSORG 三维点 移动UCS原点时 X,Y,Z坐标
UCSXDIR / UCSYDIR 三维点 旋转UCS时 方向向量
WORLDUCS 整数 进入/退出世界坐标系时 0/1

二、标注与文字样式类

变量名 数据类型 触发场景 典型值范围
DIMSCALE 实数 修改全局标注比例时 >0
DIMTXT 实数 修改标注文字高度时 >0
DIMSTYLE 字符串 切换当前标注样式、修改标注样式参数时 标注样式名称
TEXTSTYLE 字符串 切换当前文字样式、修改文字样式参数时 文字样式名称
TEXTSIZE 实数 修改默认文字高度时 >0
DIMDEC 整数 修改标注小数位数时 0--8
DIMUNIT 整数 修改标注单位格式时 1--8
DIMTOL 整数 切换标注公差显示时 0/1
DIMTP / DIMTM 实数 修改标注公差上下偏差时 实数
DIMPOST 字符串 修改标注前缀/后缀时 字符串
DIMLUNIT 整数 修改标注线性单位格式时 1--8
DIMDECIMAL 字符 修改标注小数点格式时 "." 或 ","
DIMLFAC 实数 修改线性标注测量比例因子时 >0
DIMGAP 实数 修改尺寸线与文字间距时 ≥0
DIMCLRD / DIMCLRE / DIMCLRT 整数 修改尺寸线/界线/文字颜色时 0--256
CMLEADERSTYLE 字符串 切换当前多重引线样式时 多重引线样式名称

三、界面与显示控制类

变量名 数据类型 触发场景 典型值范围
WSCURRENT 字符串 切换工作空间(如"草图与注释""三维建模")时 工作空间名称
MENUNAME 只读·字符串 加载/卸载菜单文件时 菜单文件路径
RIBBONSTATE 整数 展开/折叠功能区时 0/1
TOOLTIPS 整数 切换工具提示显示时 0/1
CURSORSIZE 整数 修改十字光标大小时 1--100(屏幕百分比)
STATUSBAR 整数 切换状态栏显示时 0/1
SCROLLBAR 整数 切换滚动条显示时 0/1
CMDDIA 整数 修改命令对话框显示模式时 0/1
FILEDIA 整数 修改文件对话框显示模式时 0/1
ATTDIA 整数 修改属性输入对话框显示模式时 0/1
LAYOUTREGENCTL 整数 修改布局重生成控制时 0/2
SHADEDGE 整数 修改着色渲染显示模式时 0--3
SHADEDIF 整数 修改漫反射光与环境光比率时 0--100
FACETRATIO 整数 修改圆柱/圆锥曲面显示精度时 0/1
ISOLINES 整数 修改曲面轮廓线数量时 0--2047
DISPSILH 整数 切换实体轮廓显示时 0/1
LWDISPLAY 整数 切换线宽显示时 0/1
MSOLESCALE 整数 修改OLE对象缩放比例时 >0

四、工作空间相关(常与WSCURRENT配合处理)

变量名 数据类型 触发场景 典型值范围
WORKSPACE 字符串 切换工作空间(同WSCURRENT别名)时 工作空间名称
MENUBAR 整数 切换菜单栏显示状态时 0/1
NAVVCUBE_DISPLAY 整数 切换ViewCube显示时 0--3
NAVVCUBE_LOCATION 整数 移动ViewCube位置时 0--5
NAVSWHEELMODE 整数 切换SteeringWheels模式时 0--9
SHOWLOCKMODE 整数 修改视口锁定显示模式时 0/1
VPLAYEROVERRIDESMODE 整数 切换视口图层特性替代模式时 0/1
LAYOUTNAME 只读·字符串 切换布局选项卡时 布局名称
CTAB 字符串 切换模型/布局空间时 "Model"/布局名称
CVPORT 整数 切换视口时 视口ID
TILEMODE 整数 切换模型/图纸空间布局时 0/1

五、性能与系统类

变量名 数据类型 触发场景 典型值范围
REGENMODE 整数 切换自动重生成模式时 0/1
HPQUICKPREVIEW 整数 修改填充图案预览模式时 0/1
HPDLGMODE 整数 修改填充图案对话框显示模式时 0--2
VTENABLE 整数 修改平滑视图过渡设置时 0--7
VTDURATION 整数 修改平滑视图过渡持续时间时 0--5000(毫秒)
LTSCALE 实数 修改全局线型比例因子时 >0
CELTSCALE 实数 修改当前对象线型比例因子时 >0
MEASUREMENT 整数 修改当前图形单位为英制/公制时 0/1
INSUNITS 整数 修改插入图块时缩放单位时 0--21
INSUNITSDEFSOURCE / INSUNITSDEFTARGET 整数 修改源/目标图形默认单位时 0--21
EDGEMODE 整数 修改修剪/延伸边界模式时 0/1
PROJECTNAME 字符串 修改工程名称时 工程名称或空
PROXYGRAPHICS 整数 切换代理图形保存模式时 0/1
XLOADCTL 整数 修改外部参照加载控制模式时 0/2
INDEXCTL 整数 修改图层索引/空间索引控制时 0--3

注意事项

  1. 并非100%覆盖 :用户界面(如Ribbon下拉菜单)的某些操作可能绕过了系统变量修改的内部通知机制。例如,CLAYER通过Ribbon图层下拉菜单切换时,SystemVariableChanged事件可能不会触发,此时需要改用Application.UIBindings.SystemVariables["CLAYER"].PropertyChanged替代方案。

  2. 只读变量不触发 :只读变量(如ACADVERCDATEVIEWSIZE等)被查询时不会触发事件------该事件仅响应"尝试修改"的动作。

  3. 执行效率 :系统变量变化非常频繁,在回调中务必通过if (e.Name == "目标变量名")进行早期过滤。

  4. 类型转换安全 :通过Application.GetSystemVariable(e.Name)获取的值是object类型,使用时需根据变量类型进行正确转换。

  5. AutoCAD版本差异:某些变量可能在不同版本中存在差异,建议查阅对应版本的产品帮助文档确认变量的有效性与适用范围。

这样一来,插件中任何地方都可以通过 WorkspaceService.WorkspaceChanged += OnWorkspaceChange; 进行关注,代码更加优雅。

希望这篇文章能帮助你解决在 AutoCAD 二次开发中遇到的类似需求。如果你有更多关于 AutoCAD .NET 编程的问题,欢迎在评论区留言讨论。


相关资源

  • AutoCAD .NET 开发者指南 -- SystemVariableChanged 事件
  • 系统变量参考 -- WSCURRENT

(本文代码适用于 AutoCAD 2013 及以上版本,较低版本可能需要调整。)

相关推荐
丷丩1 小时前
MapLibre GL JS第41课:向地图添加图标
前端·javascript·mapbox·maplibre gl js
英俊潇洒美少年1 小时前
前端性能优化:非关键脚本/第三方资源异步加载全解(彻底解决首屏阻塞)
前端·性能优化
用户3721574261351 小时前
如何使用 C# 自动调整 Excel 行高和列宽
c#
AI导出鸭PC端2 小时前
智谱清言怎么生成word文档?AI导出鸭终结乱码烦恼
人工智能·ai·c#·word·豆包·ai导出鸭
开飞机的舒克_2 小时前
vue3+router动态权限路由
前端·vue.js
VitoChang2 小时前
放弃手搓路由吧!用 SolidStart 搞 SPA,真香
前端
GuWenyue2 小时前
告别JS类型坑!Ts为什么在ai时代逐渐成为"第一"语言
前端·算法·typescript
三乐2282 小时前
事件循环是什么东西,一篇文章带你了解
前端·javascript
wuhen_n2 小时前
RAG 核心:向量嵌入与本地向量数据库实战
前端·langchain·ai编程