Haskell/GUI
Haskell 至少有四種工具包用於程式設計圖形介面
- wxHaskell - 為跨平臺 wxWidgets 工具包提供 Haskell 介面,該工具包支援 Windows、OS X 和 GNU/Linux 上的 Gtk+ 等。
- Gtk2Hs - 為 GTK+ 庫提供 Haskell 介面
- hoc(文件在 sourceforge 上) - 為 Objective-C 提供 Haskell 繫結,允許使用者訪問 MacOS X 上的 Cocoa 庫
- qtHaskell - 為 Qt Widget 庫提供一組 Haskell 繫結
在本教程中,我們將重點關注 wxHaskell 工具包。
獲取和執行 wxHaskell
[edit | edit source]要安裝 wxHaskell,請在以下位置查詢您的版本說明:GNU/Linux Mac Windows
或者wxHaskell 下載頁面,並按照 wxHaskell 下載頁面上提供的安裝說明進行操作。不要忘記將 wxHaskell 註冊到 GHC,否則它將無法執行(使用 Cabal 自動註冊)。要編譯 source.hs(它恰好使用 wxHaskell 程式碼),請開啟命令列並鍵入
ghc -package wx source.hs -o bin
GHCi 的程式碼類似
ghci -package wx
然後,您可以從 GHCi 介面中載入這些檔案。要測試一切是否正常,請轉到 $wxHaskellDir/samples/wx($wxHaskellDir 是您安裝它的目錄)並載入(或編譯)HelloWorld.hs。它應該顯示一個標題為“Hello World!”的視窗,一個帶有“檔案”和“關於”的選單欄,以及底部顯示“歡迎使用 wxHaskell”的狀態列。
如果它不起作用,您可能嘗試將 $wxHaskellDir/lib 目錄的內容複製到 ghc 安裝目錄。
Debian 和 Ubuntu 的快捷方式
[edit | edit source]如果您的作業系統是 Debian 或 Ubuntu,您可以簡單地從終端執行以下命令
sudo apt-get install g++ sudo apt-get install libglu-dev sudo apt-get install libwxgtk2.8-dev
Hello World
[edit | edit source]這是基本的 Haskell“Hello World”程式
module Main where
main :: IO ()
main = putStr "Hello World!"
它將順利編譯,但我們如何實際使用它進行 GUI 工作?首先,您必須匯入 wxHaskell 庫 Graphics.UI.WX。Graphics.UI.WXCore 有一些其他內容,但我們現在不需要它們。
要啟動 GUI,請使用 start gui。在這種情況下,gui 是一個函式的名稱,我們將使用它來構建介面。它必須具有 IO 型別。讓我們看看我們有什麼
module Main where
import Graphics.UI.WX
main :: IO ()
main = start gui
gui :: IO ()
gui = do
--GUI stuff
要製作一個框架,我們使用 frame,它具有型別 [Prop (Frame ())] -> IO (Frame ())。它接受一個“框架屬性”列表,並返回相應的框架。我們將在以後更深入地研究屬性,但屬性通常是屬性和值的組合。我們現在感興趣的是標題。它在 text 屬性中,並且具有型別 (Textual w) => Attr w String。這裡最重要的是它是一個 String 屬性。以下是我們的編碼方式
gui :: IO (Frame ())
gui = do
frame [text := "Hello World!"]
運算子 (:=) 接受一個屬性和一個值,並將兩者組合成一個屬性。請注意,frame 返回一個 IO (Frame ())。start 函式具有型別 IO a -> IO ()。您可以將 gui 的型別更改為 IO (Frame ()),但可能最好只新增 return ()。現在我們擁有自己的 GUI,它包含一個標題為“Hello World!”的框架。它的原始碼
module Main where
import Graphics.UI.WX
main :: IO ()
main = start gui
gui :: IO ()
gui = do
frame [text := "Hello World!"]
return ()
結果應類似於螢幕截圖。(它在 Linux 或 MacOS X 上可能看起來略有不同,wxhaskell 也在這些平臺上執行)
控制元件
[edit | edit source]從這裡開始,最好開啟一個帶有 wxHaskell 文件 的瀏覽器視窗或選項卡。它也位於 $wxHaskellDir/doc/index.html 中。 |
文字標籤
[edit | edit source]一個簡單的框架沒有太多作用。在本節中,我們將新增更多元素。讓我們從一個標籤開始。wxHaskell 有一個 label,但它是一個佈局的東西。我們將在下一節進行佈局。我們正在尋找的是一個 staticText。它在 Graphics.UI.WX.Controls 中。staticText 函式接受一個 Window 作為引數,以及一個屬性列表。我們有視窗嗎?是的!請檢視 Graphics.UI.WX.Frame。在那裡,我們看到 Frame 僅僅是一種特殊型別視窗的類型別名。我們將更改 gui 中的程式碼,使其看起來像這樣

