Skip to main content

skip to main content

developerWorks  >  Java technology  >

Buoy makes simple Java UI programming a snap

Keeping afloat in a sea of widgets

developerWorks
Document options

Document options requiring JavaScript are not displayed

Sample code


Rate this page

Help us improve this content


Level: Intermediate

Peter Seebach (developerworks@seebs.plethora.net), Writer and developer, Freelance

22 Mar 2005

Buoy, a free user-interface (UI) tool kit built on top of Swing, offers convenience and simplicity to UI developers. In this article, developer and writer Peter Seebach takes a look at what Buoy does and why it works, using a simple fractal UI program.

The first time I tried to build a simple user interface (UI) using the Java™ language, I was a little surprised at the complexity of the Swing interface. It was more than a little daunting, to say the least. Recently, a friend mentioned to me that the rendering program he uses, Art of Illusion (see Resources), was based on a different tool kit: Buoy. He recommended it as a friendlier interface. The first time he referred to it, I thought he was saying "BUI," and it turns out the similarity of the name to GUI is intentional. The B sort of stands for better, but the name Buoy isn't an acronym.

Buoy is free. In fact, it's public domain. It's not that it's been released under a reasonably open license but it's actually governed by no license at all. This means that you can use Buoy in any project written in a version of the Java language that can run it, with no worries about licensing. Because complete source is provided, it's easy to touch up or extend the tool kit if you feel the need. This article is based on release 1.3 of Buoy. It assumes basic familiarity with Swing, although you can probably muddle through without it.

The sample program

The first application I attempted to build using Swing ended in failure. To see how the tool kits compared, I decided to use that same program to put Buoy through its paces. The code samples in this article all come from the Buoy version of this program. The program generates fractals -- specifically, iterative fractals. The basic idea is simple: Define a series of line segments on a plane, oriented around an arbitrary unit line from (0,0) to (1,0). Draw these segments; then, for each segment, draw the same set of line segments transformed to use that segment as the unit vector. Easier done than said, as you can see in Figure 1.


Figure 1. A fractal in the fractal editor
A fractal in the fractal editor

This program's interface is fairly simple. It has a handful of interface widgets, a canvas to draw pretty pictures on, and support for manipulating the pretty pictures with the mouse. Really, all you have to be able to do is manipulate the points that make up the original line, which will be drawn iteratively. It also has a very minimal menu; it can open and save files, close the window, or save a PNG of the current image. Though simple, the interface provides a brief whirlwind tour of a reasonable sample of Buoy widgets, as well as a fair amount of experimentation with the event-handling system.

The code for the actual guts of the program, the fractal generator, was already written, making this an excellent test program. Of course, I found and fixed a few bugs in the process of updating it.

The distribution package contains the source code for the sample program, as well as the compiled class files and the Buoy JAR file (click the Code icon at the top or bottom of this article to download fractal.tar). It also contains a directory called frac that contains a bunch of sample fractals. If you've got a UNIX®-like machine with a Java compiler in your path, you can just run make to run it. Otherwise, set your classpath to include the current directory and the Buoy JAR file, and run the FractalViewer class. On a Windows® system, the right command line appears to be java -classpath .;Buoy.jar FractalViewer.



Back to top


sed -e s/J/B/g

When you first dig into the code, you may get the impression that converting Swing code to Buoy code is as simple as replacing J with B in the names of UI elements. For instance, the FractalViewer class no longer extends JFrame; it now extends BFrame. The names of major widgets are, likewise, predictable. Spinners and sliders have the same names as before, with only the one letter changed. A MenuBar still consists of Menus that in turn hold MenuItems.

Some naming conventions are a little different. Where Swing refers to a BorderLayout, Buoy has a BorderContainer. In general, Buoy's naming conventions are fairly consistent, although they aren't always the same as Swing's. One obvious difference is that Buoy mostly combines the notions of containers and layout managers; each type of container knows how it likes to do layout. This simplifies design a lot. For instance, the LabelWidget class used in the fractal generator is a BorderContainer; in Swing, this would probably have been a JPanel used with a BorderLayout layout manager.

Still, there's a lot of similarity. This helps a lot in getting used to things. More importantly, Buoy is built on top of Swing. That means, in general, that you can take a Buoy object and get past it to the Swing object it's a wrapper around, if you need something that isn't easily done within Buoy. For that matter, if you want to attach some Swing component that has no Buoy analog, you can simply wrap it in an AWTWidget object, which gives you a very thin wrapper, providing access to the Buoy widget API for all your widgets, not just the native Buoy ones. You might need to do this if, for instance, you found yourself really wanting a GridBagLayout.

For example, the FractalPanel class is an AWTWidget. In an earlier draft, it was a subclass of JPanel, but, in fact, I didn't need any of the JPanel code. Instead, I built the class to wrap around a custom class, FractalCanvas, which is itself a subclass of plain old Canvas. Making this an AWTWidget makes it possible to use Buoy's streamlined event handling with it.

