基线:全量源码首提(D1 版本控制落地,含第一轮优化 B1-B4/A1/A4/缩放修复)

This commit is contained in:
dqb-dev
2026-10-01 22:15:22 +08:00
commit 1e65792b09
499 changed files with 204579 additions and 0 deletions
@@ -0,0 +1,166 @@
---
name: sd-enhanced-components
description: SharpDevelop 5.x WinForms 设计器**全部工具箱**(5 分类 81 条目)的属性手册与 Designer.cs 代码生成规则:增强组件(Smart.CustomComponents 13 个:停靠面板/停靠窗口、圆角按钮、图片按钮、侧边菜单、功能区 RibbonStrip、增强数据表格、多选下拉框、可调高文本框/下拉框、OK/NG 状态灯、变量绑定控件、日志组件)+ 标准控件(Button/TextBox/ListView/TreeView/TabControl/菜单工具栏/对话框等 45 个)+ 数据/组件/打印(BindingSource、Timer、SerialPort、PrintDocument 等 23 个)。每个控件含全部属性(类型/默认值/序列化规则)、事件与运行时 API。当用户要在 WinForms 项目里使用、询问属性、手写或生成任何 *.Designer.cs 代码、生成停靠窗口类、排查"?"载入错误或"valid theme"运行错误时,必须使用本技能。提到"增强组件""Smart.CustomComponents""停靠窗口""功能区""RibbonStrip""工具箱""某个控件有哪些属性""生成设计器代码"即触发。新建解决方案时用 copy-to-solution.cmd 把本技能复制到项目里。
---
# SharpDevelop 增强组件(Smart.CustomComponents)手册
库源码:`F:\Visual Studio\1\33\SharpDevelop-master\src\Libraries\CustomComponents\`(本技能内容即从该源码与编译产物反射逐项提取,属性名/类型/默认值以源码为准)。
工具箱类别名:**增强组件**(SharpDevelopControlLibrary.sdcl 注册,13 个控件)。
## 使用本技能的典型任务
1. 在用户项目里手工使用/推荐这些控件的属性("这个控件有哪些属性")
2. 生成或校对 `*.Designer.cs`(SharpDevelop CodeDom 序列化风格)
3. 从停靠窗口集合配置生成窗口类三件套(等价于"生成停靠窗口代码"命令)
4. 排错:设计器载入 `?`、运行时 "DockPanel.Theme must be set to a valid theme"
## 工具箱全量清单(5 分类 81 条目,权威来源 data\options\SharpDevelopControlLibrary.sdcl)
| 分类 | 条目数 | 属性手册 |
|---|---|---|
| 增强组件(Smart.CustomComponents) | 13 | `references/components.md` |
| Windows Forms(标准控件+菜单+对话框) | 45 | `references/winforms-controls.md` |
| Data(绑定/表格/数据集) | 7 | `references/winforms-components.md` |
| Components(后台/IO/系统组件) | 11 | `references/winforms-components.md` |
| Printing(打印全家桶) | 5 | `references/winforms-components.md` |
(Timer/ErrorProvider/HelpProvider/ImageList 在 Windows Forms 与 Components 两个分类重复出现,规则一致;Data 分类里的 DataNavigator 在 .NET 4.8 不存在,是死条目,用 BindingNavigator。)
**读哪个文件**:增强组件 → `references/components.md`;标准控件/菜单/对话框 → `references/winforms-controls.md`;组件/数据/打印 → `references/winforms-components.md`;**写任何 Designer.cs 代码前再读 `references/codegen.md`**(骨架模板、Content 集合、菜单嵌套、ListView/TreeView、RibbonStrip 三层集合、非可视组件、停靠窗口三文件流程)。
## 复制到新解决方案(三种方式,已自动化)
本技能的分发链(源→编译产物→新方案):
```
用户级正本 C:\Users\099978\.agents\skills\sd-enhanced-components\ ←源(改这里)
① 编译 Smart.CustomComponents 时同步到 SharpDevelop 仓库根副本 + bin\sd-enhanced-components\
(bin = 设计器编译输出/测试运行目录)
② SharpDevelop 新建解决方案时自动复制到 <新方案>\.agents\skills\sd-enhanced-components\
```
1. **全自动**:本定制版 SharpDevelop 在"文件→新建→解决方案"落盘后自动复制(`ProjectTemplate.CreateAndOpenSolution` 里调 `SkillFolderSync`,日志在 `bin\SkillSync.Log.txt`)。源找不到时自动回退:先 bin,再应用根目录。
2. **手动脚本**:`copy-to-solution.cmd <解决方案目录>`(同样复制到 `<目录>\.agents\skills\sd-enhanced-components\`)。
3. **全局生效**:用户级技能目录本就全局生效——复制是留档与给其他 AI 工具。
改手册后同步三处:用户级正本 → 重编 Smart.CustomComponents(自动进仓库根副本 + bin)→ 新方案由 SharpDevelop 自动带。
## 零、项目组织铁律(用户强制,最高优先级)
1. **UI 只写主窗口**:新建解决方案自带的主窗口 MainForm(MainForm.cs / MainForm.Designer.cs)是唯一 UI 承载体,所有控件/布局/Designer.cs 生成一律写进 MainForm;Program.cs 保持 `Application.Run(new MainForm())`。
2. **绝不另开窗口写 UI**:不要为了放界面新建 TestForm/Form2 之类第二个窗体;多窗口只在用户明确要求时才做。
3. **AI 生成 Designer.cs 前必须先写引用(否则死锁)**:手写/生成 Designer.cs 用了增强控件而项目尚无程序集引用时,设计器加载第一步(类型解析)就失败(一片 CS0246),而"自动补引用"恰恰跑在设计器加载流程内部——类型解析不过它就没机会执行,形成死循环:**引用缺失 → 设计器加载失败 → 自动补引用不触发 → 引用依然缺失**。所以**写 Designer.cs 的同一次改动里必须先把 9 个引用写进 csproj**(完整清单见第一章)。HintPath **默认指向 SharpDevelop 安装目录** `C:\Program Files (x86)\SharpDevelop\5.2\bin`——装完即存在、机器间一致;**装在非默认位置时先用第一章"定位实际安装目录"找到真实 bin 再写**;开发机要立刻用仓库最新控件时可临时换仓库 bin `F:\Visual Studio\1\33\SharpDevelop-master\bin`(仅本机存在,**交付/通用模板别用**)。前提:安装目录必须是最新构建(jk55 案例:08-27 旧安装包缺 RibbonStrip/Logger,13 控件只认 11——须重打 MSI 重装或用仓库 bin 最新 DLL 覆盖安装目录)。"切设计标签自动补引用"只在**工具箱拖放**场景有效(那时设计器本来就能加载)。`<Private>True</Private>` 编译时会把 DLL 复制进项目 bin\Debug,之后运行/分发不再依赖 HintPath 原位置。案例:jk55 项目(2026-09-01)。
4. 历史测试项目里 AI 写的 TestForm(如 ww123)是测试产物,**不是范本**;交付写法以本节为准。
5. **属性白名单 + 报错后完全重启(s23 案例 2026-09-01)**:Designer.cs 每个属性必须能在 components.md 该控件条目下查到——跨控件抄属性会抛 `CodeDomSerializerException`("没有名为 X 的属性")并**中止整个设计器加载**(已知易错:VariableEditTextBox 无 PlaceholderText、VariableDisplayLabel 无 BorderStyle)。改完报错的 Designer.cs 后**必须完全退出 SharpDevelop.exe 重开**(关标签页没用)——设计器 CodeDom 反序列化的 AppDomain 在进程内缓存旧异常,不重启会一直回放。
6. **Designer.cs 内绝不调用自定义辅助方法(H1 案例 2026-09-01,适用于所有项目)**:`InitializeComponent()` 里只写标准 CodeDom 语句(new/属性赋值/集合 Add/AddRange/事件挂接/Controls.Add)。自定义方法调用(如 `ConfigurePage(...)`)会被反序列化错误解析成基类 Form 方法,抛 `MissingMethodException` 并中止加载。处置:只改该项目自己的 Designer.cs(删调用+删方法定义,展开成属性赋值),**不动 SharpDevelop 本身、不动设计器配置**;重新编译项目后完全退出 SharpDevelop 再重开(清进程内异常缓存)。
7. **控件库修 bug 后,用户项目必须重编一次才吃到**(成套报价软件案例 2026-09-01):项目引用是 `<Private>True</Private>`——编译时把控件 DLL **拷贝**进项目 `bin\Debug`,之后运行/设计器用的都是这份拷贝。所以控件库更新后:改控件 → 重编 Smart.CustomComponents → **用户项目也要重编**(拷贝才会刷新);排查"控件还有老 bug"先看项目 `bin\Debug\Smart.CustomComponents.dll` 时间戳,不是看仓库 bin。
## 一、项目前提(缺一不可)
用这些控件的用户项目必须是:
```xml
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<PlatformTarget>x86</PlatformTarget> <!-- SharpDevelop 调试器仅 32 位;SQLite 为 x86 混合模式 -->
<Prefer32Bit>true</Prefer32Bit>
<ApplicationManifest>app.manifest</ApplicationManifest> <!-- dpiAware,否则运行时缩放 2/3 -->
```
引用清单(老式 csproj 引用不传递,**9 个全要**,HintPath 默认指向 **SharpDevelop 安装目录** `C:\Program Files (x86)\SharpDevelop\5.2\bin`(稳定、装完即有);开发机临时用仓库 bin 见零章第 3 条):
| 程序集 | 备注 |
|---|---|
| Smart.CustomComponents, Version=1.0.0.0, Culture=neutral, PublicKeyToken=ba044a4aa43e0dc4 | 主库,强命名 |
| WeifenLuo.WinFormsUI.Docking | DockSurface 基类 DockPanel |
| WeifenLuo.WinFormsUI.Docking.ThemeVS2015 | VS2015 主题(含关闭按钮补丁) |
| System.Data.SQLite | x86 混合模式 1.0.119(增强表格用,无需 Interop.dll) |
| log4net | **Logger 日志组件后端**(漏了 Logger 报类型解析失败) |
| System.Resources.Extensions / System.Memory / System.Runtime.CompilerServices.Unsafe / System.Numerics.Vectors | **二级依赖链**(主题资源加载必需,漏了运行时报 valid theme) |
HintPath 模板:`C:\Program Files (x86)\SharpDevelop\5.2\bin\<dll 名>`,并加 `<Private>True</Private>`(可复制 csproj 片段见 codegen.md §0.1)。
工具箱拖放会自动补齐引用(此时设计器能加载,自动补才跑得起来);AI 手写 Designer.cs 必须按零章第 3 条先写引用。
### 定位实际安装目录(装在非默认位置时,AI 按顺序执行)
注册表卸载键的 `InstallLocation` 是**空的**(WiX 安装包不写该值),别指望它。按可靠性依次试:
```powershell
# ① IDE 正在运行:进程路径(exe 所在目录即 bin,最直接)
Get-Process SharpDevelop -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Path
# ② 开始菜单快捷方式解析(已实测可用)
$s = New-Object -ComObject WScript.Shell
Get-ChildItem "$env:ProgramData\Microsoft\Windows\Start Menu\Programs", "$env:AppData\Microsoft\Windows\Start Menu\Programs" -Recurse -Filter '*SharpDevelop*.lnk' -ErrorAction SilentlyContinue | ForEach-Object { $s.CreateShortcut($_.FullName).TargetPath }
# ③ 已有项目的 HintPath(能跑通的项目里记的就是真实安装位置)
Get-ChildItem "$env:USERPROFILE\Documents\SharpDevelop Projects" -Recurse -Filter *.csproj -ErrorAction SilentlyContinue | Select-String 'Smart\.CustomComponents\.dll.*HintPath'
# ④ 常见默认位置探测
Test-Path 'C:\Program Files (x86)\SharpDevelop\5.2\bin\SharpDevelop.exe'
Test-Path 'C:\Program Files\SharpDevelop\5.2\bin\SharpDevelop.exe'
```
找到的 `SharpDevelop.exe` 所在目录就是 HintPath 前缀(安装布局固定为 `...\SharpDevelop\<版本>\bin\`)。仍找不到时问用户装在哪,不要瞎猜路径写进 csproj。
## 二、控件速查(详见 references/components.md;标准控件见 winforms-controls.md)
| 控件 | 类型全名(命名空间 Smart.CustomComponents) | 核心属性 | 生成代码注意 |
|---|---|---|---|
| 停靠面板 DockSurface | DockSurface : DockPanel | DockTheme、DockWindows 集合、LayoutFileName | Content 集合 + 三文件生成(见 codegen.md) |
| 圆角按钮 RoundedButton | RoundedButton : Control | Style(8 种配色)、Radius、三态色、Border | 枚举序列化用英文名 |
| 图片按钮 ImageButton | ImageButton : Control | **属性名就是中文**:图片路径/图片缩放/图文布局… | 生成代码直接写中文标识符 |
| 侧边菜单 SideMenuPanel | SideMenuPanel : UserControl | MenuItems、Pages、菜单宽度/行高/配色 10 项 | 两个 Content 集合,页面走 sideMenuPageN 字段 |
| 功能区 RibbonStrip | RibbonStrip : UserControl | Tabs→Groups→Buttons 三层集合、AccentColor 等 4 色、ActiveTabIndex | 图标走 `RibbonButton.FromFile("路径")`;点击统一 ButtonClick(e.Tab/e.Group/e.Button) |
| 增强数据表格 EnhancedDataGridView | EnhancedDataGridView : DataGridView | EnableExcelStyleSelection、Sqlite 三件套、EnableCellPaste | 列走标准 DataGridView Columns 序列化 |
| 多选下拉框 MultiSelectComboBox | MultiSelectComboBox : Control | Items、MultiSelect、Searchable、AllowCustomInput、Token 配色 | Items 是 Content 字符串集合 |
| 可调高下拉框 ResizableComboBox | ResizableComboBox : Control | Items、SelectedIndex、Flat、箭头/边框配色 | SelectedText 不序列化 |
| 可调高文本框 ResizableTextBox | ResizableTextBox : Control | PlaceholderText、Flat、三态边框色、密码字符 | 高度自由(这是它的存在意义) |
| OK/NG 状态灯 OKNGStatusControl | OKNGStatusControl : Control | IsOK、BindVariable | **绝不写 Text**(自绘覆盖) |
| 变量显示标签 VariableDisplayLabel | VariableDisplayLabel : Control | BindVariable、DisplayValue、TextAlign | 只读,事件 ValueChanged |
| 变量编辑框 VariableEditTextBox | VariableEditTextBox : TextBox | BindVariable(Value=Text) | 即 TextBox,仅多绑定属性 |
| 日志记录器 Logger(非可视) | Logger : Component | 日志文件名/日志级别/按天滚动/最多保留份数 | log4net 后端;代码调 写信息/写警告/写错误/写异常;详见 components.md 第 12 节 |
标准控件最常用的"总是写/绝不写"清单(完整规则在 winforms-controls.md):Label 的 `AutoSize = true`、CheckBox 与 TabPage 的 `UseVisualStyleBackColor = true` 总是写;DateTimePicker 的 Value、Timer 的 Enabled、各对话框的运行时结果绝不写。
## 三、代码生成五条铁律
1. **只序列化非默认值**。属性表里给了每个属性的默认值——与默认相同就不写进 Designer.cs(SharpDevelop 序列化器行为,手写也要遵守,否则设计器重载后会"多出"赋值行)。
2. **枚举一律用英文标识符**(属性网格显示的中文来自 TypeConverter,`ChineseEnumConverter.ConvertTo` 只管显示;`EnumCodeDomSerializer` 序列化走原始名)。例:`this.roundedButton1.Style = Smart.CustomComponents.RoundedButtonStyle.Success;` 唯一例外是 ImageButton——它的属性/枚举**本身就是中文标识符**(`图文布局.图左文右`)。
3. **Content 集合**(MenuItems/Pages/Items/DockWindows)逐条目生成:`new` 条目 → 赋非默认属性 → `父.集合.Add(条目临时变量)`。标了 `Hidden/Browsable(false)` 的属性(PersistString、ContentXml、ColumnsXml、SourceFile、Selected* 等)**绝不写**。
4. **事件**在属性赋值之后、`Controls.Add` 之前挂:`this.roundedButton1.Click += new System.EventHandler(this.roundedButton1_Click);`
5. **颜色属性带 ShouldSerialize 默认值**(见属性表"序列化条件"列),与出厂色相同则省略。
## 四、排错速查
| 症状 | 根因 | 处置 |
|---|---|---|
| **RibbonStrip 分组名只显示半个字**(下半截被裁,成套报价软件案例 2026-09-01) | 旧版控件布局是固定像素(分组名带 18px/选项卡条 26px/大按钮文字带 20px,按 96dpi 宋体 12px 调的);项目给功能区设了高字体(如微软雅黑 9F,字高~17px)装不下,TextRenderer 裁掉下半截 | **控件已根治**(09-01 21:54 起自适应字体高度)。老症状=项目在用旧 DLL:把项目 Smart.CustomComponents.dll 升到 21:56+(HintPath 指仓库 bin 或新 MSI 安装目录),**项目重编一次**拉新拷贝 |
| **RibbonStrip 大按钮文字显示不全**(截成省略号,同案例 2026-09-01) | 旧版大按钮宽度写死 58px,4+ 汉字按钮被 EndEllipsis 截断(小按钮一直是动态宽,大按钮漏了) | **控件已根治**(09-01 21:56 起按文字实测宽度 `max(58, 文字宽+12)`)。处置同上:升级 DLL + 项目重编 |
| 设计器抛 `MissingMethodException`:未找到方法 `System.Windows.Forms.Form.ConfigurePage`,窗体全不显示(通用规则,H1 为案例) | **当前用户项目自己的** `MainForm.Designer.cs` 在 `InitializeComponent()` 中调用了设计器无法处理的自定义辅助方法;这不是修改 SharpDevelop 本身,也不是修改设计器配置。SharpDevelop CodeDom 反序列化把它错误当成基类 Form 方法解析 | 对任何项目都只改该项目自己的 Designer.cs:删除 `ConfigurePage(...)` 调用和方法定义,展开为标准 `page.BackColor`/`Dock`/`MenuKey`/`Name` 属性赋值;Designer.cs 只保留标准 CodeDom 语句,辅助方法只能放该项目 MainForm.cs 的运行时代码。重新编译项目后**完全退出 SharpDevelop 再重新打开项目**,清除旧异常缓存 |
| 设计器抛 `CodeDomSerializerException`:"XX 没有名为 YY 的属性",窗体全不显示(s23 案例) | Designer.cs 写了该控件没有的属性(跨控件抄属性);反序列化遇未知属性即中止整个加载 | 对照 components.md 该控件的白名单删掉错行;删完**完全退出重启 IDE**(设计器 AppDomain 缓存旧异常,不重启继续报) |
| 构建报 MSB3644"找不到 v4.8 参考程序集"+ 一串 MSB3247 版本冲突;设计器偶发无输出(s23 案例) | 系统只装了 4.8.1 Developer Pack,`Reference Assemblies\...\.NETFramework\v4.8` 目录为空,MSBuild 只能从 GAC 捞到 v2.0/v4.0 混解析 | 装 .NET 4.8 Developer Pack 离线包(NDP48-DevPack-ENU.exe 约 140MB,静默装);装完 v4.8 目录 133 个参考 DLL,MSB3644/MSB3247 一起消失 |
| **AI 生成项目后设计区空白/一片 CS0246**(jk55 案例) | Designer.cs 用了增强控件但 csproj 无程序集引用;设计器加载第一步类型解析就失败,"自动补引用"跑在加载流程内部永远不触发——死锁:引用缺失→加载失败→自动补不触发→引用仍缺失 | **同一次改动里手动把 9 个引用写进 csproj**(清单见第一章),HintPath 指向安装目录;写完再开设计视图即正常。自动补引用只在工具箱拖放场景可用 |
| 自动补引用兜底后仍剩个别 CS0246(RibbonStrip/Logger 认不出) | 安装目录 `C:\Program Files (x86)\SharpDevelop\5.2\bin` 的 DLL 是旧构建(08-27 包缺这两个类型,13 控件只认 11) | **重打 MSI 重装**(或把仓库 bin 最新 DLL 覆盖安装目录),让稳定路径带全 13 控件;开发机可临时把 HintPath 指仓库 bin 过渡 |
| 设计器载入 "Could not find type '?'" | 项目引用解析不到(裸引用无 HintPath) | 08-25 13:53 及以后的 MSI 已在引用解析层中心兜底(自动回退到安装 bin);旧包则补 HintPath |
| 运行时 "DockPanel.Theme must be set to a valid theme" | 缺二级依赖链(Resources.Extensions 等 4 个) | 13:53+ 的库带 AssemblyResolve 运行时兜底;或按第一节补全 8 个引用。诊断看 `bin\Debug\DockWindows.Log.txt` |
| 运行时菜单重复/集合翻倍 | 设计期默认内容放进了构造函数 | 默认集合内容只能在 Designer.Initialize 且 `!host.Loading` 时注入(SideMenuPanel 已修,新控件照此办理) |
| 自定义集合编辑器改了不保存 | 原地改集合没发 IComponentChangeService 通知 | 编辑器写回前后发 OnComponentChanging/Changed |
| CellStyle Builder/列编辑器等框架对话框全英文 | .NET Framework 中文语言包未装(GAC 无 System.Design.resources zh-Hans) | 装 .NET 4.8 简体中文语言包(fwlink 2053984,`/q /norestart`),一次修复所有控件的框架编辑器**外壳**(标题/按钮) |
| 对话框外壳已中文但**内部属性行**仍英文 | 内部 PropertyGrid 的属性行走翻译层词典;`DataGridViewCellStyle` 不是 Component,单独注册 | DesignerTranslation.Install() 已为 DataGridViewCellStyle 补挂类型级提供器;缺行名就往词典加一条(如 SortMode→排序模式) |
| 改 CoreProperties.UILanguage 为 zh-CN 后整个 IDE 变英文 | SD 自家资源只有中性 zh 命名,具体文化不被识别回落 en | 保持 UILanguage=zh;用 ResourceService 里 IsNeutralCulture→CreateSpecificCulture 升格线程文化的代码补丁(勿动配置) |
| 属性面板改列标题高度模式后不生效/被顶回 | Designer.cs 残留显式模式行 + 引用指向旧版库 DLL | 删掉 Designer.cs 中该行;确认项目 HintPath 指向当前编译产物;EDG.Ctor.Log.txt 有"谁改的模式+调用栈" |
诊断优先级(用户要求):**读日志文件**(DockWindows.Log.txt / EDG.Ctor.Log.txt / DesignerTranslation.log),截图识别是最后手段。
## 五、Smart.CustomComponents 控件库修复历史速查(判断"项目是不是在用旧 DLL 复现旧 bug")
| 日期 | 修复 | 载体与判定 |
|---|---|---|
| 09-01 21:54 | RibbonStrip 带区高度自适应字体(分组名带 max(18,字高+4)、选项卡条、大按钮文字带;OnFontChanged 重画)——修"分组名只显示半个字" | 仓库 bin 与 21:54+ 编译的 DLL 均含;安装 MSI 13:14 版**不含** |
| 09-01 21:56 | RibbonStrip 大按钮宽度按文字实测 max(58, 文字宽+12)——修"按钮文字截成省略号" | 同上;判定项目是否吃到:看项目 `bin\Debug\Smart.CustomComponents.dll` 时间戳 ≥ 21:56 |
| 09-01 00:01 | SkillFolderSync(新建解决方案自动复制技能手册) | ICSharpCode.SharpDevelop.dll;13:14 MSI 已含 |
| 08-31 | 翻译词典收录 RibbonStrip→"增强选项卡"等全部工具箱中文名 | FormsDesigner.dll;13:14 MSI 已含 |
| 08-25 13:53 | 裸引用解析兜底、主题链 AssemblyResolve 运行时兜底 | 详见各排错行 |
控件库再修 bug 时:往这张表加一行,并提醒用户项目重编(零章第 7 条)。
@@ -0,0 +1,20 @@
@echo off
rem Copy the sd-enhanced-components skill into a solution folder.
rem Usage: copy-to-solution.cmd <solution-dir>
setlocal
set "SKILL_DIR=%~dp0"
if "%~1"=="" (
echo Usage: %~nx0 ^<solution-dir^>
echo Example: %~nx0 "F:\pycode\wpf\src\WinFormsDesigner.Fx48\Samples\COOO\scr\MyApp"
exit /b 1
)
if not exist "%~1" (
echo ERROR: directory not found: %~1
exit /b 1
)
set "DEST=%~1\.agents\skills\sd-enhanced-components"
xcopy "%SKILL_DIR%SKILL.md" "%DEST%\" /Y /I >nul
xcopy "%SKILL_DIR%references" "%DEST%\references\" /Y /E /I >nul
echo Copied skill to: %DEST%
echo Files: SKILL.md + references ^(components / winforms-controls / winforms-components / codegen^)
endlocal
@@ -0,0 +1,394 @@
# Designer.cs 代码生成规则(SharpDevelop CodeDom 序列化风格)
对应实现:SharpDevelop NRefactoryDesignerLoader 序列化 + `Commands/GenerateDockWindows.cs` 生成器。缩进用 **Tab**,语句带 `this.`,非 System 类型用全名。
## 0. 目标窗体约定(用户强制)
- 生成/手写 Designer.cs 的目标永远是解决方案**自带主窗口 MainForm**(MainForm.Designer.cs),事件运行时代码放 MainForm.cs;**绝不新建第二个窗体承载 UI**(TestForm/Form2 之类只在该项目本身就是测试工具时才允许)。
- **写 Designer.cs 前先补引用,否则设计器死锁**(jk55 案例):设计器加载第一步是类型解析,项目没引用增强控件程序集就直接一片 CS0246;而"切设计标签自动补引用"的代码跑在设计器加载流程**内部**,类型解析不过它永远不执行——引用缺失→加载失败→自动补不触发→引用仍缺失。**AI 生成 Designer.cs 的同一次改动必须先写 §0.1 的 9 个引用。** 工具箱拖放场景不受影响(那时设计器本来就能加载)。
### 0.1 增强控件引用九件套(写 Designer.cs 时同步写进 csproj)
HintPath **默认指向 SharpDevelop 安装目录** `C:\Program Files (x86)\SharpDevelop\5.2\bin`:装完即存在、机器间一致(工具箱自动补引用与引用解析兜底用的同一规范位置)。三条变通:
- **装在非默认位置**:先用下面的命令找到真实安装 bin,把 9 个 HintPath 整体替换成实际路径(注册表卸载键 InstallLocation 为空,别用它):
```powershell
# ① IDE 在跑:进程路径(exe 所在目录即 bin)
Get-Process SharpDevelop -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Path
# ② 开始菜单快捷方式(已实测)
$s = New-Object -ComObject WScript.Shell
Get-ChildItem "$env:ProgramData\Microsoft\Windows\Start Menu\Programs","$env:AppData\Microsoft\Windows\Start Menu\Programs" -Recurse -Filter '*SharpDevelop*.lnk' -ErrorAction SilentlyContinue | ForEach-Object { $s.CreateShortcut($_.FullName).TargetPath }
# ③ 已有项目 HintPath
Get-ChildItem "$env:USERPROFILE\Documents\SharpDevelop Projects" -Recurse -Filter *.csproj -ErrorAction SilentlyContinue | Select-String 'Smart\.CustomComponents\.dll.*HintPath'
```
都找不到就问用户,不要瞎猜路径。完整说明见 SKILL.md 第一章"定位实际安装目录"。
- **开发机临时追新**:仓库 bin `F:\Visual Studio\1\33\SharpDevelop-master\bin` 有最新控件时可把 HintPath 临时换成它,但该路径仅本机存在,**交付/给用户机的模板一律用安装目录**。
- **前提:安装目录必须是最新构建**。jk55 案例发现 08-27 旧安装包缺 RibbonStrip/Logger(13 控件只认 11)——先重打 MSI 重装,或把仓库 bin 最新 DLL 覆盖到安装目录。
```xml
<ItemGroup>
<Reference Include="Smart.CustomComponents, Version=1.0.0.0, Culture=neutral, PublicKeyToken=ba044a4aa43e0dc4">
<HintPath>C:\Program Files (x86)\SharpDevelop\5.2\bin\Smart.CustomComponents.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="WeifenLuo.WinFormsUI.Docking">
<HintPath>C:\Program Files (x86)\SharpDevelop\5.2\bin\WeifenLuo.WinFormsUI.Docking.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="WeifenLuo.WinFormsUI.Docking.ThemeVS2015">
<HintPath>C:\Program Files (x86)\SharpDevelop\5.2\bin\WeifenLuo.WinFormsUI.Docking.ThemeVS2015.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Data.SQLite">
<HintPath>C:\Program Files (x86)\SharpDevelop\5.2\bin\System.Data.SQLite.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="log4net">
<HintPath>C:\Program Files (x86)\SharpDevelop\5.2\bin\log4net.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Resources.Extensions">
<HintPath>C:\Program Files (x86)\SharpDevelop\5.2\bin\System.Resources.Extensions.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Memory">
<HintPath>C:\Program Files (x86)\SharpDevelop\5.2\bin\System.Memory.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Runtime.CompilerServices.Unsafe">
<HintPath>C:\Program Files (x86)\SharpDevelop\5.2\bin\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Numerics.Vectors">
<HintPath>C:\Program Files (x86)\SharpDevelop\5.2\bin\System.Numerics.Vectors.dll</HintPath>
<Private>True</Private>
</Reference>
</ItemGroup>
```
`<Private>True</Private>` 会在编译时把 DLL 复制进项目 `bin\Debug`,之后项目运行/分发不再依赖 HintPath 原位置(换机器、重装 SharpDevelop 都不受影响)。
## 1. 标准骨架(单控件)
```csharp
namespace DemoApp
{
partial class MainForm
{
private System.ComponentModel.IContainer components = null;
private Smart.CustomComponents.RoundedButton roundedButton1;
protected override void Dispose(bool disposing)
{
if (disposing && (components != null)) components.Dispose();
base.Dispose(disposing);
}
private void InitializeComponent()
{
this.roundedButton1 = new Smart.CustomComponents.RoundedButton();
this.SuspendLayout();
//
// roundedButton1
//
this.roundedButton1.Location = new System.Drawing.Point(40, 60);
this.roundedButton1.Name = "roundedButton1";
this.roundedButton1.Radius = 20;
this.roundedButton1.Size = new System.Drawing.Size(140, 44);
this.roundedButton1.Style = Smart.CustomComponents.RoundedButtonStyle.Success;
this.roundedButton1.TabIndex = 0;
this.roundedButton1.Text = "保存";
this.roundedButton1.Click += new System.EventHandler(this.roundedButton1_Click);
//
// MainForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(784, 561);
this.Controls.Add(this.roundedButton1);
this.Name = "MainForm";
this.Text = "DemoApp";
this.ResumeLayout(false);
}
}
}
```
规则:每个控件一段注释头;Location/Name/Size/TabIndex 总是写;其余属性只写**非默认值**(对照 components.md 表);事件在属性后、Controls.Add 前;容器结尾 `ResumeLayout(false)`,有子布局的控件加 `PerformLayout()` 对。
## 2. Content 集合生成
### 2.1 字符串项集合(MultiSelectComboBox.Items / ResizableComboBox.Items)
```csharp
this.multiSelectComboBox1.Items.Add("选项A");
this.multiSelectComboBox1.Items.Add("选项B");
```
### 2.2 递归对象集合(SideMenuPanel.MenuItems,条目有 Children)
```csharp
Smart.CustomComponents.SideMenuItem sideMenuItem1 = new Smart.CustomComponents.SideMenuItem();
sideMenuItem1.Text = "首页";
Smart.CustomComponents.SideMenuItem sideMenuItem2 = new Smart.CustomComponents.SideMenuItem();
sideMenuItem2.Text = "数据管理";
Smart.CustomComponents.SideMenuItem sideMenuItem3 = new Smart.CustomComponents.SideMenuItem();
sideMenuItem3.Text = "导出记录";
sideMenuItem2.Children.Add(sideMenuItem3);
this.sideMenuPanel1.MenuItems.Add(sideMenuItem1);
this.sideMenuPanel1.MenuItems.Add(sideMenuItem2);
```
### 2.3 页面集合(SideMenuPanel.Pages)
页面是 Panel 派生 → 走**字段**路径(设计器把页面注册进 IDesignerHost.Container,命名为 sideMenuPageN):
```csharp
private Smart.CustomComponents.SideMenuPage sideMenuPage1;
this.sideMenuPage1 = new Smart.CustomComponents.SideMenuPage();
this.sideMenuPage1.BackColor = System.Drawing.Color.White;
this.sideMenuPage1.Dock = System.Windows.Forms.DockStyle.Fill;
this.sideMenuPage1.Location = ...;
this.sideMenuPage1.MenuKey = "首页";
this.sideMenuPage1.Name = "sidePage_首页"; // 用户可改
this.sideMenuPanel1.Pages.Add(this.sideMenuPage1);
```
注意:页面一般由菜单自动同步生成(MenuKey=菜单文字);手写时 MenuKey 必须与某菜单项 Text 匹配才会被点击切换。**页面内子控件用 EnableDesignMode 暴露,不要生成 `父.页面` 形式的点号字段名**(历史崩溃点:`sideMenuPanel1.Page_0_首页` 是非法 C# 字段名)。
### 2.4 停靠窗口集合(DockSurface.DockWindows,DockWindowItem 条目)
```csharp
Smart.CustomComponents.DockWindows.DockWindowItem dockWindowItem1 = new Smart.CustomComponents.DockWindows.DockWindowItem();
dockWindowItem1.ClassName = "ToolWindow";
dockWindowItem1.Title = "工具箱";
dockWindowItem1.DockState = WeifenLuo.WinFormsUI.Docking.DockState.DockLeft;
dockWindowItem1.DefaultWidth = 240;
dockWindowItem1.ShowCloseButton = false;
this.dockSurface1.DockWindows.Add(dockWindowItem1);
```
只写非默认属性(DefaultHeight=180、FixedPosition=false、ShowCloseButton=true 是默认,省略)。
### 2.5 功能区三层集合(RibbonStrip.Tabs → RibbonTab.Groups → RibbonGroup.Buttons → RibbonButton)
```csharp
Smart.CustomComponents.RibbonTab ribbonTab1 = new Smart.CustomComponents.RibbonTab();
ribbonTab1.Text = "开始";
Smart.CustomComponents.RibbonGroup ribbonGroup1 = new Smart.CustomComponents.RibbonGroup();
ribbonGroup1.Text = "文件";
Smart.CustomComponents.RibbonButton ribbonButton1 = new Smart.CustomComponents.RibbonButton();
ribbonButton1.Text = "打开";
ribbonButton1.Image = Smart.CustomComponents.RibbonButton.FromFile("C:\\icons\\open.png"); // 有图标时
ribbonButton1.Enabled = false; // 非默认才写
ribbonGroup1.Buttons.Add(ribbonButton1);
ribbonTab1.Groups.Add(ribbonGroup1);
this.ribbonStrip1.Tabs.Add(ribbonTab1);
```
规则:
- 逐层 `new` → 赋非默认属性 → `Add` 进上一级集合;只写非默认(Text 无默认标记通常总写,SizeStyle=Small、Enabled=true 是默认省略)。
- **图标**:`按钮.Image = RibbonButton.FromFile("路径")`(RibbonImageConverter 序列化形态;ImageFile 本身隐藏不写)。无图标则不写 Image 行。
- 事件统一走一个 `this.ribbonStrip1.ButtonClick += new System.EventHandler<Smart.CustomComponents.RibbonButtonEventArgs>(this.ribbonStrip1_ButtonClick);`,处理器里按 `e.Button.Text` 分发(参数带 e.Tab/e.Group/e.Button)。
- `this.ribbonStrip1.ActiveTabIndex = 0;` 是默认,省略。
## 3. 停靠窗口三文件生成(等价"生成停靠窗口代码"命令)
集合配置好后生成 3 类文件(幂等:窗口类已存在不覆盖,仅同步 Designer.cs 的 this.Text 行;partial 与构造函数接线按当前配置刷新):
### 3.1 窗口类 `DockWindows\<类名>\<类名>.cs`
```csharp
using System;
using WeifenLuo.WinFormsUI.Docking;
namespace DemoApp
{
/// <summary>停靠窗口:工具箱。内容在本类的设计器视图中编辑。</summary>
public partial class ToolWindow : DockContent
{
public ToolWindow()
{
InitializeComponent();
Text = "工具箱";
TabText = "工具箱";
}
}
}
```
### 3.2 窗口类设计器骨架 `DockWindows\<类名>\<类名>.Designer.cs`
```csharp
namespace DemoApp
{
partial class ToolWindow
{
private System.ComponentModel.IContainer components = null;
protected override void Dispose(bool disposing)
{
if (disposing && (components != null)) components.Dispose();
base.Dispose(disposing);
}
#region Designer generated code
private void InitializeComponent()
{
this.SuspendLayout();
this.Name = "ToolWindow";
this.Text = "工具箱";
this.ResumeLayout(false);
}
#endregion
}
}
```
(双击 .cs 可继续用 SharpDevelop 设计器往里拖控件,序列化规则同第 1、2 节。)
### 3.3 创建/停靠 partial `DockWindows\<Form>.<surface>.DockWindows.cs`
```csharp
// 本文件由『生成停靠窗口代码』自动生成,集合变更后会自动刷新,请勿手工编辑。
using System;
using WeifenLuo.WinFormsUI.Docking;
namespace DemoApp
{
public partial class MainForm
{
void CreateDockWindows_dockSurface1()
{
var dockSurface1 = this.dockSurface1 as Smart.CustomComponents.DockSurface;
if (dockSurface1 == null) return;
dockSurface1.EnsureDefaultTheme(); // 必须:Show 前保证有效主题
var w1 = new ToolWindow();
w1.CloseButtonVisible = false; // 仅 ShowCloseButton=false 时生成
w1.DockAreas = WeifenLuo.WinFormsUI.Docking.DockAreas.DockLeft; // 仅 FixedPosition=true 时
w1.TabText = "工具箱"; // Title 非空时生成
w1.Text = "工具箱";
w1.Show(dockSurface1, DockState.DockLeft);
var w2 = new FloatWindow2();
w2.Size = new System.Drawing.Size(320, 240); // 仅 DockState.Float 时
w2.Show(dockSurface1, DockState.Float);
}
}
}
```
属性设置**必须在 Show 之前**。FixedPosition 的 DockAreas 映射:
| DockState | DockAreas |
|---|---|
| DockLeft / DockLeftAutoHide | DockLeft |
| DockRight / DockRightAutoHide | DockRight |
| DockTop / DockTopAutoHide | DockTop |
| DockBottom / DockBottomAutoHide | DockBottom |
| Document | Document |
| Float | Float |
| Hidden / Unknown | 不锁定(不生成 DockAreas 行) |
### 3.4 构造函数接线(MainForm.cs)
`InitializeComponent();` 之后插入(已存在则跳过):
```csharp
InitializeComponent();
CreateDockWindows_dockSurface1();
```
自动触发链:集合编辑器"确定" → IComponentChangeService.OnComponentChanged → 400ms 防抖静默生成。多面板时每个 DockSurface 各一个 `CreateDockWindows_<面板名>` 方法。
## 4. 特殊控件生成注意
- **Designer.cs 只能写标准 Designer 语句(适用于所有项目;H1 是已验证案例)**:`InitializeComponent()` 内只允许 `new`、属性赋值、集合 `Add/AddRange`、事件挂接和 `Controls.Add` 等标准 CodeDom 语句。**不要调用自定义辅助方法**(例如 `ConfigurePage(this.overviewPage, "概览", "sidePage_Overview")`):SharpDevelop 反序列化时可能把它错误解析成 `System.Windows.Forms.Form.ConfigurePage(...)`,继而抛 `MissingMethodException`(未找到方法 `System.Windows.Forms.Form.ConfigurePage`),整个设计器加载中止。页面配置必须展开成标准属性赋值:`page.BackColor`、`page.Dock`、`page.MenuKey`、`page.Name` 等;辅助方法放到 MainForm.cs 的运行时代码中,或不要使用。
**边界说明(适用于所有项目)**:此修复只修改**用户项目自己的** `MainForm.Designer.cs`:删除 `ConfigurePage(...)` 调用和 Designer 文件末尾的方法定义,重新编译项目(如生成 `<项目>\bin\Debug\<项目>.exe`);没有修改 SharpDevelop 本身,也没有修改任何设计器配置。若当前 SharpDevelop 进程已经缓存旧的加载异常,修复后自动完全退出 SharpDevelop、重新打开项目再进设计器(这是清缓存,不是因为修改了 SharpDevelop)。
- **属性白名单铁律(s23 案例 2026-09-01)**:Designer.cs 里写的每个属性必须能在 components.md 该控件条目下查到(自有属性表或继承属性行)。**跨控件抄属性是头号翻车点**——写一个该控件没有的属性,CodeDom 反序列化直接抛 `CodeDomSerializerException`("XX 没有名为 YY 的属性"),**整个设计器加载中止**、窗体全黑。已知最易抄错的两对:
- `VariableEditTextBox` **没有 PlaceholderText**(那是 ResizableTextBox 的;它继承 TextBox,自有属性只有 BindVariable/Value,要占位提示就用 ResizableTextBox);
- `VariableDisplayLabel` **没有 BorderStyle**(它继承 Control 不是 Label,自有属性只有 BindVariable/DisplayValue/TextAlign)。
- **ImageButton**:属性名/枚举是中文标识符,直接写:`this.imageButton1.图文布局 = Smart.CustomComponents.图文布局枚举.图左文右;`
- **OKNGStatusControl**:不写 Text(自绘 OK/NG);IsOK=true 默认省略。
- **EnhancedDataGridView**:列与普通 DataGridView 相同(`this.enhancedDataGridView1.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] {...})` + 每列独立字段段);Sqlite 三属性按字符串直写。
- **事件处理器签名**:`private void roundedButton1_Click(object sender, EventArgs e)`,写在主 .cs 文件(不放 Designer.cs)。
- **图片属性**(SideMenuItem.Icon、Image 类型):走 .resx 资源(`System.ComponentModel.ComponentResourceManager resources = new ...typeof(MainForm));` + `resources.GetObject("$this.sideMenuItem1.Icon")`),SharpDevelop 风格同 WinForms。
## 5. 生成的运行时诊断(生成器自带,勿删)
partial 里附带了写 `bin\Debug\DockWindows.Log.txt` 的日志:主题类型/页签条/补丁重载数 + 每窗口 Show 前后的 CloseButtonVisible/DockAreas/DockState/TabText。排错时**先读这个文件**(用户要求:日志优先,截图最后)。
## 6. 非可视组件模板
属性表见 `winforms-components.md`。要点:无 Location/Size/TabIndex;构造带 IContainer 的(Timer/ImageList/ErrorProvider/ToolTip/BindingSource/NotifyIcon/HelpProvider/BackgroundWorker/SerialPort)写 `new ...(this.components)` 且不再 Add;不带的(PrintDocument/Process/EventLog/FileSystemWatcher/PerformanceCounter)用 `this.components.Add(this.xxx);`;DataSet/BindingSource 用 ISupportInitialize 的 BeginInit/EndInit 包裹。扩展提供器(ToolTip/ErrorProvider/HelpProvider)对其他控件逐行 `SetXxx(目标控件, 值)`。
## 7. 菜单/工具栏嵌套模板(每个菜单项/工具栏项都是字段)
```csharp
private System.Windows.Forms.MenuStrip menuStrip1;
private System.Windows.Forms.ToolStripMenuItem 文件ToolStripMenuItem;
private System.Windows.Forms.ToolStripMenuItem 退出ToolStripMenuItem;
this.menuStrip1 = new System.Windows.Forms.MenuStrip();
this.文件ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.退出ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
//
// 退出ToolStripMenuItem
//
this.退出ToolStripMenuItem.Name = "退出ToolStripMenuItem";
this.退出ToolStripMenuItem.Size = new System.Drawing.Size(180, 22); // 布局值,设计器会写
this.退出ToolStripMenuItem.Text = "退出";
this.退出ToolStripMenuItem.Click += new System.EventHandler(this.退出ToolStripMenuItem_Click);
//
// 文件ToolStripMenuItem
//
this.文件ToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.退出ToolStripMenuItem});
this.文件ToolStripMenuItem.Text = "文件";
//
// menuStrip1
//
this.menuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.文件ToolStripMenuItem});
this.menuStrip1.Location = new System.Drawing.Point(0, 0);
this.menuStrip1.Name = "menuStrip1";
this.menuStrip1.Size = ...; this.menuStrip1.TabIndex = ...;
this.menuStrip1.Text = "menuStrip1";
// 窗体段:
this.MainMenuStrip = this.menuStrip1;
this.Controls.Add(this.menuStrip1);
```
ContextMenuStrip 同构(无 Dock、无 MainMenuStrip 行);StatusStrip 用 ToolStripStatusLabel(Spring=true 占满 + BorderSide 等);ToolStrip 混用 ToolStripButton/ToolStripSeparator/ToolStripTextBox…,均独立字段。MenuStrip/ToolStrip 的 Items 段**不写每项 Name 之外的 Text 之外的默认值**;`DropDownItems.AddRange` 表示层级。
## 8. ListView / TreeView 集合模板
```csharp
// ListView:列为字段,行可 ctor 一行带上
this.columnHeader1 = new System.Windows.Forms.ColumnHeader();
this.columnHeader1.Text = "姓名"; this.columnHeader1.Width = 120;
this.columnHeader2 = new System.Windows.Forms.ColumnHeader();
this.columnHeader2.Text = "分数";
this.listView1.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] {
this.columnHeader1, this.columnHeader2});
this.listView1.FullRowSelect = true; // View=Details 时常用组合
this.listView1.View = System.Windows.Forms.View.Details;
this.listView1.Items.AddRange(new System.Windows.Forms.ListViewItem[] {
new System.Windows.Forms.ListViewItem(new string[] {"张三", "95"}),
new System.Windows.Forms.ListViewItem(new string[] {"李四", "88"})});
// 分组(可选)
this.listViewGroup1 = new System.Windows.Forms.ListViewGroup("一组");
this.listView1.Groups.AddRange(new System.Windows.Forms.ListViewGroup[] {this.listViewGroup1});
// TreeView:节点递归,ctor 带文本
System.Windows.Forms.TreeNode treeNode1 = new System.Windows.Forms.TreeNode("根节点");
System.Windows.Forms.TreeNode treeNode2 = new System.Windows.Forms.TreeNode("子节点1");
System.Windows.Forms.TreeNode treeNode3 = new System.Windows.Forms.TreeNode(new string[] {
"子节点1", "子节点2"}); // 兄弟数组写法(Name1,Name2 平铺)
treeNode1.Nodes.AddRange(new System.Windows.Forms.TreeNode[] {treeNode3});
this.treeView1.Nodes.AddRange(new System.Windows.Forms.TreeNode[] {treeNode1});
```
TreeView 的 designer 实际写法是临时变量 treeNodeN 逐级挂 Nodes.AddRange;ImageKey/SelectedImageKey 在绑定 ImageList 后写。
@@ -0,0 +1,593 @@
# 增强组件属性手册(Smart.CustomComponents 全部 13 控件 + 条目类型 + 枚举,穷尽版)
> 生成来源:反射 `Smart.CustomComponents.dll`(bin\Debug 编译产物)+ 源码逐文件核对(src\Libraries\CustomComponents\Controls)。属性名/类型/默认值以 DLL 与源码为准。
> 工具箱类别:**增强组件**(13 个控件)。Designer.cs 代码生成规则另见 `codegen.md`。
> 序列化列约定:**Content 集合**=逐条目生成;不序列化=设计器/手写都不写;非默认才写=与默认值相同则省略;ShouldSerialize=与出厂值比较不同才写。
## 目录
- 停靠面板 DockSurface(WeifenLuo.WinFormsUI.Docking.DockPanel)
- 圆角按钮 RoundedButton(System.Windows.Forms.Control)
- 图片按钮 ImageButton(System.Windows.Forms.Control)
- 增强数据表格 EnhancedDataGridView(System.Windows.Forms.DataGridView)
- 多选下拉框 MultiSelectComboBox(System.Windows.Forms.Control)
- 可调高下拉框 ResizableComboBox(System.Windows.Forms.Control)
- 可调高文本框 ResizableTextBox(System.Windows.Forms.Control)
- OK/NG 状态灯 OKNGStatusControl(System.Windows.Forms.Control)
- 侧边菜单 SideMenuPanel(System.Windows.Forms.UserControl)
- 功能区 RibbonStrip(System.Windows.Forms.UserControl)
- 变量显示标签 VariableDisplayLabel(System.Windows.Forms.Control)
- 变量编辑框 VariableEditTextBox(System.Windows.Forms.TextBox)
- 日志记录器 Logger(非可视组件)(System.ComponentModel.Component)
---
## 停靠面板 DockSurface
**继承**:`Smart.CustomComponents.DockSurface` ← `WeifenLuo.WinFormsUI.Docking.DockPanel`
停靠窗口宿主容器。设计器拖入窗体后通过 DockWindows 集合声明若干停靠窗口,用"生成停靠窗口代码"(等价手写三文件)生成窗口类。主题=VS2015Light 起步。
### 属性(全部 4 个自有属性)
| 属性 | 类型 | 默认值 | 序列化 | 说明 |
|---|---|---|---|---|
| DockTheme | SurfaceTheme | VS2015Light | 非默认才写 | 停靠主题;Designer.cs 生成配套 Theme 对象(VS2015Light→ThemeVS2015Light),改主题须同步改引用的 Theme 程序集 |
| TabDirection | TabTipDirection | Left | 非默认才写 | 选项卡提示方向(左/右),少量布局场景才改 |
| DockWindows | DockWindowItemCollection | | **Content 集合** | 停靠窗口声明集合,条目类型 DockWindowItem;生成走"三文件流程"(codegen.md §3) |
| LayoutFileName | String | dock_layout.xml | 非默认才写 | 运行时布局持久化 XML 文件名,相对 exe 目录 |
### 继承属性(名称全列)
- 继承 DockPanel(33 个):DockBackColor, ActiveAutoHideContent, AllowEndUserDocking, AllowEndUserNestedDocking, Contents(RO), RightToLeftLayout, ShowDocumentIcon, DocumentTabStripLocation, Extender(RO), DockPaneFactory(RO), FloatWindowFactory(RO), DockWindowFactory(RO), Panes(RO), DockArea(RO), DockBottomPortion, DockLeftPortion, DockRightPortion, DockTopPortion, DockWindows(RO), DocumentsCount(RO), Documents(RO), FloatWindows(RO), DefaultFloatWindowSize, DocumentStyle, SupportDeeplyNestedContent, ShowAutoHideContentOnHover, DocumentWindowBounds(RO), ActiveContent(RO), ActivePane(RO), ActiveDocument(RO), ActiveDocumentPane(RO), Skin(RO), Theme
- 继承 Panel(5 个):AutoSize, AutoSizeMode, BorderStyle, TabStop, Text
- 继承 ScrollableControl(8 个):AutoScroll, AutoScrollMargin, AutoScrollPosition, AutoScrollMinSize, DisplayRectangle(RO), HorizontalScroll(RO), VerticalScroll(RO), DockPadding(RO)
- 继承 Control(70 个):AccessibilityObject(RO), AccessibleDefaultActionDescription, AccessibleDescription, AccessibleName, AccessibleRole, AllowDrop, Anchor, AutoScrollOffset, LayoutEngine(RO), BackColor, BackgroundImage, BackgroundImageLayout, BindingContext, Bottom(RO), Bounds, CanFocus(RO), CanSelect(RO), Capture, CausesValidation, ClientRectangle(RO), ClientSize, CompanyName(RO), ContainsFocus(RO), ContextMenu, ContextMenuStrip, Controls(RO), Created(RO), Cursor, DataBindings(RO), DeviceDpi(RO), IsDisposed(RO), Disposing(RO), Dock, Enabled, Focused(RO), Font, ForeColor, Handle(RO), HasChildren(RO), Height, IsHandleCreated(RO), InvokeRequired(RO), IsAccessible, IsMirrored(RO), Left, Location, Margin, MaximumSize, MinimumSize, Name, Parent, ProductName(RO), ProductVersion(RO), RecreatingHandle(RO), Region, Right(RO), RightToLeft, Site, Size, TabIndex, Tag, Top, TopLevelControl(RO), UseWaitCursor, Visible, Width, WindowTarget, PreferredSize(RO), Padding, ImeMode
- 继承 Component(1 个):Container(RO)
### 事件
自有事件:无(仅基类事件)。
- 运行时错误 "DockPanel.Theme must be set to a valid theme" = 二级依赖链(System.Resources.Extensions 等 4 个)没引用全。
- 诊断日志:bin\Debug\DockWindows.Log.txt。
- API:EnsureDefaultTheme()(代码兜底挂主题)。
- 不可当作普通 Control 用(没有 Size/Location 语义),占满窗体或容器使用。
---
## 圆角按钮 RoundedButton
**继承**:`Smart.CustomComponents.RoundedButton` ← `System.Windows.Forms.Control`
Element-UI 配色的圆角按钮,8 种风格预设 + 三态(常态/悬停/按下)颜色可覆写。点击用基类 Click 事件。
### 属性(全部 9 个自有属性)
| 属性 | 类型 | 默认值 | 序列化 | 说明 |
|---|---|---|---|---|
| Style | RoundedButtonStyle | Primary | 非默认才写 | 风格预设 Primary/Success/Warning/Danger/Info/Dark/Light/Outline,Designer.cs 写英文名(如 RoundedButtonStyle.Success) |
| Radius | Int32 | 10 | 非默认才写 | 圆角半径 px,0=直角 |
| NormalColor | Color | | — | 常态底色;出厂 24,144,255(随 Style 预设,ShouldSerialize 按出厂色比较) |
| HoverColor | Color | | — | 悬停底色;出厂 64,169,255 |
| PressColor | Color | | — | 按下底色;出厂 9,109,217 |
| BorderWidth | Int32 | 0 | 非默认才写 | 边框宽 px,0=无边框(Outline 风格通常设 1~2) |
| BorderColor | Color | | — | 边框色;出厂 217,217,217 |
| TextAlign | ContentAlignment | MiddleCenter | 非默认才写 | 文字对齐(ContentAlignment),默认 MiddleCenter |
| BackColor | Color | | 不序列化 | 被隐藏(自绘不用),绝不写 |
### 继承属性(名称全列)
- 继承 Control(73 个):AccessibilityObject(RO), AccessibleDefaultActionDescription, AccessibleDescription, AccessibleName, AccessibleRole, AllowDrop, Anchor, AutoSize, AutoScrollOffset, LayoutEngine(RO), BackgroundImage, BackgroundImageLayout, BindingContext, Bottom(RO), Bounds, CanFocus(RO), CanSelect(RO), Capture, CausesValidation, ClientRectangle(RO), ClientSize, CompanyName(RO), ContainsFocus(RO), ContextMenu, ContextMenuStrip, Controls(RO), Created(RO), Cursor, DataBindings(RO), DeviceDpi(RO), DisplayRectangle(RO), IsDisposed(RO), Disposing(RO), Dock, Enabled, Focused(RO), Font, ForeColor, Handle(RO), HasChildren(RO), Height, IsHandleCreated(RO), InvokeRequired(RO), IsAccessible, IsMirrored(RO), Left, Location, Margin, MaximumSize, MinimumSize, Name, Parent, ProductName(RO), ProductVersion(RO), RecreatingHandle(RO), Region, Right(RO), RightToLeft, Site, Size, TabIndex, TabStop, Tag, Text, Top, TopLevelControl(RO), UseWaitCursor, Visible, Width, WindowTarget, PreferredSize(RO), Padding, ImeMode
- 继承 Component(1 个):Container(RO)
### 事件
自有事件:无(仅基类事件)。
- 三色只在改变出厂预设时序列化(ShouldSerialize 与出厂色比较)。
- 改色想完整自定义时三态色都要写,避免悬停跳回蓝色。
---
## 图片按钮 ImageButton
**继承**:`Smart.CustomComponents.ImageButton` ← `System.Windows.Forms.Control`
图片+文字的按钮。**属性名与枚举值本身就是中文标识符**,Designer.cs 直接写中文(全项目唯一例外)。
### 属性(全部 12 个自有属性)
| 属性 | 类型 | 默认值 | 序列化 | 说明 |
|---|---|---|---|---|
| BackgroundImage | Image | | 不序列化 | |
| BackgroundImageLayout | ImageLayout | | 不序列化 | |
| BackColor | Color | | 不序列化 | |
| 图片路径 | String | | — | 图片文件绝对/相对路径;非空才序列化(ShouldSerialize图片路径) |
| 图片缩放 | 图片缩放模式 | Fit | 非默认才写 | Original/Fit/Stretch/Tile,默认 Fit |
| 图片锚点 | 图片锚点位置 | Center | 非默认才写 | Center/N/S/E/W,默认 Center |
| 图片透明度 | Double | 1 | 非默认才写 | 0.0~1.0,默认 1 |
| 图文布局 | 图文布局枚举 | 文字居中 | 非默认才写 | 文字居中/图下文上/图上文下/图右文左/图左文右/纯图片;枚举成员是中文 |
| 文字间距 | Int32 | 1 | 非默认才写 | 图与文字间距 px,默认 1 |
| Text | String | | — | 按钮文字(类目 外观) |
| Font | Font | | — | 文字字体 |
| ForeColor | Color | | — | 文字颜色 |
### 继承属性(名称全列)
- 继承 Control(68 个):AccessibilityObject(RO), AccessibleDefaultActionDescription, AccessibleDescription, AccessibleName, AccessibleRole, AllowDrop, Anchor, AutoSize, AutoScrollOffset, LayoutEngine(RO), BindingContext, Bottom(RO), Bounds, CanFocus(RO), CanSelect(RO), Capture, CausesValidation, ClientRectangle(RO), ClientSize, CompanyName(RO), ContainsFocus(RO), ContextMenu, ContextMenuStrip, Controls(RO), Created(RO), Cursor, DataBindings(RO), DeviceDpi(RO), DisplayRectangle(RO), IsDisposed(RO), Disposing(RO), Dock, Enabled, Focused(RO), Handle(RO), HasChildren(RO), Height, IsHandleCreated(RO), InvokeRequired(RO), IsAccessible, IsMirrored(RO), Left, Location, Margin, MaximumSize, MinimumSize, Name, Parent, ProductName(RO), ProductVersion(RO), RecreatingHandle(RO), Region, Right(RO), RightToLeft, Site, Size, TabIndex, TabStop, Tag, Top, TopLevelControl(RO), UseWaitCursor, Visible, Width, WindowTarget, PreferredSize(RO), Padding, ImeMode
- 继承 Component(1 个):Container(RO)
### 事件
自有事件:无(仅基类事件)。
- Designer.cs 示例:this.imageButton1.图文布局 = Smart.CustomComponents.图文布局枚举.图左文右;
- 点击用基类 Click。
---
## 增强数据表格 EnhancedDataGridView
**继承**:`Smart.CustomComponents.EnhancedDataGridView` ← `System.Windows.Forms.DataGridView`
在标准 DataGridView 上增加:Excel 风格选择高亮、剪贴板多单元格粘贴、SQLite 三键绑定(x86 混合模式 System.Data.SQLite 1.0.119,无需 Interop.dll)。列仍用标准 Columns 序列化。
### 属性(全部 9 个自有属性)
| 属性 | 类型 | 默认值 | 序列化 | 说明 |
|---|---|---|---|---|
| EnableExcelStyleSelection | Boolean | True | 非默认才写 | 列头/行头点击整列整行高亮 + 框选区域高亮,默认开 |
| EnableCellPaste | Boolean | False | 非默认才写 | 允许从 Excel 复制后粘贴多单元格,默认关 |
| SqliteDatabasePath | String | | — | SQLite 数据库文件路径(.db);相对路径相对 exe |
| SqliteQuery | String | | — | 绑定用的 SELECT 查询语句 |
| AutoBindSqliteOnLoad | Boolean | False | 非默认才写 | 窗体载入时自动执行 BindSqliteData() |
| CreateTableButton | String | | — | 设计时动作:属性面板点"…"按当前列生成建表 SQL/执行(值恒空,不序列化,绝不手写) |
| BindDataButton | String | | — | 设计时动作:点"…"立即按三件套绑定一次(值恒空,不序列化) |
| ClearBindButton | String | | — | 设计时动作:点"…"解除数据绑定(值恒空,不序列化) |
| ColumnHeadersHeightSizeMode | DataGridViewColumnHeadersHeightSizeMode | EnableResizing | 非默认才写 | 遮蔽基类同名属性;默认 EnableResizing(表头可拖拽调高),与基类一致不用写 |
### 继承属性(名称全列)
- 继承 DataGridView(79 个):AdjustedTopLeftHeaderBorderStyle(RO), AdvancedCellBorderStyle(RO), AdvancedColumnHeadersBorderStyle(RO), AdvancedRowHeadersBorderStyle(RO), AllowUserToAddRows, AllowUserToDeleteRows, AllowUserToOrderColumns, AllowUserToResizeColumns, AllowUserToResizeRows, AlternatingRowsDefaultCellStyle, AutoGenerateColumns, AutoSize, AutoSizeColumnsMode, AutoSizeRowsMode, BackColor, BackgroundColor, BackgroundImage, BackgroundImageLayout, BorderStyle, CellBorderStyle, ClipboardCopyMode, ColumnCount, ColumnHeadersBorderStyle, ColumnHeadersDefaultCellStyle, ColumnHeadersHeight, ColumnHeadersVisible, Columns(RO), CurrentCell, CurrentCellAddress(RO), CurrentRow(RO), DataMember, DataSource, DefaultCellStyle, DisplayRectangle(RO), EditMode, EditingControl(RO), EditingPanel(RO), EnableHeadersVisualStyles, FirstDisplayedCell, FirstDisplayedScrollingColumnHiddenWidth(RO), FirstDisplayedScrollingColumnIndex, FirstDisplayedScrollingRowIndex, ForeColor, Font, GridColor, HorizontalScrollingOffset, IsCurrentCellDirty(RO), IsCurrentCellInEditMode(RO), IsCurrentRowDirty(RO), MultiSelect, NewRowIndex(RO), Padding, ReadOnly, RowCount, RowHeadersBorderStyle, RowHeadersDefaultCellStyle, RowHeadersVisible, RowHeadersWidth, RowHeadersWidthSizeMode, Rows(RO), RowsDefaultCellStyle, RowTemplate, ScrollBars, SelectedCells(RO), SelectedColumns(RO), SelectedRows(RO), SelectionMode, ShowCellErrors, ShowCellToolTips, ShowEditingIcon, ShowRowErrors, SortedColumn(RO), SortOrder(RO), StandardTab, Text, TopLeftHeaderCell, UserSetCursor(RO), VerticalScrollingOffset(RO), VirtualMode
- 继承 Control(65 个):AccessibilityObject(RO), AccessibleDefaultActionDescription, AccessibleDescription, AccessibleName, AccessibleRole, AllowDrop, Anchor, AutoScrollOffset, LayoutEngine(RO), BindingContext, Bottom(RO), Bounds, CanFocus(RO), CanSelect(RO), Capture, CausesValidation, ClientRectangle(RO), ClientSize, CompanyName(RO), ContainsFocus(RO), ContextMenu, ContextMenuStrip, Controls(RO), Created(RO), Cursor, DataBindings(RO), DeviceDpi(RO), IsDisposed(RO), Disposing(RO), Dock, Enabled, Focused(RO), Handle(RO), HasChildren(RO), Height, IsHandleCreated(RO), InvokeRequired(RO), IsAccessible, IsMirrored(RO), Left, Location, Margin, MaximumSize, MinimumSize, Name, Parent, ProductName(RO), ProductVersion(RO), RecreatingHandle(RO), Region, Right(RO), RightToLeft, Site, Size, TabIndex, TabStop, Tag, Top, TopLevelControl(RO), UseWaitCursor, Visible, Width, WindowTarget, PreferredSize(RO), ImeMode
- 继承 Component(1 个):Container(RO)
### 事件
自有事件:无(仅基类事件)。
- API:BindSqliteData()(按三件套绑定);CreateSqliteTableFromColumns(dbPath, tableName, dropIfExists)(按列建表,返回 SQL);PreviewCreateTableSql(tableName, dropIfExists)。
- Designer.cs 列走 DataGridViewColumn 序列化(DataGridViewTextBoxColumn 等),与本库无关。
- 排错:EDG.Ctor.Log.txt 记录"谁改了列高模式+调用栈";属性面板改了不生效 → 查 Designer.cs 残留显式模式行 + HintPath 是否指向旧版 DLL。
---
## 多选下拉框 MultiSelectComboBox
**继承**:`Smart.CustomComponents.MultiSelectComboBox` ← `System.Windows.Forms.Control`
带勾选、可搜索、可自定义输入的多选下拉框,选中项渲染成 Token(标签)。
### 属性(全部 19 个自有属性)
| 属性 | 类型 | 默认值 | 序列化 | 说明 |
|---|---|---|---|---|
| Items | ComboBoxItemCollection | | **Content 集合** | 下拉项字符串集合(Content 集合,逐条生成) |
| Text | String | | — | 显示文本;运行时取已选用 SelectedItems/SelectedValues |
| MultiSelect | Boolean | True | 非默认才写 | 允许多选(Token 叠加);false 时单选 |
| AllowCustomInput | Boolean | False | 非默认才写 | 允许输入集合外自定义项 |
| Searchable | Boolean | True | 非默认才写 | 下拉内搜索框开关 |
| SelectedIndices | List<Int32> | | 不序列化 | |
| SelectedValues | List<String> | | 不序列化 | |
| SelectedItems | List<Object> | | 不序列化 | |
| SelectedText | String | | 不序列化 | 隐藏,不序列化 |
| MaxDropDownItems | Int32 | 8 | 非默认才写 | 下拉最大可见行数,默认 8 |
| DropDownWidth | Int32 | 0 | 非默认才写 | |
| DropDownHeight | Int32 | 0 | 非默认才写 | |
| DroppedDown | Boolean | | 不序列化 | |
| TokenBackColor | Color | | — | Token 底色,出厂 64,158,255 |
| TokenForeColor | Color | | — | Token 文字色,出厂 White |
| BorderColor | Color | | — | 边框色,出厂 173,178,184 |
| HoverBorderColor | Color | | — | 悬停边框色,出厂 64,158,255 |
| ArrowColor | Color | | — | 箭头色,出厂 96,98,102 |
| ArrowHoverColor | Color | | — | 箭头悬停色,出厂 64,158,255 |
### 继承属性(名称全列)
- 继承 Control(73 个):AccessibilityObject(RO), AccessibleDefaultActionDescription, AccessibleDescription, AccessibleName, AccessibleRole, AllowDrop, Anchor, AutoSize, AutoScrollOffset, LayoutEngine(RO), BackColor, BackgroundImage, BackgroundImageLayout, BindingContext, Bottom(RO), Bounds, CanFocus(RO), CanSelect(RO), Capture, CausesValidation, ClientRectangle(RO), ClientSize, CompanyName(RO), ContainsFocus(RO), ContextMenu, ContextMenuStrip, Controls(RO), Created(RO), Cursor, DataBindings(RO), DeviceDpi(RO), DisplayRectangle(RO), IsDisposed(RO), Disposing(RO), Dock, Enabled, Focused(RO), Font, ForeColor, Handle(RO), HasChildren(RO), Height, IsHandleCreated(RO), InvokeRequired(RO), IsAccessible, IsMirrored(RO), Left, Location, Margin, MaximumSize, MinimumSize, Name, Parent, ProductName(RO), ProductVersion(RO), RecreatingHandle(RO), Region, Right(RO), RightToLeft, Site, Size, TabIndex, TabStop, Tag, Top, TopLevelControl(RO), UseWaitCursor, Visible, Width, WindowTarget, PreferredSize(RO), Padding, ImeMode
- 继承 Component(1 个):Container(RO)
### 事件
| 事件 | 签名 |
|---|---|
| SelectedItemsChanged | EventHandler |
| DropDown | EventHandler |
| DropDownClosed | EventHandler |
| TextChangedEx | EventHandler |
- API:SelectIndex(i)/UnselectIndex(i)/ToggleIndex(i)/ClearSelection()/SelectAll();ToggleDropDown()/OpenDropDown()/CloseDropDown()。
- 事件:SelectedItemsChanged、DropDown、DropDownClosed、TextChangedEx。
- 取值:selectedValues 属性(List<string>)或 SelectedIndices。
---
## 可调高下拉框 ResizableComboBox
**继承**:`Smart.CustomComponents.ResizableComboBox` ← `System.Windows.Forms.Control`
高度可自由调整的下拉框(标准 ComboBox 高度锁死,这是它存在的意义)。下拉面板自绘,支持排序/整合高度。
### 属性(全部 17 个自有属性)
| 属性 | 类型 | 默认值 | 序列化 | 说明 |
|---|---|---|---|---|
| Items | ComboBoxItemCollection | | **Content 集合** | 下拉项字符串集合(Content 集合) |
| Text | String | | — | 当前显示文本 |
| SelectedIndex | Int32 | -1 | 非默认才写 | 选中索引,默认 -1 |
| SelectedItem | Object | | 不序列化 | 选中项(隐藏,运行时用) |
| SelectedText | String | | 不序列化 | 隐藏,不序列化 |
| DropDownStyle | ComboBoxStyle | DropDownList | 非默认才写 | DropDownList(只可选)/DropDown(可输入),默认 DropDownList |
| MaxDropDownItems | Int32 | 8 | 非默认才写 | 默认 8 |
| DropDownWidth | Int32 | 0 | 非默认才写 | |
| DropDownHeight | Int32 | 0 | 非默认才写 | |
| IntegralHeight | Boolean | True | 非默认才写 | 行高取整,默认 true |
| Sorted | Boolean | False | 非默认才写 | 自动排序,默认 false |
| Flat | Boolean | False | 非默认才写 | 扁平外观开关 |
| BorderColor | Color | | — | 出厂 173,178,184 |
| HoverBorderColor | Color | | — | 出厂 64,158,255 |
| ArrowColor | Color | | — | 出厂 96,98,102 |
| ArrowHoverColor | Color | | — | 出厂 64,158,255 |
| DroppedDown | Boolean | | 不序列化 | 只读状态 |
### 继承属性(名称全列)
- 继承 Control(73 个):AccessibilityObject(RO), AccessibleDefaultActionDescription, AccessibleDescription, AccessibleName, AccessibleRole, AllowDrop, Anchor, AutoSize, AutoScrollOffset, LayoutEngine(RO), BackColor, BackgroundImage, BackgroundImageLayout, BindingContext, Bottom(RO), Bounds, CanFocus(RO), CanSelect(RO), Capture, CausesValidation, ClientRectangle(RO), ClientSize, CompanyName(RO), ContainsFocus(RO), ContextMenu, ContextMenuStrip, Controls(RO), Created(RO), Cursor, DataBindings(RO), DeviceDpi(RO), DisplayRectangle(RO), IsDisposed(RO), Disposing(RO), Dock, Enabled, Focused(RO), Font, ForeColor, Handle(RO), HasChildren(RO), Height, IsHandleCreated(RO), InvokeRequired(RO), IsAccessible, IsMirrored(RO), Left, Location, Margin, MaximumSize, MinimumSize, Name, Parent, ProductName(RO), ProductVersion(RO), RecreatingHandle(RO), Region, Right(RO), RightToLeft, Site, Size, TabIndex, TabStop, Tag, Top, TopLevelControl(RO), UseWaitCursor, Visible, Width, WindowTarget, PreferredSize(RO), Padding, ImeMode
- 继承 Component(1 个):Container(RO)
### 事件
| 事件 | 签名 |
|---|---|
| SelectedIndexChanged | EventHandler |
| SelectedValueChanged | EventHandler |
| DropDown | EventHandler |
| DropDownClosed | EventHandler |
| TextChangedEx | EventHandler |
- API:ToggleDropDown()/OpenDropDown()/CloseDropDown()。
- 事件:SelectedIndexChanged、SelectedValueChanged、DropDown、DropDownClosed、TextChangedEx。
- Designer.cs 里 SelectedText 绝不写。
---
## 可调高文本框 ResizableTextBox
**继承**:`Smart.CustomComponents.ResizableTextBox` ← `System.Windows.Forms.Control`
高度可自由调整的单行文本框,带占位提示与三态边框色(常态/悬停/聚焦)。
### 属性(全部 15 个自有属性)
| 属性 | 类型 | 默认值 | 序列化 | 说明 |
|---|---|---|---|---|
| Text | String | | — | 文本内容 |
| MaxLength | Int32 | 32767 | 非默认才写 | 最大长度,默认 32767 |
| ReadOnly | Boolean | False | 非默认才写 | 只读 |
| CharacterCasing | CharacterCasing | Normal | 非默认才写 | 大小写转换,默认 Normal |
| PasswordChar | Char | \0(不掩码,写法 \0) | 非默认才写 | 密码掩码字符,默认 \0(不掩码) |
| TextAlign | HorizontalAlignment | Left | 非默认才写 | Left/Center/Right,默认 Left |
| PlaceholderText | String | | — | 占位提示文字 |
| Flat | Boolean | False | 非默认才写 | 扁平外观(去圆角) |
| BorderColor | Color | | — | 常态边框色,出厂 173,178,184 |
| HoverBorderColor | Color | | — | 悬停边框色,出厂 64,158,255 |
| FocusBorderColor | Color | | — | 聚焦边框色,出厂 64,158,255 |
| PlaceholderColor | Color | | — | 占位文字色,出厂 192,196,206 |
| SelectionStart | Int32 | | 不序列化 | |
| SelectionLength | Int32 | | 不序列化 | |
| SelectedText | String | | 不序列化 | |
### 继承属性(名称全列)
- 继承 Control(73 个):AccessibilityObject(RO), AccessibleDefaultActionDescription, AccessibleDescription, AccessibleName, AccessibleRole, AllowDrop, Anchor, AutoSize, AutoScrollOffset, LayoutEngine(RO), BackColor, BackgroundImage, BackgroundImageLayout, BindingContext, Bottom(RO), Bounds, CanFocus(RO), CanSelect(RO), Capture, CausesValidation, ClientRectangle(RO), ClientSize, CompanyName(RO), ContainsFocus(RO), ContextMenu, ContextMenuStrip, Controls(RO), Created(RO), Cursor, DataBindings(RO), DeviceDpi(RO), DisplayRectangle(RO), IsDisposed(RO), Disposing(RO), Dock, Enabled, Focused(RO), Font, ForeColor, Handle(RO), HasChildren(RO), Height, IsHandleCreated(RO), InvokeRequired(RO), IsAccessible, IsMirrored(RO), Left, Location, Margin, MaximumSize, MinimumSize, Name, Parent, ProductName(RO), ProductVersion(RO), RecreatingHandle(RO), Region, Right(RO), RightToLeft, Site, Size, TabIndex, TabStop, Tag, Top, TopLevelControl(RO), UseWaitCursor, Visible, Width, WindowTarget, PreferredSize(RO), Padding, ImeMode
- 继承 Component(1 个):Container(RO)
### 事件
| 事件 | 签名 |
|---|---|
| TextChanged | EventHandler |
- 事件:TextChanged(基类)。
- API:SelectAll()、Select(start, length)。
---
## OK/NG 状态灯 OKNGStatusControl
**继承**:`Smart.CustomComponents.OKNGStatusControl` ← `System.Windows.Forms.Control`
圆形 OK(绿)/NG(红) 状态灯,可绑定变量名由变量系统驱动。
### 属性(全部 2 个自有属性)
| 属性 | 类型 | 默认值 | 序列化 | 说明 |
|---|---|---|---|---|
| BindVariable | String | | — | 绑定的变量名(变量绑定编辑器选择) |
| IsOK | Boolean | | — | true=OK(绿),false=NG(红);源码初始值 true |
### 继承属性(名称全列)
- 继承 Control(74 个):AccessibilityObject(RO), AccessibleDefaultActionDescription, AccessibleDescription, AccessibleName, AccessibleRole, AllowDrop, Anchor, AutoSize, AutoScrollOffset, LayoutEngine(RO), BackColor, BackgroundImage, BackgroundImageLayout, BindingContext, Bottom(RO), Bounds, CanFocus(RO), CanSelect(RO), Capture, CausesValidation, ClientRectangle(RO), ClientSize, CompanyName(RO), ContainsFocus(RO), ContextMenu, ContextMenuStrip, Controls(RO), Created(RO), Cursor, DataBindings(RO), DeviceDpi(RO), DisplayRectangle(RO), IsDisposed(RO), Disposing(RO), Dock, Enabled, Focused(RO), Font, ForeColor, Handle(RO), HasChildren(RO), Height, IsHandleCreated(RO), InvokeRequired(RO), IsAccessible, IsMirrored(RO), Left, Location, Margin, MaximumSize, MinimumSize, Name, Parent, ProductName(RO), ProductVersion(RO), RecreatingHandle(RO), Region, Right(RO), RightToLeft, Site, Size, TabIndex, TabStop, Tag, Text, Top, TopLevelControl(RO), UseWaitCursor, Visible, Width, WindowTarget, PreferredSize(RO), Padding, ImeMode
- 继承 Component(1 个):Container(RO)
### 事件
自有事件:无(仅基类事件)。
- **绝不写 Text**(自绘覆盖文字)。
- 运行时 this.okngStatusControl1.IsOK = false; 切红灯。
---
## 侧边菜单 SideMenuPanel
**继承**:`Smart.CustomComponents.SideMenuPanel` ← `System.Windows.Forms.UserControl`
折叠式侧边菜单 + 内容页面:MenuItems 声明菜单树(可递归子项),Pages 声明页面(MenuKey 关联菜单项),点击菜单自动切页。
### 属性(全部 19 个自有属性)
| 属性 | 类型 | 默认值 | 序列化 | 说明 |
|---|---|---|---|---|
| MenuItems | BindingList<SideMenuItem> | | **Content 集合** | 菜单项集合(Content,条目 SideMenuItem 可递归 Children) |
| Pages | BindingList<SideMenuPage> | | **Content 集合** | 页面集合(Content,条目 SideMenuPage : Panel,设计器里页面字段名 sideMenuPageN) |
| ActivePage | SideMenuPage | | 不序列化 | 当前页(只读,隐藏) |
| MenuWidth | Int32 | 160 | 非默认才写 | 菜单宽 px,默认 160 |
| MenuCollapsed | Boolean | False | 非默认才写 | 折叠菜单,默认 false |
| ItemHeight | Int32 | 34 | 非默认才写 | 菜单行高 px,默认 34 |
| Indent | Int32 | 18 | 非默认才写 | 子级缩进 px,默认 18 |
| IconSize | Int32 | 18 | 非默认才写 | 图标尺寸 px,默认 18 |
| ShowIcons | Boolean | True | 非默认才写 | 显示图标,默认 true |
| MenuBackColor | Color | | — | 出厂 White |
| MenuForeColor | Color | | — | 出厂 80,80,80 |
| SelectedColor | Color | | — | 选中底色,出厂 230,247,255 |
| SelectedForeColor | Color | | — | 选中文字色,出厂 24,144,255 |
| HoverColor | Color | | — | 悬停底色,出厂 245,245,245 |
| ArrowColor | Color | | — | 展开箭头色,出厂 150,150,150 |
| SplitterColor | Color | | — | 菜单/内容分隔线色,出厂 235,235,235 |
| ContentPanel | SideMenuContentPanel | | 不序列化 | 内容容器(只读,隐藏;运行时向它加控件) |
| SelectedNode | SideMenuItem | | 不序列化 | 当前选中菜单项(隐藏) |
| ForeColor | Color | | 不序列化 | 隐藏(用 MenuForeColor 系列代替) |
### 继承属性(名称全列)
- 继承 UserControl(5 个):AutoSize, AutoSizeMode, AutoValidate, BorderStyle, Text
- 继承 ContainerControl(6 个):AutoScaleDimensions, AutoScaleMode, BindingContext, ActiveControl, CurrentAutoScaleDimensions(RO), ParentForm(RO)
- 继承 ScrollableControl(8 个):AutoScroll, AutoScrollMargin, AutoScrollPosition, AutoScrollMinSize, DisplayRectangle(RO), HorizontalScroll(RO), VerticalScroll(RO), DockPadding(RO)
- 继承 Control(69 个):AccessibilityObject(RO), AccessibleDefaultActionDescription, AccessibleDescription, AccessibleName, AccessibleRole, AllowDrop, Anchor, AutoScrollOffset, LayoutEngine(RO), BackColor, BackgroundImage, BackgroundImageLayout, Bottom(RO), Bounds, CanFocus(RO), CanSelect(RO), Capture, CausesValidation, ClientRectangle(RO), ClientSize, CompanyName(RO), ContainsFocus(RO), ContextMenu, ContextMenuStrip, Controls(RO), Created(RO), Cursor, DataBindings(RO), DeviceDpi(RO), IsDisposed(RO), Disposing(RO), Dock, Enabled, Focused(RO), Font, Handle(RO), HasChildren(RO), Height, IsHandleCreated(RO), InvokeRequired(RO), IsAccessible, IsMirrored(RO), Left, Location, Margin, MaximumSize, MinimumSize, Name, Parent, ProductName(RO), ProductVersion(RO), RecreatingHandle(RO), Region, Right(RO), RightToLeft, Site, Size, TabIndex, TabStop, Tag, Top, TopLevelControl(RO), UseWaitCursor, Visible, Width, WindowTarget, PreferredSize(RO), Padding, ImeMode
- 继承 Component(1 个):Container(RO)
### 事件
| 事件 | 签名 |
|---|---|
| SelectedNodeChanged | EventHandler< |
- API:SelectNode(SideMenuItem)、ShowPage(string key)、CanDropAt(Point)、GetActiveDropTarget()。
- 事件:SelectedNodeChanged(参数 SelectedNodeChangedEventArgs.Node)。
- 设计期默认集合内容只能在 Designer.Initialize 且 !host.Loading 时注入——绝不写进构造函数(会运行时翻倍)。
---
## 功能区 RibbonStrip
**继承**:`Smart.CustomComponents.RibbonStrip` ← `System.Windows.Forms.UserControl`
Excel 风格功能区:Tabs(选项卡)→ Groups(分组)→ Buttons(按钮)三层集合。设计器支持拖拽重排按钮、双击选项卡改名;点击按钮在运行时统一走 ButtonClick 事件。
### 属性(全部 10 个自有属性)
| 属性 | 类型 | 默认值 | 序列化 | 说明 |
|---|---|---|---|---|
| Tabs | BindingList<RibbonTab> | | **Content 集合** | 选项卡集合(Content,条目 RibbonTab.Groups→RibbonGroup.Buttons→RibbonButton) |
| ActiveTabIndex | Int32 | 0 | 非默认才写 | 当前选项卡索引,默认 0 |
| ActiveTab | RibbonTab | | 不序列化 | 当前选项卡(只读,隐藏) |
| AccentColor | Color | A=255, R=33, G=115, B=70 | 非默认才写 | 主题强调色(选中选项卡下划线等),出厂 33,115,70(Excel 绿) |
| TabBarColor | Color | A=255, R=243, G=242, B=241 | 非默认才写 | 选项卡条底色,出厂 243,242,241 |
| ContentColor | Color | White | 非默认才写 | 内容区底色,出厂 White |
| SeparatorColor | Color | A=255, R=180, G=180, B=180 | 非默认才写 | 组分隔线色,出厂 180,180,180 |
| ForeColor | Color | | 不序列化 | 隐藏(组名/文字色内部固定) |
| BackgroundImage | Image | | 不序列化 | |
| BackgroundImageLayout | ImageLayout | | 不序列化 | |
### 继承属性(名称全列)
- 继承 UserControl(5 个):AutoSize, AutoSizeMode, AutoValidate, BorderStyle, Text
- 继承 ContainerControl(6 个):AutoScaleDimensions, AutoScaleMode, BindingContext, ActiveControl, CurrentAutoScaleDimensions(RO), ParentForm(RO)
- 继承 ScrollableControl(8 个):AutoScroll, AutoScrollMargin, AutoScrollPosition, AutoScrollMinSize, DisplayRectangle(RO), HorizontalScroll(RO), VerticalScroll(RO), DockPadding(RO)
- 继承 Control(67 个):AccessibilityObject(RO), AccessibleDefaultActionDescription, AccessibleDescription, AccessibleName, AccessibleRole, AllowDrop, Anchor, AutoScrollOffset, LayoutEngine(RO), BackColor, Bottom(RO), Bounds, CanFocus(RO), CanSelect(RO), Capture, CausesValidation, ClientRectangle(RO), ClientSize, CompanyName(RO), ContainsFocus(RO), ContextMenu, ContextMenuStrip, Controls(RO), Created(RO), Cursor, DataBindings(RO), DeviceDpi(RO), IsDisposed(RO), Disposing(RO), Dock, Enabled, Focused(RO), Font, Handle(RO), HasChildren(RO), Height, IsHandleCreated(RO), InvokeRequired(RO), IsAccessible, IsMirrored(RO), Left, Location, Margin, MaximumSize, MinimumSize, Name, Parent, ProductName(RO), ProductVersion(RO), RecreatingHandle(RO), Region, Right(RO), RightToLeft, Site, Size, TabIndex, TabStop, Tag, Top, TopLevelControl(RO), UseWaitCursor, Visible, Width, WindowTarget, PreferredSize(RO), Padding, ImeMode
- 继承 Component(1 个):Container(RO)
### 事件
| 事件 | 签名 |
|---|---|
| ButtonClick | EventHandler< |
- 事件:ButtonClick(object sender, RibbonButtonEventArgs e);e.Tab/e.Group/e.Button 分别为所在选项卡/分组/按钮(用 e.Button.Text 分发)。
- 图标:RibbonButton.Image 序列化为 `buttonN.Image = RibbonButton.FromFile("路径")`(有来源路径时;ImageFile 属性本身隐藏)。
- Designer.cs 生成规则(三层 Content 集合)见 codegen.md §2.5。
---
## 变量显示标签 VariableDisplayLabel
**继承**:`Smart.CustomComponents.VariableDisplayLabel` ← `System.Windows.Forms.Control`
只读展示绑定变量当前值的标签,值变化触发 ValueChanged。
### 属性(全部 3 个自有属性)
| 属性 | 类型 | 默认值 | 序列化 | 说明 |
|---|---|---|---|---|
| BindVariable | String | | — | 绑定的变量名(变量绑定编辑器选择) |
| DisplayValue | String | | — | 当前显示值,默认 "——"(运行时由变量系统刷新) |
| TextAlign | ContentAlignment | MiddleLeft | 非默认才写 | ContentAlignment,默认 MiddleLeft |
### 继承属性(名称全列)
- 继承 Control(74 个):AccessibilityObject(RO), AccessibleDefaultActionDescription, AccessibleDescription, AccessibleName, AccessibleRole, AllowDrop, Anchor, AutoSize, AutoScrollOffset, LayoutEngine(RO), BackColor, BackgroundImage, BackgroundImageLayout, BindingContext, Bottom(RO), Bounds, CanFocus(RO), CanSelect(RO), Capture, CausesValidation, ClientRectangle(RO), ClientSize, CompanyName(RO), ContainsFocus(RO), ContextMenu, ContextMenuStrip, Controls(RO), Created(RO), Cursor, DataBindings(RO), DeviceDpi(RO), DisplayRectangle(RO), IsDisposed(RO), Disposing(RO), Dock, Enabled, Focused(RO), Font, ForeColor, Handle(RO), HasChildren(RO), Height, IsHandleCreated(RO), InvokeRequired(RO), IsAccessible, IsMirrored(RO), Left, Location, Margin, MaximumSize, MinimumSize, Name, Parent, ProductName(RO), ProductVersion(RO), RecreatingHandle(RO), Region, Right(RO), RightToLeft, Site, Size, TabIndex, TabStop, Tag, Text, Top, TopLevelControl(RO), UseWaitCursor, Visible, Width, WindowTarget, PreferredSize(RO), Padding, ImeMode
- 继承 Component(1 个):Container(RO)
### 事件
| 事件 | 签名 |
|---|---|
| ValueChanged | EventHandler |
- 只读控件:不接受输入(PreProcessMessage 恒 false)。
- **⚠ 没有 BorderStyle**(继承 Control 不是 Label,s23 案例抄错即设计器中止加载);要边框容器就用 Panel/GroupBox 包一层。
- 事件:ValueChanged。
---
## 变量编辑框 VariableEditTextBox
**继承**:`Smart.CustomComponents.VariableEditTextBox` ← `System.Windows.Forms.TextBox`
标准 TextBox + 变量绑定:BindVariable 指向变量,Value 与 Text 同步。
### 属性(全部 2 个自有属性)
| 属性 | 类型 | 默认值 | 序列化 | 说明 |
|---|---|---|---|---|
| BindVariable | String | | — | 绑定的变量名(变量绑定编辑器选择) |
| Value | String | | 不序列化 | 与 Text 同步的镜像值(隐藏,不序列化) |
### 继承属性(名称全列)
- 继承 TextBox(11 个):AcceptsReturn, AutoCompleteMode, AutoCompleteSource, AutoCompleteCustomSource, CharacterCasing, Multiline, PasswordChar, ScrollBars, Text, TextAlign, UseSystemPasswordChar
- 继承 TextBoxBase(21 个):AcceptsTab, ShortcutsEnabled, AutoSize, BackColor, BackgroundImage, BackgroundImageLayout, BorderStyle, CanUndo(RO), ForeColor, HideSelection, Lines, MaxLength, Modified, Padding, PreferredHeight(RO), ReadOnly, SelectedText, SelectionLength, SelectionStart, TextLength(RO), WordWrap
- 继承 Control(67 个):AccessibilityObject(RO), AccessibleDefaultActionDescription, AccessibleDescription, AccessibleName, AccessibleRole, AllowDrop, Anchor, AutoScrollOffset, LayoutEngine(RO), BindingContext, Bottom(RO), Bounds, CanFocus(RO), CanSelect(RO), Capture, CausesValidation, ClientRectangle(RO), ClientSize, CompanyName(RO), ContainsFocus(RO), ContextMenu, ContextMenuStrip, Controls(RO), Created(RO), Cursor, DataBindings(RO), DeviceDpi(RO), DisplayRectangle(RO), IsDisposed(RO), Disposing(RO), Dock, Enabled, Focused(RO), Font, Handle(RO), HasChildren(RO), Height, IsHandleCreated(RO), InvokeRequired(RO), IsAccessible, IsMirrored(RO), Left, Location, Margin, MaximumSize, MinimumSize, Name, Parent, ProductName(RO), ProductVersion(RO), RecreatingHandle(RO), Region, Right(RO), RightToLeft, Site, Size, TabIndex, TabStop, Tag, Top, TopLevelControl(RO), UseWaitCursor, Visible, Width, WindowTarget, PreferredSize(RO), ImeMode
- 继承 Component(1 个):Container(RO)
### 事件
自有事件:无(仅基类事件)。
- 其余全部属性/事件与标准 TextBox 一致(Text/Multiline/PasswordChar/TextChanged…见 winforms-controls.md#TextBox)。
- Designer.cs 只需写 BindVariable 与常用 TextBox 属性。
- **⚠ 没有 PlaceholderText**(那是 ResizableTextBox 的自有属性,s23 案例抄错即设计器中止加载);要占位提示就用 ResizableTextBox。
---
## 日志记录器 Logger(非可视组件)
**继承**:`Smart.CustomComponents.Logger` ← `System.ComponentModel.Component`
拖到窗体托盘的日志组件,代码里直接调中文方法写日志。
### 属性(全部 4 个自有属性)
| 属性 | 类型 | 默认值 | 序列化 | 说明 |
|---|---|---|---|---|
| 日志文件名 | String | logs\app.log | 非默认才写 | 日志文件路径,默认 logs\app.log |
| 日志级别 | 日志级别枚举 | 信息 | 非默认才写 | 调试/信息/警告/错误/致命,默认 信息 |
| 按天滚动 | Boolean | True | 非默认才写 | 按日期滚动文件,默认 true |
| 最多保留份数 | Int32 | 30 | 非默认才写 | 滚动保留份数,默认 30 |
### 继承属性(名称全列)
- 继承 Component(2 个):Site, Container(RO)
### 事件
自有事件:无(仅基类事件)。
- API:写调试(msg)/写信息(msg)/写警告(msg)/写错误(msg)/写异常(msg, ex);设计模式下调用是空操作。
- 事件:无(仅 Disposed)。
---
## Content 集合条目类型(全部属性)
### DockWindowItem(DockSurface.DockWindows 的条目)
| 属性 | 类型 | 默认值 | 序列化 | 说明 |
|---|---|---|---|---|
| ClassName | String | DockWindow | 非默认才写 | |
| Title | String | 停靠窗口 | 非默认才写 | |
| PersistString | String | | 不序列化 | |
| DockState | DockState | DockLeft | 非默认才写 | |
| DefaultWidth | Int32 | 220 | 非默认才写 | |
| DefaultHeight | Int32 | 180 | 非默认才写 | |
| FixedPosition | Boolean | False | 非默认才写 | |
| ShowCloseButton | Boolean | True | 非默认才写 | |
| ContentXml | String | | 不序列化 | |
| ColumnsXml | String | | 不序列化 | |
| SourceFile | String | | 不序列化 | |
| HasContent | Boolean | | 不序列化 | |
- Hidden 属性(PersistString/ContentXml/ColumnsXml/SourceFile)由三文件生成器消费,绝不手写。
- HasContent 只读,指示是否已挂窗口内容。
### SideMenuItem(SideMenuPanel.MenuItems 的条目)
| 属性 | 类型 | 默认值 | 序列化 | 说明 |
|---|---|---|---|---|
| Text | String | | — | |
| Icon | Image | | — | |
| IsExpanded | Boolean | False | 非默认才写 | |
| Children | BindingList<SideMenuItem> | | **Content 集合** | |
- Children 可递归嵌套子菜单(Content 集合)。
### SideMenuPage(SideMenuPanel.Pages 的条目): Panel
| 属性 | 类型 | 默认值 | 序列化 | 说明 |
|---|---|---|---|---|
| MenuKey | String | | — | |
- MenuKey 与某个菜单项对应(点击该菜单项显示本页);其余属性同 Panel。
### RibbonTab(RibbonStrip.Tabs 的条目)
| 属性 | 类型 | 默认值 | 序列化 | 说明 |
|---|---|---|---|---|
| Text | String | | — | |
| Groups | BindingList<RibbonGroup> | | **Content 集合** | |
- Groups 为分组集合(Content)。
### RibbonGroup(RibbonTab.Groups 的条目)
| 属性 | 类型 | 默认值 | 序列化 | 说明 |
|---|---|---|---|---|
| Text | String | | — | |
| Buttons | BindingList<RibbonButton> | | **Content 集合** | |
- Buttons 为按钮集合(Content)。
### RibbonButton(RibbonGroup.Buttons 的条目)
| 属性 | 类型 | 默认值 | 序列化 | 说明 |
|---|---|---|---|---|
| Name | String | | — | |
| ImageFile | String | | 不序列化 | |
| Image | Image | | — | |
| Text | String | | — | |
| SizeStyle | RibbonButtonSize | Small | 非默认才写 | |
| Enabled | Boolean | True | 非默认才写 | |
- Image 有路径时序列化为 `= RibbonButton.FromFile("路径")`;ImageFile 本身隐藏。
- Name 用于设计器标识;运行时分发用 Text。
---
## 枚举全值表(Designer.cs 一律写英文标识符,ImageButton 枚举例外——本身就是中文)
### RoundedButtonStyle:Primary=0、Success=1、Warning=2、Danger=3、Info=4、Dark=5、Light=6、Outline=7
### Smart.CustomComponents.DockSurface+SurfaceTheme:VS2015Light=0、VS2015Blue=1、VS2015Dark=2、VS2013Light=3、VS2013Blue=4、VS2013Dark=5、VS2012Light=6、VS2005=7、VS2003=8、Default=9
### Smart.CustomComponents.DockSurface+TabTipDirection:Left=0、Right=1
### RibbonButtonSize:Large=0、Small=1
### 日志级别枚举:调试=0、信息=1、警告=2、错误=3、致命=4
### 图片锚点位置:Center=0、N=1、S=2、E=3、W=4
### 图片缩放模式:Original=0、Fit=1、Stretch=2、Tile=3
### 图文布局枚举:文字居中=0、图下文上=1、图上文下=2、图右文左=3、图左文右=4、纯图片=5
## DockState 枚举(DockWindowItem.DockState 用,WeifenLuo)
值:Unknown、Float、DockTop、DockBottom、DockLeft、DockRight、Document、Hidden(常用:DockLeft/DockRight/DockBottom/Document/Float)。
## 共享基类属性速览
大多数增强控件继承 System.Windows.Forms.Control;Control 基类全部 73 个属性(含默认值)的完整表格见 `winforms-controls.md` 第 0 节"Control 基类属性总表",此处不重复。
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,33 @@
# 整合方案 v3(按用户要求调整):插件改源码、左侧工具栏 + 网页 CAD 文档区
## 布局(Smart.CustomComponents.DockSurface 停靠面板)
| 窗口 | 位置 | 内容 |
|---|---|---|
| **QuoteWindow(插件工具栏)** | **DockLeft**(宽 ~420,不可关闭) | Form3 报价界面(cadcj 改造版,作为左侧工具面板) |
| **NewProjectWindow** | DockLeft(第二个标签页或 DockBottom) | 新建项目窗体 |
| **CadViewerWindow** | **Document(中央文档区,独立窗口)** | WebView2 → http://localhost:5173 网页 CAD |
## A. 网页端(CAD-Viewer 本地仓库,packages/cad-viewer-example 新增 src/cadbridge.ts)
与 v2 相同:
- 连续扒图命令(仿 AcApRectCmd 两点 jig;正向=逐条/反向=合并;画彩色 AcDbPolyline 框;回传 text/frameHandle/textHandle)
- 消息监听:zoomToHandle / deleteHandles / clearHighlight;删除实体回发 entityErased
- 注册 patu/paxh/payj 命令
## B. 插件改造(直接修改 cadcj 源码,生成 cadbt 专用版本)
- 复制 Form3.cs/Form3.ui.cs/新建项目.cs/新建项目.ui.cs 到 cadbt 项目(cadcj 原插件目录不动,保持 AutoCAD 版独立)
- Form3 中约 15 处 `Autodesk.AutoCAD.*` 调用直接改写为 `CadBridge` 调用(扒图命令、ZoomToFrameHandle、DeleteFramesByHandles、ObjectErased 订阅→桥事件、SaveCurrentDwg 删除)
- PaMode/PaTextResult 保留;PaTextSelector 的桩改为 CadBridge.StartPick / EntityErased 事件
- 好处:无 stub 假类型,编译不依赖 acdbmgd,代码可读可维护
## C. 宿主程序(cadbt)
- csproj:增强组件九件套 + AdvancedDataGridView + System.Data.SQLite(安装目录/..\cadcj HintPath)+ WebView2(nuget 解压到 libs\webview2,x86 loader 复制输出)
- MainForm.Designer.cs:dockSurface1 + DockWindows 集合(左:QuoteWindow/NewProjectWindow;Document:CadViewerWindow)
- CadViewerWindow:代码创建 WebView2,导航 localhost:5173,WebMessageReceived 分发到 CadBridge 事件;失败提示启动 pnpm dev
- MainForm.DockWindows.cs:EnsureDefaultTheme + Show
## D. 验证
编译 cadbt → 运行 → 三面板布局正确 → 联调扒图/定位/删框链路;网页端 pnpm dev 已验证可跑
## 说明
- cadcj 原目录不动,AutoCAD 插件版继续可用;cadbt 用改造副本
- 后续可选:pnpm build 出 dist + SetVirtualHostNameToFolderMapping 实现离线嵌入
+20
View File
@@ -0,0 +1,20 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 2012
# SharpDevelop 5.2
VisualStudioVersion = 12.0.20827.3
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "cadbt", "cadbt\cadbt.csproj", "{0A91D57B-B279-4672-9440-78088D022EB1}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{0A91D57B-B279-4672-9440-78088D022EB1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0A91D57B-B279-4672-9440-78088D022EB1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0A91D57B-B279-4672-9440-78088D022EB1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0A91D57B-B279-4672-9440-78088D022EB1}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
Submodule cadbt/cadbt/CAD-Viewer added at c5962cc3f9
+213
View File
@@ -0,0 +1,213 @@
// ============================================================
// CadBridge:WinForms 宿主与网页 CAD(CAD-Viewer)之间的互操作桥
// · 下行(C#→网页):PostMessage 发 startPick/zoomToHandle/deleteHandles/clearHighlight
// · 上行(网页→C#):WebMessageReceived 收 paPicked/entityErased/paExited
// · 扒图结果与实体删除事件以 C# 事件形式提供给 Form3(替代 AutoCAD 版
// 的 PaTextSelector.BatchTextsPicked 与 Database.ObjectErased)
// ============================================================
using System;
using System.Collections.Generic;
using System.Web.Script.Serialization;
using Microsoft.Web.WebView2.Core;
using Microsoft.Web.WebView2.WinForms;
namespace cadbt
{
public static class CadBridge
{
private static WebView2 _wv;
private static readonly JavaScriptSerializer _json = new JavaScriptSerializer();
/// <summary>网页 CAD 是否已挂载(WebView2 已附加)。</summary>
public static bool IsViewerReady
{
get { return _wv != null && _wv.CoreWebView2 != null; }
}
/// <summary>网页 CAD 中实体被删除(句柄十六进制字符串)。</summary>
public static event Action<string> EntityErased;
/// <summary>网页 CAD 扒图完成一批(图号/箱号/元件)。</summary>
public static event Action<MyApp.PaMode, List<MyApp.PaTextResult>> PaPicked;
/// <summary>用户按 ESC 退出网页扒图连续模式。</summary>
public static event Action PaExited;
/// <summary>用户按 F1(焦点在网页或宿主时都触发)——元件两两合并。</summary>
public static event Action F1Requested;
/// <summary>用户按 F2——元件三个一组合并。</summary>
public static event Action F2Requested;
/// <summary>用户按 F3——元件四个一组合并。</summary>
public static event Action F3Requested;
/// <summary>触发 F1 请求(消息过滤器等宿主侧来源调用)。</summary>
public static void RaiseF1()
{
if (F1Requested != null) F1Requested();
}
/// <summary>触发 F2 请求(消息过滤器等宿主侧来源调用)。</summary>
public static void RaiseF2()
{
if (F2Requested != null) F2Requested();
}
/// <summary>触发 F3 请求(消息过滤器等宿主侧来源调用)。</summary>
public static void RaiseF3()
{
if (F3Requested != null) F3Requested();
}
/// <summary>把 WebView2 挂到桥上(在控件初始化完成后调用一次)。</summary>
public static void Attach(WebView2 wv)
{
if (_wv == wv) return;
if (_wv != null) _wv.WebMessageReceived -= OnMessage;
_wv = wv;
if (_wv != null) _wv.WebMessageReceived += OnMessage;
}
private static void OnMessage(object sender, CoreWebView2WebMessageReceivedEventArgs e)
{
try
{
if (e.TryGetWebMessageAsString() != null) return; // 只处理 JSON 消息
}
catch { }
try
{
var msg = _json.Deserialize<Dictionary<string, object>>(e.WebMessageAsJson);
if (msg == null || !msg.ContainsKey("type")) return;
string type = msg["type"] as string;
if (type == "paPicked")
{
int mode = Convert.ToInt32(msg["mode"]);
var results = new List<MyApp.PaTextResult>();
var arr = msg["results"] as System.Collections.ArrayList;
if (arr != null)
{
foreach (Dictionary<string, object> item in arr)
{
results.Add(new MyApp.PaTextResult
{
Text = Convert.ToString(item["text"]) ?? "",
FrameHandle = Convert.ToString(item["frameHandle"]) ?? "",
TextHandle = Convert.ToString(item["textHandle"]) ?? ""
});
}
}
// WebMessageReceived 在 UI 线程触发,可直接调用 Form3
if (PaPicked != null)
PaPicked((MyApp.PaMode)mode, results);
}
else if (type == "entityErased")
{
string handle = Convert.ToString(msg["handle"]);
if (!string.IsNullOrWhiteSpace(handle) && EntityErased != null)
EntityErased(handle);
}
else if (type == "paError")
{
string errMsg = Convert.ToString(msg["message"]);
System.Windows.Forms.MessageBox.Show(
"网页 CAD 返回错误:\n" + errMsg,
"扒数据", System.Windows.Forms.MessageBoxButtons.OK,
System.Windows.Forms.MessageBoxIcon.Warning);
}
else if (type == "f1")
{
if (F1Requested != null) F1Requested();
}
else if (type == "f2")
{
if (F2Requested != null) F2Requested();
}
else if (type == "f3")
{
if (F3Requested != null) F3Requested();
}
else if (type == "paExited")
{
if (PaExited != null) PaExited();
}
}
catch (Exception)
{
// 消息格式异常直接忽略,不影响宿主
}
}
private static void Post(object msg)
{
if (!IsViewerReady) return;
try
{
_wv.CoreWebView2.PostWebMessageAsJson(_json.Serialize(msg));
}
catch { }
}
/// <summary>
/// 发送扒图命令。接受原 CAD 命令名(CADPATU/CADPAXH/CADPAYJ/CADPACL)
/// 或网页命令名(patu/paxh/payj)。
/// </summary>
public static void SendPaCommand(string cmd)
{
if (string.IsNullOrWhiteSpace(cmd)) return;
string c = cmd.Trim().ToUpperInvariant();
int mode = 0;
if (c == "CADPATU" || c == "PATU") mode = 0;
else if (c == "CADPAXH" || c == "PAXH") mode = 1;
else if (c == "CADPAYJ" || c == "PAYJ") mode = 2;
else if (c == "CADPACL" || c == "PACL")
{
ClearHighlight();
return;
}
else return;
Post(new { type = "startPick", mode = mode });
}
/// <summary>缩放居中到指定 Handle 的实体并高亮(点击表格行定位框)。</summary>
public static void ZoomToFrameHandle(string handle)
{
if (string.IsNullOrWhiteSpace(handle)) return;
Post(new { type = "zoomToHandle", handle = handle });
}
/// <summary>批量删除指定 Handle 的实体(删表格行联动删框)。</summary>
public static void DeleteFramesByHandles(IEnumerable<string> handles)
{
if (handles == null) return;
var list = new List<string>();
foreach (string h in handles)
if (!string.IsNullOrWhiteSpace(h)) list.Add(h);
if (list.Count == 0) return;
Post(new { type = "deleteHandles", handles = list });
}
/// <summary>
/// 合并蒙版:保留 keep 句柄的蒙版并扩大到所有 removes 蒙版的并集,
/// 被合并行的蒙版从网页 CAD 移除(元件合并时蒙版同步合并)。
/// </summary>
public static void MergeMasks(string keep, System.Collections.Generic.IEnumerable<string> removes)
{
if (string.IsNullOrWhiteSpace(keep)) return;
var list = new List<string>();
if (removes != null)
foreach (string h in removes)
if (!string.IsNullOrWhiteSpace(h) && h != keep) list.Add(h);
if (list.Count == 0) return;
Post(new { type = "mergeMasks", keep = keep, removes = list });
}
/// <summary>清除网页 CAD 中的所有高亮。</summary>
public static void ClearHighlight()
{
Post(new { type = "clearHighlight" });
}
}
}
+27
View File
@@ -0,0 +1,27 @@
namespace cadbt.DockWindows
{
partial class CadViewerWindow
{
private System.ComponentModel.IContainer components = null;
protected override void Dispose(bool disposing)
{
if (disposing && (components != null)) components.Dispose();
base.Dispose(disposing);
}
#region Designer generated code
private void InitializeComponent()
{
this.SuspendLayout();
//
// CadViewerWindow
//
this.Name = "CadViewerWindow";
this.ResumeLayout(false);
}
#endregion
}
}
+187
View File
@@ -0,0 +1,187 @@
// ============================================================
// 停靠窗口:CAD 查看器(中央文档区)
// WebView2 承载网页版 CAD-Viewer(localhost:5173),并挂到 CadBridge。
// ★ 网页服务自启动:exe 旁 node\node.exe + CAD-Viewer\dist\server.cjs
// (静态产物零依赖,客户机无需 Node 环境/pnpm dev;主程序同款端口探测模式)
// ============================================================
using System;
using System.Diagnostics;
using System.IO;
using System.Net.Sockets;
using System.Windows.Forms;
using Microsoft.Web.WebView2.WinForms;
namespace cadbt.DockWindows
{
/// <summary>停靠窗口:网页 CAD 查看器(DockState.Document)。</summary>
public partial class CadViewerWindow : WeifenLuo.WinFormsUI.Docking.DockContent
{
private WebView2 _webView;
private Label _hint;
/// <summary>静态服务进程句柄(退出时杀进程树)。</summary>
private static Process _serverProcess;
public CadViewerWindow()
{
InitializeComponent();
Text = "CAD 查看器";
TabText = "CAD 查看器";
FormClosed += (s, e) => 停止网页服务();
LoadViewer();
}
/// <summary>CAD 网页服务端口(与 server.cjs 一致)。</summary>
private const int 服务端口 = 5173;
private async void LoadViewer()
{
try
{
_webView = new WebView2();
_webView.Dock = DockStyle.Fill;
Controls.Add(_webView);
await _webView.EnsureCoreWebView2Async(null);
CadBridge.Attach(_webView);
// ★ 禁用浏览器快捷键(F5/Ctrl+R/Ctrl+F 等):刷新会丢掉所有扒图蒙版
_webView.CoreWebView2.Settings.AreBrowserAcceleratorKeysEnabled = false;
// ★ 先确保静态服务在线(复刻主程序 StartPriceWebView 的探测+拉起模式)
启动网页服务();
// 服务不可达时给用户明确提示
_webView.CoreWebView2.NavigationCompleted += (s, e) =>
{
if (!e.IsSuccess && _hint == null)
ShowLoadHint("无法连接网页 CAD 服务(端口 " + 服务端口 + ")。\r\n请确认 cadbt\\node\\node.exe 与 cadbt\\CAD-Viewer\\dist 目录完整,然后点击\"重新加载\"。");
};
_webView.CoreWebView2.Navigate("http://localhost:" + 服务端口 + "/");
}
catch (Exception ex)
{
ShowLoadHint("WebView2 初始化失败: " + ex.Message + "\r\n请确认系统已安装 Microsoft Edge WebView2 运行时。");
}
}
/// <summary>启动 CAD 网页静态服务(端口已通=复用;不通=node server.cjs 后台拉起)。</summary>
private void 启动网页服务()
{
try
{
if (端口已通()) return; // 上次实例或手动 dev server 已在服务
string baseDir = AppDomain.CurrentDomain.BaseDirectory;
string nodeExe = Path.Combine(baseDir, "node", "node.exe");
// dist 与 server.cjs 同目录;找不到时尝试上一级 CAD-Viewer 结构(开发机)
// 安装布局: {app}\cadbt\CAD-Viewer\dist\ (make_package 已展平 packages 层)
string serverScript = Path.Combine(baseDir, "CAD-Viewer", "dist", "server.cjs");
if (!File.Exists(serverScript))
{
// 开发机回退: bin\Debug 上两级 = cadbt\cadbt\,保留 monorepo 原始层级
serverScript = Path.GetFullPath(Path.Combine(baseDir, @"..\..\CAD-Viewer\packages\cad-viewer-example\dist\server.cjs"));
}
if (!File.Exists(nodeExe) || !File.Exists(serverScript))
{
WriteDiag("[CAD服务] 缺少 node.exe 或 server.cjs: node=" + nodeExe + " script=" + serverScript);
return;
}
var psi = new ProcessStartInfo
{
FileName = nodeExe,
Arguments = "\"" + Path.GetFullPath(serverScript) + "\"",
WorkingDirectory = Path.GetDirectoryName(Path.GetFullPath(serverScript)),
UseShellExecute = false,
CreateNoWindow = true,
WindowStyle = ProcessWindowStyle.Hidden,
};
_serverProcess = Process.Start(psi);
WriteDiag("[CAD服务] 已启动 node PID=" + (_serverProcess == null ? "?" : _serverProcess.Id.ToString()));
// 等端口就绪(最多 8 秒)
for (int i = 0; i < 40; i++)
{
System.Threading.Thread.Sleep(200);
if (端口已通()) { WriteDiag("[CAD服务] 端口就绪, 耗时 " + (i + 1) * 200 + "ms"); return; }
}
WriteDiag("[CAD服务] 端口 8 秒未就绪(继续导航,由 NavigationCompleted 提示)");
}
catch (Exception ex) { WriteDiag("[CAD服务] 启动异常: " + ex.Message); }
}
/// <summary>快速探测本机端口。</summary>
private static bool 端口已通()
{
try
{
using (var client = new TcpClient())
{
var ar = client.BeginConnect("127.0.0.1", 服务端口, null, null);
bool ok = ar.AsyncWaitHandle.WaitOne(400);
if (ok && client.Connected) { client.EndConnect(ar); return true; }
}
}
catch { }
return false;
}
/// <summary>退出清理:杀 node 静态服务进程树(taskkill /T 覆盖可能的子进程)。</summary>
private void 停止网页服务()
{
try
{
if (_serverProcess != null && !_serverProcess.HasExited)
{
int pid = _serverProcess.Id;
Process.Start(new ProcessStartInfo("taskkill", "/PID " + pid + " /T /F")
{
CreateNoWindow = true, UseShellExecute = false, WindowStyle = ProcessWindowStyle.Hidden
});
WriteDiag("[CAD服务] 已停止 PID=" + pid);
}
_serverProcess = null;
}
catch (Exception ex) { WriteDiag("[CAD服务] 停止异常: " + ex.Message); }
}
/// <summary>简单诊断日志(logs\cad服务.log)。</summary>
private static void WriteDiag(string msg)
{
try
{
string dir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "logs");
Directory.CreateDirectory(dir);
File.AppendAllText(Path.Combine(dir, "cad服务.log"),
DateTime.Now.ToString("HH:mm:ss.fff") + " " + msg + Environment.NewLine);
}
catch { }
}
private void ShowLoadHint(string text)
{
if (_hint != null) return;
_hint = new Label();
_hint.Dock = DockStyle.Fill;
_hint.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
_hint.Text = text;
var reload = new Button();
reload.Text = "重新加载";
reload.Dock = DockStyle.Bottom;
reload.Click += (s, e) =>
{
Controls.Remove(_hint);
_hint.Dispose();
_hint = null;
启动网页服务();
if (_webView != null && _webView.CoreWebView2 != null)
_webView.CoreWebView2.Navigate("http://localhost:" + 服务端口 + "/");
};
Controls.Add(reload);
Controls.Add(_hint);
_hint.BringToFront();
}
}
}
+27
View File
@@ -0,0 +1,27 @@
namespace cadbt.DockWindows
{
partial class QuoteWindow
{
private System.ComponentModel.IContainer components = null;
protected override void Dispose(bool disposing)
{
if (disposing && (components != null)) components.Dispose();
base.Dispose(disposing);
}
#region Designer generated code
private void InitializeComponent()
{
this.SuspendLayout();
//
// QuoteWindow
//
this.Name = "QuoteWindow";
this.ResumeLayout(false);
}
#endregion
}
}
+26
View File
@@ -0,0 +1,26 @@
// ============================================================
// 停靠窗口:报价插件工具栏(左侧)
// 把 cadcj 的 Form3 报价界面作为子控件嵌入(TopLevel=false)。
// ============================================================
using System.Windows.Forms;
namespace cadbt.DockWindows
{
/// <summary>停靠窗口:报价插件工具栏(DockState.DockLeft)。</summary>
public partial class QuoteWindow : WeifenLuo.WinFormsUI.Docking.DockContent
{
public QuoteWindow()
{
InitializeComponent();
Text = "报价工具";
TabText = "报价工具";
var form = new MyApp.Form3();
form.TopLevel = false;
form.FormBorderStyle = FormBorderStyle.None;
form.Dock = DockStyle.Fill;
Controls.Add(form);
form.Show();
}
}
}
+69
View File
@@ -0,0 +1,69 @@
namespace cadbt
{
partial class MainForm
{
/// <summary>
/// Designer variable used to keep track of non-visual components.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Disposes resources used by the form.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing) {
if (components != null) {
components.Dispose();
}
}
base.Dispose(disposing);
}
/// <summary>
/// This method is required for Windows Forms designer support.
/// Do not change the method contents inside the source code editor. The Forms designer might
/// not be able to load this method if it was changed manually.
/// </summary>
private void InitializeComponent()
{
this.dockSurface1 = new Smart.CustomComponents.DockSurface();
Smart.CustomComponents.DockWindows.DockWindowItem dockWindowItem1 = new Smart.CustomComponents.DockWindows.DockWindowItem();
Smart.CustomComponents.DockWindows.DockWindowItem dockWindowItem2 = new Smart.CustomComponents.DockWindows.DockWindowItem();
this.SuspendLayout();
//
// dockSurface1
//
dockWindowItem1.ClassName = "QuoteWindow";
dockWindowItem1.Title = "报价工具";
dockWindowItem1.DockState = WeifenLuo.WinFormsUI.Docking.DockState.DockLeft;
dockWindowItem1.DefaultWidth = 440;
dockWindowItem1.ShowCloseButton = false;
this.dockSurface1.DockWindows.Add(dockWindowItem1);
dockWindowItem2.ClassName = "CadViewerWindow";
dockWindowItem2.Title = "CAD 查看器";
dockWindowItem2.DockState = WeifenLuo.WinFormsUI.Docking.DockState.Document;
dockWindowItem2.ShowCloseButton = false;
this.dockSurface1.DockWindows.Add(dockWindowItem2);
this.dockSurface1.Dock = System.Windows.Forms.DockStyle.Fill;
this.dockSurface1.Location = new System.Drawing.Point(0, 0);
this.dockSurface1.Name = "dockSurface1";
this.dockSurface1.Size = new System.Drawing.Size(1184, 661);
this.dockSurface1.TabIndex = 0;
//
// MainForm
//
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(1184, 661);
this.Controls.Add(this.dockSurface1);
this.Name = "MainForm";
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
this.Text = "cadbt - 报价工具 + 网页 CAD";
this.ResumeLayout(false);
this.PerformLayout();
}
private Smart.CustomComponents.DockSurface dockSurface1;
}
}
+28
View File
@@ -0,0 +1,28 @@
// 本文件由『生成停靠窗口代码』自动生成格式参考手写,集合变更后请同步刷新。
using System;
using WeifenLuo.WinFormsUI.Docking;
namespace cadbt
{
public partial class MainForm
{
void CreateDockWindows_dockSurface1()
{
var dockSurface1 = this.dockSurface1 as Smart.CustomComponents.DockSurface;
if (dockSurface1 == null) return;
dockSurface1.EnsureDefaultTheme(); // 必须:Show 前保证有效主题
var quote = new cadbt.DockWindows.QuoteWindow();
quote.CloseButtonVisible = false;
quote.TabText = "报价工具";
quote.Text = "报价工具";
quote.Show(dockSurface1, DockState.DockLeft);
var viewer = new cadbt.DockWindows.CadViewerWindow();
viewer.CloseButtonVisible = false;
viewer.TabText = "CAD 查看器";
viewer.Text = "CAD 查看器";
viewer.Show(dockSurface1, DockState.Document);
}
}
}
+59
View File
@@ -0,0 +1,59 @@
/*
* 由SharpDevelop创建。
* 用户: 099978
* 日期: 2026-09-04
* 时间: 12:24
*
* 要改变这种模板请点击 工具|选项|代码编写|编辑标准头文件
*/
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
namespace cadbt
{
/// <summary>
/// Description of MainForm.
/// </summary>
public partial class MainForm : Form
{
public MainForm()
{
//
// The InitializeComponent() call is required for Windows Forms designer support.
//
InitializeComponent();
CreateDockWindows_dockSurface1();
// ★ 全局 F1 过滤器:焦点在任何 WinForms 控件上都触发元件合并
// (焦点在网页时由网页端转发,两路都汇聚到 CadBridge.F1Requested)
Application.AddMessageFilter(new F1MessageFilter());
FormClosed += (s, e) => Application.RemoveMessageFilter(f1Filter);
}
private readonly F1MessageFilter f1Filter = new F1MessageFilter();
/// <summary>把 F1 按键(0x70)转成 CadBridge.F1Requested,不拦截按键本身。</summary>
private class F1MessageFilter : IMessageFilter
{
public bool PreFilterMessage(ref Message m)
{
const int WM_KEYDOWN = 0x0100;
if (m.Msg == WM_KEYDOWN && (int)m.WParam == 0x70)
{
try { CadBridge.RaiseF1(); } catch { }
}
else if (m.Msg == WM_KEYDOWN && (int)m.WParam == 0x71)
{
try { CadBridge.RaiseF2(); } catch { }
}
else if (m.Msg == WM_KEYDOWN && (int)m.WParam == 0x72)
{
try { CadBridge.RaiseF3(); } catch { }
}
return false;
}
}
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+29
View File
@@ -0,0 +1,29 @@
// ============================================================
// 扒图数据类型(与原 cadcj 插件 / 网页端 cadbridge.ts 保持一致)
// ============================================================
using System.Collections.Generic;
namespace MyApp
{
/// <summary>
/// 扒数据模式:图号/箱号/元件
/// </summary>
public enum PaMode
{
Tuhao, // 扒图号
Xianghao, // 扒箱号
Yuanjian // 扒元件
}
/// <summary>
/// 扒图结果:一条文本 + 它对应的彩色框的 Handle(句柄字符串)
/// 用于关联表格数据行与网页 CAD 框实体,实现点击行定位、删除行删框。
/// ★ TextHandle:源文本实体自身的 Handle,用于判断"是否同一个文本"。
/// </summary>
public class PaTextResult
{
public string Text;
public string FrameHandle;
public string TextHandle = "";
}
}
+132
View File
@@ -0,0 +1,132 @@
using System;
using System.Collections.Generic;
using System.Windows.Forms;
namespace MyApp
{
/// <summary>
/// 新建项目 窗口(简化版,不依赖企业信息)。
/// UI 布局见 新建项目.ui.cs,本文件只含交互逻辑。
/// </summary>
public partial class 新建项目 : Form
{
// 价格格式化选项(与主项目 价格格式化.cs 保持一致)
private static readonly string[] 小数位选项 =
{
"保留整数", "保留1位小数", "保留2位小数", "保留3位小数",
"保留4位小数", "保留5位小数", "保留6位小数"
};
private static readonly string[] 取整方式选项 =
{
"不取整", "四舍五入", "四舍五入到角", "四舍五入到元",
"四舍五入到十元", "向上取整到元", "最近5元", "最近10元"
};
private readonly string _dbPath;
private readonly string _tableName;
public 新建项目() : this(null, null) { }
/// <summary>带数据库路径的构造函数(用于自动生成项目编号)。</summary>
public 新建项目(string dbPath, string tableName)
{
InitializeComponent();
_dbPath = dbPath;
_tableName = tableName;
InitPriceFormatCombos();
this.Load += 新建项目_Load;
}
private void InitPriceFormatCombos()
{
cmb单台价格.Items.Clear();
cmb单台价格.Items.AddRange(小数位选项);
cmb项目总价.Items.Clear();
cmb项目总价.Items.AddRange(小数位选项);
comboBox1.Items.Clear();
comboBox1.Items.AddRange(取整方式选项);
comboBox2.Items.Clear();
comboBox2.Items.AddRange(取整方式选项);
}
private void 新建项目_Load(object sender, EventArgs e)
{
FillAutoFields();
}
/// <summary>自动填充项目日期(当天)和项目编号(yyyyMMdd+3位序号)。</summary>
private void FillAutoFields()
{
DateTime today = DateTime.Today;
txt项目日期.Text = today.ToString("yyyy-MM-dd");
string datePart = today.ToString("yyyyMMdd");
int seq = GetNextSeqForDate(datePart);
txt项目编号.Text = datePart + seq.ToString("000");
txt项目编号.ReadOnly = true;
txt项目编号.TabStop = false;
txt项目编号.BackColor = System.Drawing.SystemColors.Control;
}
/// <summary>查数据库当天最大序号+1,失败返回1。</summary>
private int GetNextSeqForDate(string datePart)
{
if (string.IsNullOrEmpty(datePart) ||
string.IsNullOrEmpty(_dbPath) ||
string.IsNullOrEmpty(_tableName) ||
!System.IO.File.Exists(_dbPath))
{
return 1;
}
try
{
string connStr = "Data Source=" + _dbPath + ";Version=3;";
using (var conn = new System.Data.SQLite.SQLiteConnection(connStr))
{
conn.Open();
string sql =
"SELECT MAX(CAST(SUBSTR(项目编号, 9) AS INTEGER)) " +
"FROM " + _tableName + " " +
"WHERE SUBSTR(项目编号, 1, 8) = '" + datePart + "';";
using (var cmd = new System.Data.SQLite.SQLiteCommand(sql, conn))
{
object result = cmd.ExecuteScalar();
if (result == null || result == DBNull.Value) return 1;
int maxSeq;
if (int.TryParse(result.ToString(), out maxSeq)) return maxSeq + 1;
return 1;
}
}
}
catch { return 1; }
}
/// <summary>收集对话框字段,以列名为键返回(供 Form3 写入数据库)。</summary>
public Dictionary<string, string> GetInputValues()
{
var values = new Dictionary<string, string>();
AddIfNotEmpty(values, "项目编号", txt项目编号.Text);
AddIfNotEmpty(values, "项目日期", txt项目日期.Text);
AddIfNotEmpty(values, "项目名称", txt项目名称.Text);
AddIfNotEmpty(values, "客户名称", txt客户名称.Text);
AddIfNotEmpty(values, "客户地址", txt客户地址.Text);
AddIfNotEmpty(values, "联系人", txt联系人.Text);
AddIfNotEmpty(values, "联系电话", txt联系电话.Text);
AddIfNotEmpty(values, "备注", txt备注.Text);
AddIfNotEmpty(values, "成套厂名称", txt成套厂名称.Text);
AddIfNotEmpty(values, "成套厂地址", txt成套厂地址.Text);
AddIfNotEmpty(values, "成套厂联系人", txt成套厂联系人.Text);
AddIfNotEmpty(values, "成套厂联系电话", txt成套厂联系电话.Text);
values["单台小数位"] = (cmb单台价格.SelectedItem ?? cmb单台价格.Text ?? "").ToString();
values["项目小数位"] = (cmb项目总价.SelectedItem ?? cmb项目总价.Text ?? "").ToString();
values["单台取整方式"] = (comboBox1.SelectedItem ?? comboBox1.Text ?? "").ToString();
values["项目取整方式"] = (comboBox2.SelectedItem ?? comboBox2.Text ?? "").ToString();
return values;
}
private static void AddIfNotEmpty(Dictionary<string, string> dict, string key, string value)
{
if (!string.IsNullOrWhiteSpace(value)) dict[key] = value.Trim();
}
}
}
File diff suppressed because it is too large Load Diff
+43
View File
@@ -0,0 +1,43 @@
/*
* 由SharpDevelop创建。
* 用户: 099978
* 日期: 2026-09-04
* 时间: 12:24
*
* 要改变这种模板请点击 工具|选项|代码编写|编辑标准头文件
*/
using System;
using System.Windows.Forms;
namespace cadbt
{
/// <summary>
/// Class with program entry point.
/// </summary>
internal sealed class Program
{
/// <summary>
/// Program entry point.
/// </summary>
[STAThread]
private static void Main(string[] args)
{
// 单实例保护:重复启动时激活已有实例并退出,避免残留多个进程
bool createdNew;
using (var mutex = new System.Threading.Mutex(true, "cadbt_SingleInstance", out createdNew))
{
if (!createdNew)
{
MessageBox.Show("cadbt 已在运行中。", "cadbt",
MessageBoxButtons.OK, MessageBoxIcon.Information);
return;
}
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MainForm());
GC.KeepAlive(mutex);
}
}
}
}
+31
View File
@@ -0,0 +1,31 @@
#region Using directives
using System;
using System.Reflection;
using System.Runtime.InteropServices;
#endregion
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("cadbt")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("cadbt")]
[assembly: AssemblyCopyright("Copyright 2026")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// This sets the default COM visibility of types in the assembly to invisible.
// If you need to expose a type to COM, use [ComVisible(true)] on that type.
[assembly: ComVisible(false)]
// The assembly version has following format :
//
// Major.Minor.Build.Revision
//
// You can specify all the values or you can use the default the Revision and
// Build Numbers by using the '*' as shown below:
[assembly: AssemblyVersion("1.0.*")]
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8" />
</startup>
</configuration>
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
</windowsSettings>
</application>
</assembly>
+147
View File
@@ -0,0 +1,147 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="Build">
<PropertyGroup>
<ProjectGuid>{0A91D57B-B279-4672-9440-78088D022EB1}</ProjectGuid>
<ProjectTypeGuids>{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<OutputType>WinExe</OutputType>
<RootNamespace>cadbt</RootNamespace>
<AssemblyName>cadbt</AssemblyName>
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<AppDesignerFolder>Properties</AppDesignerFolder>
<PlatformTarget>x86</PlatformTarget>
<ApplicationManifest>app.manifest</ApplicationManifest>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<OutputPath>bin\Debug\</OutputPath>
<DebugSymbols>True</DebugSymbols>
<DebugType>Full</DebugType>
<Optimize>False</Optimize>
<CheckForOverflowUnderflow>True</CheckForOverflowUnderflow>
<DefineConstants>DEBUG;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<OutputPath>bin\Release\</OutputPath>
<DebugSymbols>False</DebugSymbols>
<DebugType>None</DebugType>
<Optimize>True</Optimize>
<CheckForOverflowUnderflow>False</CheckForOverflowUnderflow>
<DefineConstants>TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(Platform)' == 'AnyCPU' ">
<Prefer32Bit>True</Prefer32Bit>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.CSharp">
<RequiredTargetFramework>4.0</RequiredTargetFramework>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core">
<RequiredTargetFramework>3.5</RequiredTargetFramework>
</Reference>
<Reference Include="System.Data" />
<Reference Include="System.Data.DataSetExtensions">
<RequiredTargetFramework>3.5</RequiredTargetFramework>
</Reference>
<Reference Include="System.Drawing" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml" />
<Reference Include="System.Xml.Linq">
<RequiredTargetFramework>3.5</RequiredTargetFramework>
</Reference>
<Reference Include="System.Web.Extensions">
<RequiredTargetFramework>3.5</RequiredTargetFramework>
</Reference>
<Reference Include="System.IO.Compression" />
<Reference Include="System.IO.Compression.FileSystem" />
<Reference Include="Smart.CustomComponents, Version=1.0.0.0, Culture=neutral, PublicKeyToken=ba044a4aa43e0dc4">
<HintPath>C:\Program Files (x86)\SharpDevelop\5.2\bin\Smart.CustomComponents.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="WeifenLuo.WinFormsUI.Docking">
<HintPath>C:\Program Files (x86)\SharpDevelop\5.2\bin\WeifenLuo.WinFormsUI.Docking.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="WeifenLuo.WinFormsUI.Docking.ThemeVS2015">
<HintPath>C:\Program Files (x86)\SharpDevelop\5.2\bin\WeifenLuo.WinFormsUI.Docking.ThemeVS2015.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Data.SQLite">
<HintPath>C:\Program Files (x86)\SharpDevelop\5.2\bin\System.Data.SQLite.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="log4net">
<HintPath>C:\Program Files (x86)\SharpDevelop\5.2\bin\log4net.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Resources.Extensions">
<HintPath>C:\Program Files (x86)\SharpDevelop\5.2\bin\System.Resources.Extensions.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Memory">
<HintPath>C:\Program Files (x86)\SharpDevelop\5.2\bin\System.Memory.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Runtime.CompilerServices.Unsafe">
<HintPath>C:\Program Files (x86)\SharpDevelop\5.2\bin\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Numerics.Vectors">
<HintPath>C:\Program Files (x86)\SharpDevelop\5.2\bin\System.Numerics.Vectors.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="AdvancedDataGridView">
<HintPath>libs\AdvancedDataGridView.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.Web.WebView2.Core">
<HintPath>libs\webview2\Microsoft.Web.WebView2.Core.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.Web.WebView2.WinForms">
<HintPath>libs\webview2\Microsoft.Web.WebView2.WinForms.dll</HintPath>
<Private>True</Private>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="MainForm.cs" />
<Compile Include="MainForm.Designer.cs">
<DependentUpon>MainForm.cs</DependentUpon>
</Compile>
<Compile Include="MainForm.DockWindows.cs">
<DependentUpon>MainForm.cs</DependentUpon>
</Compile>
<Compile Include="Program.cs" />
<Compile Include="CadBridge.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Plugin\Form3.cs" />
<Compile Include="Plugin\Form3.ui.cs">
<DependentUpon>Form3.cs</DependentUpon>
</Compile>
<Compile Include="Plugin\PaTypes.cs" />
<Compile Include="Plugin\新建项目.cs" />
<Compile Include="Plugin\新建项目.ui.cs">
<DependentUpon>新建项目.cs</DependentUpon>
</Compile>
<Compile Include="DockWindows\CadViewerWindow.cs" />
<Compile Include="DockWindows\CadViewerWindow.Designer.cs">
<DependentUpon>CadViewerWindow.cs</DependentUpon>
</Compile>
<Compile Include="DockWindows\QuoteWindow.cs" />
<Compile Include="DockWindows\QuoteWindow.Designer.cs">
<DependentUpon>QuoteWindow.cs</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<None Include="app.config" />
<None Include="app.manifest" />
</ItemGroup>
<ItemGroup>
<Content Include="libs\webview2\WebView2Loader.dll">
<Link>WebView2Loader.dll</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>
Binary file not shown.
+350
View File
@@ -0,0 +1,350 @@
// ============================================================
// CAD 2018 插件入口(cadcj.dll)
// · 实现 IExtensionApplication,在 CAD 启动时自动加载
// · 注册命令 "CADCJ" 打开 Form3 报价窗口
// · 使用 PaletteSet 把 Form3 嵌成 CAD 可停靠工具栏窗口
// 启动时自动贴靠到 CAD 左侧,用户可拖到右/上/下边或浮动
// · 编译:/target:library /platform:x64
// · 加载:在 CAD 里 NETLOAD 选择 cadcj.dll,然后输入 CADCJ 命令
// ============================================================
using System;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.Windows;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using AcAp = Autodesk.AutoCAD.ApplicationServices.Application;
[assembly: CommandClass(typeof(MyApp.CadPlugin))]
[assembly: ExtensionApplication(typeof(MyApp.CadPlugin))]
namespace MyApp
{
/// <summary>
/// CAD 2018 插件入口。CAD 启动时自动初始化,注册 CADCJ 命令打开报价窗口。
/// 报价窗口用 PaletteSet 承载,可像 CAD 工具选项板一样停靠/浮动。
/// </summary>
public class CadPlugin : IExtensionApplication
{
// PaletteSet 单例:CAD 里常驻,可停靠
private static PaletteSet _ps = null;
// Form3 单例(作为 PaletteSet 的内容)
private static Form3 _form3 = null;
// ★ 宿主容器:PaletteSet 只加 Panel,Form3 塞进去(规避主题同步崩溃)
private static System.Windows.Forms.Panel _hostPanel = null;
#region IExtensionApplication 成员
public void Initialize()
{
// ★ 最简单的文件日志,不依赖任何外部代码,确认 Initialize 是否被调用
try
{
string logPath = System.IO.Path.Combine(
System.IO.Path.GetDirectoryName(
new System.Uri(System.Reflection.Assembly.GetExecutingAssembly().CodeBase).LocalPath),
"cadcj_load.log");
System.IO.File.WriteAllText(logPath,
"[" + System.DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + "] Initialize 被调用" + System.Environment.NewLine);
}
catch { }
// ★ 解决 CAD 安全提示:把插件目录添加到 TRUSTEDPATHS,并关闭 SECURELOAD
// 这样 CAD 不会每次都弹"始终加载/加载一次"的安全提示
try
{
string pluginDir = System.IO.Path.GetDirectoryName(
new System.Uri(System.Reflection.Assembly.GetExecutingAssembly().CodeBase).LocalPath);
var doc = AcAp.DocumentManager.MdiActiveDocument;
if (doc != null)
{
// ★ 关闭 SECURELOAD(0=不检查可执行文件签名)
try
{
object sl = Autodesk.AutoCAD.ApplicationServices.Application.GetSystemVariable("SECURELOAD");
if (sl == null || Convert.ToInt32(sl) != 0)
{
Autodesk.AutoCAD.ApplicationServices.Application.SetSystemVariable("SECURELOAD", 0);
}
}
catch { }
// ★ 把插件目录添加到 TRUSTEDPATHS
object cur = Autodesk.AutoCAD.ApplicationServices.Application.GetSystemVariable("TRUSTEDPATHS");
string curPaths = cur == null ? "" : cur.ToString();
bool found = false;
string[] parts = curPaths.Split(';');
foreach (string p in parts)
{
if (string.Equals(p.Trim(), pluginDir.Trim(), System.StringComparison.OrdinalIgnoreCase))
{
found = true;
break;
}
}
if (!found)
{
string newPaths = string.IsNullOrEmpty(curPaths) ? pluginDir : curPaths + ";" + pluginDir;
Autodesk.AutoCAD.ApplicationServices.Application.SetSystemVariable("TRUSTEDPATHS", newPaths);
}
}
}
catch (System.Exception ex)
{
try
{
var doc0 = AcAp.DocumentManager.MdiActiveDocument;
if (doc0 != null) doc0.Editor.WriteMessage("\n[CADCJ] 设置可信路径失败: " + ex.Message);
}
catch { }
}
try
{
var doc = AcAp.DocumentManager.MdiActiveDocument;
if (doc != null)
{
doc.Editor.WriteMessage("\n[CADCJ] 成套报价插件已加载,报价窗口将随文档自动打开。");
}
// ★ 监听文档创建/激活事件:每次新建/打开文件时自动显示报价窗口
AcAp.DocumentManager.DocumentCreated += OnDocumentCreated;
// ★ 关键修复:acaddoc.lsp 的 NETLOAD 发生在文档已打开之后,
// DocumentCreated 事件早已错过;且 Idle 事件在挂接时若 CAD 已空闲
// 可能永不触发 → 改为直接显示(Initialize 在命令上下文里可安全创建 UI)
if (AcAp.DocumentManager.MdiActiveDocument != null && _ps == null && _form3 == null)
{
PluginLog("[Initialize] 直接调用 OpenForm3");
OpenForm3();
}
}
catch (System.Exception ex2)
{
PluginLog("[Initialize] ⚠ 直接打开失败: " + ex2.Message);
}
}
/// <summary>文档创建时自动打开/显示报价窗口(停靠左侧)。</summary>
private void OnDocumentCreated(object sender, DocumentCollectionEventArgs e)
{
try
{
// 延迟到 Idle 时执行,避免在文档创建过程中操作 UI
AcAp.Idle -= OnCadIdleShowPalette;
AcAp.Idle += OnCadIdleShowPalette;
}
catch { }
}
/// <summary>Idle 时显示报价窗口(只执行一次后取消订阅)。</summary>
private void OnCadIdleShowPalette(object sender, EventArgs e)
{
AcAp.Idle -= OnCadIdleShowPalette;
try
{
OpenForm3();
}
catch { }
}
public void Terminate()
{
try
{
if (_ps != null)
{
_ps.Visible = false;
_ps = null;
}
if (_form3 != null && !_form3.IsDisposed)
{
_form3.Dispose();
_form3 = null;
}
}
catch { }
}
#endregion
#region 命令
/// <summary>CADCJ 命令:打开/激活报价窗口(PaletteSet,可停靠)。</summary>
[CommandMethod("CADCJ", CommandFlags.Modal)]
public void OpenForm3()
{
try
{
// 已有 PaletteSet 则激活显示并重新停靠到左侧
if (_ps != null)
{
_ps.Visible = true;
try
{
_ps.DockEnabled = DockSides.Left;
_ps.Dock = DockSides.Left;
}
catch { }
return;
}
PluginLog("[CADCJ] OpenForm3 开始");
// 新建 Form3,转成子控件模式嵌入容器
_form3 = new Form3();
_form3.TopLevel = false;
_form3.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
_form3.Dock = System.Windows.Forms.DockStyle.Fill;
_form3.Visible = true;
PluginLog("[CADCJ] Form3 就绪");
// ★ 根治 ResyncToTheme 崩溃:PaletteSet 只加 UserControl 容器,
// 不直接加 Form(CAD 主题同步强改 Form.BackColor 会抛
// "控件不支持透明的背景色")。Form3 塞进 Panel 再加。
_hostPanel = new System.Windows.Forms.Panel();
_hostPanel.Dock = System.Windows.Forms.DockStyle.Fill;
_hostPanel.BackColor = System.Drawing.SystemColors.Control;
_hostPanel.Controls.Add(_form3);
// 新建 PaletteSet
_ps = new PaletteSet("扒图", new System.Guid("A3F2B5C1-7D8E-4A6B-9C0D-1E2F3A4B5C6D"));
PluginLog("[CADCJ] PaletteSet 创建成功");
_ps.Style = PaletteSetStyles.Snappable
| PaletteSetStyles.UsePaletteNameAsTitleForSingle;
_ps.Add("扒图", _hostPanel);
PluginLog("[CADCJ] Add 成功");
// ★ 启动时自动贴靠到 CAD 左侧,并设置足够尺寸
_ps.Size = new System.Drawing.Size(540, 800);
_ps.MinimumSize = new System.Drawing.Size(300, 400);
// ★ 先显示,再设置停靠(CAD PaletteSet 的特殊行为)
_ps.Visible = true;
PluginLog("[CADCJ] Visible=true 完成");
// ★ 启用左侧停靠 + 强制停靠到左侧
_ps.DockEnabled = DockSides.Left;
_ps.Dock = DockSides.Left;
PluginLog("[CADCJ] OpenForm3 全部完成 ✓");
}
catch (System.Exception ex)
{
PluginLog("[CADCJ] ⚠ 打开窗口失败: " + ex);
var doc = AcAp.DocumentManager.MdiActiveDocument;
if (doc != null)
{
doc.Editor.WriteMessage("\n[CADCJ] 打开窗口失败: " + ex.Message);
}
}
}
/// <summary>插件级文件日志(写 cadcj 目录 diag.log,独立于 Form3)。</summary>
private static void PluginLog(string msg)
{
try
{
string dir = System.IO.Path.GetDirectoryName(
new System.Uri(System.Reflection.Assembly.GetExecutingAssembly().CodeBase).LocalPath);
System.IO.File.AppendAllText(System.IO.Path.Combine(dir, "diag.log"),
DateTime.Now.ToString("HH:mm:ss.fff") + " " + msg + System.Environment.NewLine);
}
catch { }
}
// ============================================================
// 扒图号/扒箱号/扒元件 命令
// · 由 Form3 的按钮通过 SendStringToExecute 触发
// · 只允许框选 DBText/MText(过滤掉线条)
// · 选中文本用半透明颜色高亮
// · 选中的文本传回 Form3 插入到对应表格
// ============================================================
/// <summary>扒图号命令:CADPATU</summary>
[CommandMethod("CADPATU", CommandFlags.Modal)]
public void PaTuhaoCommand()
{
ExecutePaCommand(MyApp.PaMode.Tuhao);
}
/// <summary>扒箱号命令:CADPAXH</summary>
[CommandMethod("CADPAXH", CommandFlags.Modal)]
public void PaXianghaoCommand()
{
ExecutePaCommand(MyApp.PaMode.Xianghao);
}
/// <summary>扒元件命令:CADPAYJ</summary>
[CommandMethod("CADPAYJ", CommandFlags.Modal)]
public void PaYuanjianCommand()
{
ExecutePaCommand(MyApp.PaMode.Yuanjian);
}
/// <summary>清除所有高亮颜色命令:CADPACL</summary>
[CommandMethod("CADPACL", CommandFlags.Modal)]
public void PaClearCommand()
{
try
{
PaTextSelector.ClearAllHighlight();
var doc = AcAp.DocumentManager.MdiActiveDocument;
if (doc != null) doc.Editor.WriteMessage("\n[扒数据] 已清除所有染色文本。");
}
catch (System.Exception ex)
{
var doc = AcAp.DocumentManager.MdiActiveDocument;
if (doc != null) doc.Editor.WriteMessage("\n[扒数据] 清除失败: " + ex.Message);
}
}
/// <summary>执行扒数据命令的核心逻辑(连续框选模式)。</summary>
private void ExecutePaCommand(PaMode mode)
{
var doc = AcAp.DocumentManager.MdiActiveDocument;
if (doc == null) return;
var ed = doc.Editor;
try
{
// 订阅事件:每次框选完一批文本,立即通过 Invoke 调用 Form3 的插入方法
PaTextSelector.BatchTextsPicked -= OnPaBatchTextsPicked;
PaTextSelector.BatchTextsPicked += OnPaBatchTextsPicked;
// 进入连续框选循环(直到用户按 ESC)
PaTextSelector.PickTextsContinuous(mode);
// 用户按 ESC 退出后,触发一次最终的"完成"提示(可选)
ed.WriteMessage("\n[扒数据] 已退出连续模式。");
// 取消订阅
PaTextSelector.BatchTextsPicked -= OnPaBatchTextsPicked;
}
catch (System.Exception ex)
{
ed.WriteMessage("\n[扒数据] 异常: " + ex.Message);
}
}
/// <summary>PaTextSelector 事件回调:把一批文本+框Handle传给 Form3 插入到表格。</summary>
private void OnPaBatchTextsPicked(PaMode mode, System.Collections.Generic.List<PaTextResult> results)
{
if (_form3 == null || _form3.IsDisposed) return;
try
{
_form3.Invoke(new System.Action(() =>
{
try { _form3.OnPaTextsPicked(mode, results); }
catch (System.Exception ex)
{
var doc = AcAp.DocumentManager.MdiActiveDocument;
if (doc != null) doc.Editor.WriteMessage("\n[扒数据] 插入数据异常: " + ex.Message);
}
}));
}
catch { }
}
#endregion
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+34
View File
@@ -0,0 +1,34 @@
// ============================================================
// Form3 依赖清单
// 此文件记录窗体所使用的控件及其运行时依赖。
// ============================================================
// NuGet 包依赖:
// ----------------
// System.Data.SQLite.Core (版本 1.0.119.0)
// 安装命令: dotnet add package System.Data.SQLite.Core --version 1.0.119.0
// 或在 Visual Studio 中: 右键项目 → 管理 NuGet 包 → 搜索 System.Data.SQLite.Core
// DLL 文件依赖:
// ----------------
// 来自 未知:
// - AdvancedDataGridView.dll (托管 DLL)
// 来自 System.Data.SQLite.Core:
// - System.Data.SQLite.dll (托管 DLL)
// 编译说明:
// ----------------
// 1. 确保 NuGet 包已安装到项目
// 2. 编译时使用正确的 CPU 架构(x64 或 x86)
// 3. 运行时确保所有 DLL 文件与 exe 在同一目录
// 4. ★ 推荐用同目录下的 build.bat 一键编译(已自动配置 response file 和 manifest)
// manifest 的 DPI 策略与设计器一致(dpiAware=unaware),保证所见即所得
// 编译命令示例(x64):
// csc /target:winexe /platform:x64 /out:Form3.exe
// /win32manifest:app.manifest
// /r:System.Windows.Forms.dll /r:System.Drawing.dll
// /r:"AdvancedDataGridView.dll"
// /r:"System.Data.SQLite.dll"
// Form3.cs Form3.ui.cs
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+830
View File
@@ -0,0 +1,830 @@
// ============================================================
// 扒图号/扒箱号/扒元件 - CAD文本框选交互
// · 直接框选一次完成(无需点完成按钮)
// · 只框选 DBText/MText(线条自动过滤)
// · 不改文字颜色,而是在每个文字周围画彩色矩形框
// (图号=红色框, 箱号=绿色框, 元件=蓝色框)
// · 清除时删除所有彩色矩形框
// ============================================================
using System;
using System.Collections.Generic;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.Colors;
using AcAp = Autodesk.AutoCAD.ApplicationServices.Application;
namespace MyApp
{
/// <summary>
/// 扒数据模式:图号/箱号/元件
/// </summary>
public enum PaMode
{
Tuhao, // 扒图号
Xianghao, // 扒箱号
Yuanjian // 扒元件
}
/// <summary>
/// 扒图结果:一条文本 + 它对应的彩色框的 Handle(句柄字符串)
/// 用于关联表格数据行与 CAD 框实体,实现点击行定位、删除行删框。
/// ★ TextHandle:源文本实体自身的 Handle,用于判断"是否同一个文本"
/// (不同位置的相同内容文本 Handle 不同,不算重复)。
/// </summary>
public class PaTextResult
{
public string Text;
public string FrameHandle;
public string TextHandle = "";
}
/// <summary>
/// CAD 文本框选交互:框选一次即完成,在文字周围画彩色矩形框(不改文字颜色)。
/// </summary>
public static class PaTextSelector
{
// 三种模式对应不同的矩形框颜色(ACI:3=绿,4=青,1=红)
private const short TuhaoColorIndex = 3; // 图号=绿
private const short XianghaoColorIndex = 4; // 箱号=青
private const short YuanjianColorIndex = 1; // 元件=红
// 矩形框图层名(便于批量清除)
public const string PaLayerName = "__CADCJ_PA_FRAME";
/// <summary>
/// 连续扒数据事件:每次框选完成(松开鼠标)就触发一次,把这一批文本+框Handle送给调用方。
/// 调用方在 Form3 里把数据插入到表格,然后控制流回到 PickTexts 继续下一次框选。
/// </summary>
public static event System.Action<PaMode, List<PaTextResult>> BatchTextsPicked;
/// <summary>
/// 连续框选模式:用户点一次按钮进入,可以连续框选多次,每次框选立即送出一批文本。
/// 按回车/空格完成当前批次,ESC 退出连续模式。
/// 方向决定行为:
/// · 左上→右下(正向框选):每个文本一条记录
/// · 右下→左上(反向框选,CAD里称为"交叉框选"):多条文本合并为一条
/// </summary>
/// <param name="mode">扒数据模式(决定矩形框颜色)</param>
public static void PickTextsContinuous(PaMode mode)
{
var doc = AcAp.DocumentManager.MdiActiveDocument;
if (doc == null) return;
var ed = doc.Editor;
// ★ 强制开启透明度显示(TRANSPARENCYDISPLAY=1)
// 否则 Hatch.Transparency 在某些电脑上不生效,蒙版显示为不透明
try { AcAp.SetSystemVariable("TRANSPARENCYDISPLAY", 1); } catch { }
// ★ 当前模式(允许右键切换,所以用变量保存当前模式)
PaMode currentMode = mode;
try
{
var typeValues = new TypedValue[]
{
new TypedValue(-4, "<OR"),
new TypedValue((int)DxfCode.Start, "TEXT"),
new TypedValue((int)DxfCode.Start, "MTEXT"),
new TypedValue(-4, "OR>")
};
var filter = new SelectionFilter(typeValues);
// ★ 连续循环:每次框选完立即触发事件,直到用户按 ESC
while (true)
{
// 根据当前模式获取颜色和名称
short colorIndex;
string modeName;
switch (currentMode)
{
case PaMode.Tuhao: colorIndex = TuhaoColorIndex; modeName = "图号"; break;
case PaMode.Xianghao: colorIndex = XianghaoColorIndex; modeName = "箱号"; break;
default: colorIndex = YuanjianColorIndex; modeName = "元件"; break;
}
// 第一角点
// ★ AllowNone=true:右键会返回 None,用来切换模式;ESC 返回 Cancel 退出
var po1 = new PromptPointOptions("\n框选【" + modeName + "】起点(右键切换模式,ESC结束): ");
po1.AllowNone = true;
var pres1 = ed.GetPoint(po1);
if (pres1.Status == PromptStatus.Cancel) break; // ESC 退出
if (pres1.Status == PromptStatus.None)
{
// ★ 右键:切换到下一个模式(图号→箱号→元件→退出)
PaMode next = GetNextMode(currentMode);
if (next == currentMode)
{
// 元件模式右键 → 退出
ed.WriteMessage("\n[扒数据] 已退出连续模式。");
break;
}
currentMode = next;
ed.WriteMessage("\n[扒数据] 切换到【" + GetModeName(currentMode) + "】模式。");
continue;
}
if (pres1.Status != PromptStatus.OK) break;
Point3d pt1 = pres1.Value;
// 对角点
// ★ AllowNone=true:右键也允许切换模式(回到第一角点重新开始)
var co = new PromptCornerOptions("\n对角点: ", pt1);
co.AllowNone = true;
var cres = ed.GetCorner(co);
if (cres.Status == PromptStatus.Cancel) break; // ESC 退出
if (cres.Status == PromptStatus.None)
{
// ★ 右键:切换到下一个模式,回到第一角点
PaMode next = GetNextMode(currentMode);
if (next == currentMode)
{
ed.WriteMessage("\n[扒数据] 已退出连续模式。");
break;
}
currentMode = next;
ed.WriteMessage("\n[扒数据] 切换到【" + GetModeName(currentMode) + "】模式。");
continue;
}
if (cres.Status != PromptStatus.OK) break;
Point3d pt2 = cres.Value;
// ★ 关键:判断方向(任意一项反向即为"交叉框选"=合并模式)
// pt1.X > pt2.X = 从右往左框选(pt1 在 pt2 右边)
// pt1.Y < pt2.Y = 从下往上框选(pt1 在 pt2 下方)
// 任一条件满足都视为反向框选 → 多文本合并为一条
bool isCrossing = (pt1.X > pt2.X) || (pt1.Y < pt2.Y);
double minX = Math.Min(pt1.X, pt2.X);
double maxX = Math.Max(pt1.X, pt2.X);
double minY = Math.Min(pt1.Y, pt2.Y);
double maxY = Math.Max(pt1.Y, pt2.Y);
Point3d minPt = new Point3d(minX, minY, 0);
Point3d maxPt = new Point3d(maxX, maxY, 0);
// ★ 统一用交叉窗口选择:只要框碰到文字(部分重叠)即选中,
// 不再要求把文字完全框住(旧版正向框选用 SelectWindow 必须全包,漏选严重)。
// 框选方向只决定业务模式(正向=逐条,反向=合并),不再影响选择方式。
PromptSelectionResult res = ed.SelectCrossingWindow(minPt, maxPt, filter);
if (res.Status != PromptStatus.OK) continue; // 这一次没选中,继续下一次
ObjectId[] ids = res.Value.GetObjectIds();
if (ids.Length == 0) continue;
// 收集文本
var items = new List<TextItem>();
using (var tr = doc.Database.TransactionManager.StartTransaction())
{
foreach (ObjectId id in ids)
{
var ent = tr.GetObject(id, OpenMode.ForRead) as Entity;
if (ent == null) continue;
string text = "";
Point3d pos = Point3d.Origin;
Extents3d? bounds = null;
var dt = ent as DBText;
var mt = ent as MText;
if (dt != null)
{
text = dt.TextString;
try
{
// ★ 用 CAD 自身的 GeometricExtents 获取文本框边界
// 宽度通常准确,高度包含字体预留空间偏大
// 所以高度用字高重新估算,宽度用 GeometricExtents
bounds = dt.GeometricExtents;
if (bounds.HasValue)
{
double h = dt.Height;
if (h > 0)
{
// ★ 宽度:直接用 GeometricExtents 的宽度(CAD 自身文本框宽度)
double left = bounds.Value.MinPoint.X;
double right = bounds.Value.MaxPoint.X;
// ★ 高度:用字高紧凑估算(底部=baseline下0.2h,顶部=baseline上1.0h)
double bottom = dt.Position.Y - h * 0.2;
double top = dt.Position.Y + h * 1.0;
bounds = new Extents3d(
new Point3d(left, bottom, 0),
new Point3d(right, top, 0));
}
}
}
catch { }
// ★ 用文字中心点作为排序基准
if (bounds.HasValue)
pos = new Point3d(
(bounds.Value.MinPoint.X + bounds.Value.MaxPoint.X) / 2.0,
(bounds.Value.MinPoint.Y + bounds.Value.MaxPoint.Y) / 2.0,
0);
else
pos = dt.Position;
}
else if (mt != null)
{
text = mt.Text;
try { bounds = mt.GeometricExtents; } catch { }
if (bounds.HasValue)
pos = new Point3d(
(bounds.Value.MinPoint.X + bounds.Value.MaxPoint.X) / 2.0,
(bounds.Value.MinPoint.Y + bounds.Value.MaxPoint.Y) / 2.0,
0);
else
pos = mt.Location;
}
if (!string.IsNullOrEmpty(text))
{
items.Add(new TextItem
{
Text = text.Trim(),
Position = pos,
Bounds = bounds,
ObjectId = id,
TextHandle = ent.Handle.ToString()
});
}
}
tr.Commit();
}
if (items.Count == 0) continue;
// ★ 按列分组排序:先从左到右分列,列内从上到下
// 用户需求示例:1在上 2在下(左列) 3在上 4在下(右列)
// 期望顺序:1、2、3、4(先左列从上到下,再右列从上到下)
// 算法:
// 1. 按X左边界升序
// 2. 扫描分列:X范围有重叠的归为同一列(垂直方向对齐的文字)
// 3. 每列内按Y降序(上在前)
// 4. 列间按X升序(左在前)合并
{
System.Func<TextItem, double> getLeft = it =>
it.Bounds.HasValue ? it.Bounds.Value.MinPoint.X : it.Position.X;
System.Func<TextItem, double> getRight = it =>
it.Bounds.HasValue ? it.Bounds.Value.MaxPoint.X : it.Position.X;
System.Func<TextItem, double> getCenterY = it =>
it.Bounds.HasValue ? (it.Bounds.Value.MinPoint.Y + it.Bounds.Value.MaxPoint.Y) / 2.0 : it.Position.Y;
// 1. 按X左边界升序
items.Sort((a, b) => getLeft(a).CompareTo(getLeft(b)));
// 2. 扫描分列:X范围有重叠的归为同一列
var columns = new List<List<TextItem>>();
double curMaxRight = double.NegativeInfinity;
List<TextItem> curCol = null;
foreach (var it in items)
{
double left = getLeft(it);
double right = getRight(it);
if (curCol == null || left > curMaxRight)
{
// 新列
curCol = new List<TextItem>();
columns.Add(curCol);
curMaxRight = right;
}
else
{
// 加入当前列,更新右边界
if (right > curMaxRight) curMaxRight = right;
}
curCol.Add(it);
}
// 3. 每列内按Y降序(上在前)
foreach (var col in columns)
{
col.Sort((a, b) => getCenterY(b).CompareTo(getCenterY(a)));
}
// 4. 合并(列间已按X升序)
items.Clear();
foreach (var col in columns) items.AddRange(col);
}
var result = new List<PaTextResult>();
if (isCrossing && items.Count > 1)
{
// ★ 反向框选 + 多条文本:合并模式
// 画一个大框,所有文本共用这个框Handle
// 每个文本作为独立的 PaTextResult 传出(共用同一个框Handle),
// 让 Form3 能检查每个文本是否已存在并自动合并已存在的元件
string mergedHandle = DrawMergedFrame(items, colorIndex);
foreach (var it in items)
{
if (!string.IsNullOrEmpty(it.Text))
{
result.Add(new PaTextResult
{
Text = it.Text.Trim(),
FrameHandle = mergedHandle,
TextHandle = it.TextHandle
});
}
}
}
else if (currentMode == PaMode.Yuanjian)
{
// 元件模式(正向框选或单条):每个文字一条,每个文字一个框
var handles = DrawTextFrames(items, colorIndex);
for (int i = 0; i < items.Count; i++)
{
string h = i < handles.Count ? handles[i] : "";
result.Add(new PaTextResult
{
Text = items[i].Text,
FrameHandle = h,
TextHandle = items[i].TextHandle
});
}
}
else
{
// 图号/箱号(正向框选或单条):去重(同名只算一条),框对应
var seen = new HashSet<string>();
var seenTextHandles = new HashSet<string>();
var handles = DrawTextFrames(items, colorIndex);
for (int i = 0; i < items.Count; i++)
{
// ★ 用文本实体Handle去重(同一个文本实体只算一条),
// 不同位置的相同内容文本算两条
if (seenTextHandles.Add(items[i].TextHandle) && seen.Add(items[i].Text))
{
string h = i < handles.Count ? handles[i] : "";
result.Add(new PaTextResult
{
Text = items[i].Text,
FrameHandle = h,
TextHandle = items[i].TextHandle
});
}
}
}
// ★ 立即把这一批数据(文本+框Handle)送给调用方(插入到表格)
if (result.Count > 0)
{
var handler = BatchTextsPicked;
if (handler != null)
{
try { handler(currentMode, result); } catch { }
}
// ★ 扒完一批后自动切换到下一个模式(图号→箱号→元件)
// 元件模式扒完后保持元件模式不变(继续扒下一个箱号的元件)
if (currentMode == PaMode.Tuhao)
{
currentMode = PaMode.Xianghao;
ed.WriteMessage("\n[扒数据] 图号已扒完,自动切换到【箱号】模式。");
}
else if (currentMode == PaMode.Xianghao)
{
currentMode = PaMode.Yuanjian;
ed.WriteMessage("\n[扒数据] 箱号已扒完,自动切换到【元件】模式。");
}
}
// 继续下一次框选
}
}
catch (System.Exception ex)
{
try { ed.WriteMessage("\n[扒数据] 异常: " + ex.Message); } catch { }
}
}
/// <summary>
/// 获取右键切换的下一个扒图模式。
/// 顺序:图号 → 箱号 → 元件 → 退出(返回当前模式表示退出)。
/// </summary>
private static PaMode GetNextMode(PaMode current)
{
switch (current)
{
case PaMode.Tuhao: return PaMode.Xianghao;
case PaMode.Xianghao: return PaMode.Yuanjian;
default: return PaMode.Yuanjian; // 元件右键 → 返回自身,外层判断退出
}
}
/// <summary>获取模式的中文名称(用于切换提示)。</summary>
private static string GetModeName(PaMode mode)
{
switch (mode)
{
case PaMode.Tuhao: return "图号";
case PaMode.Xianghao: return "箱号";
default: return "元件";
}
}
/// <summary>
/// 反向框选合并时:计算所有文字的总边界,画一个大框包住所有文字。
/// 返回:大框的 Handle 字符串(失败返回空字符串)。
/// </summary>
private static string DrawMergedFrame(List<TextItem> items, short colorIndex)
{
string handle = "";
try
{
var doc = AcAp.DocumentManager.MdiActiveDocument;
if (doc == null) return handle;
var db = doc.Database;
// 先计算所有文字的合并 Extents
double? minX = null, minY = null, maxX = null, maxY = null;
foreach (var item in items)
{
if (item.Bounds == null) continue;
Extents3d ext = item.Bounds.Value;
if (minX == null || ext.MinPoint.X < minX.Value) minX = ext.MinPoint.X;
if (minY == null || ext.MinPoint.Y < minY.Value) minY = ext.MinPoint.Y;
if (maxX == null || ext.MaxPoint.X > maxX.Value) maxX = ext.MaxPoint.X;
if (maxY == null || ext.MaxPoint.Y > maxY.Value) maxY = ext.MaxPoint.Y;
}
if (minX == null) return handle;
using (var tr = db.TransactionManager.StartTransaction())
{
// 确保专用图层存在
LayerTable lt = (LayerTable)tr.GetObject(db.LayerTableId, OpenMode.ForRead);
ObjectId layerId;
if (lt.Has(PaLayerName))
{
layerId = lt[PaLayerName];
}
else
{
lt.UpgradeOpen();
var layerDef = new LayerTableRecord { Name = PaLayerName };
layerId = lt.Add(layerDef);
tr.AddNewlyCreatedDBObject(layerDef, true);
}
var ms = (BlockTableRecord)tr.GetObject(db.CurrentSpaceId, OpenMode.ForWrite);
// ★ 先删除这批文字之前已有的旧框(避免小框和大框同时存在)
// 遍历当前空间,找出 __CADCJ_PA_FRAME 图层上与这批文字 Bounds 相交的实体,
// 通过 EraseGroupForEntity 删除整个组(边框+蒙版一起删)
var idsToDelete = new List<ObjectId>();
foreach (ObjectId entId in ms)
{
var ent = tr.GetObject(entId, OpenMode.ForRead, false, true) as Entity;
if (ent == null) continue;
if (ent.LayerId != layerId) continue;
if (ent.IsErased) continue;
Extents3d? entExt = null;
try { entExt = ent.GeometricExtents; } catch { }
if (!entExt.HasValue) continue;
// 检查是否与任何文字 item 的 Bounds 相交
foreach (var item in items)
{
if (item.Bounds == null) continue;
if (BoundsIntersect(entExt.Value, item.Bounds.Value))
{
idsToDelete.Add(entId);
break;
}
}
}
foreach (ObjectId id in idsToDelete)
{
// ★ 先删除同组的其他实体(Hatch),再删除主实体
EraseGroupForEntity(tr, id);
var ent = tr.GetObject(id, OpenMode.ForWrite);
if (!ent.IsErased) ent.Erase();
}
// ★ 边距极小:总宽高的 2%,最小 0.5
double totalW = maxX.Value - minX.Value;
double totalH = maxY.Value - minY.Value;
double padX = Math.Max(totalW * 0.02, 0.5);
double padY = Math.Max(totalH * 0.02, 0.5);
// 大框四角点(带边距,X和Y方向各自按比例)
Point3d p1 = new Point3d(minX.Value - padX, minY.Value - padY, 0);
Point3d p2 = new Point3d(maxX.Value + padX, minY.Value - padY, 0);
Point3d p3 = new Point3d(maxX.Value + padX, maxY.Value + padY, 0);
Point3d p4 = new Point3d(minX.Value - padX, maxY.Value + padY, 0);
var pl = new Polyline();
pl.AddVertexAt(0, new Point2d(p1.X, p1.Y), 0, 0, 0);
pl.AddVertexAt(1, new Point2d(p2.X, p2.Y), 0, 0, 0);
pl.AddVertexAt(2, new Point2d(p3.X, p3.Y), 0, 0, 0);
pl.AddVertexAt(3, new Point2d(p4.X, p4.Y), 0, 0, 0);
pl.Closed = true;
pl.Color = Color.FromColorIndex(ColorMethod.ByAci, colorIndex);
pl.LayerId = layerId;
pl.LineWeight = LineWeight.LineWeight070;
ms.AppendEntity(pl);
tr.AddNewlyCreatedDBObject(pl, true);
// ★ 记录大框的 Handle
handle = pl.Handle.ToString();
// ★ 添加半透明填充蒙版(用 Hatch 实体,支持透明度)
var hatch = new Hatch();
hatch.SetHatchPattern(HatchPatternType.PreDefined, "SOLID");
hatch.Color = Color.FromColorIndex(ColorMethod.ByAci, colorIndex);
hatch.Transparency = new Transparency(65); // 65% 透明度
hatch.LayerId = layerId;
ms.AppendEntity(hatch);
tr.AddNewlyCreatedDBObject(hatch, true);
ObjectIdCollection hatchLoopIds = new ObjectIdCollection();
hatchLoopIds.Add(pl.Id);
hatch.AppendLoop(HatchLoopTypes.Default, hatchLoopIds);
// ★ 把 Polyline 和 Hatch 创建为一个组(整体选中/整体删除)
MakeFrameGroup(db, tr, pl.Id, hatch.Id);
tr.Commit();
}
doc.Editor.UpdateScreen();
doc.Editor.Regen();
}
catch (Exception ex)
{
try
{
var doc = AcAp.DocumentManager.MdiActiveDocument;
if (doc != null) doc.Editor.WriteMessage("\n[扒数据] 画合并框失败: " + ex.Message);
}
catch { }
}
return handle;
}
/// <summary>
/// 检查两个 Extents3d 是否相交(用于删除旧框时判断范围重叠)。
/// </summary>
private static bool BoundsIntersect(Extents3d a, Extents3d b)
{
// X 方向不相交
if (a.MaxPoint.X < b.MinPoint.X || a.MinPoint.X > b.MaxPoint.X) return false;
// Y 方向不相交
if (a.MaxPoint.Y < b.MinPoint.Y || a.MinPoint.Y > b.MaxPoint.Y) return false;
return true;
}
/// <summary>
/// ★ 把 Polyline(边框) 和 Hatch(蒙版) 创建为一个命名组,
/// 这样用户在 CAD 里选中/删除其中一个时,整个组(边框+蒙版)会一起被选中/删除。
/// </summary>
private static void MakeFrameGroup(Database db, Transaction tr, ObjectId plId, ObjectId hatchId)
{
try
{
var groupDict = (DBDictionary)tr.GetObject(db.GroupDictionaryId, OpenMode.ForWrite);
// 组名用 Polyline 的 Handle 保证唯一
string groupName = "CADCJ_PA_FRAME_" + plId.Handle.ToString();
var group = new Group(groupName, true); // true = 可选中
group.Append(new ObjectIdCollection(new[] { plId, hatchId }));
groupDict.SetAt(groupName, group);
tr.AddNewlyCreatedDBObject(group, true);
}
catch { }
}
/// <summary>
/// ★ 删除包含指定实体的所有 Group 内的所有实体(连同 Group 本身一起删除)。
/// 用于"删除框时自动删除关联的蒙版"。
/// </summary>
public static void EraseGroupForEntity(Transaction tr, ObjectId entId)
{
try
{
var ent = tr.GetObject(entId, OpenMode.ForRead) as Entity;
if (ent == null) return;
// 获取实体的所有 PersistentReactor(Group 会作为 reactor 记录)
ObjectIdCollection reactors = ent.GetPersistentReactorIds();
var idsToErase = new List<ObjectId>();
var groupsToErase = new List<ObjectId>();
foreach (ObjectId reactorId in reactors)
{
var grp = tr.GetObject(reactorId, OpenMode.ForRead) as Group;
if (grp == null) continue;
// 收集 Group 内所有实体
foreach (ObjectId memberId in grp.GetAllEntityIds())
{
if (!idsToErase.Contains(memberId)) idsToErase.Add(memberId);
}
groupsToErase.Add(reactorId);
}
// 删除 Group 内所有实体
foreach (ObjectId memberId in idsToErase)
{
if (memberId == entId) continue; // 主实体由调用方删除
try
{
var member = tr.GetObject(memberId, OpenMode.ForWrite);
if (!member.IsErased) member.Erase();
}
catch { }
}
// 删除 Group 本身
foreach (ObjectId groupId in groupsToErase)
{
try
{
var grp = tr.GetObject(groupId, OpenMode.ForWrite);
if (!grp.IsErased) grp.Erase();
}
catch { }
}
}
catch { }
}
/// <summary>
/// 在每个文字周围画一个彩色矩形框(LWPolyline),不改文字本身的颜色。
/// 所有矩形框放在专用图层 __CADCJ_PA_FRAME 上,方便批量清除。
/// 返回:每个 item 对应的框 Handle 字符串列表(和 items 顺序一致;无 Bounds 的为空字符串)。
/// </summary>
private static List<string> DrawTextFrames(List<TextItem> items, short colorIndex)
{
var handles = new List<string>();
try
{
var doc = AcAp.DocumentManager.MdiActiveDocument;
if (doc == null) return handles;
var db = doc.Database;
using (var tr = db.TransactionManager.StartTransaction())
{
// 确保专用图层存在(如果不存在就创建)
LayerTable lt = (LayerTable)tr.GetObject(db.LayerTableId, OpenMode.ForRead);
ObjectId layerId;
if (lt.Has(PaLayerName))
{
layerId = lt[PaLayerName];
}
else
{
lt.UpgradeOpen();
var layerDef = new LayerTableRecord { Name = PaLayerName };
layerId = lt.Add(layerDef);
tr.AddNewlyCreatedDBObject(layerDef, true);
}
// 获取当前空间(模型空间或图纸空间)
var bt = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead);
var ms = (BlockTableRecord)tr.GetObject(db.CurrentSpaceId, OpenMode.ForWrite);
foreach (var item in items)
{
if (item.Bounds == null) { handles.Add(""); continue; }
Extents3d ext = item.Bounds.Value;
// ★ 用文字的实际位置和高度估算更紧凑的范围
// GeometricExtents 可能包含字体预留的 ascent/descent 空间,
// 这里用文字中心点和估算的宽高来定义框,使其更贴合文字
double textW, textH;
if (ext.MaxPoint.Y - ext.MinPoint.Y > ext.MaxPoint.X - ext.MinPoint.X * 3)
{
// 如果高度远大于宽度(多行或特殊情况),用 GeometricExtents
textW = ext.MaxPoint.X - ext.MinPoint.X;
textH = ext.MaxPoint.Y - ext.MinPoint.Y;
}
else
{
// 单行文字:用中心点和估算尺寸
textW = ext.MaxPoint.X - ext.MinPoint.X;
textH = ext.MaxPoint.Y - ext.MinPoint.Y;
}
// ★ 边距极小:文字宽高的 3%,最小 0.3
double padX = Math.Max(textW * 0.03, 0.3);
double padY = Math.Max(textH * 0.03, 0.3);
// 矩形四角点(带极小边距)
Point3d p1 = new Point3d(ext.MinPoint.X - padX, ext.MinPoint.Y - padY, 0);
Point3d p2 = new Point3d(ext.MaxPoint.X + padX, ext.MinPoint.Y - padY, 0);
Point3d p3 = new Point3d(ext.MaxPoint.X + padX, ext.MaxPoint.Y + padY, 0);
Point3d p4 = new Point3d(ext.MinPoint.X - padX, ext.MaxPoint.Y + padY, 0);
// 创建 LWPolyline 矩形
var pl = new Polyline();
pl.AddVertexAt(0, new Point2d(p1.X, p1.Y), 0, 0, 0);
pl.AddVertexAt(1, new Point2d(p2.X, p2.Y), 0, 0, 0);
pl.AddVertexAt(2, new Point2d(p3.X, p3.Y), 0, 0, 0);
pl.AddVertexAt(3, new Point2d(p4.X, p4.Y), 0, 0, 0);
pl.Closed = true;
// 设置颜色和图层
pl.Color = Color.FromColorIndex(ColorMethod.ByAci, colorIndex);
pl.LayerId = layerId;
// 线宽更粗,看得清
pl.LineWeight = LineWeight.LineWeight070;
ms.AppendEntity(pl);
tr.AddNewlyCreatedDBObject(pl, true);
// ★ 记录框的 Handle,用于关联表格数据行
handles.Add(pl.Handle.ToString());
// ★ 添加半透明填充蒙版(用 Hatch 实体,支持透明度)
var hatch = new Hatch();
hatch.SetHatchPattern(HatchPatternType.PreDefined, "SOLID");
hatch.Color = Color.FromColorIndex(ColorMethod.ByAci, colorIndex);
hatch.Transparency = new Transparency(65); // 65% 透明度
hatch.LayerId = layerId;
ms.AppendEntity(hatch);
tr.AddNewlyCreatedDBObject(hatch, true);
ObjectIdCollection hatchLoopIds = new ObjectIdCollection();
hatchLoopIds.Add(pl.Id);
hatch.AppendLoop(HatchLoopTypes.Default, hatchLoopIds);
// ★ 把 Polyline 和 Hatch 创建为一个组(整体选中/整体删除)
MakeFrameGroup(db, tr, pl.Id, hatch.Id);
}
tr.Commit();
}
// 刷新显示
doc.Editor.UpdateScreen();
doc.Editor.Regen();
}
catch (Exception ex)
{
try
{
var doc = AcAp.DocumentManager.MdiActiveDocument;
if (doc != null) doc.Editor.WriteMessage("\n[扒数据] 画框失败: " + ex.Message);
}
catch { }
}
return handles;
}
/// <summary>
/// 清除所有扒数据时画的彩色矩形框(删除 __CADCJ_PA_FRAME 图层上的所有对象)。
/// 不动文字本身,文字保持原色。
/// </summary>
public static void ClearAllHighlight()
{
try
{
var doc = AcAp.DocumentManager.MdiActiveDocument;
if (doc == null) return;
var db = doc.Database;
using (var tr = db.TransactionManager.StartTransaction())
{
var bt = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead);
// 遍历所有块定义(模型空间+所有布局),删除目标图层上的对象
foreach (ObjectId btrId in bt)
{
var btr = (BlockTableRecord)tr.GetObject(btrId, OpenMode.ForRead);
var ids = new List<ObjectId>();
foreach (ObjectId entId in btr)
{
var ent = tr.GetObject(entId, OpenMode.ForRead, false, true) as Entity;
if (ent == null) continue;
if (ent.Layer == PaLayerName)
{
ids.Add(entId);
}
}
if (ids.Count > 0)
{
btr.UpgradeOpen();
foreach (ObjectId id in ids)
{
// ★ 先删除同组的其他实体,再删除主实体
EraseGroupForEntity(tr, id);
var ent = tr.GetObject(id, OpenMode.ForWrite);
if (!ent.IsErased) ent.Erase();
}
}
}
tr.Commit();
}
doc.Editor.UpdateScreen();
doc.Editor.Regen();
try { doc.Editor.WriteMessage("\n[扒数据] 已清除所有标记框。"); } catch { }
}
catch (Exception ex)
{
try
{
var doc = AcAp.DocumentManager.MdiActiveDocument;
if (doc != null) doc.Editor.WriteMessage("\n[扒数据] 清除失败: " + ex.Message);
}
catch { }
}
}
private class TextItem
{
public string Text;
public Point3d Position;
public Extents3d? Bounds;
public ObjectId ObjectId;
public string TextHandle = "";
}
}
}
Binary file not shown.
+29
View File
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity version="1.0.0.0" name="MyApplication.app"/>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
<security>
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
<requestedExecutionLevel level="asInvoker" uiAccess="false"/>
</requestedPrivileges>
</security>
</trustInfo>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- Windows 10/11 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
<!-- Windows 8.1 -->
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
<!-- Windows 8 -->
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
<!-- Windows 7 -->
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
</application>
</compatibility>
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">false</dpiAware>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">unaware</dpiAwareness>
</windowsSettings>
</application>
</assembly>
+19
View File
@@ -0,0 +1,19 @@
@echo off
REM ============================================
REM Auto-generated build script
REM All non-ASCII paths are in build.rsp (UTF-8)
REM Keep this .bat file ASCII-only to avoid cmd codepage issues
REM ============================================
cd /d "%~dp0"
"%SystemRoot%\Microsoft.NET\Framework64\v4.0.30319\csc.exe" @build.rsp
if %ERRORLEVEL% EQU 0 (
echo Build OK. Launching...
powershell -NoProfile -Command "Start-Process -FilePath ('%~dp0' + (Select-String -Path build.rsp -Pattern '^/out:(.+)$').Matches.Groups[1].Value)"
) else (
echo Build FAILED - see messages above
pause
)
+27
View File
@@ -0,0 +1,27 @@
# Auto-generated csc response file
/target:library
/platform:x64
/utf8output
/out:cadcj.dll
/win32manifest:app.manifest
/r:System.Windows.Forms.dll
/r:System.Drawing.dll
/r:System.dll
/r:System.Data.dll
/r:System.Core.dll
/r:System.Xml.dll
/r:"C:\Windows\Microsoft.NET\Framework64\v4.0.30319\WPF\PresentationCore.dll"
/r:"C:\Windows\Microsoft.NET\Framework64\v4.0.30319\WPF\WindowsBase.dll"
/r:System.IO.Compression.dll
/r:System.IO.Compression.FileSystem.dll
/r:"AdvancedDataGridView.dll"
/r:"System.Data.SQLite.dll"
/r:"E:\cad2\AutoCAD 2018\acdbmgd.dll"
/r:"E:\cad2\AutoCAD 2018\acmgd.dll"
/r:"E:\cad2\AutoCAD 2018\accoremgd.dll"
CadPlugin.cs
Form3.cs
Form3.ui.cs
PaTextSelector.cs
新建项目.cs
新建项目.ui.cs
+25
View File
@@ -0,0 +1,25 @@
/target:library
/platform:x64
/utf8output
/out:cadcj_v2.dll
/r:System.Windows.Forms.dll
/r:System.Drawing.dll
/r:System.dll
/r:System.Data.dll
/r:System.Core.dll
/r:System.Xml.dll
/r:"C:\Windows\Microsoft.NET\Framework64\v4.0.30319\WPF\PresentationCore.dll"
/r:"C:\Windows\Microsoft.NET\Framework64\v4.0.30319\WPF\WindowsBase.dll"
/r:System.IO.Compression.dll
/r:System.IO.Compression.FileSystem.dll
/r:"AdvancedDataGridView.dll"
/r:"System.Data.SQLite.dll"
/r:"D:\ProgramData\cad\AutoCAD 2018\acdbmgd.dll"
/r:"D:\ProgramData\cad\AutoCAD 2018\acmgd.dll"
/r:"D:\ProgramData\cad\AutoCAD 2018\accoremgd.dll"
CadPlugin.cs
Form3.cs
Form3.ui.cs
PaTextSelector.cs
新建项目.cs
新建项目.ui.cs
Binary file not shown.
+7
View File
@@ -0,0 +1,7 @@
{
"ApiKey": "sk-65b3963488ab420aa9df3e4dafa02768",
"BaseUrl": "https://api.deepseek.com",
"Model": "deepseek-v4-flash",
"Enabled": false,
"WebSearch": true
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
+26
View File
@@ -0,0 +1,26 @@
@echo off
chcp 936 >nul
title 成套报价 CAD 插件卸载程序
REM 切换到批处理文件所在目录(支持从任意位置运行)
cd /d "%~dp0"
REM 检查 PowerShell 脚本是否存在
if not exist "卸载CAD插件.ps1" (
echo [错误] 未找到 卸载CAD插件.ps1
echo 请确保本批处理文件与 卸载CAD插件.ps1 在同一目录。
pause
exit /b 1
)
REM 以管理员身份运行 PowerShell 脚本(避免 HKLM 删除提权弹窗中断)
powershell -NoProfile -ExecutionPolicy Bypass -Command "Start-Process powershell -ArgumentList '-NoProfile','-ExecutionPolicy','Bypass','-File','%~dp0卸载CAD插件.ps1' -Verb RunAs -Wait"
REM 若用户取消提权,则非提权运行(HKLM 删除会自动提权,acaddoc.lsp 和 TRUSTEDPATHS 无需管理员)
if errorlevel 1 (
echo.
echo [提示] 管理员提权被取消,尝试非提权运行...
powershell -NoProfile -ExecutionPolicy Bypass -File "卸载CAD插件.ps1"
)
pause
+263
View File
@@ -0,0 +1,263 @@
# ===========================================================================
# 成套报价 CAD 插件 - 通用卸载脚本
# 自动检测 AutoCAD 并移除所有安装时写入的配置
# 用法:以管理员身份运行,或在 PowerShell 中执行本脚本
# ===========================================================================
# --- 0. 环境与自检 ---
$ErrorActionPreference = 'Stop'
# $PSScriptRoot 在 -File 模式下可靠返回脚本所在目录(PS 3.0+)
$scriptDir = if ($PSScriptRoot) { $PSScriptRoot } else { Split-Path -Parent $MyInvocation.MyCommand.Path }
$scriptDir = $scriptDir.TrimEnd('\') # 统一去除尾部反斜杠,便于比较
# 检测是否管理员(写 HKLM 需要)
$isAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
if (-not $isAdmin) {
Write-Host "[提示] 当前未以管理员身份运行,HKLM 注册表删除将自动提权。" -ForegroundColor Yellow
Write-Host " acaddoc.lsp 和 TRUSTEDPATHS 无需管理员权限。" -ForegroundColor Yellow
Write-Host ""
}
Write-Host "======================================================" -ForegroundColor Cyan
Write-Host " 成套报价 CAD 插件卸载程序" -ForegroundColor Cyan
Write-Host " 插件目录: $scriptDir" -ForegroundColor Cyan
Write-Host "======================================================" -ForegroundColor Cyan
Write-Host ""
# --- 1. 检测已安装的 AutoCAD 版本 ---
Write-Host "[1/4] 检测 AutoCAD 安装..." -ForegroundColor Green
$acadInstalls = @()
$regRoots = @(
'HKLM:\SOFTWARE\Autodesk\AutoCAD',
'HKLM:\SOFTWARE\WOW6432Node\Autodesk\AutoCAD'
)
foreach ($root in $regRoots) {
if (-not (Test-Path $root)) { continue }
Get-ChildItem $root -ErrorAction SilentlyContinue | Where-Object { $_.PSChildName -match '^R\d+\.0$' } | ForEach-Object {
$verKey = $_.PSPath
$verNum = $_.PSChildName
Get-ChildItem $verKey -ErrorAction SilentlyContinue | Where-Object { $_.PSChildName -match '^ACAD-\d+:\d+$' } | ForEach-Object {
$langKey = $_.PSPath
$props = Get-ItemProperty -Path $langKey -ErrorAction SilentlyContinue
$loc = $props.AcadLocation
$prod = $props.ProductName
if ($loc -and (Test-Path $loc)) {
$acadInstalls += [PSCustomObject]@{
Version = $verNum
LangKey = $_.PSChildName
Location = $loc
Product = $prod
HKLMKey = $langKey
}
}
}
}
}
if ($acadInstalls.Count -eq 0) {
Write-Host "[警告] 未检测到已安装的 AutoCAD,无法清理注册表。" -ForegroundColor Yellow
Write-Host " 如有手动残留的 acaddoc.lsp,请手动检查。" -ForegroundColor Yellow
if (-not $env:CADCJ_SILENT) { Read-Host "按回车退出" }
exit 0
}
Write-Host "找到 $($acadInstalls.Count) 个 AutoCAD 安装:" -ForegroundColor Green
for ($i = 0; $i -lt $acadInstalls.Count; $i++) {
$a = $acadInstalls[$i]
Write-Host (" [{0}] {1} ({2}) - {3}" -f $i, $a.Product, $a.Version, $a.Location) -ForegroundColor White
}
Write-Host ""
# 若有多个,让用户选择
$target = $acadInstalls[0]
if ($acadInstalls.Count -gt 1) {
if ($env:CADCJ_SILENT) {
Write-Host "[静默模式] 检测到多个 AutoCAD,默认选择第一个: $($target.Product)" -ForegroundColor Yellow
} else {
do {
$sel = Read-Host "请选择要卸载的 AutoCAD 序号 (0-$($acadInstalls.Count - 1)),默认 0"
if ([string]::IsNullOrWhiteSpace($sel)) { $sel = '0' }
} until ($sel -match '^\d+$' -and [int]$sel -lt $acadInstalls.Count)
$target = $acadInstalls[[int]$sel]
}
}
Write-Host ""
Write-Host ("目标: {0} ({1})" -f $target.Product, $target.Version) -ForegroundColor Cyan
Write-Host ""
# --- 2. 删除 HKLM 注册表 CADCJ 节点 ---
Write-Host "[2/4] 删除 HKLM 自动加载注册表..." -ForegroundColor Green
$hklmPath = $target.HKLMKey -replace '^HKLM:\\', ''
$appsPath = "HKLM:\$hklmPath\Apps\CADCJ"
if (-not (Test-Path $appsPath)) {
Write-Host " HKLM 中无 CADCJ 节点,跳过" -ForegroundColor DarkGray
} else {
if ($isAdmin) {
Remove-Item -Path $appsPath -Recurse -Force -ErrorAction SilentlyContinue
if (-not (Test-Path $appsPath)) {
Write-Host " 已删除 HKLM 注册表节点" -ForegroundColor Green
} else {
Write-Host " [警告] 删除失败,请检查权限" -ForegroundColor Yellow
}
} else {
# 提权删除
$tmpPs1 = Join-Path $env:TEMP "cadcj_uninstall_hklm.ps1"
$tmpContent = @"
`$ErrorActionPreference = 'Stop'
`$path = '$appsPath'
if (Test-Path `$path) { Remove-Item -Path `$path -Recurse -Force }
if (-not (Test-Path `$path)) { Write-Host ' HKLM 注册表节点已删除' } else { Write-Host ' [警告] 删除失败' }
"@
$tmpContent | Out-File -FilePath $tmpPs1 -Encoding UTF8 -Force
$p = Start-Process -FilePath "powershell.exe" -ArgumentList "-NoProfile","-ExecutionPolicy","Bypass","-File",$tmpPs1 -Verb RunAs -Wait -PassThru
if ($p.ExitCode -eq 0) {
Write-Host " 已删除 HKLM 注册表节点" -ForegroundColor Green
} else {
Write-Host " [警告] HKLM 删除失败(用户取消提权?)" -ForegroundColor Yellow
}
Remove-Item $tmpPs1 -Force -ErrorAction SilentlyContinue
}
}
Write-Host ""
# --- 3. 从所有 TRUSTEDPATHS 移除插件目录 ---
Write-Host "[3/4] 移除受信任路径(TRUSTEDPATHS)..." -ForegroundColor Green
$hkcuRoot = "HKCU:\SOFTWARE\Autodesk\AutoCAD\$($target.Version)\$($target.LangKey)"
$targets = @()
$topVar = Join-Path $hkcuRoot 'Variables'
if (Test-Path $topVar) { $targets += $topVar }
$profRoot = Join-Path $hkcuRoot 'Profiles'
if (Test-Path $profRoot) {
Get-ChildItem $profRoot -ErrorAction SilentlyContinue | ForEach-Object {
$pv = Join-Path $_.PSPath 'Variables'
if (Test-Path $pv) { $targets += $pv }
}
}
$removedCount = 0
foreach ($t in $targets) {
$cur = (Get-ItemProperty -Path $t -Name 'TRUSTEDPATHS' -ErrorAction SilentlyContinue).TRUSTEDPATHS
if (-not $cur) { continue }
# 分割后去除每段首尾空白,统一比较
$allParts = $cur.Split(';') | ForEach-Object { $_.Trim() } | Where-Object { $_ -ne '' }
$keepParts = $allParts | Where-Object { -not $_.Equals($scriptDir, [StringComparison]::OrdinalIgnoreCase) }
$hasOurs = ($allParts | Where-Object { $_.Equals($scriptDir, [StringComparison]::OrdinalIgnoreCase) }).Count -gt 0
$shortName = $t -replace '.*\\Profiles\\','Profile\' -replace '\\Variables',''
if (-not $hasOurs) {
Write-Host " 未包含: $shortName 跳过" -ForegroundColor DarkGray
continue
}
if ($keepParts.Count -eq 0) {
# 全部都是本目录,删除整个值
Remove-ItemProperty -Path $t -Name 'TRUSTEDPATHS' -ErrorAction SilentlyContinue
Write-Host " 已清空: $shortName" -ForegroundColor Green
} else {
$newVal = $keepParts -join ';'
Set-ItemProperty -Path $t -Name 'TRUSTEDPATHS' -Value $newVal -Type String -Force
Write-Host " 已移除: $shortName" -ForegroundColor Green
}
$removedCount++
}
if ($removedCount -eq 0) {
Write-Host " 无需修改(TRUSTEDPATHS 中未包含本目录)" -ForegroundColor DarkGray
}
Write-Host ""
# --- 4. 清理 acaddoc.lsp 中的 CADCJ 加载逻辑 ---
Write-Host "[4/4] 清理 acaddoc.lsp 自动加载脚本..." -ForegroundColor Green
$acadDirName = Split-Path $target.Location -Leaf
$roamingBase = Join-Path $env:APPDATA "Autodesk\$acadDirName\$($target.Version)\chs\Support"
$installSupport = Join-Path $target.Location "Support"
$candidateDirs = @($roamingBase, $installSupport) | Select-Object -Unique
$supDir = $null
foreach ($d in $candidateDirs) {
if (Test-Path $d) { $supDir = $d; break }
}
if (-not $supDir) {
Write-Host " 未找到 Support 目录,跳过" -ForegroundColor DarkGray
} else {
$lspPath = Join-Path $supDir 'acaddoc.lsp'
if (-not (Test-Path $lspPath)) {
Write-Host " acaddoc.lsp 不存在,跳过" -ForegroundColor DarkGray
} else {
# 读取(尝试 GB2312,失败则用默认编码)
$content = $null
try {
$content = [System.IO.File]::ReadAllText($lspPath, [System.Text.Encoding]::GetEncoding('GB2312'))
} catch {
$content = Get-Content $lspPath -Raw -ErrorAction SilentlyContinue
}
if (-not $content) {
Write-Host " acaddoc.lsp 为空或无法读取,跳过" -ForegroundColor DarkGray
} elseif ($content -notmatch 'CADCJ|cadcj') {
Write-Host " acaddoc.lsp 中无 CADCJ 加载逻辑,跳过" -ForegroundColor DarkGray
} else {
# 判断是否为纯 CADCJ 文件(以 ;;; CADCJ 开头或整个文件就是 CADCJ 块)
# 移除 CADCJ 加载块(从 ;;; CADCJ 标记开始到 (princ) 结束)
$pattern = '(?s)\s*;;; CADCJ.*?\(princ\)\s*'
$newContent = [regex]::Replace($content, $pattern, "`r`n")
# 清理多余空行
$newContent = $newContent -replace "(`r`n){3,}", "`r`n`r`n"
$newContent = $newContent.TrimEnd()
try {
if ([string]::IsNullOrWhiteSpace($newContent)) {
# 文件清空了,删除文件
Remove-Item $lspPath -Force
Write-Host " 已删除: $lspPath(文件仅含 CADCJ 内容)" -ForegroundColor Green
} else {
# 保留其他内容,只移除 CADCJ 块
[System.IO.File]::WriteAllText($lspPath, $newContent + "`r`n", [System.Text.Encoding]::GetEncoding('GB2312'))
Write-Host " 已清理 CADCJ 加载逻辑,保留其他内容: $lspPath" -ForegroundColor Green
}
} catch {
# 写入失败时提权
Write-Host " 写入失败,尝试提权: $($_.Exception.Message)" -ForegroundColor Yellow
$tmpLsp = Join-Path $env:TEMP 'cadcj_acaddoc_clean.lsp'
if ([string]::IsNullOrWhiteSpace($newContent)) {
$delCmd = "Remove-Item '$lspPath' -Force -ErrorAction SilentlyContinue"
$p = Start-Process -FilePath "powershell.exe" -ArgumentList "-NoProfile","-Command",$delCmd -Verb RunAs -Wait -PassThru
if ($p.ExitCode -eq 0) {
Write-Host " 已删除(提权): $lspPath" -ForegroundColor Green
}
} else {
[System.IO.File]::WriteAllText($tmpLsp, $newContent + "`r`n", [System.Text.Encoding]::GetEncoding('GB2312'))
$copyCmd = "Copy-Item '$tmpLsp' '$lspPath' -Force"
$p = Start-Process -FilePath "powershell.exe" -ArgumentList "-NoProfile","-Command",$copyCmd -Verb RunAs -Wait -PassThru
if ($p.ExitCode -eq 0) {
Write-Host " 已清理(提权): $lspPath" -ForegroundColor Green
}
}
Remove-Item $tmpLsp -Force -ErrorAction SilentlyContinue
}
}
}
}
Write-Host ""
# --- 完成 ---
Write-Host "======================================================" -ForegroundColor Cyan
Write-Host " 卸载完成!" -ForegroundColor Green
Write-Host "======================================================" -ForegroundColor Cyan
Write-Host ""
Write-Host "卸载摘要:" -ForegroundColor White
Write-Host " AutoCAD: $($target.Product) ($($target.Version))" -ForegroundColor White
Write-Host " HKLM 注册: 已删除 CADCJ 节点" -ForegroundColor White
Write-Host " TRUSTEDPATHS: 已移除 $scriptDir" -ForegroundColor White
Write-Host " acaddoc.lsp: 已清理 CADCJ 加载逻辑" -ForegroundColor White
Write-Host ""
Write-Host "插件文件本身未删除(cadcj.dll 等),如需彻底删除请手动移除目录:" -ForegroundColor Yellow
Write-Host " $scriptDir" -ForegroundColor Yellow
Write-Host ""
if (-not $env:CADCJ_SILENT) { Read-Host "按回车退出" }
+26
View File
@@ -0,0 +1,26 @@
@echo off
chcp 936 >nul
title 成套报价 CAD 插件安装程序
REM 切换到批处理文件所在目录(支持从任意位置运行)
cd /d "%~dp0"
REM 检查 PowerShell 脚本是否存在
if not exist "安装CAD插件.ps1" (
echo [错误] 未找到 安装CAD插件.ps1
echo 请确保本批处理文件与 安装CAD插件.ps1 在同一目录。
pause
exit /b 1
)
REM 以管理员身份运行 PowerShell 脚本(避免 HKLM 写入提权弹窗中断)
powershell -NoProfile -ExecutionPolicy Bypass -Command "Start-Process powershell -ArgumentList '-NoProfile','-ExecutionPolicy','Bypass','-File','%~dp0安装CAD插件.ps1' -Verb RunAs -Wait"
REM 若用户取消提权,则非提权运行(HKLM 写入会自动提权,acaddoc.lsp 和 TRUSTEDPATHS 无需管理员)
if errorlevel 1 (
echo.
echo [提示] 管理员提权被取消,尝试非提权运行...
powershell -NoProfile -ExecutionPolicy Bypass -File "安装CAD插件.ps1"
)
pause
+309
View File
@@ -0,0 +1,309 @@
# ===========================================================================
# 成套报价 CAD 插件 - 通用安装脚本
# 适用于任何电脑、任何已安装的 AutoCAD 版本(2010+)
# 用法:以管理员身份运行,或在 PowerShell 中执行本脚本
# ===========================================================================
# --- 0. 环境与自检 ---
$ErrorActionPreference = 'Stop'
# $PSScriptRoot 在 -File 模式下可靠返回脚本所在目录(PS 3.0+)
$scriptDir = if ($PSScriptRoot) { $PSScriptRoot } else { Split-Path -Parent $MyInvocation.MyCommand.Path }
$scriptDir = $scriptDir.TrimEnd('\') # 统一去除尾部反斜杠,便于比较
$dllPath = Join-Path $scriptDir 'cadcj.dll'
if (-not (Test-Path $dllPath)) {
Write-Host "[错误] 未找到 cadcj.dll,请确保脚本与 cadcj.dll 在同一目录。" -ForegroundColor Red
Write-Host " 当前目录: $scriptDir" -ForegroundColor Yellow
if (-not $env:CADCJ_SILENT) { Read-Host "按回车退出" }
exit 1
}
# 检测是否管理员(写 HKLM 需要)
$isAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
if (-not $isAdmin) {
Write-Host "[提示] 当前未以管理员身份运行,HKLM 注册表写入将自动提权。" -ForegroundColor Yellow
Write-Host " acaddoc.lsp 和 TRUSTEDPATHS 无需管理员权限。" -ForegroundColor Yellow
Write-Host ""
}
Write-Host "======================================================" -ForegroundColor Cyan
Write-Host " 成套报价 CAD 插件安装程序" -ForegroundColor Cyan
Write-Host " 插件目录: $scriptDir" -ForegroundColor Cyan
Write-Host "======================================================" -ForegroundColor Cyan
Write-Host ""
# --- 1. 检测已安装的 AutoCAD 版本 ---
Write-Host "[1/4] 检测 AutoCAD 安装..." -ForegroundColor Green
# 同时检测 64 位和 32 位注册表视图
$acadInstalls = @()
$regRoots = @(
'HKLM:\SOFTWARE\Autodesk\AutoCAD',
'HKLM:\SOFTWARE\WOW6432Node\Autodesk\AutoCAD'
)
foreach ($root in $regRoots) {
if (-not (Test-Path $root)) { continue }
# 遍历每个 Rxx.0 版本(如 R22.0=2018, R23.0=2020, R24.0=2022...)
Get-ChildItem $root -ErrorAction SilentlyContinue | Where-Object { $_.PSChildName -match '^R\d+\.0$' } | ForEach-Object {
$verKey = $_.PSPath
$verNum = $_.PSChildName # 如 R22.0
# 遍历该版本下的语言变体(如 ACAD-1001:804)
Get-ChildItem $verKey -ErrorAction SilentlyContinue | Where-Object { $_.PSChildName -match '^ACAD-\d+:\d+$' } | ForEach-Object {
$langKey = $_.PSPath
$props = Get-ItemProperty -Path $langKey -ErrorAction SilentlyContinue
$loc = $props.AcadLocation
$prod = $props.ProductName
if ($loc -and (Test-Path $loc)) {
$acadInstalls += [PSCustomObject]@{
Version = $verNum
LangKey = $_.PSChildName
Location = $loc
Product = $prod
RegRoot = $root
HKLMKey = $langKey
}
}
}
}
}
if ($acadInstalls.Count -eq 0) {
Write-Host "[错误] 未检测到已安装的 AutoCAD。" -ForegroundColor Red
if (-not $env:CADCJ_SILENT) { Read-Host "按回车退出" }
exit 1
}
Write-Host "找到 $($acadInstalls.Count) 个 AutoCAD 安装:" -ForegroundColor Green
for ($i = 0; $i -lt $acadInstalls.Count; $i++) {
$a = $acadInstalls[$i]
Write-Host (" [{0}] {1} ({2}) - {3}" -f $i, $a.Product, $a.Version, $a.Location) -ForegroundColor White
}
Write-Host ""
# 若有多个,让用户选择
$target = $acadInstalls[0]
if ($acadInstalls.Count -gt 1) {
if ($env:CADCJ_SILENT) {
# 静默模式:默认选第一个,不交互
Write-Host "[静默模式] 检测到多个 AutoCAD,默认选择第一个: $($target.Product)" -ForegroundColor Yellow
} else {
do {
$sel = Read-Host "请选择要安装的 AutoCAD 序号 (0-$($acadInstalls.Count - 1)),默认 0"
if ([string]::IsNullOrWhiteSpace($sel)) { $sel = '0' }
} until ($sel -match '^\d+$' -and [int]$sel -lt $acadInstalls.Count)
$target = $acadInstalls[[int]$sel]
}
}
Write-Host ""
Write-Host ("目标: {0} ({1})" -f $target.Product, $target.Version) -ForegroundColor Cyan
Write-Host ("路径: {0}" -f $target.Location) -ForegroundColor Cyan
Write-Host ""
# --- 2. 修正 MODULE 路径到 HKLM ---
Write-Host "[2/4] 注册 AutoCAD 自动加载(HKLM)..." -ForegroundColor Green
# HKLM 路径(去除 HKLM: 前缀,得到 SOFTWARE\Autodesk\AutoCAD\Rxx.0\ACAD-xxxx:xxx\Apps\CADCJ)
$hklmPath = $target.HKLMKey -replace '^HKLM:\\', ''
$appsPath = "HKLM:\$hklmPath\Apps\CADCJ"
# 写入函数(管理员直接写,非管理员生成临时 ps1 提权执行)
function Write-HklmReg {
param($Path, $Dll)
if (-not (Test-Path $Path)) { New-Item -Path $Path -Force | Out-Null }
Set-ItemProperty -Path $Path -Name 'DESCRIPTION' -Value '成套报价插件' -Type String -Force
Set-ItemProperty -Path $Path -Name 'LOADCTRLS' -Value 2 -Type DWord -Force
Set-ItemProperty -Path $Path -Name 'MANAGED' -Value 1 -Type DWord -Force
Set-ItemProperty -Path $Path -Name 'MODULE' -Value $Dll -Type String -Force
}
if ($isAdmin) {
Write-HklmReg -Path $appsPath -Dll $dllPath
Write-Host " HKLM 注册完成" -ForegroundColor Green
} else {
# 生成临时 ps1 提权执行(避免字符串嵌套转义问题)
$tmpPs1 = Join-Path $env:TEMP "cadcj_install_hklm.ps1"
$tmpContent = @"
`$ErrorActionPreference = 'Stop'
`$path = '$appsPath'
`$dll = '$dllPath'
if (-not (Test-Path `$path)) { New-Item -Path `$path -Force | Out-Null }
Set-ItemProperty -Path `$path -Name 'DESCRIPTION' -Value '成套报价插件' -Type String -Force
Set-ItemProperty -Path `$path -Name 'LOADCTRLS' -Value 2 -Type DWord -Force
Set-ItemProperty -Path `$path -Name 'MANAGED' -Value 1 -Type DWord -Force
Set-ItemProperty -Path `$path -Name 'MODULE' -Value `$dll -Type String -Force
Write-Host ' HKLM 注册完成'
"@
$tmpContent | Out-File -FilePath $tmpPs1 -Encoding UTF8 -Force
$p = Start-Process -FilePath "powershell.exe" -ArgumentList "-NoProfile","-ExecutionPolicy","Bypass","-File",$tmpPs1 -Verb RunAs -Wait -PassThru
if ($p.ExitCode -ne 0) {
Write-Host "[警告] HKLM 写入失败(用户取消提权?)。不影响后续安装,acaddoc.lsp 会自动加载。" -ForegroundColor Yellow
} else {
Write-Host " HKLM 注册完成" -ForegroundColor Green
}
Remove-Item $tmpPs1 -Force -ErrorAction SilentlyContinue
}
Write-Host ""
# --- 3. 添加 TRUSTEDPATHS 到所有用户配置(HKCU) ---
Write-Host "[3/4] 添加受信任路径(TRUSTEDPATHS)..." -ForegroundColor Green
# 确定 HKCU 根路径
$hkcuRoot = "HKCU:\SOFTWARE\Autodesk\AutoCAD\$($target.Version)\$($target.LangKey)"
$targets = @()
# 顶层 Variables
$topVar = Join-Path $hkcuRoot 'Variables'
if (Test-Path $topVar) { $targets += $topVar }
# 各 Profile 下的 Variables
$profRoot = Join-Path $hkcuRoot 'Profiles'
if (Test-Path $profRoot) {
Get-ChildItem $profRoot -ErrorAction SilentlyContinue | ForEach-Object {
$pv = Join-Path $_.PSPath 'Variables'
if (Test-Path $pv) { $targets += $pv }
}
}
$addedCount = 0
foreach ($t in $targets) {
$cur = (Get-ItemProperty -Path $t -Name 'TRUSTEDPATHS' -ErrorAction SilentlyContinue).TRUSTEDPATHS
if ($cur -and ($cur.Split(';') -contains $scriptDir)) {
Write-Host " 已存在: $($t -replace '.*\\Profiles\\','Profile\') 跳过" -ForegroundColor DarkGray
continue
}
if ($cur) {
$newVal = "$cur;$scriptDir"
} else {
$newVal = $scriptDir
}
try {
New-ItemProperty -Path $t -Name 'TRUSTEDPATHS' -Value $newVal -PropertyType String -Force | Out-Null
$shortName = $t -replace '.*\\Profiles\\','Profile\' -replace '\\Variables',''
Write-Host " 已添加: $shortName" -ForegroundColor Green
$addedCount++
} catch {
Write-Host " 失败: $t - $($_.Exception.Message)" -ForegroundColor Red
}
}
if ($addedCount -eq 0 -and $targets.Count -gt 0) {
Write-Host " 所有配置已包含本目录,无需修改" -ForegroundColor DarkGray
}
Write-Host ""
# --- 4. 创建 acaddoc.lsp 自动加载(核心机制) ---
Write-Host "[4/4] 创建 acaddoc.lsp 自动加载脚本..." -ForegroundColor Green
# 确定 Support 目录(优先用户漫游目录,其次 AutoCAD 安装目录)
# 漫游目录路径模式:%APPDATA%\Autodesk\AutoCAD 20XX\Rxx.0\chs\Support
$acadDirName = Split-Path $target.Location -Leaf # 如 "AutoCAD 2018"
$roamingBase = Join-Path $env:APPDATA "Autodesk\$acadDirName\$($target.Version)\chs\Support"
$installSupport = Join-Path $target.Location "Support"
$candidateDirs = @($roamingBase, $installSupport) | Select-Object -Unique
$supDir = $null
foreach ($d in $candidateDirs) {
if (Test-Path $d) { $supDir = $d; break }
}
if (-not $supDir) {
# 创建漫游目录
$supDir = $roamingBase
New-Item -ItemType Directory -Path $supDir -Force | Out-Null
Write-Host " 创建目录: $supDir" -ForegroundColor Yellow
}
$lspPath = Join-Path $supDir 'acaddoc.lsp'
# LISP 代码(路径用双反斜杠转义)
$dllPathEscaped = $dllPath -replace '\\','\\\\'
$lispCode = @"
;;; CADCJ 自动加载 - 由安装脚本生成
;;; 每会话首次打开文档时通过 NETLOAD 加载成套报价插件
;;; 使用内存全局变量,进程退出后自动清除,避免持久化导致跳过加载
(vl-load-com)
(if (not *cadcj-loaded*)
(progn
(setq *cadcj-loaded* t)
(setq #cadcj-fd (getvar "FILEDIA"))
(setvar "FILEDIA" 0)
(vl-catch-all-apply
'(lambda ()
(command "NETLOAD" "$dllPathEscaped")
)
)
(setvar "FILEDIA" #cadcj-fd)
(vl-propagate '*cadcj-loaded*)
)
)
(princ)
"@
# 检查是否已有 acaddoc.lsp(避免覆盖用户现有内容)
$needMerge = $false
if (Test-Path $lspPath) {
$existing = Get-Content $lspPath -Raw -ErrorAction SilentlyContinue
if ($existing -and ($existing -match 'CADCJ|cadcj')) {
Write-Host " acaddoc.lsp 已包含 CADCJ 加载逻辑,跳过" -ForegroundColor DarkGray
} elseif ($existing) {
$needMerge = $true
Write-Host " 检测到现有 acaddoc.lsp,将追加 CADCJ 加载逻辑" -ForegroundColor Yellow
}
}
try {
if ($needMerge) {
# 追加到现有文件末尾
$existing + "`r`n" + $lispCode | Out-File -FilePath $lspPath -Encoding Default -Append -Force
} else {
# GB2312 编码写入(AutoCAD 正确读取中文路径)
[System.IO.File]::WriteAllText($lspPath, $lispCode, [System.Text.Encoding]::GetEncoding('GB2312'))
}
Write-Host " 已创建: $lspPath" -ForegroundColor Green
} catch {
# AppData 写入失败时提权
Write-Host " 写入失败,尝试提权: $($_.Exception.Message)" -ForegroundColor Yellow
$tmpLsp = Join-Path $env:TEMP 'cadcj_acaddoc.lsp'
[System.IO.File]::WriteAllText($tmpLsp, $lispCode, [System.Text.Encoding]::GetEncoding('GB2312'))
$copyCmd = "Copy-Item '$tmpLsp' '$lspPath' -Force"
$p = Start-Process -FilePath "powershell.exe" -ArgumentList "-NoProfile","-Command",$copyCmd -Verb RunAs -Wait -PassThru
if ($p.ExitCode -eq 0) {
Write-Host " 已创建(提权): $lspPath" -ForegroundColor Green
} else {
Write-Host " [错误] 无法创建 acaddoc.lsp" -ForegroundColor Red
}
Remove-Item $tmpLsp -Force -ErrorAction SilentlyContinue
}
Write-Host ""
# --- 5. 同步更新 .reg 文件(便于记录) ---
$regFile = Join-Path $scriptDir '注册CAD自动加载.reg'
$regKeyHklm = $appsPath -replace '^HKLM:\\','HKEY_LOCAL_MACHINE\'
$regContent = @"
Windows Registry Editor Version 5.00
[$regKeyHklm]
"DESCRIPTION"="成套报价插件"
"LOADCTRLS"=dword:00000002
"MANAGED"=dword:00000001
"MODULE"="$($dllPath -replace '\\','\\')"
"@
try {
$regContent | Out-File -FilePath $regFile -Encoding Default -Force
Write-Host " 已更新: $regFile" -ForegroundColor DarkGray
} catch {}
Write-Host ""
# --- 完成 ---
Write-Host "======================================================" -ForegroundColor Cyan
Write-Host " 安装完成!" -ForegroundColor Green
Write-Host "======================================================" -ForegroundColor Cyan
Write-Host ""
Write-Host "安装摘要:" -ForegroundColor White
Write-Host " AutoCAD: $($target.Product) ($($target.Version))" -ForegroundColor White
Write-Host " 插件DLL: $dllPath" -ForegroundColor White
Write-Host " LSP文件: $lspPath" -ForegroundColor White
Write-Host " 加载方式: acaddoc.lsp (NETLOAD)" -ForegroundColor White
Write-Host ""
Write-Host "现在启动 AutoCAD 并打开任意图纸,插件会自动加载。" -ForegroundColor Yellow
Write-Host ""
if (-not $env:CADCJ_SILENT) { Read-Host "按回车退出" }
+71
View File
@@ -0,0 +1,71 @@
@echo off
chcp 65001 >nul
REM ============================================
REM 成套报价 CAD 插件 - 就地安装脚本
REM 功能:把【本脚本所在目录】的 cadcj.dll 注册为 CAD 自动加载
REM 原则:在哪个目录运行,就安装哪个目录(测试目录/安装目录通用)
REM 用法:双击运行(无需管理员权限,写的是用户级注册表 HKCU)
REM 更新 DLL 后:先关 CAD → 替换本目录 cadcj.dll → 再运行本脚本不需要,
REM 注册表已指向本目录,直接重启 CAD 即加载新版
REM ============================================
setlocal enabledelayedexpansion
REM 取脚本所在目录(去掉尾部反斜杠)
set "INSTDIR=%~dp0"
if "%INSTDIR:~-1%"=="\" set "INSTDIR=%INSTDIR:~0,-1%"
set "DLLPATH=%INSTDIR%\cadcj.dll"
REM 检查本目录是否有 cadcj.dll
if not exist "%DLLPATH%" (
echo [错误] 本目录没有 cadcj.dll,无法安装。
echo 当前目录: %INSTDIR%
pause
exit /b 1
)
echo ==============================================
echo 成套报价 CAD 插件 - 就地安装
echo 插件目录: %INSTDIR%
echo ==============================================
echo.
REM 检查 CAD 是否在运行(在运行也能写注册表,但新DLL要重启CAD才生效)
tasklist /FI "IMAGENAME eq acad.exe" 2>nul | find /I "acad.exe" >nul
if %ERRORLEVEL% EQU 0 (
echo [提示] AutoCAD 正在运行:注册完成后请重启 CAD 才能生效。
echo.
)
REM 枚举所有已安装的 AutoCAD 版本(HKCU 用户级,无需管理员)
REM ★ findstr "^HKEY_" 只取子键行,过滤掉 CurVer 这类值行
set FOUND=0
for /f "delims=" %%K in ('reg query "HKCU\SOFTWARE\Autodesk\AutoCAD" 2^>nul ^| findstr /I /C:"HKEY_"') do (
for /f "delims=" %%P in ('reg query "%%K" 2^>nul ^| findstr /I /C:"HKEY_" ^| findstr /I "ACAD-"') do (
set "APPSKEY=%%P\Apps\CADCJ"
reg add "!APPSKEY!" /v LOADCTRLS /t REG_DWORD /d 2 /f >nul
reg add "!APPSKEY!" /v MANAGED /t REG_DWORD /d 1 /f >nul
reg add "!APPSKEY!" /v MODULE /t REG_SZ /d "%DLLPATH%" /f >nul
reg add "!APPSKEY!" /v DESCRIPTION /t REG_SZ /d "ChengTao BaoJia Plugin" /f >nul
echo [OK] 已注册: !APPSKEY!
echo MODULE = %DLLPATH%
set FOUND=1
)
)
if %FOUND% EQU 0 (
echo [错误] 未检测到已安装的 AutoCAD(R22.0=2018 等)。
echo 请确认 AutoCAD 已安装后重试。
pause
exit /b 1
)
echo.
echo ==============================================
echo 安装完成!
echo CAD 自动加载: %DLLPATH%
echo 用户库CSV : 本目录下最新的 用户库_*.csv
echo 数据库sjk : 本目录 sjk\ 下
echo 重启 AutoCAD 即生效。
echo ==============================================
pause
File diff suppressed because it is too large Load Diff
+132
View File
@@ -0,0 +1,132 @@
using System;
using System.Collections.Generic;
using System.Windows.Forms;
namespace MyApp
{
/// <summary>
/// 新建项目 窗口(简化版,不依赖企业信息)。
/// UI 布局见 新建项目.ui.cs,本文件只含交互逻辑。
/// </summary>
public partial class 新建项目 : Form
{
// 价格格式化选项(与主项目 价格格式化.cs 保持一致)
private static readonly string[] 小数位选项 =
{
"保留整数", "保留1位小数", "保留2位小数", "保留3位小数",
"保留4位小数", "保留5位小数", "保留6位小数"
};
private static readonly string[] 取整方式选项 =
{
"不取整", "四舍五入", "四舍五入到角", "四舍五入到元",
"四舍五入到十元", "向上取整到元", "最近5元", "最近10元"
};
private readonly string _dbPath;
private readonly string _tableName;
public 新建项目() : this(null, null) { }
/// <summary>带数据库路径的构造函数(用于自动生成项目编号)。</summary>
public 新建项目(string dbPath, string tableName)
{
InitializeComponent();
_dbPath = dbPath;
_tableName = tableName;
InitPriceFormatCombos();
this.Load += 新建项目_Load;
}
private void InitPriceFormatCombos()
{
cmb单台价格.Items.Clear();
cmb单台价格.Items.AddRange(小数位选项);
cmb项目总价.Items.Clear();
cmb项目总价.Items.AddRange(小数位选项);
comboBox1.Items.Clear();
comboBox1.Items.AddRange(取整方式选项);
comboBox2.Items.Clear();
comboBox2.Items.AddRange(取整方式选项);
}
private void 新建项目_Load(object sender, EventArgs e)
{
FillAutoFields();
}
/// <summary>自动填充项目日期(当天)和项目编号(yyyyMMdd+3位序号)。</summary>
private void FillAutoFields()
{
DateTime today = DateTime.Today;
txt项目日期.Text = today.ToString("yyyy-MM-dd");
string datePart = today.ToString("yyyyMMdd");
int seq = GetNextSeqForDate(datePart);
txt项目编号.Text = datePart + seq.ToString("000");
txt项目编号.ReadOnly = true;
txt项目编号.TabStop = false;
txt项目编号.BackColor = System.Drawing.SystemColors.Control;
}
/// <summary>查数据库当天最大序号+1,失败返回1。</summary>
private int GetNextSeqForDate(string datePart)
{
if (string.IsNullOrEmpty(datePart) ||
string.IsNullOrEmpty(_dbPath) ||
string.IsNullOrEmpty(_tableName) ||
!System.IO.File.Exists(_dbPath))
{
return 1;
}
try
{
string connStr = "Data Source=" + _dbPath + ";Version=3;";
using (var conn = new System.Data.SQLite.SQLiteConnection(connStr))
{
conn.Open();
string sql =
"SELECT MAX(CAST(SUBSTR(项目编号, 9) AS INTEGER)) " +
"FROM " + _tableName + " " +
"WHERE SUBSTR(项目编号, 1, 8) = '" + datePart + "';";
using (var cmd = new System.Data.SQLite.SQLiteCommand(sql, conn))
{
object result = cmd.ExecuteScalar();
if (result == null || result == DBNull.Value) return 1;
int maxSeq;
if (int.TryParse(result.ToString(), out maxSeq)) return maxSeq + 1;
return 1;
}
}
}
catch { return 1; }
}
/// <summary>收集对话框字段,以列名为键返回(供 Form3 写入数据库)。</summary>
public Dictionary<string, string> GetInputValues()
{
var values = new Dictionary<string, string>();
AddIfNotEmpty(values, "项目编号", txt项目编号.Text);
AddIfNotEmpty(values, "项目日期", txt项目日期.Text);
AddIfNotEmpty(values, "项目名称", txt项目名称.Text);
AddIfNotEmpty(values, "客户名称", txt客户名称.Text);
AddIfNotEmpty(values, "客户地址", txt客户地址.Text);
AddIfNotEmpty(values, "联系人", txt联系人.Text);
AddIfNotEmpty(values, "联系电话", txt联系电话.Text);
AddIfNotEmpty(values, "备注", txt备注.Text);
AddIfNotEmpty(values, "成套厂名称", txt成套厂名称.Text);
AddIfNotEmpty(values, "成套厂地址", txt成套厂地址.Text);
AddIfNotEmpty(values, "成套厂联系人", txt成套厂联系人.Text);
AddIfNotEmpty(values, "成套厂联系电话", txt成套厂联系电话.Text);
values["单台小数位"] = (cmb单台价格.SelectedItem ?? cmb单台价格.Text ?? "").ToString();
values["项目小数位"] = (cmb项目总价.SelectedItem ?? cmb项目总价.Text ?? "").ToString();
values["单台取整方式"] = (comboBox1.SelectedItem ?? comboBox1.Text ?? "").ToString();
values["项目取整方式"] = (comboBox2.SelectedItem ?? comboBox2.Text ?? "").ToString();
return values;
}
private static void AddIfNotEmpty(Dictionary<string, string> dict, string key, string value)
{
if (!string.IsNullOrWhiteSpace(value)) dict[key] = value.Trim();
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,7 @@
Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE\Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Autodesk\AutoCAD\R22.0\ACAD-1001:804\Apps\CADCJ]
"DESCRIPTION"="成套报价插件"
"LOADCTRLS"=dword:00000002
"MANAGED"=dword:00000001
"MODULE"="C:\\Users\\099978\\Documents\\SharpDevelop Projects\\成套报价软件\\成套报价软件\\cadcj\\cadcj.dll"
File diff suppressed because it is too large Load Diff
Binary file not shown.
Binary file not shown.
Binary file not shown.