gui :: IO ()
gui = do
f <- frame [text := "Hello World!"]
staticText f [text := "Hello StaticText!"]
return ()
同樣,text 是 staticText 物件的一個屬性,因此它有效。試試看!
按鈕
[edit | edit source]現在讓我們進行更多互動。一個按鈕。我們不會在關於事件的章節之前為它新增功能,但當您單擊它時,您會看到一些可見的東西。
一個 button 是一個控制元件,就像 staticText 一樣。在 Graphics.UI.WX.Controls 中查詢它。
同樣,我們需要一個視窗和一個屬性列表。我們將再次使用框架。text 也是按鈕的一個屬性

gui :: IO ()
gui = do
f <- frame [text := "Hello World!"]
staticText f [text := "Hello StaticText!"]
button f [text := "Hello Button!"]
return ()
將其載入到 GHCi 中(或使用 GHC 編譯它),然後... 嘿?!這是什麼?按鈕被標籤遮住了!我們將在下一節解決這個問題。
佈局
[edit | edit source]標籤和按鈕重疊的原因是我們還沒有為框架設定佈局。佈局是使用 Graphics.UI.WXCore.Layout 文件中找到的函式建立的。請注意,您無需匯入 Graphics.UI.WXCore 即可使用佈局。
文件說我們可以使用 `widget` 函式將小部件類的一個成員變成一個佈局。另外,視窗也是小部件類的一個成員。但是等等… 我們只有一個視窗,那就是框架!不… 我們還有更多,看看 `Graphics.UI.WX.Controls`,然後點選 `Control` 這個單詞的所有出現位置。你會被帶到 `Graphics.UI.WXCore.WxcClassTypes`,在那裡我們可以看到,`Control` 也是一種特殊型別視窗的型別同義詞。我們需要稍微修改一下程式碼,但這裡就是它。
gui :: IO ()
gui = do
f <- frame [text := "Hello World!"]
st <- staticText f [text := "Hello StaticText!"]
b <- button f [text := "Hello Button!"]
return ()
現在我們可以使用 `widget st` 和 `widget b` 建立一個靜態文字和按鈕的佈局。`layout` 是框架的一個屬性,所以我們將在此處設定它。

gui :: IO ()
gui = do
f <- frame [text := "Hello World!"]
st <- staticText f [text := "Hello StaticText!"]
b <- button f [text := "Hello Button!"]
set f [layout := widget st]
return ()
`set` 函式將在下面關於屬性的部分進行介紹。嘗試一下程式碼,有什麼問題嗎?這隻會顯示靜態文字,而不是按鈕。我們需要一種方法將兩者組合在一起。我們將為此使用 *佈局組合器*。`row` 和 `column` 看起來不錯。它們接收一個整數和一個佈局列表。我們可以很容易地製作一個按鈕和靜態文字的佈局列表。整數是列表元素之間的間距。讓我們試一試

gui :: IO ()
gui = do
f <- frame [text := "Hello World!"]
st <- staticText f [text := "Hello StaticText!"]
b <- button f [text := "Hello Button!"]
set f [layout :=
row 0 [widget st, widget b]
]
return ()
嘗試改變整數並看看會發生什麼。另外,將 `row` 更改為 `column`。嘗試更改列表中元素的順序,以瞭解它的工作原理。為了好玩,嘗試在列表中多次新增 `widget b`。會發生什麼?
這裡有一些練習可以激發你的想象力。記得使用文件!
| 練習 |
|---|
|
完成練習後,最終結果應該看起來像這樣

