跳轉到內容

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.WXGraphics.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]

文字標籤

[edit | edit source]

一個簡單的框架做不了太多事情。在本節中,我們將新增更多元素。讓我們從一個標籤開始。wxHaskell 有一個 label,但這是一個佈局元素。我們將在下一節之前不做佈局。我們要找的是一個 staticText。它在 Graphics.UI.WX.Controls 中。staticText 函式接受一個 Window 作為引數,以及一個屬性列表。我們有視窗嗎?是的!看看 Graphics.UI.WX.Frame。在那裡,我們看到 Frame 僅僅是一種特殊型別的視窗的類型別名。我們將更改 gui 中的程式碼,使其看起來像這樣

Hello StaticText!(winXP)
gui :: IO ()
gui = do
  f <- frame [text := "Hello World!"]
  staticText f [text := "Hello StaticText!"]
  return ()

同樣,textstaticText 物件的一個屬性,因此它可以正常工作。試試看!

按鈕

[edit | edit source]

現在來進行一些更互動的操作。一個按鈕。我們將在“事件”部分之前不新增功能,但當你點選它時,它已經會發生一些可見的變化。

一個 button 是一個控制元件,就像 staticText 一樣。在 Graphics.UI.WX.Controls 中查詢它。

同樣,我們需要一個視窗和一個屬性列表。我們將再次使用框架。text 也是按鈕的一個屬性

重疊的按鈕和 StaticText(winXP)
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 stwidget b 來建立靜態文字和按鈕的佈局。layout 是框架的一個屬性,所以我們將在此處設定它。

帶有佈局的靜態文字 (winXP)
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 函式將在下一節關於屬性的介紹中介紹。嘗試一下程式碼,有什麼問題嗎?這隻會顯示靜態文字,而不是按鈕。我們需要一種方法將兩者組合起來。我們將為此使用佈局組合器rowcolumn 看起來不錯。它們接受一個整數和一個佈局列表。我們可以輕鬆地製作一個包含按鈕和靜態文字佈局的列表。整數是列表元素之間的間距。讓我們試一試。

行佈局 (winXP)
間距為 25 的列布局 (winXP)
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。會發生什麼?

以下是一些練習,可以激發您的想象力。請記住使用文件!


練習
  1. 新增一個複選框控制元件。它目前不需要執行任何操作,只需確保在使用行佈局時,它出現在靜態文字和按鈕旁邊,或者在使用列布局時出現在它們下方。text 也是複選框的一個屬性。
  2. 請注意,rowcolumn 接受一個佈局列表,並且它們本身也會生成一個佈局。利用這一事實,使您的複選框出現在靜態文字和按鈕的左側,靜態文字和按鈕位於一個列中。
  3. 你能弄清楚單選按鈕控制元件是如何工作的嗎?採用上一練習的佈局,在複選框、靜態文字和按鈕下方新增一個帶有兩個(或多個)選項的單選按鈕。使用文件!
  4. 使用 boxed 組合器在四個控制元件周圍建立一個好看的邊框,並在靜態文字和按鈕周圍再建立一個邊框。(注意:boxed 組合器可能在 MacOS X 上不起作用 - 您可能會遇到無法互動的小部件。這很可能是 wxhaskell 中的一個錯誤。


完成練習後,最終結果應如下所示

練習答案

您可以使用不同的間距來設定 rowcolumn,或者使單選按鈕的選項水平顯示。

屬性

[edit | edit source]

在完成所有這些操作後,您可能想知道:“set 函式是從哪裡突然出現的?” 以及“我該如何知道 text 是否是某個事物的屬性?”。這兩個問題的答案都可以在 wxHaskell 的屬性系統中找到。

設定和修改屬性

[edit | edit source]

在 wxHaskell 程式中,您可以透過兩種方式設定小部件的屬性

  1. 在建立期間:f <- frame [ text := "Hello World!" ]
  2. 使用 set 函式:set f [ layout := widget st ]

set 函式接受兩個引數:型別為 w 的內容以及 w 的屬性。在 wxHaskell 中,它們將是小部件和這些小部件的屬性。某些屬性只能在建立期間設定,例如 textEntryalignment,但您可以在程式中的任何 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 atext 是一個 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 添加了 textappendText 函式。如果一個 widget 是 Textual 類的例項,則意味著它具有 text 屬性!

請注意,雖然 StaticText 沒有例項列表,但它仍然是 Control,而 Control 是某種 Window 的同義詞。在檢視 Textual 類時,它指出 Window 是它的一個例項。這是文件方面的一個錯誤!

讓我們看看框架的屬性。它們可以在 Graphics.UI.WX.Frame 中找到。這裡還有一個文件錯誤:它指出 Frame 例項化了 HasImage。這在 wxHaskell 的舊版本中是正確的。它應該寫 Pictured。除此之外,我們還有 FormTextualDimensionsColoredAble 以及更多。我們已經看到了 TextualForm。任何是 Form 例項的事物都有一個 layout 屬性。

Dimensions 添加了(除其他屬性之外)clientSize 屬性。它是一個 Size 型別的屬性,可以使用 sz 建立。請注意,layout 屬性也可以更改大小。如果您想使用 clientSize,則應在 layout 之後設定它。

Colored 添加了 colorbgcolor 屬性。

Able 添加了布林值 enabled 屬性。這可以用來啟用或停用某些表單元素,這通常顯示為灰顯選項。

還有很多其他屬性,請閱讀每個類的文件。

事件

[edit | edit source]

有一些類值得特別注意。它們是 Reactive 類和 Commanding 類。正如您在這些類的文件中看到的那樣,它們不會新增屬性(Attr w a 的形式),而是新增事件Commanding 類添加了 command 事件。我們將使用一個按鈕來演示事件處理。

這裡有一個簡單的 GUI,帶有一個按鈕和一個靜態文字

之前 (winXP)
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 acommand 的型別為 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 ] ]

在此處插入有關事件過濾器的文字

華夏公益教科書