The event-handling code is pretty straightforward. When a mouse button goes down, Buoy sends a new MousePressedEvent, which is sent, through the magic of addEventLink(), to a function called mousePressed(). I lazily ignore the question of which button was pressed and just assume that it's a shift-click or a plain click. A plain click selects the nearest point, while a shift-click re-centers the display. Then, if the mouse is moved, Buoy starts sending a MouseDraggedEvent every time movement is noticed. When processing these events, the FractalPanel generates events of its own.

A closer look at PointChangedEvent

To help make some of this discussion more concrete, let's take a look at PointChangedEvent. This was an experimental class, and if you don't like it, you'll have to blame a very large cup of coffee. The idea of this class was to have a single class indicating any change in the state of points. The editor keeps track of the "current" point -- that is, the point currently being edited by the editor widgets. You can select a new point using those widgets or by clicking in the fractal panel, which selects the nearest point.

I came to the conclusion that there were three likely kinds of events involving points that may need to be sent from one class to another within my code.

One is changing the characteristics of a single point: the POINT event type. If sent by the editor, this tells the fractal to alter that point on its prototype line and request a redraw. If sent by the fractal, it's telling the editor the qualities of the point just selected.

The next is selecting a point. Selection can be done by index or by location. Thus, if only an index or only a location is provided, the constructor assumes that the intent is to get the other value filled in. There's a bit of special magic in that the point index -1 is used to indicate no selected point, so -2 has to be used to indicate that the editor is looking for a point at a given location. This isn't pretty, but it works.

Of some interest is how the Fractal class responds to a SELECT event. If it successfully selects a point, it sends back a new PointChangedEvent, of type POINT, as illustrated in Listing 1.


Listing 1. Let me answer your event with an event

case PointChangedEvent.SELECT:
    if (e.getIndex() >= -1)
        selectPoint(e.getIndex());
    else
        selectPoint(e.getPoint());
    // just in case they don't know
    event(new FractalChangedEvent(FractalChangedEvent.SIZE, size));
    if (selectedPoint >= 0 && selectedPoint < size)
        event(new PointChangedEvent(selectedPoint, points[selectedPoint]));
    else
        event(new PointChangedEvent(selectedPoint, null));
    event(new FractalChangedEvent(FractalChangedEvent.REDRAW));
    break;

Finally, moving a point is a special case, if none of the point's other attributes, such as color, are changing, and all we have is a location. This is the MOVE event type. This is very similar in effect to the POINT event type, but doesn't require the event's generator (generally, the FractalPanel class) to pay attention to attributes it knows nothing about.

The INSERT and DELETE event types are only marginally related and perhaps should have been in FractalChangedEvent.



Back to top


Event handling

As you've already begun to see, event handling is where Buoy is most visibly different from Swing. Event handling provides a great deal of flexibility. The native Buoy set of events is fairly rich and allows you to send events from any widget to any other objects, picking and choosing the events you care about. For instance, if you want to catch a mouse event in Swing, the class that will catch the event needs to implement MouseListener. This gives you five functions to implement, even if they're just stubs. You must use the provided function names. Worse, they must be public parts of the listener's interface; either you expose this as part of your public interface or you make an inner class that does nothing but wrap up your event listener code.

In Buoy, each widget is an EventSource. That means that you can listen to it for events. What type of events? Any type you want. The key function is addEventLink(). This allows you to specify a class, a listener, and, optionally, a method. Whenever the EventSource dispatches an event of that class or its subclasses, the listener will receive the event, either through a method called processEvent() or through whatever method name you provided in the initial call to addEventLink(). The function you provide has to take no arguments or take an object of a class compatible with the provided event type; superclasses and interfaces are OK.

This is a convenient setup. You can route different events to different functions or to the same function. For instance, MousePressedEvent and MouseReleasedEvent could be treated separately, or you could grab them both by listening for a WidgetMouseEvent. In my sample program, I have separate routines for mouse presses, releases, and drags, as illustrated in Listing 2. Note that this is more than a Swing MouseListener can do. If I were programming in Swing, I'd need to implement both MouseListener and MouseMotionListener.


Listing 2. Cherry-picking the events I care about

this.addEventLink(MousePressedEvent.class, this, "mousePressed");
this.addEventLink(MouseReleasedEvent.class, this, "mouseReleased");
this.addEventLink(MouseDraggedEvent.class, this, "mouseDragged");
[...]
public void mouseReleased(WidgetMouseEvent ev) {
	lastCenter = null;
	dispatchEvent(new FractalChangedEvent(FractalChangedEvent.SLOW));
	setAntiAliasing(true);
}

