分類  >  編程 >

第八章 用戶界面(1)

tags:    時間:2014-05-04 18:39:38
第八章 用戶界面(一)

第八章 用戶界面(一)

 

在這一章,我們將學習程序員最重要的任務之一:把像素放到屏幕上藝術。在 F# 中,所要做的就是調用庫和 API,在這方面有很多選擇,因為有了 .NET 平台,就更加多樣。第一個選擇是要確定是想創建桌面應用程序,它運行在本地,用一系列窗口和控制項把信息顯示給用戶;還是想創建網站應用程序,應用程序的界面用 HTML 定義,然後,通過瀏覽器渲染。

用 .NET 創建桌面應用程序,可以有四種選擇:WinForms、Windows Presentation Foundation (WPF,[ 原文這裡有筆誤 ])、GTK# 和 DirectX。在這一章,我們將學習 WinForms、WPF 和GTK#,但不包括 DirectX。WinForms、WPF 和GTK# 對窗口和控制項有基本相同的隱喻(metaphors)[ 不懂說的是什麼 ]。WinForms 最古老也最簡單,我們已經遇到過幾個WinForms 的示例了;WPF 是一個新的庫,比WinForms 稍微複雜一些,但是,也更一致,提供了更多的功能,包括令人印象深刻的3D 圖形;GTK# 提供了比其他兩個庫更好的平台。因為有了 Mono,現在WinForms 可以運行在所有平台上,Mono 團隊建議 GTK# 運行在非 Windows 平台。DirectX 的目標是想得到更快 3D 圖形的遊戲製造商;WPF [ 同誤 ] 提供了更簡單的產生 3D 圖形的方法,因此,本書不討論DirectX。

要創建網站應用程序,可以使用 ASP.NET 框架,它提供了簡單的方法創建基於伺服器的、動態 HTML 的應用程序。ASP.NET 提供了靈活的方式產生 HTML,以響應來自瀏覽器的 HTML 請求。近年來,網站應用程序已經有了極大地進步,因此,ASP.NET 平台的進步也毫不為奇。ASP.NET 中增加了ASP.NET AJAX、ASP.NET MVC 和 Silverlight,作為競爭對手的平台Mono Rail,也有了很大的進步。然而,本書並不討論這些事情,因為我們的目標只是為了展示用 F# 生成 HTML 的基礎知識。

本章將要學習的每個主題,已經寫在全書中了,因此,這裡只關注一些基本知識。無論選擇哪種技術創建用戶界面,都需要在之前花點時間學習如何使用庫。

 

 

介紹WinForm

 

WinForms 是包含在 System.Drawing.dll和 System.Forms.Windows.dll 的類,編譯所有的 WinForms 都需要添加對它們的引用。這個庫是基於 System.Windows.Forms.Form 類的,它代表顯示給用戶的窗口。當創建這個類的實例時,本質上就可創建了一個新的窗口。然後,必須創建一個事件循環(event loop),它可以保證用戶響應與窗口的交互;通過調用 System.Windows.Application.Run 方法,並把創建的窗體對象傳遞進去即可完成。通過設置屬性、調用方法,可以控制窗體的外觀。看下面的例子:

 

open System.Drawing

openSystem.Windows.Forms

 

//create a new form

let form = new Form(BackColor = Color.Purple, Text = "IntroducingWinForms")

 

//show the form

Application.Run(form)

 

[

1、需要引用 System.Drawing,System.Windows.Forms;

2、交互運行用 form.Show()

以下同

]

 

這個例子不能運行在 F# 的交互模式 fsi 下,是因為不能從 fsi 啟動事件循環。為了能在 fsi 下運行,有一個簡單的方法,調用窗體的 Show 方法,或設置窗體的 Visible 為 true。看下面的代碼:

 

open System.Drawing

open System.Windows.Forms

 

let form = newForm(BackColor=Color.Purple,Text="IntroducingWinForms",Visible=true);;

 

不論哪種方法,都可以動態地和窗體進行交互。例如:

 

> form.Text <- "Dynamic!!!";;

 

使用 WinForms,有兩種可選的方案:自己畫窗體,或者使用控制項構建。下面我們首先看一下如何自己畫窗體,然後再嘗試用控制項。

 

 

