# 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 C:\Program Files (x86)\SharpDevelop\5.2\bin\Smart.CustomComponents.dll True C:\Program Files (x86)\SharpDevelop\5.2\bin\WeifenLuo.WinFormsUI.Docking.dll True C:\Program Files (x86)\SharpDevelop\5.2\bin\WeifenLuo.WinFormsUI.Docking.ThemeVS2015.dll True C:\Program Files (x86)\SharpDevelop\5.2\bin\System.Data.SQLite.dll True C:\Program Files (x86)\SharpDevelop\5.2\bin\log4net.dll True C:\Program Files (x86)\SharpDevelop\5.2\bin\System.Resources.Extensions.dll True C:\Program Files (x86)\SharpDevelop\5.2\bin\System.Memory.dll True C:\Program Files (x86)\SharpDevelop\5.2\bin\System.Runtime.CompilerServices.Unsafe.dll True C:\Program Files (x86)\SharpDevelop\5.2\bin\System.Numerics.Vectors.dll True ``` `True` 会在编译时把 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(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 { /// 停靠窗口:工具箱。内容在本类的设计器视图中编辑。 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\
..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. 特殊控件生成注意 - **属性白名单铁律(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 后写。