The mouseReleased() function has the least work of any to do. It just cleans up after the mousePressed() function and tells the Fractal object that it's time to start drawing in full depth again.

Buoy's event handling has another interesting feature. You can make new event types, if you want. An event type is a class. That's it. It doesn't even need to subclass anything or implement anything. It's just a class. If an object of that class is passed to dispatchEvent(), then any listeners for it or its superclasses will get called. You can make new event types in Swing, but you're entirely on your own; you have to design a Listener interface and write your own code to generate events and listen for them. In the sample program, I designed the Fractal class to show the comparative ease of bolting event handling onto any old class. It simply declares an EventSource that the FractalViewer class uses to attach listeners. The FractalViewer class then sets up event links from event sources, such as the FractalEditor, to their listeners, as shown in Listing 3.


Listing 3. The ties that bind

private void tieEvents() {
	// Set up event handling relations.
	addEventLink(WindowResizedEvent.class, this, "layoutChildren");
	addEventLink(WindowResizedEvent.class, panel, "repaint");
	tieControlEvents();
	tieFractalEvents();
	tiePanelEvents();
}

Custom event classes tend to represent user actions. In Buoy, events are normally generated only by user actions, not by the system interface -- unless you want to generate them yourself by explicitly calling dispatchEvent(). The control panel with all the widgets would like to be notified any time the fractal object changes in a way that would lead to updates in fields. So, we invent a new class, ParameterChangedEvent, that can be used to indicate that parameters have changed. Or, if what's changed is the location -- or index -- of the selected point, we send a new PointChangedEvent. The event handler doesn't even need to take an argument if the behavior is obvious enough. As an example of how events are processed, have a look at Listing 4, which illustrates the beginning of the parameterChanged() method of FractalEditor.


Listing 4. The parameters, they are a-changing

