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