Haskell has at least four toolkits for programming a graphical interface:
- wxHaskell - provides a Haskell interface to the cross-platform wxWidgets toolkit which supports Windows, OS X, and Gtk+ on GNU/Linux, among others.
- Gtk2Hs - provides a Haskell interface to the GTK+ library
- hoc (documentation at sourceforge) - provides a Haskell to Objective-C binding which allows users to access to the Cocoa library on MacOS X
- qtHaskell - provides a set of Haskell bindings for the Qt Widget Library
In this tutorial, we will focus on the wxHaskell toolkit.
Getting and running wxHaskell
or the wxHaskell download page and follow the installation instructions provided on the wxHaskell download page. Don't forget to register wxHaskell with GHC, or else it won't run (automatically registered with Cabal). To compile source.hs (which happens to use wxHaskell code), open a command line and type:
ghc -package wx source.hs -o bin
Code for GHCi is similar:
ghci -package wx
You can then load the files from within the GHCi interface. To test if everything works, go to $wxHaskellDir/samples/wx ($wxHaskellDir is the directory where you installed it) and load (or compile) HelloWorld.hs. It should show a window with title "Hello World!", a menu bar with File and About, and a status bar at the bottom, that says "Welcome to wxHaskell".
If it doesn't work, you might try to copy the contents of the $wxHaskellDir/lib directory to the ghc install directory.
Shortcut for Debian and Ubuntu
If your operating system is Debian or Ubuntu, you can simply run these commands from the terminal:
sudo apt-get install g++ sudo apt-get install libglu-dev sudo apt-get install libwxgtk2.8-dev
Here's the basic Haskell "Hello World" program:
module Main where main :: IO () main = putStr "Hello World!"
It will compile just fine, but how do we actually do GUI work with this? First, you must import the wxHaskell library
Graphics.UI.WXCore has some more stuff, but we won't need that now.
To start a GUI, use
start gui. In this case,
gui is the name of a function which we'll use to build the interface. It must have an IO type. Let's see what we have:
module Main where import Graphics.UI.WX main :: IO () main = start gui gui :: IO () gui = do --GUI stuff
To make a frame, we use
frame which has the type
[Prop (Frame ())] -> IO (Frame ()). It takes a list of "frame properties" and returns the corresponding frame. We'll look deeper into properties later, but a property is typically a combination of an attribute and a value. What we're interested in now is the title. This is in the
text attribute and has type
(Textual w) => Attr w String. The most important thing here, is that it's a
String attribute. Here's how we code it:
gui :: IO () gui = do frame [text := "Hello World!"]
(:=) takes an attribute and a value and combines both into a property. Note that
frame returns an
IO (Frame ()). You can change the type of
IO (Frame ()), but it might be better just to add
return (). Now we have our own GUI consisting of a frame with title "Hello World!". Its source:
module Main where import Graphics.UI.WX main :: IO () main = start gui gui :: IO () gui = do frame [text := "Hello World!"] return ()
The result should look like the screenshot. (It might look slightly different on Linux or MacOS X, on which wxhaskell also runs)
||From here on, its good practice to keep a browser window or tab open with the wxHaskell documentation. It's also available in $wxHaskellDir/doc/index.html.|
A text label
A simple frame doesn't do much. In this section, we're going to add some more elements. Let's start simple with a label. wxHaskell has a
label, but that's a layout thing. We won't be doing layout until next section. What we're looking for is a
staticText. It's in
staticText function takes a
Window as argument along with a list of properties. Do we have a window? Yup! Look at
Graphics.UI.WX.Frame. There, we see that a
Frame is merely a type-synonym of a special sort of window. We'll change the code in
gui so it looks like this:
gui :: IO () gui = do f <- frame [text := "Hello World!"] staticText f [text := "Hello StaticText!"] return ()
text is an attribute of a
staticText object, so this works. Try it!
Now for a little more interaction. A button. We're not going to add functionality to it until the section about events, but already something visible will happen when you click on it.
button is a control, just like
staticText. Look it up in
Again, we need a window and a list of properties. We'll use the frame again.
text is also an attribute of a button:
gui :: IO () gui = do f <- frame [text := "Hello World!"] staticText f [text := "Hello StaticText!"] button f [text := "Hello Button!"] return ()
Load it into GHCi (or compile it with GHC) and... hey!? What's that? The button's been covered up by the label! We're going to fix that next.
The reason that the label and the button overlap, is that we haven't set a layout for our frame yet. Layouts are created using the functions found in the documentation of
Graphics.UI.WXCore.Layout. Note that you don't have to import
Graphics.UI.WXCore to use layouts.
The documentation says we can turn a member of the widget class into a layout by using the
widget function. Also, windows are a member of the widget class. But, wait a minute... we only have one window, and that's the frame! Nope... we have more, look at
Graphics.UI.WX.Controls and click on any occurrence of the word Control. You'll be taken to
Graphics.UI.WXCore.WxcClassTypes, and it is there we see that a Control is also a type synonym of a special type of window. We'll need to change the code a bit, but here it is.
gui :: IO () gui = do f <- frame [text := "Hello World!"] st <- staticText f [text := "Hello StaticText!"] b <- button f [text := "Hello Button!"] return ()
Now we can use
widget st and
widget b to create a layout of the staticText and the button.
layout is an attribute of the frame, so we'll set it here:
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 function will be covered in the section below about attributes. Try the code, what's wrong? This only displays the staticText, not the button. We need a way to combine the two. We will use layout combinators for that.
column look nice. They take an integer and a list of layouts. We can easily make a list of layouts of the button and the staticText. The integer is the spacing between the elements of the list. Let's try something:
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 ()
Play around with the integer and see what happens. Also, change
column. Try to change the order of the elements in the list to get a feeling of how it works. For fun, try to add
widget b several more times in the list. What happens?
Here are a few exercises to spark your imagination. Remember to use the documentation!
After having completed the exercises, the end result should look like this:
You could have used different spacing for
column or have the options of the radiobox displayed horizontally.
After all this, you might be wondering: "Where did that
set function suddenly come from?" and "How would I know if
text is an attribute of something?". Both answers lie in the attribute system of wxHaskell.
Setting and modifying attributes
In a wxHaskell program, you can set the properties of the widgets in two ways:
- during creation:
f <- frame [ text := "Hello World!" ]
- using the
set f [ layout := widget st ]
set function takes two arguments: something of type
w along with properties of
w. In wxHaskell, these will be the widgets and the properties of these widgets. Some properties can only be set during creation, such as the
alignment of a
textEntry, but you can set most others in any IO-function in your program — as long as you have a reference to it (the
set f [stuff]).
Apart from setting properties, you can also get them. This is done with the
get function. Here's a silly example:
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!" ]
Look at the type signature of
w -> Attr w a -> IO a.
text is a
String attribute, so we have an
IO String which we can bind to
ftext. The last line edits the text of the frame. Yep, destructive updates are possible in wxHaskell. We can overwrite the properties using
(:=) anytime with
set. This inspires us to write a modify function:
modify :: w -> Attr w a -> (a -> a) -> IO () modify w attr f = do val <- get w attr set w [ attr := f val ]
First it gets the value, then it sets it again after applying the function. Surely we're not the first one to think of that...
Look at this operator:
(:~). You can use it in
set because it takes an attribute and a function. The result is a property in which the original value is modified by the function. That means we can write:
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!" ]
This is a great place to use anonymous functions with the lambda-notation.
There are two more operators we can use to set or modify properties:
(::~). They do almost the same as
(:~) except a function of type
w -> orig is expected, where
w is the widget type, and
orig is the original "value" type (
a in case of
a -> a in case of
(:~)). We won't be using them now, as we've only encountered attributes of non-IO types, and the widget needed in the function is generally only useful in IO-blocks.
How to find attributes
Now the second question. Where do we go to determine that
text is an attribute of all those things? Go to the documentation…
Let's see what attributes a button has: Go to
Graphics.UI.WX.Controls. Click the link that says "Button". You'll see that a
Button is a type synonym of a special kind of
Control, and a list of functions that can be used to create a button. After each function, there's a list of "Instances". For the normal
button function, we see Commanding -- Textual, Literate, Dimensions, Colored, Visible, Child, Able, Tipped, Identity, Styled, Reactive, Paint. That's the list of classes of which a button is an instance. Read through the Classes and types chapter. It means that there are some class-specific functions available for the button.
Textual, for example, adds the
appendText functions. If a widget is an instance of the
Textual class, it means that it has a
Note that while
StaticText hasn't got a list of instances, it's still a
Control, and that's a synonym for some kind of
Window. When looking at the
Textual class, it says that
Window is an instance of it. That's an error on the side of the documentation!
Let's take a look at the attributes of a frame. They can be found in
Graphics.UI.WX.Frame. Another error in the documentation here: It says
HasImage. This was true in an older version of wxHaskell. It should say
Pictured. Apart from that, we have
Able and a few more. We're already seen
Form. Anything that is an instance of
Form has a
Dimensions adds (among others) the
clientSize attribute. It's an attribute of the
Size type, which can be made with
sz. Please note that the
layout attribute can also change the size. If you want to use
clientSize you should set it after the
Colored adds the
Able adds the Boolean
enabled attribute. This can be used to enable or disable certain form elements, which is often displayed as a greyed-out option.
There are lots of other attributes, read through the documentation for each class.
There are a few classes that deserve special attention. They are the
Reactive class and the
Commanding class. As you can see in the documentation of these classes, they don't add attributes (of the form
Attr w a), but events. The
Commanding class adds the
command event. We'll use a button to demonstrate event handling.
Here's a simple GUI with a button and a staticText:
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 ] ]
We want to change the staticText when you press the button. We'll need the
b <- button f [ text := "Click me!" , on command := --stuff ]
The type of
Event w a -> Attr w a.
command is of type
Event w (IO ()), so we need an IO-function. This function is called the Event handler. Here's what we get:
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 ] ]
Insert text about event filters here