void parameterChanged(ParameterChangedEvent ev) {
	FractalParameters p = ev.getParams();
	int v = ev.getValue();

	switch (ev.getType()) {
	case ParameterChangedEvent.ALL:
		maxSlider.setValue(p.getMaxIterations());
		minSlider.setMaximum(p.getMaxIterations());
		minSlider.setValue(p.getMinIterations());
		maxSlider.setMinimum(p.getMinIterations());
		zoomSlider.setValue(p.getZoom());
		break;
	[...]

In this case, I used the event-handling system to pass all sorts of messages back and forth. In an earlier revision, every class had a reference to every other class, and promiscuous get methods were the order of the day. In the current revision, the Buoy event-handling system has been used to handle all sorts of notifications. For instance, the FractalChangedEvent class can be used to let other parts of the code know about changes to the fractal, whether it's a change in the number of points -- which the editor uses to define the right SpinnerNumberModel for the point selector -- or a notification that a redraw is needed, as shown in Listing 5.


Listing 5. It's apparently time to redraw

public void fractalChanged(FractalChangedEvent e) {
	switch (e.getType()) {
	case FractalChangedEvent.REDRAW:
		repaint();
		break;
	}
}

The Buoy documentation discusses the differences between the Swing event model and the Buoy event model in some detail, and the reasons for those differences. There are good reasons, and Buoy's model generally lends itself to smaller, cleaner code. Of course, you're still allowed to do abusive or silly things, as you could in any system, but at least you have a nice clean interface to do them in.



Back to top


Learning curve

I once observed that an afternoon isn't long enough to learn to use a GUI tool kit. With Buoy, I needed probably six hours, or close to a full working day. I did have excellent help, in the form of a more experienced Buoy user. My previous experience with Swing was helpful, but, in fact, I don't think it was all that necessary. The Buoy documentation is pretty good, and the simplicity really helps. There's just not as much to learn for basic UI things.

The documentation isn't as complete as the Swing documentation, but there's a lot less to cover, and it's pretty good. Furthermore, the source code is right there, so it's easy enough to answer simple questions about the interface. It'd still be nice to have more complete documentation -- but then, the project is hosted on SourceForge, and you can write more and contribute it if you'd like.

The learning curve is a big advantage for Buoy over Swing. The majority of the interface widgets will work correctly using fairly simple interfaces. To use an example from the Buoy documentation: Under Swing, a JList requires you to either use a static list or build a new class implementing the ListModel interface. In Buoy, you can just add items to the list; the hard work is done for you in the most common case.

Buoy is fairly small. The complete distribution, including source, JAR file, and documentation, weighs in at less than 1 MB. The code is well organized, and it's easy to find any given piece of code to have a look at it. If you need to tweak the design a bit, it's not hard.



Back to top


Bugs

While Buoy is a stable and usable system, it's not an absolutely perfect one. There are occasional quirks where an apparently reasonable choice results in surprising behavior. If you're thinking about doing a real project in Buoy, you need to know about bugs: how common they are, how serious they are, and how easy they are to work around.

In the course of developing this application, I ran into a handful of things that, at the time, seemed to me to be bugs. Not all of them were. A couple were probably bugs in the documentation -- cases where the code's behavior was not what I expected, but was perfectly reasonable. In fact, I'm pretty sure none of them were bugs in Buoy per se, but they do illustrate the kinds of things one might encounter debugging Buoy applications. After spending a couple of days debugging my code, I am pretty sure that every apparent bug I ran into was either my fault or an underlying design decision in Swing that I find myself unhappy with. I think it's safe to say that working in Swing would not have prevented any of these.

Slider ticks

The most persistent bugs I had, early on, involved the tick marks on the sliders. At first, I couldn't get labels to show on them.


Listing 6. Frustrating slider code

minSlider.setShowLabels(true);
minSlider.setMajorTickSpacing(2);

The code in Listing 6 doesn't work. You see, the labels only show up after you've set the tick spacing. If you haven't set the tick spacing before you tell the slider to show the labels, it ends up not showing any labels. More subtly, you can't change the spacing later; attempts to change the spacing have no effect. This is, however, not really a bug in Buoy; it's just how Swing works. Since the BSlider class is just passing the requests on to JSlider, I don't think it's fair to blame Buoy.

A more subtle bug, also related to a quirk of the underlying JSlider, involved the snap-to-tick behavior. The BSlider constructors set the minor tick spacing to 5, and the major tick spacing to 20 -- reasonable values for the default size of 100. When I created a slider with a range of 1-10, however, I didn't see any need for minor ticks, so I only set the major tick spacing value to 1. This resulted in a slider with values of 1-10, which would stick at 1 and 6 exclusively; the snap-to-tick behavior prevented it from holding other values because it snaps to minor ticks, not major ones.

While this is rooted in the JSlider implementation, it's tickled by the Buoy default behavior, and it will be fixed in the upcoming 1.4 release.

Note that the only reason this is problematic for me is that the sample program constantly updates the range of some sliders. For instance, if you have only a single line segment, it will let you go up to 50 iterations, so having every number on the slider is a bit much to ask. On the other hand, if only a few iterations are allowed, having numbers missing looks bad. In an interface where a slider's range won't constantly be updated, this is not nearly as inconvenient.

Menu shortcuts

Buoy conveniently offers constructors for menu shortcuts using characters or KeyEvents. When I first tested this, I couldn't make it work. It turns out that this actually has to do with using lowercase letters; you have to use 'W' instead of 'w' when calling the constructor, as illustrated in Listing 7.


Listing 7. Adding the close menu item

mi = new BMenuItem("Close", new Shortcut('W'));
mi.setActionCommand("close");
mi.addEventLink(CommandEvent.class, this, "menuEvent");
fileMenu.add(mi);

This may have to do with having accidentally played mix and match with the Java 5.0 SDK and the 1.42 JDK. It appears that, if you give uppercase letters to the constructor, it does the right thing as often as you could hope for. The underlying question -- what set of keys or modifiers should the JVM look for to represent Ctrl-? -- is a little much to expect a small free library to solve entirely.

File choosing

For reasons not obvious to me, Buoy defaults to the root directory when launching a new file chooser on the Mac. I had an elaborate bug report about how it seemed to be ignoring a lot of files, but then I realized that I'd moved my home directory from /Users/seebs to /Volumes/Home/seebs, and the file chooser was indeed showing me exactly what was on the disk. Score: Buoy 1, Seebs 0.

I would still like to know why it's starting at the root of the file system; this may be a quirk of the Mac implementation of the JVM.



Back to top


Summary

Buoy adheres to the famous old UNIX philosophy: 10 percent of the work solves 90 percent of the problem. Buoy does not try to be all things to all people, but it will do the majority of the work an interface user or designer needs right out of the box. It has the best possible licensing terms and is under continuing development. Best of all, if you find that it doesn't let you do something you really need to do, you're allowed to go poking around under the hood and fix it, whether by altering the Buoy source or by calling getComponent() and writing your own Swing code.

If you've found the larger UI tool kits intimidating, Buoy is a viable alternative. It lets simple UIs stay simple, leaving the complex code for the times when you need it. In practice, the few cases where Swing offers advantages over Buoy can be dealt with by writing a little Swing code directly in a program built with Buoy. This is the tool kit that's making it worth my time to use the Java language for UI programming.




Back to top


Download

DescriptionNameSizeDownload method
Source codefractal.tar340 KBHTTP
Information about download methods


Resources



About the author

Peter Seebach

Peter Seebach grew up thinking a flashy interface was one that went to a new line between messages, but is very optimistic about these new-fangled "graphical" interfaces. You can contact him at developerworks@seebs.plethora.net.




Rate this page


Please take a moment to complete this form to help us better serve you.



YesNoDon't know
 


 


12345
Not
useful
Extremely
useful
 


Back to top