J/Link Tutorial

Creating modal windows

Here is an example of a simple modal window. The window contains a button and a text field. The text field starts out displaying the value 1. Each time the button is clicked, the value is incremented.

Starting Out

You can use the com.wolfram.jlink.MathFrame class for the enclosing window. MathFrame is a simple extension to java.awt.Frame that calls dispose on itself when its close box is clicked (the standard Frame class does nothing).

frm = JavaNew["com.wolfram.jlink.MathFrame"];

button = JavaNew["java.awt.Button"];
textField = JavaNew["java.awt.TextField"];

frm@
        setLayout[
          JavaNew["java.awt.GridLayout"]];
frm@add[button];
frm@add[textField];
frm@pack[];
JavaShow[frm];

At this point, you should see a small frame window with a button on the left and a text field on the right. Now label the button and set the starting text for the field.

button@setLabel["++"];
textField@setText["1"];

Now you can add behavior to the button so that it increments the text field value. Buttons fire ActionEvents, so you need an instance of MathActionListener.

buttonListener = 
        JavaNew["com.wolfram.jlink.MathActionListener"];

It must be registered with the button by calling addActionListener.

button@addActionListener[buttonListener];

At this point, if you were to click on the ++ button, the actionPerformed method of the MathActionListener would be called, so don’t click on the button yet. The MathListener table in the previous subsection shows that the actionPerformed method will call a user-defined Mathematica function with two arguments: the ActionEvent object itself and the integer value that results from the event’s getActionCommand() method.

You have not yet set the user-defined code to be called by the actionPerformed method. That is done for all the MathListener classes with the setHandler method. This method takes two strings: the first is the name of the method in the MathListener interface, and the second is the function you want called.

buttonListener@
        setHandler["actionPerformed", "buttonFunc"];

Now you need to define buttonFunc. It must be written to take two arguments, but in this example, you are not interested in either argument.

buttonFunc[_, _] := 
      Module[{curText, newVal}, 
        curText = textField@getText[]; 
newVal = 
          ToExpression[curText] + 1; 
textField@
          setText[ToString[newVal]]]

You are almost ready to try the button, but if you click it now, the Java user interface thread will hang because it will call into Mathematica to evaluate buttonFunc and wait for the result. The result will never come because the kernel is not waiting for input to arrive on the Java link. What you need is a way to put the kernel into a state where it is continuously reading from the Java link.  This is what makes the window modal. The kernel cannot do anything else until the window is closed.

The function that implements this modal state is DoModal.

({{DoModal[], \(put\ the\ kernel\ into\ a\ state\ where\ its\ attention\ is\ solely\ directed\ at\ the\ Java\ link\)}, {EndModal[], \(what\ the\ Java\ program\ must\ call\ to\ make\ the\ DoModal\ function\ return, \ ending\ the\ modal\ state\)}})

DoModal will never return until the Java program calls back into Mathematica to evaluate EndModal[]. While DoModal is executing, the kernel is ready to handle callbacks from Java. The way to get the Java side to call EndModal[] is to use a MathListener. For example, if your window has OK and Cancel buttons, these should dismiss the window, so you would create MathActionListeners and register them with these two buttons. These MathActionListeners would be set to call EndModal[] in their actionPerformed methods.

DoModal returns whatever the block of code that calls EndModal[] returns. You would typically use this return value to determine what button was used to close the window. You could then take appropriate action. See Section 1.3.5 of the J/Link User Manual for an example of using the return value of DoModal.

In our present example, the only way to close the window is by clicking its close box. Clicking the close box fires a windowClosing event, so you use a MathWindowListener to receive notifications.

windowListener = 
        JavaNew["com.wolfram.jlink.MathWindowListener"];
frm@addWindowListener[windowListener];

Now you assign the Mathematica function to be called when the close box is clicked. All you need it to do is call EndModal[], so you can specify a pure function that ignores its arguments and does nothing but execute EndModal[].

