mcpField
W
winforms-expert

winforms-expert

github.com/dotnet开发5.8k1.1k 次安装1 天前更新
代码仓库

关于 winforms-expert

winforms-expert 是用于实现和诊断 Windows Forms 改动的技能,强调保持构建正确并兼容 Visual Studio Designer。它指导开发者先检查项目与验证流程,并区分设计器序列化代码和运行时逻辑。

主要功能

  1. 01设计器安全的序列化指导
  2. 02发现项目与验证器
  3. 03先运行针对性验证再构建
  4. 04WinForms 专项修复指导

适用场景

  1. 01新增或修改 Form 和控件
  2. 02诊断 Designer 加载或保存问题
  3. 03修复布局、绑定或 DPI 行为

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/dotnet/skills --skill winforms-expert

安装后在 Claude Code 中输入/winforms-expert确认已加载。

详细介绍

WinForms 专家

用途

实现能够成功编译、运行时行为正确,并且仍可在 Visual Studio 设计器中编辑的 WinForms 更改。将 InitializeComponent 和 *.Designer.* 视为序列化格式,而不是普通应用程序代码;将行为逻辑保留在主分部类中,并在环境允许时验证设计器是否可用。

执行约定

编辑之前,列出工作区根目录,并找到任何针对任务的验证器、测试脚本或文档说明的验证命令。选择实现方案前,先阅读其场景选择器和必需参数。编辑之后:

  1. 先运行范围最小的验证器,并修复它归因于本次更改的所有失败。
  2. 验证器通过后,再运行针对性的项目构建。
  3. 如果构建或之后的任何编辑更改了源代码,则重新运行范围最小的验证器。
  4. 将这些命令与运行时 UI 和设计器往返检查分别报告。

成功构建不能替代现有的布局、绑定、序列化或设计器安全性验证器。不要只读过验证器或只在构建成功后就结束。

使用仓库实际的命令和参数;执行顺序应如下所示:

pwsh -NoProfile -File <task-validator.ps1> <scenario-arguments>
dotnet build <project> --nologo

限制

适用于:

  • 创建或更改 WinForms 项目、Form、UserControl 或自定义控件。
  • 编辑设计器生成的布局、事件连线、资源或组件字段。
  • 诊断窗体设计器加载、序列化、缩放、绑定或工具箱问题。
  • 实现 WinForms 特有的异步、MVVM、无障碍、DPI 或深色模式行为。

不适用于:

  • 除非用户明确要求迁移,否则不要将应用程序转换为其他 UI 框架。
  • 不要仅为改善代码风格而重写正常工作的生成代码。
  • 除非请求需要,否则不要更改目标框架、语言版本、SDK、包管理策略、启动模型、DPI 模式或应用程序级主题。
  • 如果没有实际打开并操作 Visual Studio 设计器,不要声称它可以正常工作。

输入

输入必需获取方式
所请求的 UI 行为或缺陷是使用用户的请求,并在可能时复现症状。
项目语言和目标框架是检查项目文件;不要假设使用现代 .NET 或 C#。
现有的 Form/UserControl 分部文件是查找主文件、设计器文件及对应的 .resx。
仓库约定是检查附近的 WinForms 类型、构建脚本、分析器和包管理方式。
设计器是否可用否确定是否能够打开兼容的 Visual Studio WinForms 设计器。

不可妥协的设计器约定

有两种代码上下文:

上下文文件规则
设计器序列化*.Designer.cs、*.Designer.vb、InitializeComponent使用简单、确定性的语句,以便设计器解析并重新生成。
应用程序行为主 partial 类、服务、视图模型使用项目 TFM 和语言版本支持的语言特性。

