To make this more concrete, consider a very simple example. We want to open a window which contains just one button, which should be labelled Press me!. Whenever the user obligingly presses the button, it should change its label to a different random string.
The static part of this program is fairly simple. There will be an initialization function (which opens the window and such), and we want to build a button with the inscription Press me. The following code achieves this:
This introduces three important concepts in HTK:
Figure 1: A simple example.
Fig. 1 shows the result of the two operations. To specify the dynamic behaviour, we need two ingredients: firstly, we need to connect the external event of the user clicking the button with an element of the data type Event, and secondly, we need to set up the program such that it reacts to the occurence of this event by changing the button's label.
Setting up external events to produce an Event a is called
binding. When we bind an external event, we specify the
external action that we wish to bind (e.g. this button being clicked,
mouse movement over this window, or right button being clicked with
control-key being pressed and user doing a handstand whilst whistling
`Auld Lang Syne'). The general case is the bind function
which we will see below, but for the simple case of a button being
clicked, we can use the function
The composed event is the click of the
button, followed by changing the label. The following code achieves
the desired effect:
Figure 2: After clicking.
Here, randomRIO (replicate 5 ('a','z')) generates a list of five actions of type IO Char, and mapM evaluates them to a random string of length 5. The next line sets the label to this random string; how exactly this works will be explained below. Fig. 2 shows the result.
Another function requires an explanation here: forever :: Event a-> Event a takes an event, and returns this event composed with itself. Thus, synchronising on this event will synchronise on it once, then wait for this event to occur again. Had we left out the forever, our program would just wait for one button press, change the text of the button once and go on its merry way (in this case, terminate). With forever, we have it waiting for the next button press after the first one occurs.
As mentioned above, spawnEvent takes an event, and creates a concurrent thread which synchronises on this event. This is not strictly necessary here, since we do nothing else, but it is good practice to leave handling of events to threads different from the main thread. Exactly how many threads one creates -- one for each button, or just one for the whole GUI -- is a matter of taste and judgement.
At the end of the program, the main thread has to wait for the GUI to finish; if it just exited, the whole program would terminate. We do this by calling finishHTk. This also handles the case that the user closes the window by external means (e.g. the close button provided by the window manager).
Note that our program is non-terminating. If the window manager does not provide means to close a running application, we will have to use kill or xkill to stop it. This is clearly unsatisfactory, so we will now provide a second button to close the window regularly.