你可以為 `row` 和 `column` 使用不同的間距,或者讓單選按鈕的選項水平顯示。
屬性
[edit | edit source]經過這一切,你可能想知道:“那個 `set` 函式從哪裡冒出來的?”以及“我如何知道 `text` 是否是某個東西的屬性?”這兩個答案都存在於 wxHaskell 的屬性系統中。
設定和修改屬性
[edit | edit source]在 wxHaskell 程式中,你可以透過兩種方式設定小部件的屬性。
- 在建立時:`f <- frame [ text := "Hello World!" ]`
- 使用 `set` 函式:`set f [ layout := widget st ]`
`set` 函式接收兩個引數:一個型別為 `w` 的東西以及 `w` 的屬性。在 wxHaskell 中,它們將是小部件以及這些小部件的屬性。一些屬性只能在建立時設定,例如 `textEntry` 的 `alignment`,但你可以將大多數其他屬性設定在程式中的任何 IO 函式中 - 只要你有對它的引用(`set f [stuff]` 中的 `f`)。
除了設定屬性外,你還可以獲取它們。這可以透過 `get` 函式來完成。這是一個愚蠢的例子。
gui :: IO ()
gui = do
f <- frame [ text := "Hello World!" ]
st <- staticText f []
ftext <- get f text
set st [ text := ftext]
set f [ text := ftext ++ " And hello again!" ]
看看 `get` 的型別簽名。它是 `w -> Attr w a -> IO a`。`text` 是一個 `String` 屬性,所以我們有一個 `IO String`,可以將其繫結到 `ftext`。最後一行編輯了框架的文字。是的,wxHaskell 中允許進行破壞性更新。我們可以使用 `(:=)` 隨時使用 `set` 覆蓋屬性。這啟發了我們編寫一個修改函式。
modify :: w -> Attr w a -> (a -> a) -> IO ()
modify w attr f = do
val <- get w attr
set w [ attr := f val ]
首先獲取值,然後在應用函式後再次設定它。我們肯定不是第一個想到這一點的人…
看看這個運算子:`(:~)`. 你可以在 `set` 中使用它,因為它接收一個屬性和一個函式。結果是一個屬性,其中原始值被函式修改。這意味著我們可以編寫
gui :: IO ()
gui = do
f <- frame [ text := "Hello World!" ]
st <- staticText f []
ftext <- get f text
set st [ text := ftext ]
set f [ text :~ ++ " And hello again!" ]
這是一個使用 lambda 表示式的匿名函式的好地方。
我們還可以使用另外兩個運算子來設定或修改屬性:`(::=)` 和 `(::~)`. 它們與 `(:=)` 和 `(:~) `幾乎相同,只是需要一個型別為 `w -> orig` 的函式,其中 `w` 是小部件型別,而 `orig` 是原始“值”型別(`(:=)` 情況下為 `a`,`(:~) `情況下為 `a -> a`)。我們現在不會使用它們,因為我們只遇到了非 IO 型別的屬性,並且函式中需要的那個小部件通常只在 IO 塊中才有用。
如何查詢屬性
[edit | edit source]現在第二個問題。我們去哪裡確定 `text` 是所有這些東西的一個屬性?去文件…
讓我們看看按鈕有什麼屬性:轉到 Graphics.UI.WX.Controls。點選標有 "Button" 的連結。你會看到 `Button` 是一個特殊型別的 `Control` 的型別同義詞,以及一個可用於建立按鈕的函式列表。在每個函式之後,都有一個“例項”列表。對於普通的 `button` 函式,我們看到 *Commanding -- Textual, Literate, Dimensions, Colored, Visible, Child, Able, Tipped, Identity, Styled, Reactive, Paint*。這就是按鈕是其例項的類的列表。請閱讀 類和型別 章節。這意味著按鈕可以使用一些特定於類的函式。例如,`Textual` 添加了 `text` 和 `appendText` 函式。如果一個小部件是 `Textual` 類的例項,這意味著它有一個 `text` 屬性!
請注意,雖然 `StaticText` 沒有例項列表,但它仍然是 `Control`,而 `Control` 是某種 `Window` 的同義詞。當檢視 `Textual` 類時,它說 `Window` 是它的例項。這是文件方面的一個錯誤!
讓我們看看框架的屬性。它們可以在 `Graphics.UI.WX.Frame` 中找到。這裡文件中另一個錯誤:它說 `Frame` 例項化了 `HasImage`。這在 wxHaskell 的一箇舊版本中是正確的。它應該說是 `Pictured`。除此之外,我們還有 `Form`、`Textual`、`Dimensions`、`Colored`、`Able` 以及其他一些。我們已經看到了 `Textual` 和 `Form`。任何是 `Form` 例項的東西都具有 `layout` 屬性。
`Dimensions` 添加了(除其他外)`clientSize` 屬性。它是一個 `Size` 型別的屬性,可以用 `sz` 建立。請注意,`layout` 屬性也可以改變大小。如果你想使用 `clientSize`,你應該在 `layout` 之後設定它。
`Colored` 添加了 `color` 和 `bgcolor` 屬性。
`Able` 添加了布林值 `enabled` 屬性。這可以用來啟用或停用某些表單元素,這通常顯示為一個灰色的選項。
還有很多其他屬性,請閱讀每個類的文件。
事件
[edit | edit source]有一些類值得特別注意。它們是 `Reactive` 類和 `Commanding` 類。如你在這些類的文件中看到的,它們沒有新增屬性(`Attr w a` 的形式),而是添加了 *事件*。`Commanding` 類添加了 `command` 事件。我們將使用一個按鈕來演示事件處理。
這裡有一個簡單的 GUI,帶有一個按鈕和一個靜態文字。

gui :: IO ()
gui = do
f <- frame [ text := "Event Handling" ]
st <- staticText f [ text := "You haven\'t clicked the button yet." ]
b <- button f [ text := "Click me!" ]
set f [ layout := column 25 [ widget st, widget b ] ]
我們想在按下按鈕時更改靜態文字。我們需要 `on` 函式。
b <- button f [ text := "Click me!"
, on command := --stuff
]
`on` 的型別:`Event w a -> Attr w a`。`command` 的型別為 `Event w (IO ())`,所以我們需要一個 IO 函式。這個函式被稱為 *事件處理程式*。下面是我們得到的結果
gui :: IO ()
gui = do
f <- frame [ text := "Event Handling" ]
st <- staticText f [ text := "You haven\'t clicked the button yet." ]
b <- button f [ text := "Click me!"
, on command := set st [ text := "You have clicked the button!" ]
]
set f [ layout := column 25 [ widget st, widget b ] ]
在此處插入關於事件過濾器的文字