在 InitializeComponent 和设计器文件中:

  • 保留控件/组件的构造、属性赋值、集合添加、布局暂停/恢复、资源应用以及命名事件处理程序的绑定。
  • 使用类级别的控件/组件字段。不要通过局部变量将控件添加到 Controls、Items、Columns 或其他序列化集合中。
  • 使用在主 partial 文件中定义的命名事件处理程序。生成的事件绑定中绝不要使用 lambda 或匿名委托。
  • 不要添加控制流(if、循环、switch、try/catch、lock、await、goto)、局部函数、表达式主体逻辑、集合表达式、空条件或空合并表达式,或其他运行时决策逻辑。
  • 不要添加业务逻辑、数据加载、验证、服务调用,或 InitializeComponent 和 Dispose 等既定生成成员以外的方法。
  • 保留生成器现有的命名空间风格、限定方式、顺序、资源和可空性处理。不要更新生成的语法。
  • 保留设计器所依赖的现有构造函数。除非项目采用既定的自定义设计器模式,否则由设计器实例化的 Form/UserControl 必须提供可用的无参数构造路径。
  • 保持 BeginInit/EndInit、SuspendLayout/ResumeLayout 和容器所有权配对平衡。
  • 对于 C#,在设计器 partial 类的类作用域中保留生成的字段。对于 VB,保留 Friend WithEvents 字段,并优先在主文件中使用 Handles 子句。

安全的 InitializeComponent 顺序如下:

  1. 实例化控件和组件。
  2. 如果所拥有的组件需要,则实例化 IContainer。
  3. 调用所需的 BeginInit 和 SuspendLayout 方法。
  4. 赋值子控件/组件的属性。
  5. 将子控件添加到序列化集合,并绑定命名处理程序。
  6. 在子控件配置完成后,配置包含它们的 Form/UserControl。
  7. 在适当情况下,调用匹配的 EndInit、ResumeLayout 和 PerformLayout。
  8. 在生成器既定的位置声明序列化的后备字段。

将任何更复杂的内容移到主 partial 类中,通常放在 InitializeComponent 运行之后。

会改变答案的规则

根据症状选择一种 WinForms 专用修复方式。当现有项目和故障已经明确指出应采用哪种方式时,不要列出多种可能的模式。

症状或需求应做切勿验证
Form/UserControl 可以构建,但 Designer 无法实例化它保留运行时构造函数,添加可供 Designer 使用的无参数路径,并在运行时加载时再使用依赖项,同时显式检查设计模式和空值在 Designer 路径中构造生产服务、打开文件或查询数据运行时组合根仍提供真正的依赖项;设计路径会执行 InitializeComponent,且不会调用该依赖项
Designer 保存/重新打开后,控件/组件消失、重复或失效恢复生成字段/集合的结构,并确保每个 BeginInit/EndInit 和 SuspendLayout/ResumeLayout 调用都成对出现在 OnLoad 中修补症状,或将序列化控件移入局部变量保存、关闭、重新打开,并检查重新生成的差异
Designer 创建的 Timer、BindingSource、图像列表或类似组件的生命周期超过了 Form创建 components 容器,并将其传递给组件构造函数在 Designer 所有权不一致的情况下添加临时释放逻辑关闭 Form 时会释放容器所拥有的组件,且该组件仍由 Designer 管理
添加/移除列表项后,绑定的 WinForms 列表控件没有刷新使用 BindingList<T>,或使用仓库中可触发 WinForms 列表更改通知的适配器将 ObservableCollection<T> 当作可直接替换的 WinForms DataSource绑定后修改列表,并观察控件是否更新
DPI、字体或本地化变化时,嵌套内容被裁切从叶级控件开始,沿所有父级检查 AutoSize、AutoSizeMode、Dock、MinimumSize、MaximumSize 和行/列样式;移除实际的增长限制,同时保留有意设置的最小值;还要检查相对于增长链定位的同级控件,并确保它们处于响应式布局中认为 AutoSize = true 就足够,只增大一个固定的 Size,绕过父容器,或让同级控件留在扩展内容可能与之重叠的位置测试调整大小以及相关的 DPI/字体/文本扩展情况
已投递 UI 工作,但完成状态/错误丢失将操作放入返回 Task 的方法中,以便调用方/测试等待;让必需的 async void 事件处理程序只执行 await RefreshAsync();在捕获的 WinForms 上下文中更新控件,或在执行可能不在该上下文中时等待 InvokeAsync;在 finally 中恢复控件状态将整个操作放在 async void 中,使用 _ =、BeginInvoke、无实际作用的取消异常捕获,或将应用级异常钩子作为常规处理路径等待返回 Task 的方法,验证成功和失败情况;确认 UI 入口会委托给该方法
文本必须支持本地化使用现有的 .resx 和 ComponentResourceManager.ApplyResources 序列化模式在 InitializeComponent 中硬编码回退 UI 文本构建、在可能时切换区域性,并执行 Designer 保存/重新打开
VB 应用需要启动、单实例或未处理 UI 异常钩子扩展 ApplicationEvents.vb;当事件参数类型存在歧义时,使用 Microsoft.VisualBasic.ApplicationServices 限定类型;还原、激活并将现有 MainForm 置于前台;记录 e.Exception;显式设置 e.ExitApplication = True臆造 Program.vb、添加 Sub Main、替换生成的启动代码,或让错误后的继续执行行为保持不明确配置的 StartupObject 和生成的应用程序文件保持不变,不存在新的入口点,并在最终报告中说明退出选择
审查未发现实际的 Designer 缺陷让已跟踪的项目/源文件逐字节保持不变,并说明可疑结构为何无害清理警告、重新格式化生成的代码,或现代化无关语法运行范围最小的可用检查和针对性构建;确认源文件差异为空
工作区包含针对任务的验证器或测试脚本编辑后、通用构建前运行仓库提供的针对性检查;将其失败视为更改尚未完成的证据因项目可以编译就跳过专用检查,或声称静态验证证明了 Designer 往返检查分别报告准确的验证器和构建命令,然后说明实际是否能够进行运行时 UI 和 Designer 往返检查

