结论先说:不行。UserControl本身不能独立悬浮,而且就算不套Form,锁冲突问题依然存在,根源不是“有没有外层Form”
1. 为什么UserControl不能脱离Form单独悬浮
UserControl本质只是控件容器,不是顶层窗口,Windows窗体机制强制要求:所有Control必须挂靠在一个顶层Form(HWND顶级窗口)上才能显示。不存在“无Form悬浮UserControl”这个选项。
你截图里的编辑窗口,本质也是一个Form承载控件。
2. 核心:锁冲突和【有没有Form外壳完全无关】
锁异常的根本来源:
代码执行在Win32 UI消息回调上下文(Windows消息循环),而不是ZWCAD的命令执行上下文。
不管是:
- UserControl放在悬浮Form
- UserControl嵌入CAD原生停靠面板
- 任何WinForms控件的点击/双击事件
只要是WinForm控件的事件函数里直接执行CAD数据库事务操作,都属于Windows消息回调,和外层有没有Form无关,都会有概率触发eLockViolation。
哪怕你把PipeDrawToolPanel改成CAD原生Dock面板(ZwCad自带停靠窗口),TreeView双击事件依然是WinForm消息回调,这个锁问题依旧会出现。
你之前在其他地方调用EnsureLayerExist正常,是因为那些调用入口是
[CommandMethod]标记方法,跑在ZWCAD自身的命令消息循环,和WinForm消息栈隔离,所以不会锁冲突。
3. 区分两件事(不要混淆)
- ✅窗口悬浮效果:靠顶层Form / CAD原生停靠窗口实现,视觉悬浮;
- ❌CAD数据库锁安全:由代码执行所在的消息上下文决定,和窗口载体无关。
4. 简单一句话概括
视觉悬浮 = 窗口载体(Form)的能力;
锁冲突 = 代码在哪一个消息循环里跑。
哪怕去掉外层Form这件事能做到,也解决不了锁问题。
5. 最优不变方案(维持你现有悬浮窗口架构,不用改UI结构)
UI层(TreeView双击)只做选择、保存CodeDefine参数,不操作任何CAD数据库。
双击事件仅仅推送一条命令到ZWCAD命令队列,真正的图层、插块、画线业务逻辑,全部放到[CommandMethod]方法里面运行。
UI悬浮窗口完全保留,视觉效果不变,只是把CAD数据库操作剥离出双击回调。
这个方案不用改动你的悬浮窗口、Tree、Panel的任何界面结构,只改动事件内部逻辑,同时彻底解决锁冲突。
补充备选思路(不推荐)
如果硬要在UI事件里直接操作CAD数据库,需要用Application.Idle延迟执行,等CAD主线消息循环空闲再跑数据库代码,但这种方式会有交互延迟、容易漏触发,稳定性远不如CommandMethod方案,不适合绘图工具。
所以:保留现在Form承载Panel的悬浮结构,UI只选要素,绘制业务交给CAD命令,是最合适的选择。
现象解析
直接用OpenMode.ForWrite打开LayerTable直接抛eLockViolation,依然是数据库锁冲突,不是你函数逻辑写错。
底层原因:
WinForm双击回调这个Windows消息到来的时候,ZWCAD内部已经持有LayerTable的读锁(CAD后台UI、属性面板、图层管理器会在消息循环期间持续读取图层表)。
此时你在你的事务里,尝试直接ForWrite去拿LayerTable,拿不到写锁,直接抛出eLockViolation。
- 在
[CommandMethod]命令上下文:CAD把后台UI读锁释放,你可以正常ForRead,也可以直接ForWrite。 - 在WinForm消息回调上下文:CAD不会释放它自己内部持有的读锁,你既不能UpgradeOpen,也不能直接ForWrite打开LayerTable。
这就是为什么:同样的EnsureLayerExist,命令里跑万事大吉;双击事件里,不管你用UpgradeOpen还是直接ForWrite,两种写法全部锁报错。
关键点区分
- 不是你的事务内部逻辑错误;
- 不是LayerTable不能ForWrite;
- 是执行环境:WinForm消息回调中,CAD系统已经占用了LayerTable的共享读锁,你的事务申请写权限直接被拒绝。
为什么之前ForRead可以成功
OpenMode.ForRead可以拿到,因为读锁可以共享;只要你一想要写(UpgradeOpen / ForWrite),就和CAD后台的读锁冲突。
两种可行路径(不用改UI悬浮窗口)
路径A(最优,架构正确)
双击事件完全不碰任何CAD数据库对象。
只保存选中的CodeDefine实例,调用SendStringToExecute投递命令;
图层创建、块导入、插入块全部放到[CommandMethod]方法内部执行。
此时运行上下文切换到CAD命令上下文,CAD释放内部UI锁,
EnsureLayerExist原有代码完全原样复用,不会报错。
路径B(应急,不推荐产品用,仅临时调试)
UI事件内部不直接执行数据库逻辑,等待Application.Idle事件,等CAD主线消息全部处理完毕、释放锁之后,再执行绘制逻辑。
缺点:交互会有微小延迟;ESC、连续点击会出现多次触发,需要做状态锁。
重点:不要试图在TreeView双击回调里面直接操作CAD数据库写操作,无论怎么改LayerTable打开模式,都绕不开这个锁竞争。
你改LayerTable打开方式只是改变报错的位置,不能消除根源冲突。
简单总结:
ForRead:能打开(共享读允许)
UpgradeOpen / ForWrite:在WinForm回调里拿不到写锁,直接eLockViolation。
根源不在你的代码,在消息执行上下文。