OCaml comes with two graphical interface packages: a lowlevel graphics
interface, discussed in the
O'Reilly Objective Caml Book;
and the higher level Tk graphics interface. (For those of you who
are really interested, LablGtk exists as well, as does a raw X library
for OCaml.)
Tk is a widget
toolkit that features buttons, text boxes, entries, lists,
menus, toplevel windows, etc. If you already know a little
about Tk, either from working with the original Tcl/Tk or
from working with some other language binding to Tk, that
will help here. (There exist Tk bindings for Perl, Python,
Pike, Ruby, etc. It's a popular toolkit.)
OCaml's Tk bindings comes in two flavors: the old style
bindings, referred to as OCamlTk; and the new style bindings
with more recent versions (at least 3.03 and on), referred to
as LablTk. We will be discussing LablTk in this document
(and if you're doing a project for this semester and need to
use Tk, you will be using LablTk). LablTk introduces some
interesting OCaml language extensions: labels, optional
arguments, and variant types. I group these all under
'Label Extensions'. (OCaml has provisions for extending
its own syntax on the fly. You can find out more from the
OCaml Manual.)
First, we will talk about these language extensions used in
LablTk. Then we will go over some of the key concepts in working
with any Tk kit. Secondly, we will go through a few examples.
In the final section we will include a link to a tarball of
LablTk examples you can go through yourselves.
Variant types
are like ML datatypes, except
that they can be used without a preceding type
declaration. In other words, all you have to do is
mention them and they exist.
Variant tags, just like normal type constructors,
must always be capitalized, but they must additionally
be prefixed by a backquote:
let one = `END
You only need to know how to use variant types to
work with LablTk.
Labels,
or labeled arguments, allow us to
consistently label arguments when we define and
when we use functions. This means we can use
labeled arguments in any order we wish (though
any unlabeled arguments must appear in the
correct order). Functions can have both labeled
and unlabeled arguments. Labels also do interesting
things to the type checking system, as you can
imagine.
Here is the type of a function with a mix of
labeled and unlabeled arguments:
StdLabels.String.sub;;
-: string -> pos:int -> len:int -> string = <fun>
Here, the function sub expects three arguments and returns
a final string: the first is anonymous, and the next two are called
pos and len . You can call this function
in two different ways to take "Hell" out of "Hello":
String.sub "Hello" ~pos:0 ~len:4
or
String.sub "Hello" ~len:4 ~pos:0
Note that the position of the anonymous string argument demands
that we put that argument first; but String.sub could
just have easily defined the anonymous argument to go last instead.
Labeled arguments that look like pos:int in the type of
the function are required. However, labeled arguments with default
values are optional arguments.
The type of a function with
optional arguments may look something like this:
:- ?pos:int -> length:int -> string -> string
Here, pos is an optional argument.
As you've probably already figured out, you don't have to supply
optional arguments.
For more information on label extensions, refer to
Appendix B
of the OCaml Manual.
A widget is a GUI element, such as a button or a text entry, or
even an application window. Tk is a library of such widgets. Tk has
been ported to almost every operating system, including all Unix-type,
Windows-type, and Mac-type systems; thus Tk gives you the ability
to create very portable graphical interfaces for your applications.
Every program that uses Tk starts out with a root window, often
called either 'top' or 'main' in many applications. The root will be the
parent or grandparent or great-grandparent etc. (you get the idea) of all
the other widgets you create later, except for toplevel windows.
When a widget P is the immediate parent of another widget
w, and w is not a window, then w will be
drawn inside of P. In order for w to actually appear
inside P, w has to be packed. This just means
that w is placed inside P using a very simple widget
layout algorithm. (More complicated ones, such as grid, are
available; you can read about this and other things in any Tk guide.)
Widgets have various attributes that you can set to change their
appearance and behaviour. There are some attributes that are
common to every widget, such as background (background
color); and some that are common to a group of widgets, like
text (labels, buttons, etc). Attributes can be
configured on the fly. In OCaml, attributes are associated
with optional labeled arguments.
You can bind actions (in the case of OCaml, actions are
functions) to widgets and/or events. Some widgets have
'command' attributes that are called for a specific widget event;
for instance, a button has a command that is called whenever it is
pressed. Events occur when certain things happen, like gaining/losing
focus, key presses/releases, mouse button presses/releases, etc.
Running a LablTk program requires setting the paths to necessary
libraries and so on that are not usually set.
On Unix, for this reason, the labltk command is provided.
In all respects it acts just like ocaml, but with the proper
library include paths set, Tk stuff loaded, and so on.
On Windows, the labltk top-level interpreter does not exist.
However, you may run the program file this way:
ocaml -I +labltk labltk.cma ...
Since LablTk isn't on the usual include/library path of the ocamlc
or ocamlopt compilers, we need to add it with the following flag:
ocamlc -I +labltk labltk.cma ...
OCaml comes with its own module browser, called ocamlbrowser. In
its recent versions, ocamlbrowser looks an awful lot like the SmallTalk
browser. If you're working from home, you either need a ssh-tunneled
X-connection to use ocamlbrowser in the labs, or -- if you're running
Windows and don't have an X server for it -- use your own installation
of OCaml.
Unfortunately, labltk doesn't automatically load into ocamlbrowser.
You'll need to edit the path in this manner:
- Pull down the Modules menu and select the Path
editor... item.
- A new window should come up called "Edit Load
Path". There is a Load path list on the right of the window.
Double-click on the first entry to change the Directories list on
the left of the window to list the directories in OCaml's system library
directory.
- You should see the Path text entry along the top
change to something like blah/blah/blah/ocaml-3.04/lib/ocaml.
- In the Directories list, select the labltk item with
ONE click (we don't want to change into that directory, just select it).
- Press the Add to Path button below the Directories
list.
- Now the labltk
directory has been added to the search path. Click Okay to
close the dialog.
Now the Tk modules have all been loaded into ocamlbrowser. For
instance, we can
browse the Button module.
Double-clicking on one of the Tk.blah types, such as
Tk.color, will cross-reference you to the type definition.
We are going to write a very simple graphical application that takes
two numbers and displays their greatest common divisor. Hopefully,
this will illustrate the basic stuff you need to know to get your
project done. The code is here, and a walkthrough
follows:
open Tk
Here we open the Tk module for convenience, so we don't have to
keep typing Tk.blah all the time.
let rec gcd n m =
if n = 0 then
m
else if n >= m then gcd (n - m) m
else gcd m n;;
This is just the code for computing the greatest common divisor of
two integers. It's usually a good idea to put all your non-GUI
code somewhere apart from your GUI code.
let compute entry_n entry_m label_r =
fun () ->
compute actually returns a callback function whose
environment contains the values of the arguments given to compute.
We'll see this in action later on.
let n = int_of_string(Entry.get entry_n) in
let m = int_of_string(Entry.get entry_m) in
Entry.get returns what's in a text entry as a string.
let result = gcd n m in
Label.configure label_r ~text:(string_of_int result);;
We can use the configure methods of various widget modules
to change widget attributes on the fly.
let root = openTk() in
This initializes the Tk library and our root window.
let top_frame = Frame.create root in
Here we create a Frame widget whose parent is
root. This is typically how all Tk widgets are created --
with the first argument being the parent of the widget we're about
to create.
let gcd_begin = Label.create top_frame ~text:"gcd(" in
Now we've created a label widget. Labels just hold non-editable text;
you can change what a label shows by configuring it on the fly with
Label.configure.
let n = Entry.create top_frame ~width:4 ~relief:`Sunken in
let m = Entry.create top_frame ~width:4 ~relief:`Sunken in
Now we create two entries for our two numbers. Note that we see the
first use of variant types here with `Sunken.
let gcd_end = Label.create top_frame ~text:")=" in
let result = Label.create top_frame in
result is going to be used to display the gcd of what's in
n and m.
let button = Button.create root
~text: "Compute"
~command: (compute n m result) in
Now we call compute to "load up" a closure with our two entries and
the result display label, and bind the resulting closure to
button.
pack [top_frame] ~side:`Top ~expand:true ~fill:`Both ;
Now we begin a lot of imperative stuff. First we pack our
top_frame. top_frame's parent was root,
so we're telling it to display itself on the `Top side of
the root window. We also tell the pack layout algorithm that
top_frame is allowed expand when the window is
resized, filling both the X and Y directions (across and
vertical).
Note that pack takes a list of widgets to pack.
pack [gcd_begin] ~side:`Left ~anchor:`E ;
pack [n; m] ~side: `Left ~expand:true ~fill:`X ;
pack [gcd_end; result] ~side: `Left ~anchor: `W ;
pack [button] ~side: `Bottom ~expand:true ~fill:`X ;
In this way, we can pack multiple widgets at a time, as long as
they're the same type.
mainLoop();;
We call this particular function to start the event processing loop.
Now we can run labltk on the .ml file and ask it for the
gcd of 12 and 34.
Here are even more source examples for you to play with and look at,
usually fairly heavily commented. These are taken from a somewhat
hidden directory in the OCaml 3.04 distribution:
Braindead Does-nothing Hello App
A Calculator
A Clock
Eyes that Follow You
Demo of Almost All Tk Widgets
Tetris, which needs this background.
|