Buoy is a free User Interface (UI) Toolkit built on swing. It provides convenience and simplicity for UI developers. In this article, the author uses a simple fractal user interface program to introduce what buoy can do and why it does so.
I was surprised at the complexity of the swing interface when I tried to build a simple user interface in Java for the first time. Honestly, I want to leave. Recently, a friend told me that his rendering program art of transition Sion (see references) is based on a different Toolkit: buoy. One of the reasons to recommend it is that its interface is more friendly. When he mentioned it for the first time, I thought he was talking about "Bui", and it was intentionally similar to the GUI name. Here B stands for better (better), but buoy is not the abbreviation.
Buoy is free of charge. In fact, it is a public thing. It is not released with a reasonable degree of openness. In fact, it is not controlled by any license. This means that buoy can be used in any project written in Java that can run buoy, without the need to consider licensing issues. This toolkit is easy to modify and expand because it provides complete source code. This article is based on the buoy 1.3 release and requires readers to have a basic understanding of swing, although not familiar with it, to deal with the past.
Sample program
The first application I tried to build with swing ended in failure. To see the comparison between the toolkit, I decided to use buoy to build the same program. The code examples in this article are all from the buoy version of the program. The program generates some fragment. Specifically, it is the fragment of iteration. The basic idea is simple: Define a series of line segments on the plane, from (0, 0) to (1, 0), and locate any unit line. After drawing these sections, draw the same set of deformation lines and use this section as the unit vector. It is easier to do than to say, as shown in figure 1.
Figure 1. fragment in the fragment Editor |
The interface of this program is quite simple. It has some interface widgets, a canvas that draws beautiful images on the canvas, and supports mouse control of images. In fact, all you have to do is to manipulate the points that constitute the original curve, and the original curve will be drawn iteratively. The interface also has a minimal menu; it can open and close files, close the window, or save the current image as a PNG file. Although simple, this interface provides a reasonable example of a buoy widget and a considerable number of experience for the event processing system.
The actual core code of the program-the fragment generator-has been written, which turns this example into a good test program. Of course, during the update process, I also found and fixed some bugs.
The release package contains the source code of the sample program, compiled class files, and buoy jar files (click the code icon at the top or bottom of this article to download factal.tar ). The package also contains a directory named frac, which contains some sample fragges. If you use a Unix-style machine with a Java compiler in the path, you only need to run make to run it. Otherwise, you need to set the classpath to include the directory where the JAR file of the current path and buoy is located, and then run the fractalviewer class. On Windows, the correct command line should be Java-classpath.; buoy. Jar fractalviewer.
Sed-e s/J/B/g
The first in-depth study of code may make the impression that converting swing code into buoy code is as simple as converting the letter J in the UI element name to B. For example, the fractalviewer class no longer extends jframe; it now extends bframe. The name of the main widget can also be inferred from this. The spinner and slider have the same name as before, but they only change to a letter. Menubar (menu bar) is still composed of menus (menus), and menus contain menuitems.
Some name conversions are slightly different. In the place where swing references borderlayout, buoy has bordercontainer. In general, buoy name conversion is quite uniform, although it is not always the same as swing name. One obvious difference is that buoy almost combines the concept of container and layout manager; each container type knows how to layout itself. This greatly simplifies the design. For example, the labelwidget class used in the fractal generator is a bordercontainer; in swing, this may be a jpanel with the borderlayout layout manager.
However, there are many similarities between the two. This is very helpful for adapting to new things. More importantly, buoy is built on swing. This means that, in general, if you need to do something that cannot be easily completed with bouy, you can pass the buoy object to the swing object it wraps. In this case, if you want to access a swing object without a buoy pair, you can simply wrap it in an awtwidget object, which provides a very thin package through it, not only does buoy own widgets, but all the widgets can access the buoy widget API. For example, if you find that gridbaglayout is required, you may need to do so.
For example, the fractalpanel class is an awtwidget. In early design, it is a subclass of jpanel, but in fact I don't need the jpanel code. Instead, I constructed the fractalcanvas class for packaging custom classes, which is a subclass of a common canvas class. By turning it into an awtwidget, you can use the efficient event processing mechanism of buoy on it.
The event processing code is very simple. When you press the mouse button, buoy sends a new mousepressedevent event to the mousepressed () function through the magic of addeventlink. I ignore the question of which button to press. I only want to hold down SHIFT and click normally. Select the nearest point, and press shift to center the display again. Then, if you move the mouse, the mousedraggedevent event will be sent every time the buoy notices the movement. Fractalpanel generates its own events when processing these events.
Pointchangedevent
For more details, see pointchangedevent. This is an experimental class. If you don't like it, you can only blame God. The idea of this class is: let a class represent changes in the state point. The editor tracks the "current" point, that is, the point currently being edited by the editor widget. You can use these widgets or click a new vertex in the fragment panel to select the closest vertex.
I come to the conclusion that there are about three types of point-to-point events in the code that need to be sent from one class to another.
One is to change the feature of a certain point: the point event type. If it is sent by the editor, it tells the fragment to change the points on the prototype line and asks to redraw the line. If it is sent by fragment, it indicates the feature of the point just selected by the editor.
Next is to select a point. You can select by index or location. Therefore, if only the index or location is provided, the constructor considers that the intention is to fill in other values. In a special case, vertex index-1 is used to indicate the unselected vertex. Therefore, you must use-2 to indicate that the editor is searching for the vertex at the specified position. This may not be nice, but it works.
It means that the fractal Class responds to the select event. If a vertex is selected successfully, a new pointchangedevent event of the point type is returned, as shown in Listing 1.
List 1. Use events to answer events
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 vertex is a special case. If you do not need to change other properties (such as color) of a vertex, you must process the position. This is the move event type. In terms of effect, it works very much like the point event type, but it does not need the event generator (usually the fractalpanel class) to care about the attributes that it does not know at all.
The insert and delete event types are only partially related and may belong to the fractalchangedevent event.
Event Processing
As we have already seen, event processing is the most obvious difference between Buoy and swing. Event processing provides a lot of flexibility. Buoy has a rich set of events and allows you to select events you are interested in and send events from any widget to other objects. For example, if you want to capture mouse events in swing, the mouselistener interface must be implemented for the event capturing class. This interface has five functions to implement, even if they are furnishings. You must also use the function name provided by the interface. Worse, the function must be a public part of the listener interface, or make it public as part of the public interface, you can also create an internal class that does nothing but wraps the event interceptor code.
In buoy, each widget is eventsource. This means you can listen for events from every widget. What types of events? All types are supported. The key function is addeventlink (). This allows you to specify classes, listeners, and optional methods. Whenever eventsource assigns an event of this class or its subclass, the listener receives the event, either through a method called processevent () or by calling addeventlink () the method name. The provided functions cannot accept parameters or objects of classes compatible with the specified event type. The parent class and interface can.
This is a convenient setting. Different events can be routed to different functions or the same functions. For example, mousepressedevent and mousereleasedevent are processed separately. In the example program, there are different threads for pressing, releasing, and dragging the mouse, as shown in Listing 2. Note that this is far beyond what swing mouselistener can do. If you use swing programming, You need to implement the mouselistener and mousemotionlistener interfaces.
List 2. Select only events of interest
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 only has the least work to do. It only cleans up the mousepressed () function and tells the fractal object that it is time to start full re-painting.
Buoy event processing has another interesting feature. If you want to, you can create a new event type. An event type is a class. Indeed. It does not even need to inherit any class or implement anything. It is a class. If the object of this class is sent to dispatchevent (), then it or its parent class listener will be called. You can also create a new event type in swing, but you must do it yourself. You must design the listener interface and write your own code to generate events and listen for events. In the example program, the fractal class is designed to demonstrate that the event processing function can be easily added to any original class. You only need to declare a fractalviewer class to add the event source eventsource of the listener. The fractalviewer class sets the event links from the event source (such as fractaleditor) to their listeners, as shown in listing 3.
Listing 3. Binding
Private void tieevents (){
// Set up event handling relations.
Addeventlink (windowresizedevent. Class, this, "layoutchildren ");
Addeventlink (windowresizedevent. Class, panel, "repaint ");
Tiecontrolevents ();
Tiefractalevents ();
Tiepanelevents ();
} |
Custom Event classes are generally used to indicate user behavior. In buoy, events are generated only through user behavior, rather than through system interfaces-unless you want to explicitly call dispatchevent () to generate events. When the fragment object changes in a way that will cause field updates, the control panel of all parts will be notified. In this way, we create a new class parameterchangedevent, which indicates that the parameter has changed. Alternatively, if the position or index of the selected vertex changes, a new pointchangedevent is sent. If the behavior is obvious enough, the event processor does not even need to accept parameters. As an example of event processing, see Listing 4, which demonstrates the beginning of the parameterchanged () method of fractaleditor.
Listing 4. parameters changed
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 example, the event processing system is used to transmit all kinds of information before and after. In previous versions, each class has a reference to each of the other classes, and the messy get methods are sorted by day. In the current version, the buoy event processing system is used to process various notifications. For example, the fractalchangedevent class can be used to let other parts of the Code know the modification to the fractal. It may be a change in the number of vertices (the editor uses the number of vertices to define the correct spinnernumbermodel for the point selector ), or the notification to be repainted, as shown in listing 5.
Listing 5. When re-painting is finished
Public void fractalchanged (fractalchangedevent e ){
Switch (E. GetType ()){
Case fractalchangedevent. redraw:
Repaint ();
Break;
}
} |
The buoy document details the differences between the swing event model and the buoy event model, and the causes of these differences. There is a good reason, and the buoy model usually leads to smaller and clearer code. Of course, you can still do unnecessary or stupid things, as you can do in any system, but at least there is a clean and beautiful interface when doing these things.
Learning Curve
I once observed that it was not long enough to learn to use a GUI tool for one afternoon. For buoy, it may take about 6 hours or about one workday. I did get great help from more experienced buoy users. The previous experience in swing is also helpful, but in fact, I do not think that swing experience is necessary. Buoy documentation is quite good, and its simplicity does help. There is not much to learn about basic UI things.
Buoy documents are not as complete as swing documents, but cover a lot of details and are very good. In addition, the source code is also there, so it is very easy to answer some simple questions about the interface. It is certainly a good thing to have a more complete document. However, since this project is on SourceForge, you can write more to contribute to it if you want.
The learning curve of buoy is a great advantage over swing. Using a simple interface can make most of the interface widgets work correctly. To use an example in the buoy document: In swing, jlist requires either a static list or a new class to implement the listmodel interface. In buoy, you only need to add a project to the list. In most common cases, buoy has already done a hard job for you.
Buoy is quite small. The complete release package contains the source code, Jar files, and documents, less than 1 MB in total. The code is well organized and can easily find any specific code segment. It is not difficult to adjust the design if necessary.
Bug
Although buoy is a stable and useful system, it is not absolutely perfect. Occasionally, it may have a strange performance where the choice is obvious and reasonable, resulting in surprising behavior. If you want to use buoy to complete a real project, you need to understand bugs: their prevalence, severity, and difficulty.
During the development of this application, I encountered some things that looked like a bug at the time. But not all. There may be bugs in the document-in these cases, the Code behavior is not as expected, but it is very reasonable. In fact, I am very certain that it is not actually a bug in buoy, but they do present some things that may happen when debugging the buoy application. After debugging the code for a few days, I am very certain that every obvious bug I encounter is either my error or the design decision in the underlying swing that I don't like very much. Certainly, it is impossible to avoid these problems in swing.
Scroll bar Scale
One of the most common bugs I encountered in the Early Days was when dealing with the scale mark in the ruler. Initially, I couldn't get the labels displayed on them.
Listing 6. Depressing scroll bar code
Minslider. setshowlabels (true );
Minslider. setmajortickspacing (2 ); |
The code in Listing 6 does not work. As you can see, labels are only displayed after the scale spacing is set. If no scale spacing is set before the scroll bar is told to display the label, it will end if no scale is displayed. What's more subtle is that you cannot change the scale spacing subsequently. The attempt to change the scale spacing is ineffective. However, this is not actually a bug of buoy, but a swing way of working. Because the bslider class only transmits requests to jslider, it is unfair to blame buoy.
A more subtle bug related to underlying jslider problems occurs in alignment. The bslider constructor sets the secondary scale to 5 and the primary scale to 20-these two values are reasonable relative to the default size of 100. However, when you create a scroll bar in the range of 1-10, you cannot see secondary scales. Therefore, you can only set the master scale spacing to 1. The result is a scroll bar with a scale value of 1-10, and only stays at 1 and 6. Alignment of the scale prevents other values, because it is aligned to the secondary scale rather than the primary scale.
Although this problem is due to the implementation of jslider, it occurs in the default behavior of buoy and will be fixed in the forthcoming release of version 1.4.
Note that the only reason for this problem is that the sample program constantly updates the range of some scroll bars. For example, if there is a line segment that only allows up to 50 iterations, it may be a little more effort to mark each number on the scroll bar. On the other hand, if only a few iterations are allowed, it may not seem good to miss some numbers. It is very convenient to update the scroll bar range frequently.
Shortcut menu
Buoy provides constructor for menu shortcuts with characters or keyevents. I couldn't get it to work when I tested it for the first time. Lowercase letters are required, but 'w' must be used instead of 'W' when calling the constructor, as shown in listing 7.
Listing 7. Add a close menu item
Mi = new bmenuitem ("close", new shortcut ('W '));
Mi. setactioncommand ("close ");
Mi. addeventlink (commandevent. Class, this, "menuevent ");
Filemenu. Add (MI ); |
In this case, the Java 5.0 SDK and 1.42 may have to be processed in a fuzzy way. On the surface, if you pass an uppercase letter to the constructor, what you do is as expected. Underlying problem-which key or modifier does JVM use to represent Ctrl -? -- A small free database is needed to completely solve the problem.
File Selection
For some inobvious reasons, when a new file selector is started on the Mac system, buoy starts the root directory by default. I made a detailed bug report about how it seems to miss a large number of files, but then I realized that I had moved my home directory from/users/seebs to/volumes/home/seebs, and the file selector indeed showed something on the disk. Score: buoy 1, seebs 0.
I still want to know why it starts with the root of the file system. This may be a problem with the Mac Implementation of JVM.
Conclusion
Buoy follows the famous old UNIX philosophy: 90% of work solves of problems. Buoy does not want to solve all the problems for everyone, but it can do most of the work that the user or designer needs on the interface. It may have the best license terms and is still evolving. The best thing is, if you find that it does not allow you to do what you really need, you can study it, modify it as you like, either modify the source code of buoy, or call getcomponent () and write your own swing code.
If you think that the large UI toolkit is terrible, buoy is a good choice. It can make the simple UI simple and save the complex code as needed. In practice, if swing is more advantageous than buoy, you can simply write a few codes in the program built by buoy. This is a toolkit that makes it worthwhile for me to use Java for UI programming.
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 only has the least work to do. It only cleans up the mousepressed () function and tells the fractal object that it is time to start full re-painting.
Buoy event processing has another interesting feature. If you want to, you can create a new event type. An event type is a class. Indeed. It does not even need to inherit any class or implement anything. It is a class. If the object of this class is sent to dispatchevent (), then it or its parent class listener will be called. You can also create a new event type in swing, but you must do it yourself. You must design the listener interface and write your own code to generate events and listen for events. In the example program, the fractal class is designed to demonstrate that the event processing function can be easily added to any original class. You only need to declare a fractalviewer class to add the event source eventsource of the listener. The fractalviewer class sets the event links from the event source (such as fractaleditor) to their listeners, as shown in listing 3.
Listing 3. Binding
Private void tieevents (){
// Set up event handling relations.
Addeventlink (windowresizedevent. Class, this, "layoutchildren ");
Addeventlink (windowresizedevent. Class, panel, "repaint ");
Tiecontrolevents ();
Tiefractalevents ();
Tiepanelevents ();
} |
Custom Event classes are generally used to indicate user behavior. In buoy, events are generated only through user behavior, rather than through system interfaces-unless you want to explicitly call dispatchevent () to generate events. When the fragment object changes in a way that will cause field updates, the control panel of all parts will be notified. In this way, we create a new class parameterchangedevent, which indicates that the parameter has changed. Alternatively, if the position or index of the selected vertex changes, a new pointchangedevent is sent. If the behavior is obvious enough, the event processor does not even need to accept parameters. As an example of event processing, see Listing 4, which demonstrates the beginning of the parameterchanged () method of fractaleditor.
Listing 4. parameters changed
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 example, the event processing system is used to transmit all kinds of information before and after. In previous versions, each class has a reference to each of the other classes, and the messy get methods are sorted by day. In the current version, the buoy event processing system is used to process various notifications. For example, the fractalchangedevent class can be used to let other parts of the Code know the modification to the fractal. It may be a change in the number of vertices (the editor uses the number of vertices to define the correct spinnernumbermodel for the point selector ), or the notification to be repainted, as shown in listing 5.
Listing 5. When re-painting is finished
Public void fractalchanged (fractalchangedevent e ){
Switch (E. GetType ()){
Case fractalchangedevent. redraw:
Repaint ();
Break;
}
} |
The buoy document details the differences between the swing event model and the buoy event model, and the causes of these differences. There is a good reason, and the buoy model usually leads to smaller and clearer code. Of course, you can still do unnecessary or stupid things, as you can do in any system, but at least there is a clean and beautiful interface when doing these things.
Learning Curve
I once observed that it was not long enough to learn to use a GUI tool for one afternoon. For buoy, it may take about 6 hours or about one workday. I did get great help from more experienced buoy users. The previous experience in swing is also helpful, but in fact, I do not think that swing experience is necessary. Buoy documentation is quite good, and its simplicity does help. There is not much to learn about basic UI things.
Buoy documents are not as complete as swing documents, but cover a lot of details and are very good. In addition, the source code is also there, so it is very easy to answer some simple questions about the interface. It is certainly a good thing to have a more complete document. However, since this project is on SourceForge, you can write more to contribute to it if you want.
The learning curve of buoy is a great advantage over swing. Using a simple interface can make most of the interface widgets work correctly. To use an example in the buoy document: In swing, jlist requires either a static list or a new class to implement the listmodel interface. In buoy, you only need to add a project to the list. In most common cases, buoy has already done a hard job for you.
Buoy is quite small. The complete release package contains the source code, Jar files, and documents, less than 1 MB in total. The code is well organized and can easily find any specific code segment. It is not difficult to adjust the design if necessary.
Bug
Although buoy is a stable and useful system, it is not absolutely perfect. Occasionally, it may have a strange performance where the choice is obvious and reasonable, resulting in surprising behavior. If you want to use buoy to complete a real project, you need to understand bugs: their prevalence, severity, and difficulty.
During the development of this application, I encountered some things that looked like a bug at the time. But not all. There may be bugs in the document-in these cases, the Code behavior is not as expected, but it is very reasonable. In fact, I am very certain that it is not actually a bug in buoy, but they do present some things that may happen when debugging the buoy application. After debugging the code for a few days, I am very certain that every obvious bug I encounter is either my error or the design decision in the underlying swing that I don't like very much. Certainly, it is impossible to avoid these problems in swing.
Scroll bar Scale
One of the most common bugs I encountered in the Early Days was when dealing with the scale mark in the ruler. Initially, I couldn't get the labels displayed on them.
Listing 6. Depressing scroll bar code
Minslider. setshowlabels (true );
Minslider. setmajortickspacing (2 ); |
The code in Listing 6 does not work. As you can see, labels are only displayed after the scale spacing is set. If no scale spacing is set before the scroll bar is told to display the label, it will end if no scale is displayed. What's more subtle is that you cannot change the scale spacing subsequently. The attempt to change the scale spacing is ineffective. However, this is not actually a bug of buoy, but a swing way of working. Because the bslider class only transmits requests to jslider, it is unfair to blame buoy.
A more subtle bug related to underlying jslider problems occurs in alignment. The bslider constructor sets the secondary scale to 5 and the primary scale to 20-these two values are reasonable relative to the default size of 100. However, when you create a scroll bar in the range of 1-10, you cannot see secondary scales. Therefore, you can only set the master scale spacing to 1. The result is a scroll bar with a scale value of 1-10, and only stays at 1 and 6. Alignment of the scale prevents other values, because it is aligned to the secondary scale rather than the primary scale.
Although this problem is due to the implementation of jslider, it occurs in the default behavior of buoy and will be fixed in the forthcoming release of version 1.4.
Note that the only reason for this problem is that the sample program constantly updates the range of some scroll bars. For example, if there is a line segment that only allows up to 50 iterations, it may be a little more effort to mark each number on the scroll bar. On the other hand, if only a few iterations are allowed, it may not seem good to miss some numbers. It is very convenient to update the scroll bar range frequently.
Shortcut menu
Buoy provides constructor for menu shortcuts with characters or keyevents. I couldn't get it to work when I tested it for the first time. Lowercase letters are required, but 'w' must be used instead of 'W' when calling the constructor, as shown in listing 7.
Listing 7. Add a close menu item
Mi = new bmenuitem ("close", new shortcut ('W '));
Mi. setactioncommand ("close ");
Mi. addeventlink (commandevent. Class, this, "menuevent ");
Filemenu. Add (MI ); |
In this case, the Java 5.0 SDK and 1.42 may have to be processed in a fuzzy way. On the surface, if you pass an uppercase letter to the constructor, what you do is as expected. Underlying problem-which key or modifier does JVM use to represent Ctrl -? -- A small free database is needed to completely solve the problem.
File Selection
For some inobvious reasons, when a new file selector is started on the Mac system, buoy starts the root directory by default. I made a detailed bug report about how it seems to miss a large number of files, but then I realized that I had moved my home directory from/users/seebs to/volumes/home/seebs, and the file selector indeed showed something on the disk. Score: buoy 1, seebs 0.
I still want to know why it starts with the root of the file system. This may be a problem with the Mac Implementation of JVM.
Conclusion
Buoy follows the famous old UNIX philosophy: 90% of work solves of problems. Buoy does not want to solve all the problems for everyone, but it can do most of the work that the user or designer needs on the interface. It may have the best license terms and is still evolving. The best thing is, if you find that it does not allow you to do what you really need, you can study it, modify it as you like, either modify the source code of buoy, or call getcomponent () and write your own swing code.
If you think that the large UI toolkit is terrible, buoy is a good choice. It can make the simple UI simple and save the complex code as needed. In practice, if swing is more advantageous than buoy, you can simply write a few codes in the program built by buoy. This is a toolkit that makes it worthwhile for me to use Java for UI programming.