畫 WinForm

 

自己畫窗體,就要負責把像素表示到屏幕上。這種低級方法對許多 F# 用戶仍有吸引力,因為他們相信許多 WinForms 庫中控制項不可能非常適合顯示一些數據結構,或函數和演算法的結果。然而,要注意這種方法會耗費大量時間,通常更多地花在尋找抽象表現邏輯的圖形庫上。

為了畫 WinForm,需要將事件處理程序附加到窗體或控制項的 Paint 事件上。就是說,每次 Windows 請求繪製窗體時,函數都會調用。傳遞到此函數的事件參數有一個屬性 Graphics,它包含一個類的實例,也叫 Graphics,這個類的方法(如 DrawLine)可以在窗體上繪製像素。下面的例子顯示一個簡單的窗體,在上面畫了一個餅:

 

let form =

  // create a new form setting the minimum size

  let temp = new Form(MinimumSize = new Size(96, 96))

 

  // repaint the form when it is resize

  temp.Resize.Add (fun _ -> temp.Invalidate())

 

  // a brush to provide the shapes color

  let brush = new SolidBrush(Color.Red)

  temp.Paint.Add (fun e ->

    // calculate the width and height of the shape

    let width, height = temp.Width - 64, temp.Height - 64

    // draw the required shape

    e.Graphics.FillPie (brush, 32, 16, width,height, 0, 290))

    // return the form to the top level

  temp

 

Application.Run(form)

// form.Show()

 

可以在圖 8-1 中看到這個窗體,即代碼的運行結果。

圖 8-1. 包含餅圖的 WinForm

 

這個圖像的大小和窗體相關,因此,無論什麼時候改變窗體大小時,必須告訴窗體必須重畫自己,這是通過把事件處理函數附加到 Resize 事件實現的。在這個函數中,調用窗體的 Invalidate 方法,告訴窗體需要重畫自己。

現在,我們再看一個更完整的例子。假設我們想創建一個窗體,顯示 Tree 類型,在下面的代碼中定義,顯示結果如圖 8-2。

 

//The tree type

type 'a Tree =

|Node of 'a Tree * 'a Tree

|Leaf of 'a

 

//The definition of the tree

let tree =

  Node(

    Node(

      Leaf "one",

      Node(Leaf "two", Leaf"three")),

    Node(

      Node(Leaf "four", Leaf"five"),

      Leaf "six"))

 

圖 8-2. 顯示樹型結構的 WinForm

 

可以用清單8-1 的代碼畫出這個樹。

清單 8-1 畫樹

 

open System

open System.Drawing

openSystem.Windows.Forms

 

//The tree type

type 'a Tree =

|Node of 'a Tree * 'a Tree

|Leaf of 'a

 

//The definition of the tee

let tree =

  Node(

    Node(

      Leaf "one",

      Node(Leaf "two", Leaf"three")),

    Node(

      Node(Leaf "four", Leaf"five"),

      Leaf "six"))

 

// Afunction for finding the maximum depth of a tree

let getDepth t =

  let rec getDepthInner t d =

    match t with

    | Node (l, r) ->

      max

        (getDepthInner l d + 1.0F)

        (getDepthInner r d + 1.0F)

    | Leaf x -> d

  getDepthInner t 0.0F

 

//Constants required for drawing the form

let brush = new SolidBrush(Color.Black)

let pen = new Pen(Color.Black)

let font = new Font(FontFamily.GenericSerif, 8.0F)

 

// auseful function for calculating the maximum number

//of nodes at any given depth

let raise2ToPower (x : float32) =

  Convert.ToSingle(Math.Pow(2.0,Convert.ToDouble(x)))

 

