Events and callbacks

With java::bind, we created Tcl callbacks that are evaluated when an event occurred on a Java widget. If we need more flexibility, we need to understand how Java code can evaluate Tcl scripts itself. At a basic level, all that is required to evaluate a Tcl script is to pass a string to the eval method of a tcl.lang.Interp object. More realistically, we have found that it is better to use the Notifier mechanism of the Tcl interpreter for thread-safety.

Let's suppose that we want to duplicate the button example of the previous section, but set up the Tcl callback ourselves in Java. In this example, we'll set up the event processing so that a Tcl script is executed every five times we press the button. The full source code is in tutorial/tcltk98/TclButton.java.

The class has instance variables that reference a Tcl interpreter, a string containing a Tcl script, and a counter:

public class TclButton extends java.awt.Button {
  Interp tclInterp;
  String tclScript;
  int count = 5;
The constructor of TclButton requires a Tcl interpreter. Generally, you will want to provide the same interpreter that is running the rest of your Tcl/Tk interface, and which can be obtained by calling the java::getinterp procedure. The constructor is:
  public TclButton (Interp interp) {
    super("Push me!");
    tclInterp = interp;
    addActionListener(new LocalListener());
  }
We'll need a method to set the script to be executed:
  public void setScript (String script) {
    tclScript = script;
  }
As before, we create a local class that implements the ActionListener interface. This time, though, we get the "notifier" from the interpreter and pass it an instance of tcl.lang.TclEvent. The notifier ensures that the event will be processed by the primary thread of the interpreter, thus avoiding thread synchronization problems. (If, on the other hand, you execute tclInterp.eval directly in the actionPerformed method, the button hangs.) After queuing the event with the notifier, we call the sync method of the Tcl event object to ensure that the script has been evaluated before we return.
  class LocalListener implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      if (--count == 0) {
	count = 5;
	Notifier n = tclInterp.getNotifier();
	TclEvent t = new EvalEvent();
	n.queueEvent(t,TCL.QUEUE_TAIL);
	t.sync();
      }
    }
  }
Finally, we need a subclass of tcl.lang.TclEvent that overrides the processEvent method to evaluate the script:
  class EvalEvent extends TclEvent {
    public int processEvent (int flags) {
      try {
	tclInterp.eval(tclScript);
      }
      catch (Exception x) {}
      return 1;
    }
  }
To try this out, compile TclButton.java and then execute:
  set button [java::new tutorial.tcltk98.TclButton [java::getinterp]]
  $button setScript \
    "toplevel .t; label .t.l -text {Five!}; pack .t.l; after 1000 {destroy .t}"
  set window [java::new java.awt.Frame]
  $window setLocation 100 100
  $window {add java.awt.Component} $button
  $window pack
  $window show
Every five times the button is pressed, the Tcl script is executed, which pops up a little Tk message window! We hope that this example, although a bit forced, gives you the flavor of a mixed Java-Tcl event handling environment.