工作流

1. 明确项目约束
  1. 检查解决方案/项目、目标框架、语言、可空设置、包管理、应用程序启动方式以及现有构建命令。
  2. 确定应用面向的是现代 .NET 还是 .NET Framework。除非明确要求迁移,否则保留当前系列。
  3. 编辑之前,读取每个受影响的 Form/UserControl 对应的全部分部文件和 .resx。检查基类和相邻控件,了解仓库约定。
  4. 如果缺陷特定于设计器,请记录确切的加载/序列化错误,并确定问题是否在进行大范围更改前就已发生。
2. 规划序列化边界
  1. 将每项更改归类为序列化 UI 状态或运行时行为。
  2. 仅将设计器可表示的状态放入 InitializeComponent。
  3. 将条件设置、动态内容、数据加载、转换、验证和服务交互放入主分部类或其他运行时类型中。
  4. 尽量缩小设计器差异。不要对整个生成文件重新排序或重新格式化。
  5. 如果可以避免编辑生成的代码,请改用设计器或运行时初始化方法。如果必须直接编辑,请严格遵循现有的生成代码结构。
3. 实现 UI 结构

对于新布局或经过大幅调整的布局:

  • 优先使用 TableLayoutPanel、FlowLayoutPanel、SplitContainer 和嵌套的 UserControl,而不是脆弱的绝对定位。
  • 将复杂屏幕划分为较小的布局区域。避免使用单个过大的网格。
  • 标题/单行行列优先使用 AutoSize,可扩展内容使用 Percent,只有确实固定尺寸的内容才使用 Absolute。
  • 保留现有窗体的 AutoScaleMode。对于新窗体,应根据项目约定审慎选择缩放方式,而不是照搬其他 DPI 下的坐标。
  • 确保自动调整大小的子容器参与完整的尺寸调整链;自动调整大小的行中如果包含固定高度的 Panel 或 GroupBox,其内容仍可能被裁切。
  • 对可调整大小的对话框或本地化对话框设置合理的最小尺寸,避免其变得无法使用。
  • 将本地化 UI 文本保存在项目资源中,并允许标签和按钮扩展。

对于对话框:

  • 设置 AcceptButton、CancelButton 和适当的 DialogResult 值。
  • 提交时验证整个窗体。不要通过取消每次焦点更改来困住用户。
  • 按照项目的所有权模式释放模态窗体。
4. 在生成的代码之外添加行为
  • 遵循项目的语言和风格约定;现代语法仅用于常规代码。
  • 明确保持 UI 线程亲和性。从后台任务访问控件时,应封送调用。
  • 普通 WinForms async void 事件处理程序在 await 后会恢复到捕获的 UI 同步上下文,除非刻意绕过了该上下文。在此处直接访问控件是有效的。如果代码可能在上下文之外继续执行,或任务/仓库约定明确要求封送操作,请使用并等待 InvokeAsync;不要依赖未等待的投递操作。
  • 对于支持此功能的现代 .NET 版本,请根据委托是同步还是异步,以及是否返回值,选择匹配的 Control.InvokeAsync 重载。等待返回的操作;不要创建即发即弃的 UI 工作。
  • 对于必须返回 void 的 WinForms 事件处理程序和重写方法,可以使用 async void,但应在等待的工作周围捕获异常,妥善处理取消,并通过应用程序既定的错误处理路径报告意外故障。
  • 不要将应用程序级异常钩子用作处理预期故障的替代方案。Application.ThreadException 处理 UI 线程异常;AppDomain.UnhandledException 主要用于最后阶段的日志记录,并不能确保程序可以安全地继续运行。
  • 保留属性的分配语义。未经确认对象的生命周期和释放行为,不要将缓存属性改为 => new ...,也不要反向更改。