let drawTree (g : Graphics) t =

  // constants that relate to the size and position

  // of the tree

  let center = g.ClipBounds.Width / 2.0F

  let maxWidth = 32.0F * raise2ToPower (getDepth t)

  // function for drawing a leaf node

  let drawLeaf (x : float32) (y : float32) v =

    let value = sprintf "%A" v

    let l = g.MeasureString(value, font)

    g.DrawString(value, font, brush, x -(l.Width / 2.0F), y)

  // draw a connector between the nodes when necessary

  let connectNodes (x : float32) y p =

    match p with

    | Some(px, py) -> g.DrawLine(pen, px,py, x, y)

    | None -> ()

  // the main function to walk the tree structure drawingthe

  // nodes as we go

  let rec drawTreeInner t d w p =

    let x = center - (maxWidth * w)

    let y = d * 32.0F

    connectNodes x y p

    match t with

    | Node (l, r) ->

      g.FillPie(brush, x - 3.0F, y - 3.0F, 7.0F,7.0F, 0.0F, 360.0F)

      let d = (d + 1.0F)

      drawTreeInner l d (w + (1.0F / d))(Some(x, y))

      drawTreeInner r d (w - (1.0F / d))(Some(x, y))

    | Leaf v -> drawLeaf x y v

  drawTreeInner t 0.0F 0.0F None

 

//create the form object

let form =

  let temp = new Form(WindowState = FormWindowState.Maximized)

  temp.Resize.Add(fun _ -> temp.Invalidate())

  temp.Paint.Add

    (fun e ->

      e.Graphics.Clip <-

        new Region(new Rectangle(0, 0, temp.Width,temp.Height))

      drawTree e.Graphics tree)

  temp

 

form.Show()

//Application.Run(form)

 

注意如何定義函數 drawTree,它有兩個參數:Graphics 對象和需要畫的樹:

 

let drawTree (g : Graphics) t =

 

這是畫 WinForm 的通常模式,創建一個函數,參數為 Graphics 對象和需要畫的數據類型,這樣,函數可以很容易被其他窗體和控制項重用。

為了實現 drawTree,首先計算出這個函數的兩個常量center 和 maxWidth。不能在函數 drawTree 的外邊看到這些常量,這樣在函數 drawTree 的內部使用,而不必須作為參數來傳遞:

 

// constants that relate to the size andposition

// of the tree

let center = g.ClipBounds.Width / 2.0F

let maxWidth = 32.0F * raise2ToPower(getDepth t)

 

實現函數的其餘部分,是通過把任務分解成許多內部函數。定義 drawLeaf 去畫葉結點:

 

// function for drawing a leaf node

let drawLeaf (x : float32) (y : float32) v=

  letvalue = any_to_string v

  letl = g.MeasureString(value, font)

  g.DrawString(value,font, brush, x - (l.Width / 2.0F), y)

 

用 connectNodes 在適當的地方畫結點之間的連接線:

 

// draw a connector between the nodes whennecessary

let connectNodes (x : float32) y p =

  matchp with

  |Some(px, py) -> g.DrawLine(pen, px, py, x, y)

  |None -> ()

 

最後,定義一個遞歸函數 drawTreeInner,實際完成遍歷這個 Tree 類型,並畫出來:

 

// the main function to walk the treestructure drawing the

// nodes as we go

let rec drawTreeInner t d w p =

  letx = center - (maxWidth * w)

  lety = d * 32.0F

  connectNodesx y p

  matcht with

  |Node (l, r) ->

    g.FillPie(brush,x - 3.0F, y - 3.0F, 7.0F, 7.0F, 0.0F, 360.0F)

    letd = (d + 1.0F)

    drawTreeInnerl d (w + (1.0F / d)) (Some(x, y))

    drawTreeInnerr d (w - (1.0F / d)) (Some(x, y))

  |Leaf v -> drawLeaf x y v

 

該函數使用參數保存遞歸調用期間的值,可以知道,因為這是內部函數,不會被外邊程序初始化成不正確的值而濫用,在外邊根本看不到它。隱藏參數保存遞歸調用期間的臨時值,是函數編程的另一個常用模式。

在某種程度上,這個畫樹的函數還是令人滿意的,僅用了簡短的 86 行 F# 代碼,就提供了樹不錯的層次視圖。然而,這種方法只能用於小規模的圖形。當畫更為複雜的圖形時,代碼數就會迅速膨脹,規則出所有的幾何圖形是非常耗時的。為了有效地控制複雜性,F# 可以使用控制項,我們在下一章討論。

