http://www.developer.com/

Back to article

Building Java GUIs with Matisse: A Gentle Introduction


March 8, 2006

It is a curiosity that when Java was first introduced, it made a name (some would say "became synonymous with") simple interactive GUIs and animations in applets embedded on Web pages. It was the first foothold for the language. At the time, the GUI construction was simpler than a lot of the alternatives out there (it was the time of Motif, windows 3.1 and does anyone remember NeWS?).

It was also a little limiting—AWT provided a decent way of getting a few buttons and menus put into an applet or even a Java application if you were ambitious, but the results were fairly limited and simple.

To combat this and bring the possibility of richer desktop applications and a higher abstraction to GUI building in Java, Swing was created. Swing learned much from other GUI toolkits available at the time and brought in a lot more flexibility... and some more complexity.

Fast forward to a year ago and the state of the art for Swing creation revolved around numerous layout managers, the most flexible (and notorious) being the GridBagLayout. This is an amazing layout manager that lets you control just about every aspect of every widget placement in every form. The problem is, it requires you to.

Netbeans, and various other IDEs, already had good support to help with creating grid bag layouts, but these usually involved a different view of the UI from the WYSIWYG one, and it takes a while to learn all the controls and settings even in these helper tools.

In NetBeans 5.0, however, a new option is available. Matisse is a new GUI builder that uses the GroupLayout manager. This is a new layout manager that is not a part of standard Swing (yet), but can be freely distributed with your application.

Matisse is Java UI building done right. It is familiar, offers drag and drop placement, and sensible defaults that can still be altered and overridden easily for flexibility. It is a GUI builder as easy to use (with a couple of minor exceptions—more on those later) as Visual Basic, but has a huge advantage over VB in that the resulting forms and panels can be re-sized in a meaningful way, without having to write all sorts of resize event handling code.

This article will outline some of the new features in Matisse and is intended to give you a taster of what is possible with it. It details a real application (albeit very small and simple) that I recently wrote to solve a real-world need. My hope is that this will demonstrate what is possible with Matisse, and whet your appetite to find out more.

I will also point out that I am not generally a Swing or even a GUI developer. I may commit atrocities in this article from a UI perspective and, if I do, apologies. However, my point here is to show that anyone can use Matisse to make an application—even a server-side hacker like me.

Prerequisites

Before following along with this article, you will need to grab NetBeans IDE 5.0 from http://www.netbeans.org/ and install it. I also recommend reading or printing out the Matisse GUI Builder Legend at http://www.netbeans.org/kb/50/quickstart-gui_legend.html; it provides a handy cheat sheet of what the various guidelines mean when creating a GUI in Matisse.

The source code for this example can be downloaded from here.

About the Application

You may or may not be aware, but I am co-host of a podcast all about Java called the Java Posse along with three other guys (Tor Norbye, Carl Quinn, and Joe Nuxoll). We record at least once a week and have adopted the approach of everyone recording their own microphone feed, and then mixing the results into a single audio file using a multi-track audio editor. This produces great results, but also takes a fair chunk of time. The longest part of the editing process is removing the mistakes or "Flubs" as we call them. This can be long pauses, words that are tripped over, more than one of us talking at the same time, and so forth. Because we are not audio professionals, and are using skype (with the inherent lag), there tends to be quite a lot of these.

As a result, I try and capture these flubs as they happen. To start with, I just wrote them down, with a rough time, but this took too long and too much attention. So, I decided to write a simple GUI app to make the job easier, and to spit out the results in the correct format for Audacity labels, which is the multitrack editor I use to edit the shows.

The format for the labels file in Audacity is very simple. It consists of a start time and end time, in seconds (with decimal fractions), and then a text table.

