使用 CodeDom 将 WinForms => Web Forms 转换






4.82/5 (39投票s)
介绍一种将 Windows Forms 转换为 ASP.NET Web Forms 的可行方法。
目录
引言
有时,客户项目需要同时提供 Windows Forms 应用程序和基于浏览器的前端。通常,Windows Forms 应用程序提供所有功能,而 HTML 版本是其“轻量级”的变体。由于这两种用户界面都访问相同的业务逻辑,因此能够将 Windows Forms UI 转换为 Web Forms 将非常方便,从而节省了在 Web Forms 设计器中花费在繁琐复制现有 Windows Forms 上的开发时间。本文演示了一种将 Windows Forms UI 转换为 ASP.NET Web Forms 的可能方法。
免责声明
请注意,本文或随附的代码示例无意实现 Windows Forms 和 Web Forms 之间的 *完全* 转换,包括所有事件和业务逻辑。由于这两种编程模型的根本不同性质,这将是徒劳的尝试。相反,我们针对用户界面组件本身,将 Windows Forms 控件映射到相应的 Web Forms 控件。如果您正在将表单绑定到业务类 — 我们相信您会的 — 您仍负责从数据存储中检索它们的实例并将用户输入的更改写回。
背景
尝试在 Windows Forms 和 Web Forms 之间进行直接转换的主要问题是将控件及其属性相互映射。
例如,让我们以 System.Windows.Forms.TextBox
控件为例。在 System.Web.UI
命名空间中,它的匹配项的显而易见的选择是 System.Web.UI.WebControls.TextBox
控件。这很容易。然而,仔细查看这两种类型的属性会发现相当多的不匹配:将 Windows Forms 控件的 Name
属性映射到其 Web Forms 对应项的 ID
属性并不难。但像 Anchor
、BindingContext
和 SelectedText
这样的属性呢?无论您多么努力,都找不到相应的 Web 控件属性。毕竟,Web 控件最终会呈现为 HTML,而 HTML 只提供 Windows Forms 应用程序可用功能的一小部分。因此,您将不得不放弃相当一部分 Windows Forms 属性,并将自己限制在最基本的属性上,特别是那些决定位置和大小的属性。
工作原理
示例的核心类 Convert2Aspx
包含一个单独的 public
方法 Convert
,以及一些用于自定义 Web Forms 转换过程的属性。您可以设置所需的输出语言 (SourceLanguage
) 以及您想创建页面还是用户控件 (AspxType
)。
Convert
方法接受两个参数:要转换的表单或控件,以及要将输出文件写入的路径。它会在提供的位置创建 aspx/ascx 文件和相应的代码隐藏文件。控件声明插入到 aspx/ascx 文件中,并且相应的方法和变量声明添加到代码隐藏中,就像 Visual Studio .NET 所做的那样。使用级联样式表 (CSS) 指令可以实现正确的定位,这些指令可以被所有现代浏览器正确解释。CSS) 指令,这些指令可以被所有现代浏览器正确解释。
使用代码
Convert2Aspx
类允许您创建 Web Forms 页面 (*.aspx) 和用户控件 (*.ascx)。您可以通过将 AspxType
enum
属性设置为 AspxTypes.Page
或 — 您猜对了 — AspxTypes.UserControl
来确定输出格式。通过将 SourceLanguage
属性设置为所需的值,选择您喜欢的目标语言 C# 或 VB.NET,然后调用 Convert
方法。下面提供了两个说明类用法的代码片段。
将 System.Windows.Forms.Form
实例转换为 Web Forms 页面
convert2Aspx = new suite4.net.WinForms2WebForms.Convert2Aspx();
convert2Aspx.AspxType = suite4.net.WinForms2WebForms.AspxTypes.Page;
convert2Aspx.SourceLanguage = suite4.net.WinForms2WebForms.SourceLanguages.C_Sharp;
convert2Aspx.Convert(this, @"C:\WinForms2WebForms");
将 System.Windows.Forms.GroupBox
实例转换为 Web Forms 用户控件
convert2Aspx = new suite4.net.WinForms2WebForms.Convert2Aspx();
convert2Aspx.Namespace = "suite4.net.WinForms2WebForms";
convert2Aspx.RootName = "grpAdress";
convert2Aspx.FullName = convert2Aspx.Namespace + "." + convert2Aspx.RootName;
convert2Aspx.AspxType = suite4.net.WinForms2WebForms.AspxTypes.UserControl;
convert2Aspx.SourceLanguage = suite4.net.WinForms2WebForms.SourceLanguages.C_Sharp;
convert2Aspx.Convert(this.grpAdress, @"C:\WinForms2WebForms");
请注意,在第二个示例中,Namespace
、RootName
和 FullName
属性是如何显式设置的。这样做可以让你在转换输出中操作各种与命名相关的属性。如果你只是调用 Convert
而不关心它们,那么属性值会从作为第一个参数传递给方法的表单或控件中读取。
使用示例
本文附带的演示项目会获取一个简单的 Windows Form 并将其转换为 Web Form(如本页顶部所示)和 Web Forms 用户控件。要查看转换过程,请执行以下操作:
- 在 C: 盘上创建一个名为“WinForms2WebForms”的目录。
- 使用 Internet Information Services (IIS) 管理器,创建一个指向步骤 1 中创建的文件夹的虚拟目录。将其命名为“Win2Web”或任何您喜欢的名称。
- 打开 Visual Studio .NET 并创建一个新的 C# ASP.NET Web 项目。在存储目标中输入 https:///<Name-from-Step2>/。Visual Studio .NET 将创建一个新的 Web 项目并将文件存储在 C:\WinForms2WebForms 中。
- 从项目中删除 WebForm1.aspx。
- 打开本文的演示项目并运行它。当示例窗体出现时,单击“写入到 aspx”。Web Form 页面和代码隐藏文件将写入步骤 1 中指定的目录。
- 返回 Web 项目,然后在 Visual Studio 的主菜单中选择“视图”>“刷新”。新创建的文件现在将在项目资源管理器中可见。如果它们 *不* 显示,请单击“项目”>“显示所有文件”。现在您应该可以在项目资源管理器中看到它们了。
- 右键单击每个新文件,然后将其包含到项目中。
- 将 Form1.aspx 设置为启动页后,构建并运行项目。您的浏览器将显示转换后的表单。
代码生成
转换过程包括为每个 Web Forms 页面或用户控件创建两种不同的文件类型:
- aspx/ascx 文件,以及
- 其对应的 C# 或 Visual Basic 类文件的代码隐藏文件。
让我们快速了解一下如何生成它们。
创建 aspx/ascx 文件
对于 aspx/ascx 文件,我们选择使用 System.Text.StringBuilder
类来生成代码。生成过程非常直接,包括插入常见的页面元素,如 <html>
、<head>
等,然后遍历放置在 Windows Form 上的每个控件以生成其 ASP.NET 声明,例如 <asp:TextBox id="myTextbox" runat="server" style="[...]" />
。
我们选择为此任务使用简单的 StringBuilder
的原因是 aspx/ascx 文件基本上与语言无关,C#/VB.NET 语法对它们来说并不重要。唯一需要调整的是文件顶部的 <%@ Page [...] %>
声明,其余内容主要由 HTML 和 ASP.NET 控件声明组成。
下面的代码片段摘自 Convert2Aspx
类的 ConvertControls
方法,以说明基本的转换算法。
foreach(System.Windows.Forms.Control control in rootControl.Controls)
{
if(control is System.Windows.Forms.Label)
{
webLabel = new System.Web.UI.WebControls.Label();
webLabel.ID = control.Name;
this._WebControls.Add(webLabel);
stringBuilder.Append(" <asp:Label");
this.AddProperties(control, stringBuilder);
stringBuilder.AppendFormat(">{0}</asp:Label>{1}", control.Text,
System.Environment.NewLine);
}
else if(control is System.Windows.Forms.TextBox)
{
webTextBox = new System.Web.UI.WebControls.TextBox();
webTextBox.ID = control.Name;
this._WebControls.Add(webTextBox);
stringBuilder.Append(" <asp:TextBox");
this.AddProperties(control, stringBuilder);
stringBuilder.AppendFormat(">{0}</asp:TextBox>{1}", control.Text,
System.Environment.NewLine);
}
(...)
}
该片段将 System.Windows.Forms.Label
和 System.Windows.Forms.TextBox
控件转换为它们的 ASP.NET 对应项。上面使用的 AddProperties
方法负责为 ASP.NET 控件声明分配必需的 CSS 样式指令。它看起来像这样:
private void AddProperties(System.Windows.Forms.Control control,
System.Text.StringBuilder stringBuilder)
{
stringBuilder.AppendFormat(" id=\"{0}\"", control.Name);
stringBuilder.AppendFormat(" style=\"z-index:{0}; " +
"left:{1}px; top:{2}px; font-family:'{3}'; " +
"font-size:{4}pt; position:absolute;\"",
this._ZIndex++, control.Left, control.Top,
control.Font.FontFamily.Name, (int)control.Font.Size);
stringBuilder.Append(" runat=\"server\"");
stringBuilder.AppendFormat(" Width=\"{0}\"", control.Width);
stringBuilder.AppendFormat(" Height=\"{0}\"", control.Height);
if(control is System.Windows.Forms.TextBox)
{
stringBuilder.AppendFormat(" TabIndex=\"{0}\"", control.TabIndex);
}
}
到目前为止,一切都很好。我们现在知道如何正确处理 Label
和 TextBox
控件。但如果它们不仅仅是主窗体的子控件,而是它们本身的一部分,例如,一个 System.Windows.Forms.Panel
或 System.Windows.Forms.GroupBox
?答案是 — 显而易见 — 递归。下面的代码片段再次摘自 ConvertControls
方法,并说明了如何处理 System.Windows.Forms.GroupBox
的转换。
else if(control is System.Windows.Forms.GroupBox)
{
stringBuilder.Append("<fieldset");
stringBuilder.AppendFormat(" ID=\"{0}\" runat=\"server\"", control.Name);
stringBuilder.AppendFormat(" style=\"POSITION:" +
" absolute; left: {0}px; top: {1}px; width:{2}px;
height: {3}\"", control.Left, control.Top,
control.Width, control.Height);
stringBuilder.AppendFormat(">{0}", System.Environment.NewLine);
stringBuilder.Append("<legend");
stringBuilder.AppendFormat(" style=\"Z-INDEX: {0}; " +
"color:black; font-family:'{1}'; font-size:{2}pt; width=\"",
this._ZIndex++, control.Font.FontFamily.Name,
(int)control.Font.Size);
stringBuilder.AppendFormat(">{0}</legend>{1}",
control.Text, System.Environment.NewLine);
this.ConvertControls(control, stringBuilder);
stringBuilder.AppendFormat("</fieldset>{0}",
System.Environment.NewLine);
webGroupBox = new
System.Web.UI.HtmlControls.HtmlGenericControl("fieldset");
webGroupBox.ID = control.Name;
this._WebControls.Add(webGroupBox);
}
在这里,我们利用了 HTML 中鲜为人知的 <fieldset>
标签来模拟 System.Windows.Forms.GroupBox
控件的视觉外观。您可以在 此页面顶部截图 中查看结果。
示例类 Convert2Aspx
目前仅将 System.Windows.Forms.Label
、System.Windows.Forms.TextBox
和 System.Windows.Forms.GroupBox
控件转换为它们的 ASP.NET 对应项。但是,利用本文概述的原理,可以轻松支持更多控件类型。
创建代码隐藏文件
代码隐藏文件完全用 C# 或 VB.NET 编写,因此需要仔细考虑语法和语言功能。通常,有两种常见的选项可以用来处理 C#/VB.NET 代码生成:
- 创建一个带有控件和其他参数占位符的模板,然后动态地用 Windows Forms 控件属性填充占位符。缺点:您必须为每种需要的输出语言创建一个模板并维护它,这会导致潜在的同步故障,如果您对一个模板进行了更改,却忘记了修改其他模板。
- 使用
System.CodeDom
命名空间中的类来动态创建正确语法(用于任一语言)的代码隐藏文件。使用此方法,维护不成问题,因为只有一个源文件需要维护 — 包含您源代码的文件。
由于模板的使用有限,并且随着复杂性的增加,它们会变得相当笨拙,因此我们选择使用 CodeDOM 来生成代码隐藏文件。这不是一个关于 CodeDOM 用法的文章,所以我们在这里不会过多详细介绍,但正如您将看到的,代码相当自解释。这是 Convert2Aspx
类中创建代码隐藏文件中 Page_Load
方法的摘录:
private void BuildPageLoadMethod(System.CodeDom.CodeTypeDeclaration
typeDeclaration)
{
System.CodeDom.CodeMemberMethod codeMethodPageLoad;
System.CodeDom.CodeParameterDeclarationExpression
codeParameterExpression;
// Add Page_Load method
codeMethodPageLoad = new System.CodeDom.CodeMemberMethod();
codeMethodPageLoad.Name = "Page_Load";
// Add sender parameter
codeParameterExpression = new
System.CodeDom.CodeParameterDeclarationExpression(typeof(object),
"sender");
codeMethodPageLoad.Parameters.Add(codeParameterExpression);
// Add eventargs parameter
codeParameterExpression = new
System.CodeDom.CodeParameterDeclarationExpression(
typeof(System.EventArgs), "e");
codeMethodPageLoad.Parameters.Add(codeParameterExpression);
typeDeclaration.Members.Add(codeMethodPageLoad);
}
上述语句创建了一个接受两个参数的空方法,并生成以下 C# 输出:
private void Page_Load(object sender, System.EventArgs e)
{
}
您可以在 Convert2Aspx
类中找到许多类似的方法,它们的工作方式基本相同。研究它们很快就会让您感受到如何“思考” CodeDOM。使用它的最大优点是,更改或扩展生成代码中的功能只需要在一个位置进行修改。
修订历史
- 2005 年 1 月 14 日:发布了原始文章。
- 2005 年 2 月 9 日:提交了更新,其中包括以下更改:
- 增加了对嵌套控件的支持。
- 澄清了使用示例代码的过程。
- 为文章添加了目录。
- 各种次要编辑和更正。