為了更好地在 WinForm 上畫圖,應該了解 System.Drawing.dll 中的 System.Drawing 命名空間。還應該注意兩個方面,第一,需要學習如何使用 Graphics 對象,特別是重載前綴為 Draw 或 Fill 的方法。表 8-1 中匯總了Graphics 對象的方法。

 

表 8-1 System.Drawing.Graphics 對象中的重要方法

方法名

描述

DrawArc

畫橢圓的一部分

DrawBezier

繪製貝塞爾曲線,這條曲線由兩個端點和兩個控制曲線角度的自由點表示

DrawCurve

畫曲線,由點數組定義

DrawClosedCurve

畫封閉曲線,由點數組定義

DrawEllipse

畫橢圓,由矩形或點的矩形集合表示

DrawPie

畫橢圓的一部分,用矩形和兩條射線表示,說明起止角度

DrawLine

畫線,用兩點

DrawLines

畫一組線,用點數組

DrawPolygon

畫多邊形,是一組封閉直線集合,用點數組

DrawRectangle

畫矩形,用座標和寬、高表示

DrawRectangles

畫一組矩形,用矩形數組表示

FillClosedCurve

畫實心的封閉曲線,用點數組表示

FillEllipse

畫實心橢圓,由矩形、矩形點集合表示

FillPie

畫實心橢圓的一部分,用矩形和兩條射線表示,說明起止角度

FillPolygon

畫實心多邊形,由封閉線條構成,用點數組表示

FillRectangle

畫實心矩形,用座標和寬、高表示

FillRectangles

畫一組實心矩形,用矩形數組表示

DrawIcon

畫圖標,由 System.Drawing.Icon 類型指定

DrawImage

畫圖,由 System.Drawing.Image 類型指定

DrawImageUnscaled

畫不能縮放的圖,由 System.Drawing.Image 類型指定

DrawString

畫字元串

MeasureString

測量字元串,因此可以計算出放在圖形中的位置

DrawPath

畫 System.Drawing.Drawing2D.GraphicsPath 表示的輪廓。 這個類可以向其中添加前邊描述的幾何結構,比如:圓弧、矩形、橢圓和多邊形,以節省每次重新計算的時間(這對於想畫一些複雜但完全固定的圖形非常有用)

FillPath

同上面的 DrawPath 功能,但是實心圖形

 

第二個需要關注的部分是與 System.Drawing.Graphics 對象密切相關的 System.Drawing的命名空間,需要學習如何使用Graphics 對象的方法創建 Icon、Image、Pen 和 Brush 對象。表 8-2 展示了如何通過各自的構造函數創建這些對象。

 

表 8-2 使用System.Drawing.Graphics 對象創建對象

代碼段

描述

Color.FromArgb(33, 44, 55)

用 紅、綠、藍組件創建顏色

Color.FromKnownColor(KnownColor.Crimson)

用枚舉 KnownColor 的成員顏色

Color.FromName("HotPink")

用名字,以字元串形式創建顏色

new Font(FontFamily.GenericSerif, 8.0f)

創建一個新的 generic serif 字體、8  點高

Image.FromFile("myimage.jpg")

用文件創建圖片

Image.FromStream(File.OpenRead ("myimage.gif"))

用流創建圖片

new Icon("myicon.ico")

用文件創建圖標

new Icon(File.OpenRead("myicon.ico"))

用流創建圖標

new Pen(Color.FromArgb(33, 44, 55))

創建有顏色的筆,可以畫線

new Pen(SystemColors.Control, 2.0f)

用顏色、2 個像素寬度創建筆,可以畫線

new SolidBrush(Color.FromName("Black"))

創建實心筆刷,能夠畫實心圖形

new TexturedBrush(Image.FromFile ("myimage.jpg"))

用圖片創建紋理筆刷,用圖片充填圖形

 

如果你喜歡使用標準對象,可以使用 System.Drawing 命名空間中的幾個類,它預定義了一些對象,包括:Brushes、Pens、SystemBrushes、SystemColors、SystemFonts、SystemIcons、SystemPens。下面簡單例子就說明了如何使用這些預定義的對象:

 

open System.Drawing

 

let myPen = Pens.Aquamarine

let myFont = SystemFonts.DefaultFont

推薦閱讀文章

Bookmark the permalink ,來源:互聯網