一个 Visual Studio 加载项,用于将 Windows 窗体设计器生成的代码从 .vb 文件移动到 .Designer.vb 文件






4.04/5 (11投票s)
将 VB.NET 2003 Windows 窗体设计器部分的 Windows 窗体公共类文件转换为分部类文件。
引言
本文提供了安装和使用 Visual Studio 2012 加载项的说明,该加载项用于将 Visual Studio 2003 Windows 窗体设计器生成的代码从公共类文件移动到分部类文件。此外,我还讲述了我是如何创建这个加载项的故事。
当 Visual Studio 2005 发布时,Visual Studio 2003 Windows 窗体设计器生成的代码从 .vb 文件中移除,并放置在一个单独的 .Designer.vb 文件中。Visual Studio 2012 对于现有项目仍然接受 .vb 文件中的 Windows 窗体设计器生成的代码。作为我 Visual Studio 2012 学习过程的一部分,我想将 Windows 窗体设计器生成的代码分离到分部类 .Designer.vb 文件中。
宏
我在 http://www.nathanpjones.com/wp/2010/02/converting-vb-net-2003-winforms-to-20052008-partial-classes 找到了几个用于此目的的 Visual Studio 宏版本。不幸的是,我发现宏功能已从 Visual Studio 2012 中移除。在看到原始作者的评论“深入研究以使其按您想要的方式工作”后,我决定将其转换为 Visual Studio 加载项。一个新事物要学习!
我要感谢原始宏开发者和后来提交自己修订的人。这些代码为我的项目提供了一个良好的开端。原始宏遍历选定 Windows 窗体 .vb 文件的 Visual Studio 自动化对象模型,以定位由 Sub InitializeComponent
过程中的代码初始化的变量声明,并定位 Sub InitializeComponent
和 Sub Dispose
过程。这些与分部类子句一起用于创建新的 .Designer.vb 文件。宏的一个后续版本(由另一位程序员贡献)选择了派生自 System.Windows.Forms.Control
或 System.ComponentModel.IContainer
的变量。
背景
使用此 Visual Studio 加载项将 Windows 窗体设计器生成的代码从公共类 .vb 文件移动到分部类 .Designer.vb 文件。
与本文相关的 ConvertVBFiles VB.NET 项目经过编译,可处理所选项目中的所有 Windows 窗体。提供了条件编译选项,允许您编译自己的版本,仅转换当前选定的 Windows 窗体。在使用此 Visual Studio 加载项之前,请务必备份您的 Visual Studio 解决方案/项目。
使用代码
ZIP 文件中包含一个已编译的 ConvertVBFiles.dll 版本。如果您想直接使用 ConvertVBFiles.dll,并使用条件编译声明 #CONVERT_ALL_FILES_IN_SELECTED_PROJECT = True
,则跳过步骤 2 和 3。
- 将 ConvertVBFiles 项目源代码解压到 C:\Users\<USERNAME>\Documents\Visual Studio 2012\Projects。
- 将
#Const CONVERT_ALL_FILES_IN_SELECTED_PROJECT = True
的值编辑为 True 或 False。 - True 处理项目中的所有 Windows 窗体。
- False 仅处理选定的 Windows 窗体。
- 编译您的 ConvertVBFiles.DLL 版本。
- 将 ConvertVBFile.AddIn 文件解压到 C:\Users\<USERNAME>\Documents\Visual Studio 2012\Addins。
- 编辑 ConvertVBFile.AddIn 文件,将 <USERNAME> 替换为您的用户名。
<?xml version="1.0" encoding="UTF-16" standalone="no"?>
<Extensibility xmlns="http://schemas.microsoft.com/AutomationExtensibility">
<HostApplication>
<Name>Microsoft Visual Studio</Name>
<Version>11.0</Version>
</HostApplication>
<Addin>
<FriendlyName>Convert VB 'Windows Form Designer generated code' Region to a separate *.Designer.vb file</FriendlyName>
<Description>Convert VB 'Windows Form Designer generated code' Region to a separate *.Designer.vb file</Description>
<Assembly>c:\users\<USERNAME>\documents\visual studio 2012\Projects\ConvertVBFiles\ConvertVBFiles\bin\ConvertVBFiles.dll</Assembly>
<FullClassName>ConvertVBFiles.ConvertVBFiles</FullClassName>
<LoadBehavior>0</LoadBehavior>
<CommandPreload>1</CommandPreload>
<CommandLineSafe>0</CommandLineSafe>
</Addin>
</Extensibility>
如果您使用 CONVERT_ALL_FILES_IN_SELECTED_PROJECT
设置为 True
编译了您的版本,请在打开解决方案后,选择一个项目,然后单击“工具”,“转换为设计器分部类”。加载项将遍历所有 .VB 窗体,转换包含 Windows 窗体设计器生成代码的窗体,然后显示一个消息框,详细说明其操作。
如果您使用 CONVERT_ALL_FILES_IN_SELECTED_PROJECT
设置为 False
编译了您的版本,请在打开解决方案后,选择项目中的 Windows 窗体文件,然后单击“工具”,“转换为设计器分部类”。加载项将转换该文件并显示一个消息框,详细说明其操作。
卸载
要从 Visual Studio 中删除 ConvertVBFiles 加载项,请在 C:\Users\<USERNAME>\Documents\Visual Studio 2012\Addins 目录中重命名 ConvertVBFiles.AddIn 扩展名,或删除 ConvertVBFile.Addin 文件。
关注点
方便的是,Visual Studio 2012 在“新建项目”对话框中提供了一个可扩展性项目类型。我用它来启动我的新加载项项目。我将宏源粘贴到我的新项目中的一个新 Sub ExtractWinFormsDesignerFile
过程中,并从生成的 Sub Exec
过程中调用它。
我清理了一些语法问题,添加了一些 Debug.WriteLine
语句来帮助我学习流程,添加了一些断点,然后开始测试。我发现了一些情况下,对象模型对某个项产生了 Nothing 值。我添加了使用 IsNothing()
进行的检查以忽略这些项。我使用的宏代码版本移动了一些不应该移动的项。例如,如果我在 Windows 窗体设计器生成的代码区域之外声明了一个派生自 System.Windows.Form
中某个对象的变量,它就会被移动,而它不应该被移动。一个例子是 ListView.SelectedIndexCollection
类型变量的属性声明。
If
语句选择变量声明。If (member.Kind = vsCMElement.vsCMElementVariable) Then
Dim objDeclaration As CodeVariable = DirectCast(member, CodeVariable)
Dim objFieldType As CodeType = objDeclaration.Type.CodeType
If Not IsNothing(objFieldType) Then
If Not IsNothing(objFieldType.Namespace) Then
Dim isControl As Boolean = objFieldType.Namespace.FullName.StartsWith("System.Windows.Forms")
Debug.WriteLine("Namespace=" & objFieldType.Namespace.FullName)
If isControl OrElse objFieldType.IsDerivedFrom("System.ComponentModel.IContainer") Then
Debug.WriteLine("Added " & objFieldType.Namespace.FullName)
strWindowsFormsControlsDeclarations.AppendLine(extractMember(member))
intItemsConvertedCount += 1
End If
End If
End If
End If
人类可以判断哪些代码需要移动
我决定利用对象模型公开每个子程序和每个变量声明的绝对字符偏移量的优势。我使用偏移量来确定变量声明是否在 Windows 窗体设计器生成的代码区域内。此策略将利用 Visual Studio 2003 Windows 窗体设计器编写生成代码的顺序。
这是 Visual Studio 2003 Windows 窗体设计器创建生成代码的顺序
Public Class MainForm
Inherits System.Windows.Forms.Form
#Region " Windows Form Designer generated code "
Public Sub New()
…
End Sub
<System.Diagnostics.DebuggerNonUserCode()> _
Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
…
End Sub
…
… Windows Form Designer generated variable declarations here …
…
<System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()
…
End Sub
Protected Overrides Sub Finalize()
…
End Sub
我添加了一个检查,即变量声明的绝对字符偏移量小于 Sub InitializeComponent
过程的绝对字符偏移量,并且大于 Sub New
或 Sub Dispose
的字符偏移量。如果是这样,变量声明将移动到新的 .Designer.vb 文件中。如果不存在 Dispose 或 New,我将它们设置为零作为偏移量值。这样做会产生极小的可能性,即有人可能在 Windows 窗体设计器生成的代码区域之前声明了 System.Windows.Form
派生变量声明。如果他们这样做了,该变量声明也将被移动,但可以很容易地通过剪切和粘贴移回。
If (member.Kind = vsCMElement.vsCMElementVariable) Then
Dim objDeclaration As CodeVariable = DirectCast(member, CodeVariable)
Dim objFieldType As CodeType = objDeclaration.Type.CodeType
If Not IsNothing(objFieldType) Then
If Not IsNothing(objFieldType.Namespace) Then
Dim isControl As Boolean = objFieldType.Namespace.FullName.StartsWith("System.Windows.Forms")
' Character offset of this variable's declaration
Dim intOffset As Integer = objDeclaration.StartPoint.AbsoluteCharOffset
Debug.WriteLine("Namespace=" & objFieldType.Namespace.FullName)
If isControl OrElse objFieldType.IsDerivedFrom("System.ComponentModel.IContainer") Then
If intOffset_InitializeComponent > intOffset AndAlso (intOffset_Dispose < _
intOffset OrElse intOffset_New < intOffset) Then
Debug.WriteLine("Added " & objFieldType.Namespace.FullName)
strWindowsFormsControlsDeclarations.AppendLine(extractMember(member))
intItemsConvertedCount += 1
End If
End If
End If
End If
End If
带有 WithEvents 的友元
在测试过程中,我发现了一些窗体,其中我在 Windows 窗体生成代码区域之前声明了一些 System.Windows.Forms
派生变量。我对初始化 IsControl
变量的布尔语句进行了以下更改。对 Access 属性的检查确保了 Friend 和 WithEvents
的存在。这进一步降低了来自 Windows 窗体设计器生成代码区域之外的变量声明被错误地移动到新的 .Designer.vb 文件的可能性。
以前
Dim isControl As Boolean = objFieldType.Namespace.FullName.StartsWith("System.Windows.Forms")
操作后
Dim isControl As Boolean = objFieldType.Namespace.FullName.StartsWith("System.Windows.Forms") AndAlso _
CBool(objDeclaration.Access And (vsCMAccess.vsCMAccessWithEvents + vsCMAccess.vsCMAccessProject))
Visual Studio 工具菜单
我偶尔遇到一个问题,即加载项无法将其菜单项放置在 Visual Studio 工具菜单中。我认为这可能是由于在调试模式下运行,创建了 Visual Studio 的第二个实例 (DEVENV.EXE) 并且未选中“工具”、“管理加载项”中的复选框导致的。我发现如果我选中了复选框,在测试期间重新编译时就无法替换 DLL。我修改了可扩展性项目类型提供的代码中的一行代码,以允许 DLL 再次创建“工具”菜单项。在 Sub OnConnection
过程中,我进行了以下更改,以使菜单项添加代码在启动后执行。
以前
If connectMode = ext_ConnectMode.ext_cm_UISetup Then
操作后
If connectMode = ext_ConnectMode.ext_cm_UISetup OrElse connectMode = ext_ConnectMode.ext_cm_AfterStartup Then
生产力提升
我继续进行测试和清理。我最终的增强是利用条件编译功能,允许编译一个加载项版本,该版本将遍历解决方案中选定项目中的所有 .vb 文件。
仅转换项目中选定的 Windows 窗体
#Const CONVERT_ALL_FILES_IN_SELECTED_PROJECT = False
转换选定项目中的所有 Windows 窗体
#Const CONVERT_ALL_FILES_IN_SELECTED_PROJECT = True
PrintDocument
在撰写本文初版几天后,我在我的一个程序中发现了另一种类型的 Windows 窗体设计器生成的变量声明。为了将此项从 .vb 文件移动到 .Designer.vb 文件,我将 IsControl
初始化更改为以下内容。
Dim isControl As Boolean = (objFieldType.Namespace.FullName.StartsWith("System.Windows.Forms") OrElse _
objFieldType.Namespace.FullName.StartsWith("System.Drawing.Printing")) AndAlso _
CBool(objDeclaration.Access And (vsCMAccess.vsCMAccessWithEvents + vsCMAccess.vsCMAccessProject))
历史
- 2013年1月15日:初始版本。
- 2013年1月19日:添加了检查以处理 PrintDocument 声明(命名空间:System.Drawing.Printing)。