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 ] ]
在此处插入关于事件过滤器的文本