19 KiB
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 为空,别用它):
都找不到就问用户,不要瞎猜路径。完整说明见 SKILL.md 第一章"定位实际安装目录"。
# ① 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' - 开发机临时追新:仓库 bin
F:\Visual Studio\1\33\SharpDevelop-master\bin有最新控件时可把 HintPath 临时换成它,但该路径仅本机存在,交付/给用户机的模板一律用安装目录。 - 前提:安装目录必须是最新构建。jk55 案例发现 08-27 旧安装包缺 RibbonStrip/Logger(13 控件只认 11)——先重打 MSI 重装,或把仓库 bin 最新 DLL 覆盖到安装目录。
<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. 标准骨架(单控件)
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)
this.multiSelectComboBox1.Items.Add("选项A");
this.multiSelectComboBox1.Items.Add("选项B");
2.2 递归对象集合(SideMenuPanel.MenuItems,条目有 Children)
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):
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 条目)
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)
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
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
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
// 本文件由『生成停靠窗口代码』自动生成,集合变更后会自动刷新,请勿手工编辑。
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(); 之后插入(已存在则跳过):
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. 菜单/工具栏嵌套模板(每个菜单项/工具栏项都是字段)
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 集合模板
// 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 后写。