新功能
根据随新的 .NET Framework SDK 提供的、标题为“API Changes from v1.0 to v1.1”的文档,在 System.Windows.Forms 命名空间中,增加了 48 个 API,删除 14 个,并且没有破坏性改动。这条新闻一开始可能会使您为新增的 48 个功能而高兴,同时为失去 14 个旧功能而伤心,因为删去的东西将不能再使用。同时您可能也想知道,删除 14 个功能如何才能没有破坏性改动。既然没有破坏性改动就意味着所有现有的 Windows 窗体代码都应该继续运行而没有任何改动,所有已删除的功能都有确实做过改动的相应新增内容,例如,在 AxHost 类中对 ProcessDialogKey 方法的声明所做的更改,如下所示:
class AxHost {
// .NET 1.0 declaration
protected virtual bool ProcessDialogKey(Keys keyData);
// .NET 1.1 declaration
protected override bool ProcessDialogKey(Keys keyData);
}
这一更改显示 AxHost 不再提供新的虚方法,但重写现有方法意味着 ProcessDialogKey 被声明为虚方法以便进一步继承。大部分新增内容都属于该类别,使得对 Windows 窗体命名空间本身的更改看起来有些无聊。实际上,对 Windows 窗体命名空间只新增三个有价值的功能(并对 System.Drawing 命名空间新增一个有意思的功能):
• System.Windows.Forms.FolderBrowserDialog 类
• System.Windows.Forms.Application.EnableVisualStyles 方法
• System.Windows.Forms.CurrencyManager.MetaDataChanged 事件
• System.Drawing.Printing.PrintDocument.OriginAtMargins 属性
如果这就是全部内容,我就不需要用整篇文章来描述新功能了。还有很多内容,包括命名空间增加、支持在非托管客户端中宿主托管控件、支持托管 C++ 和 Visual J# 的 Windows 窗体设计器、集成访问 Compact Framework,以及新的移动代码安全设置。然而,在介绍所有这些新功能之前,我们将首先讨论如何处理多版本的公共语言运行库 (CLR)。
转移到 1.1
对于任何新版本,我都希望 Microsoft 能够增加我需要的新功能,同时又不破坏我仍需要的任何功能。与 Win32 DLLs(这里开发人员认定不会有任何版本控制问题)和 COM(这里他们认为根本不需要版本控制)不同,.NET Framework 为多版本、同时安装的组件版本和运行库本身提供全面、明确的支持。实际上,如果您运行一种 Windows 版本,它正在运行 Windows Update,则您很可能运行了 1.0.3705.288 版的 CLR,它是 .NET Framework 1.0 SP2。安装 .NET Framework 1.1 之后,您的 CLR 版本会增加到 1.1.4322.510 版,它是 .NET Framework 1.1 最终 Beta 版。然而,安装 1.1 版并不替换任何安装在您机器上的现有版本。相反,两个版本会在独立的目录中和平共处。
在运行时加载的 CLR 的版本取决于所构建的 EXE 程序集的版本。所以,如果您构建了一个针对 .NET Framework 1.0 的 Windows 窗体应用程序,即使机器上安装了 .NET Framework 1.1(或更高版本),都会加载 1.0 版的 CLR 来宿主您的应用程序。然而,如果在机器中启动 1.0 版的应用程序,但没有安装 1.0 版,则会自动加载 CLR 1.1 版。这样可以防止每个 Windows 窗体 1.0 应用程序都需要 .config 文件来表明可以在 1.1 下加载 1.0 应用程序。如果您情愿关闭该行为,则可以创建自己的 .config 文件并关闭该自动升级功能:
<!-- support only .NET 1.0 Framework --
<configuration
<startup
<requiredRuntime version="v1.0.3705"/
<supportedRuntime version="v1.0.3705"/
</startup
</configuration
在这种情况下,如果您的 1.0 版应用程序运行在没有 1.0 版 CLR 的机器上,则不管是否安装了 1.1 版,用户都会接到表明需要特定版本 .NET Framework µÄ错误消息。
最后,如果您想支持 1.0 版,但只要 1.1 版可用就选择 1.1 版(例如,因为它执行得更好),则请看以下代码:
<!-- prefer .NET Framework 1.1, but support .NET Framework 1.0 --
<configuration
<startup
<requiredRuntime version="v1.0.3705"/
<!-- order of these keys determines preference --
<supportedRuntime version="v1.1.4322"/
<supportedRuntime version="v1.0.3705"/
</startup
</configuration
不幸的是,事情并非到此为止。由于每个程序集都伴随着一连串所需程序集(包含它们的版本号),所以在 .NET Framework 1.1 版本下构建一个应用程序就需要所有程序集都有 1.1 版。在只有 1.0 版程序集的机器上运行曾在 1.1 版下构建的 1.0 版应用程序会失败,因为它不能找到合适版本的参考程序集。所以,如果想使在 1.1 版本下构建的应用程序能够真正运行在它引用的早期版本的程序集上,就需要在 .config 文件中有每个程序集项,如图 1 所示。
这些配置设置指明:当在 1.0 版 .NET Framework 下运行并加载任何版本的 System.Windows.Forms 时,都会加载 1.0 版的 System.Windows.Forms,即使代码是针对 1.1 版构建的。虽然这个便捷功能允许您使用最新的 1.1 版工具,但要设置 .config 文件以使功能生效则是件棘手的事情。幸运的是,Visual Studio .NET 2003 提供了一个额外的项目设置,可通过“项目 | 属性 | 公共属性 | 常规 | 支持的运行库”获得。这个设置通过“.NET Framework 版本”对话框公开三个运行库选项,分别为:Microsoft .NET Framework 1.1 版(默认)、1.0 版和二者。
默认情况下,在 Visual Studio .NET 2003 下编译的任何应用程序都会只面向 1.1 版的 CLR。如果您想让自己只构建 1.0 版的应用程序,则可以选中 1.0 版选项。最后一个选项是支持两种版本的 Framework,但首选 1.1。所有这三个选项都会在您的项目中添加一个 app.config 文件,具备适当的设置,包括如何映射框架程序集的详尽列表,从而免除您的麻烦。根据 CLR 的需要,在项目构建时会将生成的 app.config 文件复制到输出目录,并重命名为 projectName.exe.config。
当您停下来阅读“.NET Framework 版本”对话框的文本时,您会注意到它使用一些很显眼的语言来劝阻您在使用 Visual Studio .NET 2003 时,不要选择支持 1.0 版。问题是那个可恶的中间选项说只支持 1.0 版。虽然 Visual Studio .NET 2003 会更新 .config 文件以便恰好只需要 1.0 版的 Framework,但是当您使用在 1.0 版下不可用的类型或成员时,IntelliSense 和编译器都不会告知您它不可用。只有在运行时运行的代码与 1.0 功能列表发生冲突,然后用到所缺类型或方法的方法被实际调用时,您才能知道它不可用。如果您决定使用 Visual Studio .NET 2003 来构建面向 1.0 版的应用程序,您就需要进行一些完整的代码覆盖范围测试以确保它真正能在 1.0 版下运行。
如果您只面向 1.0 版并强烈希望使用 Visual Studio .NET 2003,但又不能放弃编译时检查和准确的 IntelliSense,那么这是个可行的办法,只管使用。不幸的是,问题并非如此简单,因为您需要做的并不仅仅是在“.NET Framework 版本”对话框中敲击几下。首先,您需要将“Project | Properties | Configuration Properties | Advanced | Do not use Mscorlib”设置为“true”(它默认为“false”),这样在编译时才不会引入 1.1 版的 mscorlib.dll。其次,您需要删除 Visual Studio .NET 2003 加入项目的所有程序集引用,因为它会默认从 1.1 版 Framework 目录中获取。最后,您需要从 1.0 版 Framework 目录添加项目引用,方法是使用“添加引用”对话框中的“浏览器”按钮来手动选择程序集(包括 mscorlib.dll 和 system.dll)。由于编译器(和 IntelliSense)会引用 1.0 版的程序集,所以这些手动步骤可以让您继续使用 Visual Studio .NET 2003,尽管只是 1.0 版的程序集。
另一方面,如果您同时面向 1.0 版和 1.1 版,则您很有可能想利用 1.1 版的新功能。当您这样做时,您需要通过 System.Environment.Version 仔细检查运行库的版本号,并且只在任何 1.0 版代码都不会调用的方法中使用 1.1 版的功能。即使在 1.0 版本下不会调用 1.1 的代码,但在第一次调用方法时,实时 (JIT) 编译器也要求所有类型和方法都存在,少了哪一个都会引发异常。图 2 提供的示例说明了隔离需要 1.1 版的代码的一种方法。请注意,只有运行库版本支持、并且包含特定于 1.1 版代码的方法,这段代码才会调用它。
命名空间增加
在讨论利用只在 1.1 版中可用的 Windows 窗体功能的同时,我们还将回顾 CurrencyManager.MetaDataChanged 事件、PrintDocument.OriginAtMargins 属性、FolderBrowserDialog 类和 Application.EnableVisualStyles 方法。如果您正在使用数据绑定,并且除了新增或更新的行和列以外,您还对底层元数据更改有兴趣,则 MetaDataChanged 事件十分有用。如果您实际想让打印的文档页边距与纸张的边缘而不是打印机的可打印区域的边缘对齐,则 OriginAtMargins 属性十分有用(前者是直觉位置,而后者只是 .NET Framework 1.0 底下的一个选项,并且在 1.1 版下仍是默认选项)。FolderBrowserDialog 类是 Win32 ShBrowseForFolder 函数和您期望的工作方式的包装:
void browseButton_Click(object sender, EventArgs e) {
FolderBrowserDialog dlg = new FolderBrowserDialog();
dlg.Description = "Please choose a folder