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 小部件庫提供一組 Haskell 繫結
在本教程中,我們將重點介紹 wxHaskell 工具包。
獲取和執行 wxHaskell
[edit | edit source]要安裝 wxHaskell,請檢視適用於您的版本的說明:GNU/Linux Mac Windows
或者訪問 wxHaskell 下載頁面 並按照 wxHaskell 下載頁面提供的安裝說明進行操作。不要忘記在 GHC 中註冊 wxHaskell,否則它將無法執行(使用 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 函式。如果一個 widget 是 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 ] ]
在此處插入有關事件過濾器的文字