windowListener@
        setHandler["windowClosing", "EndModal[]&"];

The preceding few lines are a fine example of how to use a MathWindowListener to trigger a call to EndModal[] when a window’s close box is clicked. You would use something similar to this, except with a MathActionListener, if you wanted to have an explicit Close button. In this example, though, there is an easier way. We said earlier that the MathFrame class is just a normal AWT Frame except that it calls dispose() on itself when its close box is clicked. It has one other property; it also calls EndModal[] when its close box is clicked. Thus, if you use MathFrame as the top-level window class for your interfaces, you won’t have to manually create a MathWindowListener to terminate the modal loop every time. To enable this behavior of MathFrame, you need to call its setModal method.

You must not call setModal if you are not using DoModal. After setModal has been called, the MathFrame will try to call into Mathematica when it is closed, and Mathematica needs to be in a state where it is ready for calls originating in Java. The same issue exists for any MathListener you create yourself.

Now that everything is ready, you can enter the modal state and use the window.

DoModal[]

When you are done playing with the window, click the close box in the frame, which will trigger a callback into Mathematica that calls EndModal[]. DoModal then returns, and the kernel is ready to be used from the front end. DoModal[] returns Null if you click the close box of a MathFrame.

Remember that DoModal will not return until the Java side calls EndModal. Make sure when you call DoModal that you have already established a way for the Java side to trigger a call to EndModal. As explained above, you do this by using a MathFrame as the frame window and calling its setModal method, or by creating and registering a MathListener of your own that will call EndModal in response to a user action. Once DoModal has begun, the kernel is not responsive to the front end, and it is too late to set anything up. If you call DoModal and realize that you cannot end it from Java, you can abort it from the front end by selecting Interrupt Evaluation from the Kernel menu. Then in the resulting dialog box, click the button labeled Abort Command Being Evaluated.

There is one subtlety you might notice in the code for SimpleModal that is not directly related to J/Link. In the line that calls buttonListener@setHandler, the name of the button function is passed not as the literal string "buttonFunc", but as ToString[buttonFunc]. This is because buttonFunc is a local name in a module, and thus its real name is not buttonFunc but something like buttonFunc$42. To make sure you capture its true run-time name, you call ToString on the symbolic name. You could avoid this by not making the name buttonFunc local to the module, but the way it is done here automatically cleans up the definition for buttonFunc when the module finishes.

Final Code

The following example combines all the above elements for J/Link to create a modal window.

Needs["JLink`"]

SimpleModal[] :=
	JavaBlock[
		Module[{frm, button, textField, windowListener, buttonListener, buttonFunc},

			(* Create the GUI components. *)
			frm = JavaNew["com.wolfram.jlink.MathFrame"];
			button = JavaNew["java.awt.Button"];
			textField = JavaNew["java.awt.TextField"];

			(* Configure their properties. *)
			frm@setLayout[JavaNew["java.awt.GridLayout"]];
			frm@add[button];
			frm@add[textField];
			button@setLabel["++"];
			textField@setText["1"];
			frm@pack[];

			(* Create the listener and set its handler function. *)
			buttonListener = JavaNew["com.wolfram.jlink.MathActionListener"];
			buttonListener@setHandler["actionPerformed", ToString[buttonFunc]];
			button@addActionListener[buttonListener];

			(* Define buttonFunc. *)
			buttonFunc[_, _] :=
				JavaBlock[
					Module[{curText, newVal},
						curText = textField@getText[];
						newVal = ToExpression[curText] + 1;
						textField@setText[ToString[newVal]]
					]
				];

			frm@setLocation[200, 200];
			(* Make the window visible and bring it in front of any
			   notebook windows. *)
			JavaShow[frm];

			(* Tell the frame to end the modal loop when it is closed. *)
			frm@setModal[];

			(* Enter the modal loop. *)
			DoModal[];
		]
	]

Example

You must call InstallJava prior to running this example.

InstallJava[];

SimpleModal[]