Tricks of the Trade
In and Out
Download This Issue
Building User Interfaces Using J/Link
User Interfaces with J/Link
After these basics of J/Link, we are ready to begin writing user interfaces. As we have seen, when we write J/Link programs we are writing the same sort of code that we would write in Java, except using Mathematica syntax. This means that the user interface programs we write will look almost identical to programs of similar functionality written in Java. This is both a great strength of J/Link and a drawback. As a drawback, it means that there is no higher-level “Mathematica-like” layer on top of the raw Java calls. Such a layer would attempt to make it simpler to create interfaces and reduce the amount of code that needed to be written. But the fact that the only programming functions you need to learn are the Java user interface classes has some important advantages. First, if you already have experience using the Java classes, that knowledge carries over directly. If you do not know the Java classes, then the knowledge you gain from using them with J/Link will have advantages in the future if you ever do Java programming. In other words, you do not have to invest time learning a special Mathematica-only user interface toolkit. Another important benefit is that you can take advantage of the huge number of existing books on Java programming and the large amount of sample code out there. Finally, you have exactly the amount of power that Java’s libraries give you—no less. If you use a simplified toolkit, then you risk running into limitations of the toolkit if you have special needs or want to do something very fancy. In the future, Wolfram Research may very well make available another way of creating user interfaces that sits on top of J/Link and hides some of the raw Java calls (but still allows power users to get down to the metal if necessary). For now, though, writing user interface programs in Mathematica is essentially just like writing them in Java.
As mentioned earlier, an obvious consequence of the fact that you will be making calls to the Java user interface class libraries is that you will want documentation for these libraries. You can go a long way just by copying fragments out of the J/Link example programs below and the ones included with J/Link, but there is no substitute for a reference book. Good reference materials are online at Sun’s Java pages. The JavaDocs for all the standard Java classes can be found at java.sun.com/j2se/1.4/docs/api, and there are good tutorials on Java user interfaces at java.sun.com/docs/books/tutorial/ui, if you want to use the older but simpler Abstract Window Toolkit (AWT) classes, and at java.sun.com/docs/books/tutorial/uiswing/index.html, if you want to use the newer and more powerful Swing library.
As just mentioned, there are two user interface class libraries that are a standard part of Java. The original one is called AWT, and the newer one is called Swing. The AWT is easy to use but suffers from some limitations, most notably that it can be hard to get user interfaces to look very polished on every platform. It also is not suitable for very complicated interfaces, but that would not be a limitation for most of the interfaces Mathematica programmers are likely to create. In response to perceived problems with AWT, Sun created a new user interface library called Swing. Swing is a little more complicated to use but has more features and gives you a better chance of having your interface look good on every platform. We will use Swing for the examples in this article, primarily because that is what most programmers are using nowadays. The examples included with J/Link use mostly the older AWT because when they were created, some platforms did not have Java runtimes modern enough to run Swing.
Your First Window
Let us get started right away with our first window. The Swing class that represents a top-level window is called JFrame. Instead of using JFrame, we will use a subclass of it that is included in J/Link, called MathJFrame. MathJFrame adds two features to a standard JFrame that make it useful for J/Link programmers: the window goes away when its Close box is clicked, and it has special support for so-called “modal” J/Link interfaces, which will be discussed later.
You may notice a slight delay the first time a class is loaded into Mathematica, especially if it (or one of its parent classes) has many methods, as is the case with the JFrame class. We have now created a window, but it is not visible. Before we make it visible, let us make it a reasonable size and place it somewhere other than the default location at the top left corner of the screen.
The JFrame class has methods called setVisible() and toFront() that sound like they would be useful for making the window appear on top of all other windows. Unfortunately, these methods are not enough to force the window to the foreground on all platforms, so J/Link includes a special function called JavaShow that does this for you.
Here is what the window looks like.
Congratulations, you have now created your first Mathematica user interface! For a little fun, play around with resizing and repositioning it with Mathematica.
You have just experienced one of the incredible advantages of J/Link programming versus writing programs in Java. In Mathematica you can build your interface a line at a time, while it is running. There is no need to write a complete program, compile it, and run it just to see what it does. This ability to interactively experiment with Java classes and methods makes J/Link an ideal tool for learning about a class library.
Adding a Component to the Window
An empty window is not the most useful interface, so let us add something to it: a button that changes the background color of the window every time it is clicked. The Swing class for buttons is JButton, and one of the constructors lets us specify the text in the button.
When you add components to a Swing JFrame, you do not add them directly to the JFrame—instead, you add them to a special “pane” called the content pane. The Swing documentation goes into detail about the motivation for having the content pane, but you do not really need to care. Just remember to call getContentPane().
Components that can contain other components support a special class called a layout manager, which can supply complex logic for controlling the sizes and positions of child components. We will use a layout manager in a later example, but for now we will specify the button’s size and position explicitly. To do this we need to get rid of the content pane’s default layout manager.
Next we position the button.
Now that we have the window and its button, we need to add some behavior. We want a click in the button to cause the background color of the window to change to a random color generated by Mathematica. Java user interface components fire events in response to user actions, and other components indicate their interest in these events by registering as event listeners. In our example, the button will fire an event when it is clicked, and something else will listen for the event and change the content pane’s color in response. The “something else” that listens for the button-click event is not the content pane itself. To put that logic into the content pane would require creating a subclass of the pane’s class. It would be very cumbersome to have to create a new subclass of each component class just to allow it to respond to events in a certain way. Instead, the programmer writes an “adapter” class that implements the desired event-listener interface and calls certain methods in the component in response to various events. The only specialty code goes into the adapter class, allowing the components that fire and respond to events to be generic.
Swing buttons fire an ActionEvent when clicked, and the corresponding event-listener interface is ActionListener. In our example, a Java programmer would create a tiny new class that implements the ActionListener interface. This interface has only one method, actionPerformed(), and in the body of the actionPerformed() method the programmer would set the content pane’s color. The programmer would wire up this event behavior by calling the button’s addActionListener() method, supplying an instance of this new class. When the button is clicked, it notifies all its registered listeners by calling their actionPerformed() methods.
We see from this discussion that Java programmers typically need to create a new listener class for every behavior they want to add to their program. Mathematica programmers using J/Link cannot write new Java classes, so how do we add behaviors to our programs? The answer is that J/Link comes with ready-made listener classes for the user interface events typically encountered in Java programs. These classes are referred to generically as “MathListener” classes because they listen for events and respond by calling user-specified code in Mathematica. To add a behavior to your program, you simply create an instance of the appropriate MathListener class and tell it what Mathematica function you want executed for each of the events that it is capable of responding to.
This is all very straightforward when you see it in action. Let us add the click behavior to our button. First we create an instance of the appropriate MathListener class, which for a button is MathActionListener.
Now we assign the Mathematica function we want called. To do this, we use the setHandler() method, which takes the name of the event-listener method (“actionPerformed”) and the name (as a string) of a Mathematica function to be called to implement the actionPerformed() method. Our Mathematica function will be called ButtonClickHandler.
Now we need to register our actionListener with the button. This is done by calling the button’s addActionListener() method.
All that remains is to write the ButtonClickHandler function. To see what arguments will be passed to your Mathematica handler function, consult the table in Section 22.214.171.124 of the User Guide. Your function will always be passed the event object itself, and possibly other information that might be useful (this extra information can always be obtained from the event object—it is provided separately simply as a convenience). In this example, we are not interested in any of the arguments.
Note that we wrapped the body of the function in JavaBlock to release the Color object that we created. We used the Color constructor that takes RGB values as real numbers in the range 0 to 1.
It has only taken a few lines of Mathematica code to wire up a simple behavior for the button. At this point you might be wondering how to know what events a component fires and what MathListener to use. Events and event listeners are well documented in Java, but in practice you can use a very quick shortcut. Simply look in the JavaDocs for the component you are interested in (JButton, JPanel, JComboBox, JComponent, and so on) for one or more methods with names of the form addXXXListener. Do not forget to look in the methods inherited from parent classes as well. Once you find such a method, you know that the component fires the events listed in the XXXListener interface. The corresponding MathListener will be named MathXXXListener.
In some cases you will find that there is no MathListener class provided for the event-listener interface that you need to use. For example, at the time of this writing there is no MathMenuListener in J/Link for handling events from JMenu components. If there is no existing MathListener, you can use the Mathematica function ImplementJavaInterface to create an appropriate class “on the fly.” We will see an example of using ImplementJavaInterface later, and Section 1.2.17 of the User Guide describes ImplementJavaInterface in detail and gives an example of using it to handle events from a JMenu.
Did you click the button to see if it changed the color of the window? If you have not done this yet, do not, because it will not work! One crucial step remains, which is the subject of the next section.
Modal and Modeless Interfaces
Consider what will happen if you click on the button in your running example program. Java will detect the click, and the button will notify the MathActionListener by calling its actionPerformed() method. The MathActionListener will send commands to Mathematica to execute the ButtonClickHandler function. These commands will arrive on the MathLink that connects the Mathematica kernel and Java. What will Mathematica do when these commands arrive on the link? Absolutely nothing! That is because the Mathematica kernel is not paying any attention to the link to Java. The kernel spends its life waiting for commands to arrive on the MathLink that connects to the front end. Before any Java user interface program can interact with Mathematica, Mathematica needs to be put into a state where it is receptive to commands arriving from Java.
There are two main ways for making Mathematica receptive to commands initiated in Java (a third, less commonly used method is described in Section 126.96.36.199 of the User Guide). The method you choose will depend on the requirements of your user interface program. Consider a Java dialog box that is used to control some initial values for a computation. The dialog box is created and displayed; the user enters some values and then dismisses it by clicking the OK button. The entire lifetime of the dialog box falls within the bounds of a single computation in Mathematica. In contrast, consider a Java interface that is intended to remain visible and active for a long stretch of time in a user’s session. This could be some kind of tool or utility that the user would use occasionally, similar to a palette in the front end.
The first type of interface, which is created and destroyed within the span of a single computation, is called a “modal” interface. The term “modal” is used here with a slightly different meaning than the usual one. In standard computing terminology, a modal window is one that must be dismissed before anything else can be done in the application. A typical example is a Save File dialog box. In J/Link, what we call a modal interface is not necessarily modal with respect to other Java windows—it is modal with respect to the Mathematica kernel. That is, the kernel is kept busy dealing with commands arriving from Java until the Java window is dismissed.
The second type of interface, which remains active for longer than a single kernel computation, is called “modeless.” It is modeless with respect to the Mathematica kernel, meaning that the kernel is not kept busy servicing commands from Java. The user is free to do computations using the front end while the Java interface is still active.
Modal interfaces are more common than modeless ones, and they are conceptually simpler. They also offer better performance if the Java program needs to call Mathematica many times in quick succession, such as when a slider is being dragged. Modal interfaces are run using the DoModal function, which does not return until the modal interface is dismissed. Modeless interfaces are enabled with the ShareKernel function, which puts the kernel into a state where it is receptive to commands arriving from either the front end or Java.
Let us now run our button example program in a modal way. Before we do this, go back and click the Click Me! button if you have not done so already. From the discussion in the previous few paragraphs we know it will not work yet, but it is interesting to see exactly what happens. Not only does the color of the window not change, but the Java user interface thread hangs. You can see this by clicking the button again and noting that it does not even give any visual feedback of being pressed. In fact, if you bring another window in front of the Java window and then click the Java window to bring it back in front, it will not repaint itself, a sure sign that the Java user interface thread is hanging. This makes sense because the thread is waiting for Mathematica to respond to its attempt to call the ButtonClickHandler function.
The user interface thread will hang until we tell Mathematica to start paying attention to commands arriving from Java, which we can do by calling DoModal (or ShareKernel if we want a modeless interface). The DoModal function will not return until the Java side calls EndModal. There are many ways to arrange for this to happen, but by far the easiest and most common way is to use a special facility of the MathJFrame class. If you call a MathJFrame’s setModal() method, it will call EndModal when it is closed, thus causing DoModal to return.
Now we are finally ready to run the button example as an interactive program.
The call to DoModal will not return until you close the Java window. Click the Click Me! button a few times and see how the window color changes. Here is what it looks like in action.
The Complete ButtonClick Program
We have now completed a trivial J/Link user interface program. Note that it only took a few lines of code, and all of it is written in Mathematica. The code is almost line-for-line comparable to a pure Java version of the same program. Now let us collect all the code together and package it as a simple program called ButtonClick.
The ButtonClick function is a very typical J/Link program. The whole function is wrapped in JavaBlock, as we have no need for any of the Java objects created to persist in Mathematica after the function ends. Another common idiom is to call InstallJava at the start, in case Java is not already running. One trick to note is in the line that calls setHandler(). We want the buttonClickHandler function to be local to the Module, and this means that its true name is something like buttonClickHandler$123. We have to pass the name of this function as a string in setHandler(), and we can be sure to capture its true name by calling ToString[buttonClickHandler], instead of incorrectly hard-coding "buttonClickHandler".
A More Advanced Example
We have now worked our way through a trivial J/Link user interface program. Now let us look at something considerably more advanced, and examine some new techniques along the way. In particular, we will see the following.
The program we will create allows the user to experiment with Taylor series approximations to functions. They can enter a function and a range over which to plot the function and its Taylor series approximation. They then use a slider to modify the number of terms in the series expansion. The series expansion is displayed along with a plot showing the function and the Taylor series approximation. The plot and series expansion are updated on the fly as the slider is dragged. Here is a screen shot of the program in action.
The general flow of the Taylor program is as follows.
Laying out the Window
As a first step in creating the Taylor program, we will write a preliminary version, Taylor1, that only builds the interface and displays it. Behavior will be added in subsequent revisions. The code to create the window and all its components is somewhat lengthy. One reason for this is that we will do the work necessary to ensure that the components in the window lay out nicely on all platforms and reposition correctly if the window is resized. In the ButtonClick example, we specified the button’s size and location with explicit numerical values. This works fine for simple windows that cannot be resized and have just a few components, or if you are not concerned about platforms beyond the one on which you are developing the program. But if you hardcode component positions and sizes for a more complex window, it is practically guaranteed that on some platforms (perhaps Mac OS X, or Linux with some unusual window manager and fonts) there will be layout problems. Typical examples are buttons that are too small to hold all their text, labels that are cut off by other components, and text fields that are the wrong height for a line of text.
The best way to avoid layout problems like these is to use a layout manager, which is a special Java class that knows how to lay out child components within a container component. Proper use of a layout manager helps ensure that components have their “natural” sizes on each platform and reposition sensibly if their container is resized. Using layout managers can be a tricky part of Java programming, but it is worth the effort if you want to create high-quality interfaces that look good on every machine. In the Taylor program, we will use a BoxLayout, which is a new layout manager provided in Java 1.3. BoxLayout is useful when you have several rows with different numbers of components in each. A useful tutorial on layout managers can be found at java.sun.com/docs/books/tutorial/uiswing/mini/layout.html.
The section of the program that creates the window and lays out the components is too long to try to work through and explain. Frankly, this part of any program is somewhat tedious and uninteresting, and it is beyond the scope of this article to try to teach readers how to write this type of code. The Mathematica code you write using J/Link is essentially identical to the code a Java programmer would write, and for guidance you can refer to any of the copious books, articles, and other resources on user interface programming in Java.
The code in Taylor1 is presented as a complete function, but of course this is not how it was created. Naturally, it was built up bit by bit with much experimentation. This interactive programming style is the huge advantage of using J/Link versus programming directly in Java. It is especially valuable when trying to get the layout of components just right. In fact, I had never used a BoxLayout and its helper Box class before, and being able to play with them a line at a time was extremely useful for learning.
Two things that are temporarily left out of Taylor1 are a JavaBlock wrapping the entire function and local variables specified in the Module. It is very important that these are left out because this Taylor1 program is not self-contained. What we want for now is a program that simply displays the window with its final appearance. We will then interactively add behavior via event callbacks to Mathematica. To do this we need access to the various Java objects we have created (the frame, the plot panel, the text fields, and so on), hence they cannot yet be made local to the Module or released by a JavaBlock.
Yikes, that is a lot of code, but for the most part it is straightforward: create the components, set some properties, and put them where they belong. Scattered throughout are calls to manage properties of the layout, such as borders and spacing. This will pay off in allowing the window to be resized and still keep a nice appearance. One J/Link programming issue is worth noting in this code—the line that reads:
You might be wondering why this is not the more obvious code:
The answer is that the add() method takes an Object, not a String, as its second argument. The constant BorderLayout.NORTH is a Java string, and so it arrives in Mathematica as a literal string. The problem is that J/Link does not allow you to pass a Mathematica string to an argument slot that is typed to take a Java object. What you need to pass as the second argument to add() is a JavaObject that is a reference to a Java string with the appropriate value. An easy way to do this is to use the ReturnAsJavaObject function to ask J/Link to return BorderLayout.NORTH to Mathematica as a JavaObject instead of its usual behavior of converting it into a Mathematica string.
Now that we have a function that builds the interface, let us start it running so that we can experiment with adding behavior.
The Taylor1 program shows the window and then returns, leaving the window on the screen. Now we want to add behavior—specifically, we want the two computed components (the series expansion text field and the plot panel) to recompute when either the slider is dragged or the Compute button is clicked. As we have seen, we will do this by registering event listeners that call back to Mathematica when the user manipulates either of these two controls.
The first thing we need is a function that extracts the necessary values from the input fields in the window, computes the necessary results, and updates the series expansion text box and the plot panel. We will call this function computeTaylor, and here is what it looks like.
We have made computeTaylor accept (and ignore) any sequence of arguments because it will eventually be called from Java in two different circumstances with different arguments, none of which are we interested in. The first four lines of computeTaylor extract the values from the input fields and slider. The function then computes the series expansion, puts the OutputForm of this result in the series expansion text area, and creates the plot. Because we are only interested in the Graphics object returned by Plot, we use a Block to control the value of $DisplayFunction so that no PostScript output is created.
The last line of computeTaylor causes the plot to appear in the plot panel. This panel is an instance of a special class provided with J/Link called MathGraphicsJPanel, which is a subclass of the Swing JPanel class with some special capabilities for displaying Mathematica graphics and typeset expressions. The MathGraphicsJPanel class and its counterpart for AWT-style programs, MathCanvas, are discussed in more detail in Section 1.2.8 of the User Guide. For many programs, using either of these classes is as simple as calling the setMathCommand() method, which takes a string giving a Mathematica expression that evaluates to a graphics expression. The panel evaluates the expression and displays the resulting image, automatically centered and scaled to fit the panel’s current dimensions.
We can now call computeTaylor and see its effects in the window. Make sure the input fields in the window have reasonable values and then execute this line.
The only thing left is to wire up the button and slider so that they call the computeTaylor function. Before we can allow calls to Mathematica from the Java user interface thread, we need to ensure that Mathematica is in a state where it is receptive to calls originating in Java. In the ButtonClick example, we achieved this by using DoModal to put the kernel into a loop where it was continuously reading from the link to Java. We will use DoModal again with the Taylor program, but only when we have fully packaged it in its final form. It is inconvenient to use DoModal during development because we want to be able to tinker with the program while it is running. The DoModal function does not return until the window is closed, so we would need to set up all the behavior in advance. You can use the ShareKernel function to put the kernel into a state where it is receptive to calls from either the front end or Java. This is convenient during development even for programs, like Taylor, that will be run modally when deployed.
This starts sharing.
We save the result from ShareKernel to pass to UnshareKernel later on, when we turn off the sharing state.
When a JButton is clicked, it notifies all registered ActionListeners by calling their actionPerformed() methods. Therefore the MathListener we need to use is MathActionListener.
The computeTaylor function will now be called when the button is clicked. It will be passed some arguments when it is called, but we are not interested in any of them. Try altering the input fields in the window and clicking the Compute button to see the program in action.
The Compute button is intended for when the user has edited the text fields and wants to recompute. We also want the slider to be “live,” so that dragging it automatically recomputes. To see what events a JSlider fires as it is dragged, we examine the JavaDocs for JSlider looking for methods of the form addXXXListener(). We find addChangeListener() and see that we need a class that implements the javax.swing.event.ChangeListener interface. Unfortunately, J/Link does not include a MathChangeListener class, so what do we do?
J/Link provides a function called ImplementJavaInterface that allows you to create a new Java class “on the fly” that implements any interface by calling into Mathematica. When you call ImplementJavaInterface, you specify a list of rules that associates each method in the interface with a Mathematica function to be called that provides the implementation of that method. The most common use of ImplementJavaInterface is to create MathListener-type classes for interfaces that J/Link does not have built-in MathListener classes for. Below we create the listener we need, specifying that the stateChanged() method of the ChangeListener interface should be implemented by calling the computeTaylor function.
Now register our new listener with the slider.
Play around with the slider and other fields to see the interface in action. When you are finished with the Taylor window, close it by clicking in its Close box. We have now interactively created the code we need for the final version of Taylor, and are satisfied that it looks and works as desired. We were able to create the code in stages, interactively modifying the window and its behavior while it was running.
The final version of the Taylor program will be run modally, and because we are done with the interactive development phase, we call UnshareKernel to turn off kernel sharing. We pass the value that was returned from ShareKernel as the argument to UnshareKernel.
The Completed Program
Here is the complete code for the final Taylor program. We essentially just collect all the code we have created so far into a single function.
Now run this final Taylor program. Quit the program by clicking the window’s Close box.
You can also specify the initial function, point, range, and number of terms.
About Mathematica Download Mathematica Player
Copyright © 2003 Wolfram Media, Inc. All rights reserved.