To keep my "flubber" application simple, I make the start and end time the same, so markings refer to an instant, not a time period. Also my labels are simply a counter, starting at 1 and counting up (this way I don't miss any when scrolling through the audio).

So, my file might look like this:

1.576     1.576     1
5.1215.1212
83.12283.1223

My flubber application (or jflubber as I call it, to avoid any legal issues with Robin Williams) is a simple way of starting a clock, marking flub points as instantaneous moments of time, and spitting out these moments as a text file that can be loaded into Audacity. Simple, but useful and effective.

Getting Started

Okay. Now it's time to build jFlubber. First, you need to create a Java Application, so fire up NetBeans 5.0 and:

  1. From the file menu, select New Project...
  2. In the new project dialog, select General in the left pane, and Java Application in the right pane, Click Next.
  3. Choose a project name (jFlubber?) and a package and name for the main class (I chose org.bldc.jflubber.Main because I own the bldc.org domain). Click Finish.
  4. The Main class will be created and fleshed out with a minimal implementation.

Create the UI

Now for the fun part. You will paint your UI using Matisse. First, you need a new form:

  1. In the projects pane (top left), right-click on the package you created with the Main class inside of it (note that you need to click on the package node, not the Main class), and select New -> JFrame Form...
  2. Name the form. I suggest something like JFlubberMainFrm (don't use a lowercase j at the beginning of the name because the convention in Java is for classes to start with uppercase).
  3. Leave everything else as default and click Finish.
  4. You should see a new empty grey form on the page, ready to paint the GUI into.

Your GUI needs a text label, five buttons, and a text area. The buttons are:

  • Start: To start the timer
  • Stop: To stop the timer
  • Flub: Nice big easy to hit button to mark a flub point in the recording
  • Save: Save the labels from the text area out to a text file
  • Clear: Clear the text area ready for a new flub session

The text label will be at the top and will show the current time elapsed since the start button was pressed, in minutes and seconds. Why not hours, minutes, and seconds? Why not microseconds? Well, because I prefer minutes and seconds. You are, of course, free to format this stopwatch display however you like.

The text area will hold the flub points formatted in the Audacity label format. It's not strictly necessary to see this, but it is a nicety to be able to get visual feedback when a flub point is added.

To see what you are aiming towards, the finished UI should look like this:

To create this UI in Matisse, first set up the time label:

  1. From the component palette in the top right of the IDE, drag a jLabel over the form but don't drop it right away. Instead, move it towards the top left of the form until you see two dashed lines about a quarter of an inch from the top and the left side of the form appear. These are your guidelines in Matisse; they help you position components in a way that the GroupLayout manager knows how to resize them.
  2. Next, grab the right end of the new label component and drag it towards the right of the form, until you see another guideline appear about a quarter of an inch from the right side of the form.
  3. By doing this, you have told Matisse to keep this label anchored to the top, left, and right sides of the form, expanding the label as necessary when the form is resized.
  4. Next, you want a nice big font for the timer label. Select the label by clicking on it (if it is not already selected), and look in the jLabel1 properties (right side of the IDE) for the Font property. Click on the ... .
  5. I suggest Dialog, Bold, 18 pts for the text fonts, but you can use anything you like here.
  6. Click OK, You also want the label text centered (it looks nicer), so select CENTER for the HorizontalAlignment property.
  7. I suggest renaming the label to something more meaningful than jLabel1, so find it in the inspector pane (left side of the IDE) and click on jLabel1 there and leave the mouse over it for a second or two. It should go into rename mode. Iype in a better name, like timerLabel, and click return.
  8. Finally, the label still reads jLabel1 on the form. It would be better if it read 0:00, so in the properties (or by double-clicking on the label itself) change the text to read 0:00.

That seems like a lot of steps, but that's because I took the description very deliberately. In fact, with a little practice you will be creating and placing UI components very quickly and easily in Matisse. Next, you can add the buttons:

  1. Grab a jButton from the component palette and drag it to the top left of the form again, under the timer label this time, until the guideline appears on both the top and left of the button. Drop it.
  2. Do the same for a second button, but this time drop it on the right side of the form. This time, however, instead of a guideline above the button from the label, you will probably see a new type of guideline that runs through both buttons just under the text label (to align the text) as well as the familiar anchor guideline on the right of the button. When you see this, drop it.
  3. Now, for the clever bit: Starting with the left button, drag the right hand side into approximately the middle of the form. You will not see a guideline yet. Don't worry, and you also don't have to get it exactly half way, just approximate.
  4. Next, grab the left side of the right button and drag it left until it is about a quarter of an inch from the other button. This time, you should see a guideline appear. When you do, let go.
  5. Next, you want to make the buttons actually be the same size. Select both buttons by first selecting one, then shift-clicking on the other. Check that only these two buttons are selected and no other components (this is important).
  6. Right-click on one of the buttons and select Same Size->Same Width from the popup menu.
  7. When you do this, you should see both buttons resize a bit. You might also see the form change size slightly as everything gets re-aligned. Don't worry about the form size changing; you easily can change it later.
  8. Change the names of these buttons to startButton and stopButton (in the same manner as for the label); also, change the text of the buttons to Start and Stop.
  9. Now, you want the nice big flub button. This should be the full width of the form and about twice the height of the start and stop buttons (just make it look nice and big), so grab another button and lay it out anchored to the left and right of the form, and below the start and stop buttons, then drag the height down to make it big. You should be getting the hang of the layout by now. Nice, isn't it?
  10. Rename the component flubButton in the inspector pane, and also change the button text to read Flub. I also recommend changing the font size in the button font properties to 18pt or something nice and big.
  11. The final two buttons need to be added at the bottom of the form, side by side, like the start and stop buttons. Just drag two buttons down there and lay them out just like the start and stop buttons, but anchored to the bottom of the form instead. Call these saveButton and clearButton, and rename the labels to Save and Clear respectively.
  12. Unlike before, instead of making these buttons the same width as each other, I find it easier to select the Save and Start buttons at the same time (shift-click until these are the only two components selected) and then right-click and make them the same width as each other, then do the same for the Stop and Clear buttons. This will get all of the buttons even sized.
  13. Finally, now that all of these buttons are the same width, select all four of the smaller buttons on the page, and then click the <-> icon in the toolbar at the top of the so that it is toggled on.
  14. By doing this, you are ensuring that these buttons will resize and retain their proportionate sizes to one another when resizing the whole form. You can test this out very easily by dragging the bottom right corner of the form to resize it. You should see all of the components resizing in realtime as you would expect. If not, double check the steps here and see if you missed any.

Finally, you want to add the text area to hold the flub points and display them.

  1. Grab a jTextArea from the component palette and drag it into the form until guidelines appear on the left side of the form and under the big Flub button.
  2. Drag the bottom right of the text area to resize it until guidelines appear on the right side of the form and above the Save and Clear buttons.
  3. Rename the text area to flubPointsTextArea.

At this point, it is a good idea to save all (in fact, Matisse is still fairly young, so I recommend saving a lot just as a precaution).

Again, try resizing the form in the GUI editor to make sure everything re-sizes as you would expect.

Testing It Out

You have constructed your new GUI. To give it a test, you need to add a little code to the main class to cause it to be displayed:

  1. In the IDE, find the Main class that the application creation wizard made for you and double-click on it to open it.
  2. In the main method in here, replace the TODO comment with the following two lines:
  3. JFlubberMainFrm jFlubberFrm = new JFlubberMainFrm();
    jFlubberFrm.setVisible(true);
    
  4. Note that if you called your form class something other than JFlubberMainFrm, you will naturally have to adapt the above code to use that name instead.
  5. Finally, run the project (Run from the menu or the toolbar).
  6. You should see your form appear for real, and it should resize as you would expect. Of course, none of the buttons are hooked up yet, so it will be a bit useless; however, you have used Matisse to lay out a GUI with resizing events already handled for you.

Making It Work

Close down the running application if it is still up. It's time to add some functionality to the buttons.

The main trick left to demonstrate with Matisse is hooking up the buttons. The rest is simply filling in code, and because this will be a very long article if I go into all of the code in great detail, I will provide all of the code snippets and some brief descriptions, but won't go into enormous detail in this area. Suffice it to say that the backing code is fairly straightforward and should not be surprising to anyone who takes a little time to understand it.

Also, again for brevity, I will put all of the code into the Form Class you created. This is fine for a small app, but in a larger app you will want to think carefully about how you lay out your code for maintainability and good coding practices.

The Timer Thread

One thing you will need for this app is a timer thread that, once the clock is started, keeps updating the timer label periodically with the elapsed time of the recording. You also need a few new attributes for this, so select JFlubberMainFrm.java in the editor, and then click on the Source view near the top of the edit window so that you can see the Java source.

Then, add the following code right after the public class JFlubberMainFrm extends javax.swing.JFrame {:

private Timer timer    = null;
private int labelNo    = 0;
private long currTime  = 0L;
private long startTime = 0L;

class UpdateTimeTask extends TimerTask {
   public void run() {
      long millis = System.currentTimeMillis() - startTime;
      int seconds = (int)(millis/1000);
      int minutes = seconds/60;
      seconds     = seconds % 60;

      timeLabel.setText(String.format("%d:%02d", minutes, seconds));
   }
}

This code defines four attributes used to track the time and label number, and also a TimerTask that runs a thread that will be used later in the class to update the time label periodically once the start button is pressed. Note that this code uses the new Java 5 convenient text formatting. If you are still using Java 1.4, you will need to use a message formatter instead to get the minutes and seconds formatted nicely.

After you have put in this code and saved it, you will probably see some missing class errors. Just right-click in the editor window and select Fix Imports to add in the required classes. For the TimerTask, select the java.util one rather than the swing one if asked by the Fix Imports wizard.

Button Events

Everything is now in place. You now can add some button event handlers:

This is where my own lack of knowledge of Swing may show though. There are a lot of different events in Swing and I tend to stick with the ones I know that do what I want. Whether they are strictly speaking correct or not is another matter.

In fact, this leads me to my one complaint about Matisse right now, compared with something like Java Studio Creator where you can double-click on a button and get a sensible default event generated for that button. In Matisse, you can't do this; you have to right-click and select the event you want, from a somewhat overwhelming list of options.

For example, given a button, the default event I want is when someone clicks the button. In Java Studio Creator, a double-click gives me a handler for exactly this event, which is really nice and saves me time and worry. In Swing, for the button I have a choice of ActionPerformed, MouseClicked, MousePressed, MouseReleased and many, many others. I am sure there will be times I would want to use some of these other options, but by far the most common is someone clicking the button, so couldn't Matisse be made to select a logical default in this case? If it does do this already, I have not yet found the magic way to do it.

Instead, for buttons I tend to stick with the MouseReleased event (sort of like the MouseUp from the apple way of event handling). In other words, the event is fired not when the button is pressed, but when the button is released after being pressed. This seems to work the best and most consistently.

Knowing this, adding code to the MouseReleased event on the button is pretty easy:

Start Button

  1. Select the Start Button in the Design view of the form.
  2. Right-click on the button, and select Events->Mouse->Mouse Released.
  3. After you click on that option, the view should change to the code view and you should see a new event created for you that you can fill in the handler code.
  4. Add the following event code into the new method (replace the TODO comment):
  5. if(startTime == 0L) {
       startTime = evt.getWhen();
       timer = new Timer();
       timer.schedule(new UpdateTimeTask(), 200, 200);
    }
    
  6. Save this and fix any import problems with the Fix Imports popup menu option.
  7. Don't worry too much about the code above, but in a nutshell it stores the current time into the startTime attribute, and then creates the timer thread to update the timer label every 1/5 of a second.

You want to add similar handlers for the other buttons, so repeat the above steps to attach the code below to each of the buttons as named:

Stop Button

if(timer != null) {
   timer.cancel();
   startTime = 0L;
}

Flub Button

labelNo++;
this.addLabel(Integer.toString(labelNo));

Note that this method uses a method called addLabel for convenience. You have not defined it yet, so define it in the class as:

public void addLabel(String label) {
   long millis = System.currentTimeMillis() - startTime;
   double seconds = (double)millis / 1000;
   String labelList = jTextArea1.getText();
   labelList = labelList + Double.toString(seconds) + "t" +
               Double.toString(seconds) + "t" + label + "n";
   jTextArea1.setText(labelList);
}

Save Button

You want to use this to put a filechooser up for the user to let them save the text from the text area:

JFileChooser chooser = new JFileChooser();
int rVal = chooser.showSaveDialog(this);
if(rVal == JFileChooser.APPROVE_OPTION) {
   File outputFile = chooser.getSelectedFile();
   PrintWriter outputStream;

   try {
      outputStream = new PrintWriter(new BufferedWriter(
                     new FileWriter(outputFile)));
      outputStream.print(this.jTextArea1.getText());
      outputStream.close();
   }
   catch(IOException ex) {
      ex.printStackTrace();
   }
}

Clear Button

This needs to set the text area to empty, and set the label count back to 0 (so that the labels start getting added from 1 again).

this.jTextArea1.setText("");
this.labelNo = 0;

Again, the above doesn't have a lot of explanation attached, so if you get confused as to what goes where, just take a look at the source code from the zip file included with this article and it should become apparent.

Building and Distribution

Once you have added this code, saved, and fixed any import problems (Fix Imports from the editor popup menu), select build main project from the IDE build menu.

When this builds, you should notice that the IDE reports that it has already wrapped the application up into a jar file for you (called jFlubber.jar in my case) and it also says that you can run the application using java -jar jFlubber.jar (with appropriate path information to tell java where jFlubber.jar is). However, that might not be the case, especially if you take the jar file created out of the dist directory and run it from somewhere else on your machine. Instead, you may see the error:

Exception in thread "main" java.lang.NoClassDefFoundError:
org/jdesktop/layout/GroupLayout$Group

If you see this, the reason is that the GroupLayout that Matisse uses is not part of the standard Java distro, and so when you try and run the jar file with a normal java distribution, the layout manager is not available.

Unfortunately, the fix for this is a little ugly, and the only other real complaint I have about Matisse usage in NetBeans 5.0. One can imagine that this problem will be one that crops up for every Matisse-based GUI created, and how nice it would be to have a simple project property checkbox to say "include Matisse related classes in the distribution jar file", but if there is such an option, I have yet to find it.

Instead, the fix isn't hard, but it's irritating. In a nutshell, you have to alter the ant script used to build the project and add the missing classes required. To do this:

  1. In the top left hand pane, select the Files view.
  2. Open the project nodes as necessary until you see the build.xml file, and then double-click to open it.
  3. At the end, just before the </project> line, add the following code:
  4. <target name="-post-jar">
       <jar update="true" destfile="${dist.jar}">
          <zipfileset src="${libs.swing-layout.classpath}"/>
       </jar>
    </target>
    

This defines a task that NetBeans calls automatically to roll the GroupLayout class into the jar file so that it will run anywhere. This is a useful thing to know for other such dependencies, without doubt, but if Matisse is going to be the way to create Java GUIs now, how much better it would be to make this step much easier with a project-level setting.

Note that there is a lib folder that you can keep in the same place relative to the jar file that will also keep this working; however, most developers roll a single jar file for the java -jar type of distribution simply because it is much easier to distribute that way. Your mileage may vary. Perhaps I am way out of line for suggesting the rolling up of GroupLayout into the jar file; it's simply my preference (and it makes things run much better if you are calling the jar file in a different directory).

After you make and save the change, do a Clean and Build from the Build menu; the whole thing should be rolled up with GroupLayout this time. Then, run java -jar jFlubber.jar again.

The Result

So, now you should be able to run your new jFlubber application. From the IDE, just click the run button or menu entry, or you can create a .bat file or .sh file depending what operating system you are using, and run the java -jar jFlubber.jar command that way. Either way you run it, the flubber application should be running just fine.

Here is a screenshot of my version running in Ubuntu 5.10 Linux:

Conclusion

So, this little application is unlikely to win any awards, beauty contests, or just about anything else, but it does serve as a good introduction to Matisse development, and also fills a useful role in my podcasting production tools. I am certain this program could be vastly improved, which is why I stuck it out there under the GPL license, and I hope it will be useful either as an example or an actual application to others.

Note also that it is a very simple application. It demonstrates only a fraction of what Matisse can do; furthermore, it doesn't even touch on the NetBeans Rich Client Platform (RCP) that will save an enormous amount of time for anyone doing more complex applications. Perhaps I will dive into the RCP in a future column, once I have got to know it better, but in the meantime I hope this example has been useful.

If anyone wants to point out improvements in the way this application was written, please feel free to contact me. I am eager to learn more about Swing development now that there is finally a tool that makes it easy enough for me to be interested again. It's hard to believe that it has taken this long for a really good Swing GUI creation solution like this to come along, but I am very glad that it has.

Further Reading/Resources

About the Author

Dick Wall is a Principal Systems Engineer for NewEnergy Associates, A Siemens Company based in Atlanta, GA that provides energy IT and consulting solutions for decision support and energy operations. He can be reached for comment on this and other matters at dick.wall@newenergyassoc.com. He also co-hosts The Java Posse, a podcast devoted to Java news and the Java community, which can be found at http://javaposse.com.

Sitemap | Contact Us

Thanks for your registration, follow us on our social networks to keep up-to-date