F#探险之旅(八):使用F#开发Windows应用程序(转)
我们身在何处?
前面写过的随笔主要关注的是F#与FP的基本概念,对UI涉及很少。我们知道,没有UI就没法与用户进行交互,所以写两篇随笔来了解一下这方面的内容。本文主要关注的是如何使用F#开发WinForm应用程序,后面还会介绍如何使用F#开发ASP.NET应用程序。
不过说真的,我对WinForm开发了解不多,所以如果有不妥之处,欢迎您的指正。
WinForm中的基础类型是System.Windows.Forms.Form类,通过它我们可以创建一个窗体,在窗体上我们可以根据需要进行 绘制。绘制的时候我们可以选择使用.NET提供的API手工“画”出一些图形,比如像素着色、直线、圆等等,也可以使用.NET里面的一些标准控件,比如 文本框、按钮等等。接下来基于Windows应用的事件驱动的特性,通过事件(Event)和事件处理函数(Event Handler)使用户可与程序进行交互。另外,如果标准控件或窗体不能满足需要,我们还可以对其进行扩展,创建用户控件和自定义控件。
下面就来看看如何实现这些过程。
分析
在使用C#开发的WinForm应用中可以看到,我们创建的新窗体类继承了System.Windows.Forms.Form类,在程序的入口函数Main中,有一行关键的代码:
C# Code
Application.Run(newMainForm());
也就是说创建Form类的一个实例,传给Application.Run方法,程序由此开始执行。使用C#开发的一大优势在于VS IDE提供的窗体设计器,这样就可以在设计时设置窗体和控件的属性,VS会为我们生成代码,从而大大提高了开发效率。
遗憾的是,VS还没有为F#提供这样的设计时支持,也就是说我们必须手工编写所有的代码,那如果界面很复杂,不得累死啊?也不尽然。首先考虑下面的简化了的三层架构示意图:
这里并不是说程序一定要分为三层,或者说三个Project,而是说从逻辑上大体可以这样来看。大部分情况下,与设计器相关的只有UI层(不要忘了 其它地方VS也可以生成代码),如果UI相当简单,完全手工编写这些代码也没什么大问题,如果UI相当复杂,就得考虑别的办法了。
F#基于.NET平台,在这一点上,它与C#、VB.NET是平等的,这一次我们又可以获益于它的互操作性(Interoperability) 了!既然C#类库可被F#调用,如果我们把窗体类放在一个类库内不就可以了吗?这样一方面我们可以利用VS的设计时支持,另一方面又可利用F#带来的好 处,岂不妙哉?退一步讲,我们也没必要非得使用F#开发UI部分,完全可以使用C#开发UI,其余部分使用F#开发,这也是不错的方法。
总结上面的分析,可以得到这样的结论:在开发WinForm应用时使用F#,如果UI非常简单(比如画出一条数学曲线或者只有两三个控件的小工具 等),我们完全可以手工编写所有代码;如果UI比较复杂,我们使用C#开发UI类库供F#调用,也可以只采用F#编写UI之外部分的代码。
下面我们来讨论下这三种方法的具体实现。
使用F#手工编写所有UI代码
前面写过的一篇使用F#绘制Mandelbrot集合就是这么做的,因为窗体上根本就没有任何控件,此时需要处理Paint事件完成窗体的绘制。这里再提供一个简单的例子,使用WebBrowser控件创建简单的浏览器:
F# Code - Simple Web Browser
#light
openSystem
openSystem.Drawing
openSystem.Windows.Forms
//Createtheprogressbar.
letstatusProgress=
newToolStripProgressBar(Size=newSize(200,16),
Style=ProgressBarStyle.Marquee,
Visible=false)
letstatus=newStatusStrip(Dock=DockStyle.Bottom)
status.Items.Add(statusProgress)|>ignore
//Createcontrols
lettoolbar=newToolStrip(Dock=DockStyle.Top)
letaddress=newToolStripTextBox(Size=newSize(400,25))
letbrowser=newWebBrowser(Dock=DockStyle.Fill)
letgo=newToolStripButton(DisplayStyle=ToolStripItemDisplayStyle.Text,Text="Go")
address.KeyPress.Add(fune->
if(e.KeyChar='r')then
browser.Url<-newUri(address.Text))
go.Click.Add(fune->browser.Url<-newUri(address.Text))
toolbar.Items.Add(newToolStripLabel("Address:"))|>ignore
toolbar.Items.Add(address)|>ignore
toolbar.Items.Add(go)|>ignore
//Browsercontroleventhandlers.
browser.Navigating.Add(fune->
statusProgress.Visible<-true)
browser.DocumentCompleted.Add(fune->
statusProgress.Visible<-false
address.Text<-browser.Url.AbsoluteUri)
letform=newForm(Text="WebBrowserSample",Size=newSize(800,600))
form.Controls.Add(toolbar)
form.Controls.Add(status)
form.Controls.Add(browser)
form.PerformLayout()
form.Show()
[<STAThread>]
Application.EnableVisualStyles()
Application.Run(form)
像这样的小工具,也没必要兴师动众,劳驾C#了。试试浏览一下博客园:
注意:看看上面例子的事件处理函数是怎么写的,比如go按钮的Click事件。我们知道C#中控件的事件处理函数一般有两个参数sender(:object)和e(:EventArgs),这里却只有一个,这是因为对于标准的.NET事件,F#会忽略第一个参数。
在F#中使用C#开发的UI类库
前段时间看到一个例子,是使用C#中获取本机的进程信息,感觉它难易适中,就用它做例子吧。在原文中,只有两个主要的文件,一是工具类 ProcessValidation.cs,一是窗体类Form1.cs,现在我们就是要把Form1分为两部分,UI设计器相关的部分放在C#类库中, 后台的代码则采用F#编写,顺便把ProcessValidation类也改为F#代码。
不过这里我按照自己的习惯给它们重新命名了,窗体是ProcessChecker,工具类是processHelper,然后在processFinder使用其中的窗体和控件,最后在Program类中定义了入口函数(FsSamples.UI中的其它文件可以略过)。
这个过程比较简单,不过有个小问题。在C#中定义的窗体类中,各个控件在默认情况下是private的,如何在processFinder中使用 呢?要想把它们暴露出来有几种方法,一是把private改为public,这样虽然简单,不过不是一个好的practice;二是把控件封装为 public的属性,这样做如果控件少还比较好办,多了也很麻烦;我这里采用的是第三种方法(来自Allen Lee的建议),即把控件的private修饰符改为protected,然后继承该窗体,这样仍有比较好的封装性,操作起来也比较简单,只要选中要修改 的控件,把它们的Modifiers属性设置为Protected就可以了。(下载示例代码)<
值得一提的是,在Application.Run方法之前,要添加一行代码:
Application.EnableVisualStyles()
这样就可以使用XP/Vista的可视化样式了。程序的运行效果为:
仅使用F#开发UI之外的代码
得益于F#与其它.NET语言良好的互操作性,我们可以在C#中开发UI部分,然后调用F#类库,这个过程跟调用C#开发的类库相比没有很大的区别,在此就不赘述了。
其它
关于WinForm开发的主题还有创建用户控件、自定义控件等,这方面已经有大量的资料了,相信使用本文的方法,开发起来也不会很难:)
小结
本文主要讨论了如何使用F#开发Windows应用程序。通过分析,确定了三种主要的方法,重点讨论了如何在F#中利用VS中的窗体设计器,这会使 WinForm程序的开发简单不少。其中的一个例子是processFinder,它可以获取当前机器上的进程和应用程序信息。<