对于应用程序范围的启动配置:

  • 保留当前的启动模型。C# 项目可以在 Program.cs 中配置应用程序默认值;VB 项目通常使用 VB Application Framework 和 ApplicationEvents.vb,而不是另行创建 Program.vb。
  • 使用实际 TFM 支持的 API。仅在有请求或符合应用程序现有策略时,才设置颜色模式或 DPI 默认值。
  • 维持常规 .NET WinForms DPI 行为时,优先使用 SystemAware。仅当应用程序针对每显示器缩放进行了设计和测试时,才使用 PerMonitorV2。
5. 有意识地实现绑定和序列化

对于经典绑定:

  • 对可变绑定对象使用 INotifyPropertyChanged,并使用 BindingList<T> 或成熟的适配器来通知列表更改。
  • 在符合现有设计的情况下,使用 BindingSource 作为设计器/运行时中介。
  • 将转换逻辑放在 Binding.Format 和 Binding.Parse 处理程序中,而不是嵌入设计器代码中。
  • 谨慎处理单向到源的请求:经典 WinForms Binding 不提供直接的 WPF 式模式。应记录并测试任何变通方案,而不要暗示原生支持。

对于现代 WinForms MVVM API,首先确认 TFM 包含所请求的 API:

  • 在可用时,使用 Control.DataContext 提供环境视图模型上下文。
  • 在可用时,使用 ButtonBase.Command、ToolStripItem.Command 和 CommandParameter。
  • 如果解决方案已采用 UI 无关的项目来存放视图模型,或所请求的更改因此受益,则继续采用这种方式;不要为了小修复引入新的架构。
  • 如果需要设计时对象数据源,请保留或添加项目现有的 .datasource 和 BindingSource 模式,并在设计器中验证。

对于自定义 Component/Control 属性,选择一种明确的 CodeDOM 序列化策略:

策略机制用途
仅在非默认值时序列化[DefaultValue(...)] 或相匹配的 ShouldSerializeX/ResetX具有明确默认值的稳定值
从不序列化[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]仅运行时使用、计算得出或不受支持的状态
序列化内容DesignerSerializationVisibility.Content,以及与设计器兼容的可变内容有意在属性网格中编辑的所属嵌套对象/集合

导入 System.ComponentModel(或完全限定其属性)。确保默认元数据与运行时初始化直接一致:对于 [DefaultValue(typeof(Color), "Yellow")],直接用 Color.Yellow 初始化属性或其后备字段;不要通过第二个常量或工厂隐藏实际默认值。不要组合相互矛盾的策略。对自定义序列化更改测试属性网格编辑和保存/重新加载行为。

6. 检查可用性和无障碍性
  • 设置合理的 TabIndex 顺序,并验证仅使用键盘时的遍历效果。
  • 在产品中使用助记键的地方,添加明确无歧义的助记键。
  • 如果标签或上下文尚未提供相关信息,为可操作控件设置有意义的无障碍名称/描述。
  • 验证默认焦点、确认/取消行为、调整大小、裁剪以及本地化文本增长情况。
  • 检查与此次更改相关的 DPI/主题组合。系统颜色会随主题调整;硬编码颜色和所有者绘制控件则不会。
  • 对所有者绘制、DataGridView、图标和自定义绘制,验证其在每种受支持主题下的对比度,不要假定它们会自动适配深色模式。
