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.dll 和 AcDbMgd.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}");
}
}
四、如何测试
- 使用
NETLOAD命令加载编译好的 DLL。 - 输入命令
START_WORKSPACE_WATCH,启动监听。 - 在 AutoCAD 右下角或顶部快速访问工具栏切换工作空间(例如从"草图与注释"切换到"三维建模")。
- 观察命令行输出:"工作空间已切换为: 三维建模"。
- 输入命令
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。
六、注意事项
- 线程安全 :
SystemVariableChanged事件回调运行在 AutoCAD 主线程,可以安全调用 AutoCAD API,无需额外Invoke。 - 性能 :系统变量变化非常频繁(例如
LASTANGLE、ORTHOMODE等),在回调中仅通过if (e.Name == "WSCURRENT")进行早期过滤,开销极小。 - 与 UI 联动 :如果需要在切换工作空间时自动显示/隐藏自己开发的
PaletteSet或Ribbon选项卡,建议设置一个短暂的延迟(Application.DoEvents或定时器),因为工作空间切换时界面重建并非瞬时完成。 - 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 |
注意事项
-
并非100%覆盖 :用户界面(如Ribbon下拉菜单)的某些操作可能绕过了系统变量修改的内部通知机制。例如,
CLAYER通过Ribbon图层下拉菜单切换时,SystemVariableChanged事件可能不会触发,此时需要改用Application.UIBindings.SystemVariables["CLAYER"].PropertyChanged替代方案。 -
只读变量不触发 :只读变量(如
ACADVER、CDATE、VIEWSIZE等)被查询时不会触发事件------该事件仅响应"尝试修改"的动作。 -
执行效率 :系统变量变化非常频繁,在回调中务必通过
if (e.Name == "目标变量名")进行早期过滤。 -
类型转换安全 :通过
Application.GetSystemVariable(e.Name)获取的值是object类型,使用时需根据变量类型进行正确转换。 -
AutoCAD版本差异:某些变量可能在不同版本中存在差异,建议查阅对应版本的产品帮助文档确认变量的有效性与适用范围。
这样一来,插件中任何地方都可以通过 WorkspaceService.WorkspaceChanged += OnWorkspaceChange; 进行关注,代码更加优雅。
希望这篇文章能帮助你解决在 AutoCAD 二次开发中遇到的类似需求。如果你有更多关于 AutoCAD .NET 编程的问题,欢迎在评论区留言讨论。
相关资源
- AutoCAD .NET 开发者指南 --
SystemVariableChanged事件 - 系统变量参考 --
WSCURRENT
(本文代码适用于 AutoCAD 2013 及以上版本,较低版本可能需要调整。)