跳转到内容

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

在此处插入有关事件过滤器的文本

华夏公益教科书