2011年6月3日星期五

  7.2.2 在窗体上显示绘图

7.2.2 在窗体上显示绘图

 

    绘图类似于第 4 章的示例。因为绘图需要一定的时间,我们将创建内存位图,在那儿绘制文档,然后,在窗体上显示位图,,而不是每次窗体失效时都绘制这个文档。让我们先看一下一个非常有用的函数式编程模式,我们将在这一节中使用。

 

"中间有洞"模式

 

    写代码时的一个常见情形,是执行一些初始化,然后,是函数的核心部分,最后,是一些清理工作。当你在程序的多个位置,重复类似的操作,初始化和清理没有变化,而只是核心部分不同。绘制内在位图的示例用 C# 写,可能像这样:

 

var bmp = new Bitmap(width, height)
using(var gr = Graphics.FromImage(bmp)) { 
  (...)
}

 

    这里,代码的核心部分是在代码第三行的占位符,我们将用值 gr 来绘制。问题是,只使用面向对象的概念,不能简单地把执行初始化和结束的代码,封装到一个子程序,并在做不同绘制的所有地方共享。C# 语言对于某些知名和经常使用的初始化和清理的类型,支持这种模式。using 构造完全就是这种情况。我们如何能为自己的代码模式实现类似的事情?

    在函数式编程中,解决方案是微不足道的。可以写一个高阶函数,把核心部分封装成 lambda 函数,并将作为参数值:

 

var bmp = DrawImage(width, height, gr => {
    (...)
  });

 

    从函数的角度来看,这对于使用高阶函数,是一个枯燥无味的示例,但是,我们需要执行一些初始化,接着是核心部分,然后是清理,这种情况非常普遍,所以,有一个特别的名字,它是由 Brian Hurt 在他的博客《“中间有洞“模式》[Hurt, 2007] 中首次使用。它很好地描述了这一事实,在每次使用的代码中,只有中间的部分需要用不同的功能填充。

 

    清单 7.7 显示了 F# 实现的函数,类似于侧边栏"中间有洞"模式"的 DrawImage,除了两个指定创建的位图的大小的参数之外,我们还能够指定图像边框的边距。

 

Listing 7.7 Function for drawing images (F# Interactive)

 

> let drawImage (width:int, height:int) space coreDrawingFunc =
     let bmp = new Bitmap(width, height)
     use gr = Graphics.FromImage(bmp)
     gr.Clear(Color.White)
     gr.TranslateTransform(space, space)
     coreDrawingFunc(gr)
     bmp
;;
val drawImage : int * int -> float32 -> (Graphics -> unit) –> Bitmap

 

    当我们使用这个函数来绘制图像时,绘图的核心部分是指定一个给定的函数作为最后的参数值。类型签名显示,这个函数取 Graphics 作为一个参数值,且不返回结果。在创建位图和 Graphics 对象之后,调用中间的代码。在初始化阶段,我们还调用 TranslateTransform,提供绘图的空白 。

 

    我们创建的图形对象实现了 IDisposable,所以,需要在完成绘图后进行处理。在 C# 中,我们使用知名的 using 构造。在 F# 中,由于 use 关键字,我们可以做类似的事情。在清单 7.7 中,返回结果的位图之前,自动处置图形对象。use 关键字的工作方式与 using 有点不同,我们将在第 9 章中讨论的。

    最后,我们需要在行动中看到代码的一切。现在,我们将以交互方式创建并测试窗体。清单 7.8 演示了如何绘制清单 7.5 的屏幕元素,以及把文档显示在窗体上。

 

Listing 7.8 Drawing the document using WinForms (F# Interactive)

 

> let docImage = drawImage (450, 400) 20.0f (drawElements elements)
val docImg : Bitmap

> open System.Windows.Forms
let main = new Form(Text = "Document", BackgroundImage = docImage,
                              Width = docImage.Width, Height = docImage.Height)
main.Show();;

 

    绘制位图的这一行可能要解释一下。我们调用 drawImage,它取一个函数,指定绘图的核心部分,作为函数的最后一个参数值。因为,我们已经在 drawElements 函数中实现了,可能希望我们能够将它直接作为最后一个参数值来传递。然而,drawElements 有两个参数,而 drawImage 期望的函数只有一个参数(需要绘制的图形对象)。我们使用偏函数应用去指定有 ScreenElement 值的列表。偏应用的结果是函数,取图形对象,并绘制文档,这正是我们需要的 (图 7.2)。

 

image

 

图 7.2 存储为屏幕元素的列表的示例文档,使用 drawElements 函数在窗体上绘制。

 

    我们以前表示的文档,使我们能够轻松地实现绘制,虽然必须使用的用来创建文档的代码是有点笨拙。在函数式编程中,你会经常发现不同的上下文建议不同数据结构:在某种程度上,期望的用法决定理想的表示形式。对于函数式程序,在一个程序中相同的信息,有不同的表示,并不少见。现在,我们已经有了一个合适的形式进行绘制,让我们设计一个适合结构和步骤,然后,再写一个转换函数,从一种表示到另一种表示。

没有评论:

发表评论