7. 按成本从低到高进行验证
  1. 重新阅读设计器差异,确认每条语句都可安全序列化,且每个引用的字段/处理程序都存在于正确的分部类中。
  2. 查找并运行仓库中最精简的特定任务验证脚本或测试(如果有)。通用构建不能替代设计器/布局/绑定验证器。
  3. 运行仓库中适用范围最小的还原/构建/分析器命令。如果项目类型或 .NET Framework 依赖项有要求,请使用 Visual Studio MSBuild。
  4. 运行其余相关自动化测试。
  5. 在可能的情况下启动受影响的 UI,并测试创建、加载、调整大小、键盘导航、绑定、异步/错误路径以及关闭/释放行为。
  6. 如果可以使用兼容的 WinForms Designer 的 Visual Studio:
    1. 在设计器中打开每个已更改的 Form/UserControl。
    2. 确认设计图面、工具箱集成、属性网格、组件托盘和继承控件均能正常加载,没有错误。
    3. 做出无害且可撤销的属性更改,保存、关闭、重新打开,并确认设计器能够序列化并重新加载该组件。
    4. 仅还原这项无害的验证更改,不要还原请求的实现。
    5. 序列化往返后再次构建。

可观察的完成标准

只有在环境适用的检查均可观察时,更改才算完成:

  • 项目构建成功,且没有新增编译器或分析器错误。
  • 相关测试通过。
  • 在可以启动的情况下,受影响的 UI 能正常运行,并且请求的行为已得到测试。
  • 受影响的 InitializeComponent 中没有不受支持的运行时逻辑,也没有无法解析的字段、处理程序、资源或不平衡的布局/初始化调用。
  • 在适用的情况下,自定义属性更改在属性网格保存/重新打开往返后仍然保留。
  • 在设计器可用时,每个已更改的 Form/UserControl 都能在兼容的 Visual Studio Designer 中打开并重新加载。

如果 Visual Studio、Designer、Windows 桌面访问、必需的工作负载或目标运行时不可用,请将该验证报告为未运行,说明原因,并列出实际运行的准确检查项。构建成功并不能证明 Designer 可以打开。除非实际测试了设计图面和序列化往返,否则不要声称已完成全面的设计器验证。

如果命令失败:

  1. 报告命令和第一个可采取行动的错误。
  2. 区分失败是由更改引起,还是由缺少工具/工作负载、平台不兼容、文件被锁定、还原/网络故障或仓库中原有的问题引起。
  3. 修复由更改引起的失败,并重新运行检查。
  4. 绝不要用看似成功的替代方案代替失败的检查,也不要仅凭源代码检查就声称已完成。

最后提供简洁的验证记录:

  • Build: 准确命令和结果。
  • Runtime UI: 已测试的行为;如果未运行,则说明阻碍原因。
  • Designer round trip: Form/UserControl 已打开、编辑、保存、关闭并重新打开;如果未运行,则说明阻碍原因。
  • Preserved: 有意保持不变的项目策略文件和生成/启动文件。

常见陷阱

陷阱修正规则
将 InitializeComponent 当作现代 C# 代码将其保留为简单的设计器序列化代码;将决策和行为移到主分部类中。
在生成的代码中放入 lambda 或局部控件变量使用类级字段和具名处理程序。
只编辑一个分部文件阅读并确保主文件、设计器文件、.resx、基类型和项目元数据保持一致。
重新格式化整个设计器文件参照生成的顺序和风格进行最小范围的修改。
认为构建成功就证明设计器正常打开、编辑、保存、关闭并重新打开设计界面。
为可设计类型添加构造函数依赖项保留设计器可用的无参数构造路径,或采用既有的自定义设计器方案。
固定尺寸的嵌套容器裁切控件保留自动调整大小链,并测试窗口调整大小和 DPI 缩放。
未经测试便使用绝对颜色适配深色模式优先使用系统颜色,或明确为自定义绘制设置主题并测试对比度。
将 ObservableCollection<T> 当作可直接替换的 WinForms 列表数据源使用 BindingList<T>,或使用能够桥接集合通知和属性通知的适配器。
认为 WPF 绑定或转换器功能也存在于 WinForms 中使用 WinForms 的 Binding、BindingSource、Format 和 Parse;核实特定 TFM 的 API。
对 UI 调度采用不等待的方式,或未捕获异步事件错误等待 InvokeAsync;在 async void UI 入口中捕获并报告失败。
为 VB 应用凭空创建 Program.vb使用 VB Application Framework 和 ApplicationEvents.vb 约定。
在 UI 修复期间更改 DPI、主题、TFM 或包策略除非所需行为要求更改且更改经过验证,否则保持项目策略不变。

评论

支持 Markdown