All modules required by the CamlTk interface are in the tklib.zo library archive.
The organization of the interface is designed so that, in general, you should #open only the tk module, and for the rest of the functions use the modulename__symbol syntax (e.g. text__insert). This convention is preferred because some functions (such as create and configure) exist in many different modules. In this context, referring to functions with exact qualified names makes programs clearer and less error-prone.
As usual with toolkits, the simplest way to learn how to write programs is by copy-paste-ing from available source code. In general, a Caml Light program using the Tk toolkit will have the following structure :
#open "tk";; (* Make interface functions available *) let top = openTk ();; (* Initialisation of the interface *) (* top is now the toplevel widget *) (* Widget initialisation *) let b = button__create top [Text "foobar"; Command (function () -> print_string "foobar"; print_newline(); flush stdout)] ;; (* b exists but is not yet visible *) pack [b][] ;; (* Make b visible *) mainLoop() ;; (* User interaction*) (* You can quit this program by deleting its main window *)
The first step is to initialize the interface with OpenTk, and this returns the toplevel widget. Then, the program creates some widgets, as children of the toplevel widget. These widgets form the user interface of your application. In this simple case, we have only one button, whose behaviour is to print a simple message on the standard output. Finally, the program enters the MainLoop, and user-interaction takes place. Figure shows the interface of this program.
Here is a complete working example with a few widgets and callbacks. This program may be found in the test directory of the distribution, under the name addition.ml.
#open "tk";; let main () = let top = openTk () in (* The widgets. They all have "top" as parent widget. *) let en1 = entry__create top [TextWidth 6; Relief Sunken] in let lab1 = label__create top [Text "plus"] in let en2 = entry__create top [TextWidth 6 ; Relief Sunken] in let lab2 = label__create top [Text "="] in let result_display = label__create top [] in (* References holding values of entry widgets *) let n1 = ref 0 and n2 = ref 0 in (* Refresh result *) let refresh () = label__configure result_display [Text (string_of_int (!n1 + !n2))] in (* Electric *) let get_and_refresh (w,r) = fun _ _ -> try r := int_of_string (entry__get w); refresh () with Failure "int_of_string" -> label__configure result_display [Text "error"] in (* Set the callbacks *) entry__configure en1 [XScrollCommand (get_and_refresh (en1,n1)) ]; entry__configure en2 [XScrollCommand (get_and_refresh (en2,n2)) ]; (* Map the widgets *) pack [en1;lab1;en2;lab2;result_display] []; (* Make the window resizable *) wm__minsize_set top 1 1; (* Start interaction (event-driven program) *) mainLoop () ;; printexc__f main () ;;
The XScrollCommand callback has the property of being called each time the insertion cursor blinks in an entry widget. This property is responsible for the ``electric'' behavior of this program. Another approach would have been to associate an action to the event ``key return has been pressed''. Figure shows the interface of this program.
value openTk : unit -> widget and openTkClass : string -> widget and openTkDisplayClass : string -> string -> widgetEach of these functions returns the initial toplevel widget (. in TclTk). openTkClass lets you specify the resource class of the toplevel widget, and openTkDisplayClass lets you specify the display and the resource class.
During the initialisation, the file $HOME/.camltkrc, if it exists, will be loaded by TclTk. This might be the place where to write some special customisations in Tcl, such as changing auto-load path, ...
type xEvent = ButtonPress (* also Button, but we omit it *) | ButtonPressDetail of int | ButtonRelease | ButtonReleaseDetail of int | Circulate | ColorMap | Configure | Destroy | Enter | Expose | FocusIn | FocusOut | Gravity | KeyPress (* also Key, but we omit it *) | KeyPressDetail of string (* /usr/include/X11/keysymdef.h *) | KeyRelease | KeyReleaseDetail of string | Leave | Map | Motion | Property | Reparent | Unmap | Visibility ;; type modifier = Control | Shift | Lock | Button1 | Button2 | Button3 | Button4 | Button5 | Double | Triple | Mod1 | Mod2 | Mod3 | Mod4 | Mod5 | Meta | Alt ;; type eventInfo = { mutable ev_Above : int; (* tk: %a *) mutable ev_ButtonNumber : int; (* tk: %b *) mutable ev_Count : int; (* tk: %c *) mutable ev_Detail : string; (* tk: %d *) mutable ev_Focus : bool; (* tk: %f *) mutable ev_Height : int; (* tk: %h *) mutable ev_KeyCode : int; (* tk: %k *) mutable ev_Mode : string; (* tk: %m *) mutable ev_OverrideRedirect : bool; (* tk: %o *) mutable ev_Place : string; (* tk: %p *) mutable ev_State : string; (* tk: %s *) mutable ev_Time : int; (* tk: %t *) mutable ev_Width : int; (* tk: %w *) mutable ev_MouseX : int; (* tk: %x *) mutable ev_MouseY : int; (* tk: %y *) mutable ev_Char : string; (* tk: %A *) mutable ev_BorderWidth : int; (* tk: %B *) mutable ev_SendEvent : bool; (* tk: %E *) mutable ev_KeySymString : string; (* tk: %K *) mutable ev_KeySymInt : int; (* tk: %N *) mutable ev_RootWindow : int; (* tk: %R *) mutable ev_SubWindow : int; (* tk: %S *) mutable ev_Type : int; (* tk: %T *) mutable ev_Widget : widget; (* tk: %W *) mutable ev_RootX : int; (* tk: %X *) mutable ev_RootY : int (* tk: %Y *) } ;; type eventField = Ev_Above | Ev_ButtonNumber | Ev_Count | Ev_Detail | Ev_Focus | Ev_Height | Ev_KeyCode | Ev_Mode | Ev_OverrideRedirect | Ev_Place | Ev_State | Ev_Time | Ev_Width | Ev_MouseX | Ev_MouseY | Ev_Char | Ev_BorderWidth | Ev_SendEvent | Ev_KeySymString | Ev_KeySymInt | Ev_RootWindow | Ev_SubWindow | Ev_Type | Ev_Widget | Ev_RootX | Ev_RootY ;; type bindAction = BindSet of eventField list * (eventInfo -> unit) | BindSetBreakable of eventField list * (eventInfo -> unit) | BindRemove | BindExtend of eventField list * (eventInfo -> unit) ;;
Binding are manipulated with the function
bind: widget -> (modifier list * xEvent) list -> bindAction -> unit
The first argument of type widget is naturally the widget for which this binding is defined. The second argument of type (modifier list * xEvent) list is the succession of events that will trigger the callback. The last argument is the action : BindRemove will remove a binding, BindSet will set a binding, and BindExtend will extend a binding. BindSetBreakable is equivalent to BindSet, except that the callback may call the tk__break function to break the chain of callbacks (see break in the bind(n) Tk man page).
In many cases, a callback needs information about the event that triggered it, such as the precise key that was pressed, the position of the mouse, etc. Therefore, a callback is of type EventInfo -> unit, and the callback will be invoked with argument the record containing event information. Since event information can be quite large, the user has to specify, as an EventField list, which fields in this record are relevant for this particular callback. The other fields will not contain accurate information. Check also the Tk documentation for finding out which fields are valid for a given event.
bind myCanvas [[],Motion] (BindSet([Ev_MouseX; Ev_MouseY], mouse_moved)) ;;will trigger the callback mouse_moved when the event Motion occurs in the myCanvas widget. The function mouse_moved may assume that the information in the fields Ev_MouseX, Ev_MouseY of its argument contain accurate information.
Bindings on objets in text and canvas widgets obey the same rules, and the functions are
text__tag_bind : widget -> textTag -> (modifier list * xEvent) list -> bindAction -> unit canvas__bind : widget -> tagOrId -> (modifier list * xEvent) list -> bindAction -> unitAdditional arguments are respectively the tag and the tag or id for which the binding is defined.
Binding for classes of widgets (resp. tags) are managed with
tk__class_bind : string -> (modifier list * xEvent) list -> bindAction -> unit tk__tag_bind : string -> (modifier list * xEvent) list -> bindAction -> unitThe first argument should be a widget class name (e.g. "Button"), (resp. a tag name). Widget class names in CamlTk are the exact same names as in Tk.
The tk__bindtags function determines the order of bindings for a given widget.
Since text variables are global in the Tcl space, using a large amount of them might result in space leaks. Thus, there exists a creation function which relates the life of the text variable to the life of a widget. When the associated widget is destroyed, the text variable will be reused by the system.
tk__add_fileinput : file_descr -> (unit -> unit) -> unitWhen some input is available on the file descriptor specified by the first argument, then the callback (second argument) is called.
tk__remove_fileinput : file_descr -> unitRemoves the file descriptor callback.
It is the programmer's responsability to check for end of file, and to remove the callback if the file descriptor is closed. Be aware that calling the update function will potentially destroy the sequentiality of read operations. Finally, note that during the invocation of your callback, only one call to read is guaranteed to be non-blocking.
tk__add_fileoutput and tk__remove_fileoutput work on the same principle.
Note: there can be only one callback at the same time on a given descriptor. In particular, it is not possible to have an input callback and an output callback simultaneously.
tk__add_timer : int -> (unit -> unit) -> timer tk__remove_timer : timer -> unitA callback can be scheduled to occur after a given delay using tk__add_timer. The first argument is the delay (in milliseconds). A timer callback can be removed before its execution with tk__remove_timer. If the callback has already been executed, tk__remove_timer has no effect. It is not necessary to call tk__remove_timer after a timer callback has been executed.
tk__closeTk : unit -> unitwill terminate the main interaction loop (tk__MainLoop) and destroy all widgets.
All examples included in this distribution should not be considered as working applications. They are provided for documentation purposes only.
In the directory browser you will find a yet more complete example, featuring many different kinds of widgets. The browser mimicks the Caml Light toplevel behaviour: you can open and close modules (#open and #close directives), or add a library path (#directory directive). You can enter a symbol (value, type constructor, value constructor, exception), and see its definition. You can also see the complete interface of a module by double-clicking on it. Remember that the browser will show you a pretty-printed version of compiled interfaces (.zi files), so you will not see the comments that were in the source file.
The browser can also display source files. Hypertext navigation is also available, but does not reflect the compiler's semantics, as the #open directives in the source file are not taken into account.
When available, the browser also attemps to load TAGS file, created by the mletags program in the Caml Light contributions. It is therefore possible to navigate in program sources. However, note that the browser implements only a simplified version of Emacs's tag mechanism.
Moreover, we assume a ``standard installation'' of Caml Light and CamlTk from the distribution. This means that all CamlTk library files are installed in the same directory as standard Caml Light library files.
The usual commands for compiling and linking are:
$ camlc -c addition.ml $ camlc -custom -o addition tklib.zo addition.zo \ -ccopt -L$TCLLIBDIR -ccopt -L$TKLIBDIR \ -lcamltk4 -ltk -ltcl -lX11Linking is a bit complex, because we need to tell the C compiler/linker where to find the libtk.a and libtcl.a libraries, and to link with all required pieces.
$ camllight camltktopNote however that the usage of the toplevel is awkward (for the time being), because callbacks cannot be executed until you enter MainLoop, and you cannot easily leave MainLoop.
button .myf.bok -title "Ok" -relief raisedis translated by (assuming that myf is the parent widget .myf)
let b = button__create myf [Title "Ok"; Relief Raised] in ...Then,
.myf.bok configure -state disabledwould be in Caml Light:
button__configure b [State Disabled]
This is more in the spirit of ML, but unfortunately eliminates the possibility to specify configuration of widgets by resources based on widget names. It you absolutely want to use resources then you may call the alternate create_named function:
let b = button__create_named myf "bok" [Title "Ok"; Relief Raised] in ...Assuming that myf is the widget of path .myf, then b will have path .myf.bok. As in Tk, it is your responsibility to use valid identifiers for path elements.
When widgets are mutually recursive (through callbacks, for example when linking a scrollbar to a text widget), one should first create the widgets, and then set up the callbacks by calling configure on the widgets. This is in contrast with TclTk where one may refer to a procedure that has not yet been defined.
Partially applied widget commands (such as redisplay commands) translate quite well to Caml Light, with possibly some wrapping required due to value constructors.
For the time being the best information sources are: