Files

19 KiB
Raw Permalink Blame History

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 为空,别用它):
    # ① 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 覆盖到安装目录。
<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 后写。