J/Link Tutorial

Creating a real time algebra application

This example puts together everything you have learned about modal and modeless Java user interfaces. It implements the familiar dialog box mini-application in both modal and modeless flavors. The application is inspired by the classic MathLink example program RealTimeAlgebra. The original RealTimeAlgebra written for the NeXT provides an input window into which the user types an expression that depends on certain parameters (a, b, c, and so on), an output window that displays the result of the computation, and some sliders that are used to vary the values of the parameters. The output window updates as the sliders are moved, hence the name RealTimeAlgebra. Our implementation of RealTimeAlgebra will be very simplistic with only a single slider to modify the value of one parameter.

Starting Out

It is always instructive to work a line at a time through the creation of a program, but since you are familiar with the building blocks of this example, it will save time to list the bulk of the program here.

\(CreateWindow[]\  := 
	
        Module[{scrollClass, \ frame, \ slider, \ fnt}, 
		 (*\ 
            inText\ and\ outText\ are\ globals\ because\ you\ need\ to\ refer\ to\ them\ by
		\ \ \ name\ in\ the\ scrollFunc . \ 
                This\ also\ means\ you\ must\ create\ them\ outside
		\ \ \ the\ JavaBlock\ \(\(below\)\(.\)\)
		*) 
		inText\  = \ 
            JavaNew["java.awt.TextArea", \ "Expand[(x+1)^a]", \ 8, \ 
              40]; 
		outText\  = \ 
            JavaNew["java.awt.TextArea", \ 8, \ 
              40]; 
		JavaBlock[
			frame\  = \ 
              JavaNew["com.wolfram.jlink.MathFrame", 
							\ \ \ \ "RealTimeAlgebra"]; 
			frame@
              setLayout[JavaNew["java.awt.BorderLayout"]]; 
			 (*\ 
              Load\ the\ scrollbar\ class\ explicitly\ because\ you\ need\ the
				HORIZONTAL\ constant\ before\ the\ class\ would\ be\ loaded\ by\ \(\(JavaNew\)\(.\)\)
			*) 
			scrollClass\  = \ 
              LoadClass["java.awt.Scrollbar"]; 
			slider\  = \ 
              JavaNew[scrollClass, \ Scrollbar`HORIZONTAL, \ 0, \ 1, \ 0, \ 
                20]; 
			frame@
              add[slider, \ ByRef[BorderLayout`NORTH]]; 
			frame@
              add[outText, \ ByRef[BorderLayout`CENTER]]; 
			frame@
              add[inText, \ ByRef[BorderLayout`SOUTH]]; 
			frame@
              pack[]; 
			 (*\ 
              Use\ a\ fixed - 
                width\ font\ for\ the\ output\ window\ to\ preserve
			\ \ \ formatting\ of\ multi - 
                line\ \(\(expressions\)\(.\)\)\ *) 
			outText@
              setFont[
				JavaNew["java.awt.Font", \ "Courier", \ Font`PLAIN, \ 
                  12]]; 
			listener\  = 
				JavaNew["com.wolfram.jlink.MathAdjustmentListener"]; 
			listener@
              setHandler["adjustmentValueChanged", \ "sliderFunc"]; 
			slider@addAdjustmentListener[listener]; 
			frame@
              setModal[]; 
			JavaShow[frame]
		]
	]

 (*\ 
        This\ function\ is\ called\ when\ the\ slider\ position\ is\ \(\(changed\)\(.\)\)\ *) 


      sliderFunc[evt_, \ type_, \ scrollPos_]\  := 
	
        outText@
          setText[
		Block[{a\  = \ scrollPos}, \ 
              ToString[ToExpression[inText@getText[]]]]
	]\)

The sliderFunc function is called by MathAdjustmentListener whenever the slider’s position changes. It gets the text in the inputText box, evaluates it in an environment where a has the value of the slider position (the range for this is 0.20, as established in the JavaNew call that creates the slider), and puts the resulting string into the outText box. It then calls ReleaseObject to release the first argument, which is the AdjustmentEvent object itself. This is the only object passed in as an argument. The other two arguments are integers. If you are wondering how to determine the argument sequence for sliderFunc, you can get it from the MathListener table in Section 1.2.5 of the J/Link User Manual. Note that you need to refer by name to the input and output text boxes in sliderFunc, so you cannot make their names local variables in CreateWindow’s Module, and they cannot be created inside that function’s JavaBlock.

There is one unusual thing in the code that deserves a remark. Look at the lines where the three components are added to the frame. What is the ByRef doing there? It’s a trick to work around a very unusual circumstance. The method called here is in the Frame class and has the following signature:

void add(Component comp, Object constraints);

The second argument, constraints, is typed only as Object. The value you pass in depends on the layout manager in use, but typically it is a string, as is the case here. For example, the string needed for BorderLayout.NORTH is just "NORTH". The problem is that J/Link creates a definition for this signature of add that expects a JavaObject for the second argument, and Mathematica strings do not satisfy JavaObjectQ, although they are converted to Java objects when sent. This means that you can only pass strings to methods that expect an argument of type String. In the rare cases where a Java method is typed to take an Object and you want to pass a string from Mathematica, you must first create a Java string with the value you want and pass that object instead of the raw Mathematica string. BorderLayout`NORTH calls into Java to get the value of the BorderLayout.NORTH static field, but in the process of returning this string object to Mathematica, it gets converted to a raw Mathematica string. You need the object reference, not the raw string, so you need to wrap the access in ByRef, which causes the string to be returned in the form of a reference instead of by value.

Another way to do this is to call JavaNew to create a Java string object explicitly. JavaNew always returns an object reference. Then instead of using ByRef you could write the following.

frame@
        add[slider,  
          JavaNew["java.lang.String",  BorderLayout`NORTH]]

An obvious way to avoid this entire problem is to make Mathematica strings satisfy JavaObjectQ. This would not cause any glaring problems, but it might compromise type safety by allowing illegal argument sequences to inadvertently match a method’s signature. This change also makes it more likely that J/Link will incorrectly resolve which of a set of methods with similar signatures you are trying to call.

Getting back to the RealTimeAlgebra dialog box, you are now ready to run it as a modal window. Here is a modal version that uses CreateWindow internally.

\(RealTimeAlgebraModal[]\  := 
	
        JavaBlock[
		 (*\ In\ the\ modal\ case, \ 
            you\ can\ wrap\ the\ whole\ thing\ in\ JavaBlock\ and\ be
			sure\ that\ all\ the\ objects\ get\ released, \ 
            including\ the\ ones\ created
			during\ event\ \(\(handling\)\(.\)\)
		*) 
		CreateWindow[]; 
		DoModal[];
	]\)

Final Code

The following example combines all the above elements for J/Link to create a Real Time Algebra application.

Needs["JLink`"]
\(CreateWindow[] := 
      Module[{scrollClass, frame, slider, fnt},  (*\ 
          inText\ and\ outText\ are\ globals\ because\ you\ need\ to\ refer\ to\ them\ by\ name\ in\ the\ scrollFunc . \ 
              This\ also\ means\ you\ must\ create\ them\ outside\ the\ JavaBlock\ \(\(below\)\(.\)\)\ *) 
inText = 
          JavaNew["java.awt.TextArea", "Expand[(x+1)^a]", 8, 
            40]; 
outText = 
          JavaNew["java.awt.TextArea", 8, 
            40]; 
JavaBlock[
          frame = JavaNew["com.wolfram.jlink.MathFrame", "RealTimeAlgebra"]; 
frame@
            setLayout[
              JavaNew["java.awt.BorderLayout"]]; 
 (*\ 
            Load\ the\ scrollbar\ class\ explicitly\ because\ you\ need\ the\ HORIZONTAL\ constant\ before\ the\ class\ would\ be\ loaded\ by\ \(\(JavaNew\)\(.\)\)\ *) scrollClass = 
            LoadClass["java.awt.Scrollbar"]; 
slider = 
            JavaNew[scrollClass, Scrollbar`HORIZONTAL, 0, 1, 0, 
              20]; 
frame@
            add[slider, ByRef[BorderLayout`NORTH]]; 
frame@
            add[outText, 
              ByRef[BorderLayout`CENTER]]; 
frame@
            add[inText, ByRef[BorderLayout`SOUTH]]; 
frame@
            pack[]; 
 (*\ 
            Use\ a\ fixed - 
              width\ font\ for\ the\ output\ window\ to\ preserve\ formatting\ of\ multi - line\ \(\(expressions\)\(.\)\)\ *) outText@
            setFont[JavaNew["java.awt.Font", "Courier", Font`PLAIN, 
                12]]; 
listener = 
            JavaNew["com.wolfram.jlink.MathAdjustmentListener"]; 
listener@
            setHandler["adjustmentValueChanged", "sliderFunc"]; 
slider@
            addAdjustmentListener[listener]; 
frame@
            setModal[]; 
frame@
            setLocation[200, 200]; 
JavaShow[
            frame]]]
 (*\ 
      This\ function\ is\ called\ when\ the\ slider\ position\ \(\(changes\)\(.\)\)\ *) \)
sliderFunc[evt_, type_, scrollPos_] := 
      JavaBlock[
        outText@setText[
            Block[{a = scrollPos}, 
              ToString[
                ToExpression[
                  inText@getText[]]]]]; 
ReleaseObject[
          evt]]
RealTimeAlgebraModal[] := 
      JavaBlock[  
          CreateWindow[]; 
DoModal[];]
RealTimeAlgebraModeless[] := (CreateWindow[];
ShareKernel[];
 (* 
          Note that you leak the inText and outText object references in the modeless version .  
              They could be cleaned up by calling ReleaseObject .  
              This could be done by providing the user a special clean up function to call when they are finished with the dialog box, or by arranging for it to be called automatically when the dialog box closes using MathWindowListener. *)

Examples

You must call InstallJava prior to running these examples.

InstallJava[];

This runs the dialog box in a modal way.

RealTimeAlgebraModal[]

This runs the dialog in a modeless way.

RealTimeAlgebraModeless[]

After you are finished with the modeless version, close its window and execute the following command to turn off kernel sharing.

UnshareKernel[]