4.4.1 创建用户界面
这一章中,我们使用 Windows Forms,它在很多方面更简单,但从 F# 中使用其他技术不应该成为你的问题。在 Windows Forms 中的用户界面使用组件构建 (如Form, Button, or PictureBox),因此,我们要首先要编写生成用户界面控件的代码。此任务可以通过使用图形设计器进行简化,但我们的应用程序很简单,所以,我们就手工编写代码。在一些 UI 框架(包括 WPF)中,控件的结构,可以用基于 XML 的文件描述,但在 Windows Forms中,我们将构建适当的类,通过指定其属性来配置。
在开始之前,我们需要配置在 Visual Studio 中的项目。默认情况下,F# 项目不包含对所需 .NET 程序集的引用,因此,我们需要若要添加 System.Windows.Forms 和 System.Drawing 的引用。我们可以这样做,在解决方案资源管理器中,使用添加引用选项。另外,当应用程序启动时,我们不想显示控制台窗口。可以打开项目属性,从输出类型下拉列表中选择 Windows 应用程序选项。配置项目之后, 我们可以编写应用程序的第一部分,如清单 4.6 所示。
Listing 4.6 Building the user interface (F#)
open System
open System.Drawing
open System.Windows.Forms
let mainForm = new Form(Width = 620, Height = 450, Text = "Pie Chart")
let menu = new ToolStrip()
let btnOpen = new ToolStripButton("Open")
let btnSave = new ToolStripButton("Save", Enabled = false)
ignore(menu.Items.Add(btnOpen))
ignore(menu.Items.Add(btnSave))
let boxChart =
new PictureBox
(BackColor = Color.White, Dock = DockStyle.Fill,
SizeMode = PictureBoxSizeMode.CenterImage)
mainForm.Controls.Add(menu)
mainForm.Controls.Add(boxChart)
// TODO: Drawing of the chart & user interface interactions
[<STAThread>]
do
Application.Run(mainForm)
清单首先打开 NET 命名空间,其中包含了我们的程序中要使用的类。接下来,我们开始创建表示用户界面的控件。开始构建主窗口 (也称为 窗体)。我们将使用 F# 语法,可以在初始化期间直接指定对象的属性,这使得代码更短,也隐藏了代码中的副作用。在内部,代码首先使用构造函数创建对象,然后,设置指定对象的属性,还是使用此语法,但我们可以将创建这个对象视为单个操作。当创建窗体时,我们使用无参数的构造函数,但是也可以给构造函数指定参数值。在后面的代码中,当我们创建 btnSave 时,可以中看到这一点,其构造函数取一个字符串作为参数值。创建对象的类似语法现在 C# 3.0 中也是可用的,.NET 平台上有一段有趣的历史(详细信息,请参阅边栏"在 F#、C# 3.0 和 Cω 中构建类")。
当将工具栏按钮添加到菜单项的集合,然后调用 Add 方法,该方法返回已添加的项的索引。在 C# 中,可以调用此方法,并忽略返回值,而 F# 更严格。在函数编程中,返回值是更重要的,因此,忽略它们通常就是错误。出于此原因,当我们忽略返回值时,F# 编译器会报警。修改代码很容易,我们可以把对这个调用包装在 ignore 函数的调用中。该函数取任意值作为参数值,并返回 unit(表示没有返回值),则编译器将停止报警。
清单继续构建菜单和 PictureBox 控件,我们将用来显示饼图。这一次,我们不使用 F# Interactive ,所以,在清单中有一个占位符,标记一个将来添加代码的点,用于绘制图表并把这个绘图功能与用户界面连接起来。
清单 4.6 的最后一部分是一个标准的代码块,来运行 Windows Forms 应用程序。它始于 COM 技术,线程模型的规范,是在 Windows Forms 内部使用的。指定使用标准的 .NET 属性值(STAThreadAttribute),因此,在 .NET 引用中,可以发现更多有关它的信息。在 C# 中,我们将把此属性放在 Main 方法之前,但在 F# 的源程序中,可以包含在任何地方执行的代码。因为,我们需要应用此属性,使用一个 do 块,将需要在应用程序启动时执行的代码组织在一起。
在 F#、C# 3.0 和 Cω 中构建类
我们已经提到过一些 GUI 框架,使用 XML 来指定控件如何构建。这是一种常见的方法,因为构建对象、设置它们的属性,类似于构建 XML 节点、设置它的属性。这种相似性是研究人员使用一种语言
Cω 的动机,[Meijer, Schulte, and Bierman, 2003] 微软研究院于 2003 年,在以后,它激发了许多功能,现在 C# 3.0 中存在的。在 Cω 中,我们可以编写构建 ToolStripButton 控制代码:
ToolStripButton btn = <ToolStripButton>
<Text>Save</Text>
<Enabled>True</Enabled>
<Image>{saveIco}</Image>
</ToolStripButton>
在 Cω 中,直接在语言中集成了 XML 语法。嵌套在 ToolStripButton 节点中的元素指定了对象的属性,语法使用大括号,使我们能够在类似 XML 代码中嵌入通常非 XML 的表达式。以这种方式构造对象的易用性可能激发 XAML 中的设计人员,这是一种基于 XML 的语言,在 WPF 中用于描述用户界面。在语言方面,激发了 C# 3.0 功能,称为对象初始值器(object initializers):
var btn = new ToolStripButton("Save"){ Enabled = false, Image = saveIco };
它不再使用基于 XML 的语法,但构造对象和指定其属性的一般想法在本质上是相同的。我们还可以使用此语法指定构造函数的参数,因为,在大括号在分别指定这些属性。清单 4.6 显示了 F# 中的代码,有相同的功能:
let btn = new ToolStripButton("Save", Enabled = false, Image = saveIco)
与 C# 3.0 的唯一区别是,在 F# 中,我们直接在构造函数的调用中指定属性。构造函数的参数值,是指定对象属性的一组键-值对。
类的参数化构建的另一种方法,不仅可以是任何普通的方法调用,而且可以使用命名参数值。关键的不同在于参数名是构造函数或方法声明的一部分。命名的参数也可以用于初始化不可变的类,因为,在这个类创建之后,它们不依赖属性设置。此功能可用于 F#,可以在 F# 文档中找到更多的信息。在 C# 中,命名的参数值在 4.0 版中被引入,语法类似于 F# 中的属性的规范。但是,要记住,意义有很大的不同, 这一点很重要。
到目前为止,我们已经实现了应用程序的骨架,但还不能做任何事。至少,它不能处理我们的数据。在下一节中,我们要填写代码中的缺少部分,去绘制的图表,将其显示在现有的 PictureBox 控件 boxChart 中。