There is no doubt that the Java platform has been experiencing something of a revival on the desktop since the release of Java SE 5.0 (Tiger). At no time during Java's history did Swing and the third-party libraries around it generate such an interest and improve at such a rapid pace. You don't need to look far to find evidence of this--there are numerous excellent open source libraries available here on Java.net and on sites such as SourceForge. Some examples of these include the Substance look and feel (shown in Figure 1) used to improve the visual appearance of your application, JGraph (Figure 2) for rendering graph structures, and the excellent set of SwingX components (Figure 3) made available by SwingLabs.
Figure 1. The Substance look and feel
Figure 2. A JGraph
Figure 3. One of the latest additions to SwingX: JXGraph
Unless you are implementing a thick enterprise client such as an applet or a Java Web Start application--options which have their own sets of size, deployment, and compatibility issues--you might think that there is not that much scope for using Swing within web applications, or indeed Java EE. You would, however, be making a mistake; there is no reason why web and Swing development should be mutually exclusive. In this article, we will look at putting one of Swing's most overlooked (and basic) features to work inside a web application.
JComponents as Images
Because of Swing's lightweight approach to component rendering--all visual components are drawn with Java code rather than by the operating system or native code--it is possible to "draw" virtually any component onto an image rather than the screen. This means that any subclass of JComponent, which forms the base of all Swing components, can be rendered to an image.
Accomplishing this is not as difficult as you might think, and requires only a few lines of code. Below is an excerpt of the SwingImageCreator utility class, which we will use to convert Swing components into images.
...
public class SwingImageCreator {
/**
* Creates a buffered image of type TYPE_INT_RGB
* from the supplied component. This method will
* use the preferred size of the component as the
* image's size.
* @param component the component to draw
* @return an image of the component
*/
public static BufferedImage createImage(JComponent component){
return createImage(component, BufferedImage.TYPE_INT_RGB);
}
/**
* Creates a buffered image (of the specified type)
* from the supplied component. This method will use
* the preferred size of the component as the image's size
* @param component the component to draw
* @param imageType the type of buffered image to draw
*
* @return an image of the component
*/
public static BufferedImage createImage(JComponent component,
int imageType){
Dimension componentSize = component.getPreferredSize();
component.setSize(componentSize); //Make sure these
//are the same
BufferedImage img = new BufferedImage(componentSize.width,
componentSize.height,
imageType);
Graphics2D grap = img.createGraphics();
grap.fillRect(0,0,img.getWidth(),img.getHeight());
component.paint(grap);
return img;
}
}
The class contains two overloaded methods that accept a JComponent and produce a BufferedImage instance. The system will default to the TYPE_INT_RGB image type if none is specified.
Once you have successfully created a BufferedImage instance, exporting it to a file is a breeze with the ImageIO library. Below are a few examples that demonstrate how to export the image in PNG format to either a file or an OutputStream:
//Rendering a Buffered Image to a File
ImageIO.write(bufferedImage,"png",new File("my-exported-png-file.png"));
//Rendering a Buffered Image to an OutputStream
ByteArrayOutputStream bout = new ByteArrayOutputStream();
ImageIO.write(bufferedImage,"png",bout);
Putting It All Together: Rendering Components to the Browser
Although we now know how to transform Swing components into images, we still need to put this knowledge to good use and render the images to the browser. In this article, we will be using servlets for drawing these dynamic images. It is, however, also worth noting that if your application is based on Java Server Faces, it is also possible to render these images using a PhaseListener. The PhaseListener image renderer is a useful approach when you are developing a third-party Java Server Faces library, as your end users would not be required to change their web.xml configuration file in order to use your components. The servlet approach, however, is a lot more familiar and also easier to get started with.
The SwingButtonServlet
A common requirement in many web applications is to have image buttons with "mouse over" effects. If these are done in the standard way, as static images, it can quickly become very tedious to maintain both a normal and an "active" (mouse over) version of each of the buttons. You also start running into problems when their labels need to change or if the application needs to be translated into a different language.
Instead, we will be drawing our image buttons dynamically using a servlet that simply picks up the label of the button from an URL parameter. With the buttons being drawn dynamically and from a central place, it becomes straightforward to change the "theme" of all of the buttons at once or to drive the button labels from a resource bundle/database.
Below is an excerpt of the JSP code that you can use to take advantage of these buttons--note how we have utilized a resource bundle to dynamically assign the labels.
In order to draw these buttons' images on the server side, we need to take the following steps:
Set the appropriate Swing look and feel. The Substance look and feel is currently one of the best-designed around and is freely available under an open source license; a perfect fit for our example.
Create the button and determine whether it is in the active (selected) state or not. This will be determined by whether or not the "active" parameter has been passed through on the URL request. In our example, the active (selected) state of the button will be used as the "hover over" image.
Convert the Swing button into an image using our SwingImageCreator utility class.
Render the image to the browser using the ImageIO library.
Here is the code that accomplishes the above:
..
public class SwingButtonServlet extends HttpServlet {
@Override
public void init(ServletConfig config)
throws ServletException {
try {
UIManager.setLookAndFeel(new SubstanceLookAndFeel());
} catch (UnsupportedLookAndFeelException e) {
throw new ServletException("Could look and feel",e);
}
}
@Override
public void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
//See if we can draw the button.
byte[] imageData = drawSwingButton(request);
//If it is null, then there is nothing more we can do
if(imageData == null){
response.sendError(HttpServletResponse.SC_BAD_REQUEST);
return;
}
response.setContentType("image/png");
response.setContentLength(imageData.length);
response.getOutputStream().write(imageData);
response.setStatus(HttpServletResponse.SC_OK);
}
public byte[] drawSwingButton(HttpServletRequest request){
//Check whether the button should be drawn in the active state
boolean active = request.getParameter("active") != null;
//Create the button with the 'title' parameter as it's label
String title = request.getParameter("title");
JToggleButton imageButton = new JToggleButton(title,active);
imageButton.setSize(imageButton.getPreferredSize());
//Now we need to create a byte array of this image
//possibly for caching purposes etc.
ByteArrayOutputStream bos = new ByteArrayOutputStream();
BufferedImage img = SwingImageCreator.createImage(imageButton);
try {
ImageIO.write(img,"png",bos);
} catch (IOException e) {
//Should not really happen.
return null;
}
return bos.toByteArray();
}
}
Figure 4 shows the final result of our labors: image buttons being drawn on the fly.
Figure 4. Image buttons being drawn on the fly
Drawing More Complex Components
The technique we have just used are not limited to only Swing components available in the standard Java libraries. It is also possible to draw more complex components by using third-party libraries. In Figure 5, we used the popular JGraph library to draw a hierarchical organizational diagram:
Figure 5. A more complex example: representing hierarchical data
The basic recipe for drawing the above remains the same: build the component in memory and then render it to the browser as an image. The code for this GraphServlet is in fact shorter than what was required in the SwingButtonServlet. The reason for this is that building up a hierarchical data structure is a complex task that normally occurs as part of the business logic of your application (and is slightly beyond the scope of this article). However, you can download the full source code for this article from the link in the Resources section, should you want to investigate the OrganizationGraphModel further and see how this is done.
...
public class GraphServlet extends HttpServlet {
private JGraphLayoutAlgorithm graphLayout;
@Override
public void init(ServletConfig config)
throws ServletException {
SugiyamaLayoutAlgorithm layout = new SugiyamaLayoutAlgorithm();
layout.setSpacing(new Point(170,120));
graphLayout = layout;
}
@Override
public void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException,
IOException {
JGraph graph = new JGraph();
graph.setBackground(Color.white);
graph.setEditable(false);
graph.setGridVisible(false);
//Create the graph model.
//In a real application you would probably get the model
//from the session where it was set by something like
//a JSF bean / Struts Action
GraphModel model = new OrganizationGraphModel();
graph.setModel(model);
//Layout the graph
graphLayout.run(graph,graph.getRoots());
graph.clearSelection();
//Now render the graph back to the user
BufferedImage graphImage = SwingImageCreator.createImage(graph);
//Now write the content back to the user.
response.setContentType("image/png");
OutputStream ous = response.getOutputStream();
ImageIO.write(graphImage,"png",ous);
}
}
Animated Images
So far, all of the images that we have drawn have been static. The next logical step is to make these images dynamically update themselves and appear to be "animated" from an end user's point of view.
Although this is not achievable using pure HTML, it is possible to implement it using a small amount of JavaScript. The JavaScript will basically instruct the browser to continually poll the server for the latest version of the image. To ensure that the browser does not cache the image, a unique URL parameter is passed to the server on each request:
<img id="dynamicImage" src="jxgraph.png"/>
<script>
function reloadImage(){
var img = document.getElementById('dynamicImage');
//Set a random parameter to ensure that the
//browser does not cache the image
img.setAttribute('src',"jxgraph.png?randomParam=" + Math.random());
setTimeout('reloadImage();',1000);
}
setTimeout('reloadImage();',1000);
</script>
We need to ensure that the server draws a new image every time it is invoked. In the servlet example below, we will simply use an attribute in the user's session (HttpSession) to keep track of the previous request and ensure that the new component is different. The component we are using--JXGraph--has only recently been contributed to SwingLabs, though it is already clear that there is a huge amount of applications that can benefit from its features.
...
public class JXGraphServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
JXGraph graph = new JXGraph();
HttpSession session = request.getSession();
//Get the previous value from the session
Double previousValue = (Double)session.getAttribute("previousValue");
if(previousValue == null){
previousValue = 0.1;
}
final double prevValue = previousValue.doubleValue();
graph.addPlots(Color.RED, new JXGraph.Plot() {
public double compute(double value) {
return Math.sin((value * 6)- prevValue);
}
});
session.setAttribute("previousValue",(prevValue + 0.1));
//Now render the graph back to the user
BufferedImage graphImage = SwingImageCreator.createImage(graph);
//Now write the content back to the user.
response.setContentType("image/png");
OutputStream ous = response.getOutputStream();
ImageIO.write(graphImage,"png",ous);
}
}
Figure 6 shows one frame of this animated graph as seen in the browser.
Figure 6. The final result: a dynamic (animated) image
Ideas for the Future
We have only covered the tip of the iceberg in terms of what is possible with Swing on the server side. For instance, by adding in a bit more logic around the rendering of the components, it would be possible to calculate the location of each of the elements (i.e., the "CEO" box in our organizational chart example in Figure 5) on the image. These locations can then be used to create an HTML image map, which can add additional interactivity in the browser.
Despite the various AJAX frameworks available on the Web today, there still seems to be no easy way to draw images using JavaScript. The best JavaScript client-side solution currently available seems to be the HTML Canvas offered by Mozilla. This solution suffers from the drawback that it is non-standard and will probably not be supported by the other major browsers for some time to come.
By combining JavaScript on the client side with the rendering of Swing components on the server side, it would be possible to overcome these problems. A JavaScript component could, for instance, compute and send a request to the server specifying the exact type of component image it requires. The result would be a library that would be a thin client with a number of advanced Swing components and compatibility across a number of different browsers. The only real rival would be an actual desktop Swing application.
Conclusion
If you were take only one thing away from reading this article, then I would hope that it would be the realization that Swing is a technology that is not only useful in desktop applications, but also in enterprise/web development. In this article, I offered Java EE developers some insights on how it is possible to take advantage of the huge strides made by Swing in recent years and to use its advanced "desktop" components in enterprise/web applications.
Resources
Source code for this article. Please refer to the dependencies.txt file for details of the JARs you require.
The JGraph library: Used to draw the hierarchical organizational diagram.
SwingLabs: The home of various useful Swing components, including the JXGraph component used in this article.
Jacobus Steenkamp has been developing in Java for the last 4.5 years and has worked in various sectors including the financial, pharmaceutical and energy industries.
What do you think of this combination of Swing and web apps?
Showing messages 1 through 19 of 19.
Compatible with Java version 1.4.2?
2008-06-27 03:41:17 nawazijaz
[Reply | View]
Hi, i want to generate organizational chart by using JGraph library in my java web application under the lights of your tutorial. Is it compatible with Java version 1.4.2?
/ Nawaz Ijaz
Having trouble getting things to lay out properly
2006-09-11 17:35:38 bentley
[Reply | View]
The article is well written and describes exactly what I need. However, i'm having some problems with getting nested JComponents to lay out properly when drawn to an image bufer (see forum thread http://forums.java.net/jive/thread.jspa?threadID=18141&tstart=0). Any suggestions would be most helpful.
Thanks,
Bentley
Having trouble getting things to lay out properly
2006-09-14 02:31:15 kooss
[Reply | View]
The cause of this seems to be the fact that the JPanel class actually tries to get its GraphicsConfiguration from its peer prior to drawing its children. This obviously causes a problem when trying to paint a panel that is not added to a frame.
My first attempt at resolving this was to use an overridden version of the JPanel class that exposes its GraphicsConfiguration and then to set it using the BufferedImage's GraphicsConfiguration. I.e. something like this :
BufferedImage img = new BufferedImage(100,100,BufferedImage.TYPE_INT_RGB);
Graphics2D grap = img.createGraphics();
overriddenPanel.setGraphicsConfiguration(grap.getDeviceConfiguration());
Unfortunately this didn't seem to work, so I would need to do some more reading into the Swing source code to find a better way of doing this.
There is however also a rather hackish solution to get this working. Just before you pass your JPanel into the SwingImageCreator.createImage method, put in this bit of code to give it a peer:
JPanel panel = new JPanel();
panel.add(new JButton("Testing"));
panel.setSize(400,400);
JFrame pane = new JFrame();
pane.add(panel);
pane.pack();
pane.setVisible(true);
pane.setVisible(false);
Far from ideal, but the JPanel should now paint all of its children. If you are desperate to get it working, you can use this hack until a better solution comes along.
Having trouble getting things to lay out properly
2006-09-17 12:11:38 bentley
[Reply | View]
Thanks for the response. The JFrame code that you suggest was very similar to what I have in the original Swing app and things displayed just fine. However, i'm trying to get the image to display in a 'headless' environment without any local windowing graphics support (other than the remote browser), and i'm lead to believe that you can't use any related heavyweight peers in such a mode. (In fact, I recall getting an X11-related exception when I introduce the peer.)
Given the strong market drive towards providing web-2.0 lightweight clients, providing such support in Swing would make a great deal of sense for continuing to make it a viable presentation API in the face of alternatives (such as Eclipse).
Maybe, a look at frameworks like wingS (www.j-wings.org) might be worth the time. wingS tries to take the swing API to the web and I (swing development background) had a clean and easy start with it.
trying to understand
2006-04-25 09:07:57 eitan
[Reply | View]
hello. i don't mean to sound critical and am not critical. i think this is a terrific article. but please help me understand. i question the value of what is being done here. so we're drawing components and we're taking a snapshot and then we're including the image on a web page.
my impression is that text-based buttons should be preferred over image buttons. the additional images increase latency and if the image doesn't come up then you won't know what the button is saying. or the image comes up the page load takes longer. you have extra network calls to the server to get each image. so the user is made to wait until the entire page loads, with all the images, to be able to tell whether the button says 'ok' or 'cancel'.
when i think of swing as the client, i think of replacing the web browser and the html with a custom, rich, gui; not of somehow playing in the web page sandbox alongside html.
i strongly believe that a swing client is an improvement over an html client, to the extent that a swing client can be developed in the same amount of effort (or less) than web.
in my mind, the issue is coming up with a good mechanism to deal with the distance, the latency of the network communication: the transport layer. data models that exist on the server must be bound to the client in a manner that does not significantly increase the chattiness of the conversation (and thus increasing the load on the server possibly considerably). the client inevitably provides means for the end user to make changes to that model, changes that must be communicated back to the server. we need a mechanism to make this all scale to dozens of concurrent users, possibly more.
it'd be nice also to consider not being restricted to the http protocol model of opening and closing connections. preferring maintaining a connection with the server open for the duration of the session, thus eliminating jumping through hoops with cookies. on the other hand, do such servers exist off the shelf? i want to see that infrastructure develop, something that one day will become as standard or ubiquitous as http is today.
this certainly won't scale
2006-04-26 03:49:41 richard_ballestero
[Reply | View]
I agree that the bandwidth requirements of the approach proposed in this article are a major issue. A further problem is scalability on the server side: if you have lots of sessions, rendering Swing components to images on the server side is not really effective. And what about event handling? How do you want to support Swing's event handling efficiently?
Drawing in headless mode
2006-04-24 01:45:02 kirillcool
[Reply | View]
Specifically for Substance (and generally for other third-party LAFs) you can invoke direct draw methods in the internal implementation classes on headless machines. Of course, you will have to dig much deeper in the code and when you will want to switch to another LAF you will have to throw it all away and start anew. But it's still an option.
Only useful if this is integrated with something like JSF/AJAX in the browser
2006-04-21 09:38:08 red3
[Reply | View]
I would be really interested to see this integrated with JSF & AJAX to provide a full rich web client.
Just drawing the components is easy, it's getting the feedback to the server dynamically that's harder.
For example, in your Login form example, what happens when you mouseOver or click the button. To be a true SWING client in the browser, the component should change state responsively. Otherwise this whole idea is just eye candy...
i don't remember my exact results from messing with a headless machine, there where packages available for linux to provide a "head" for headless systems via some version of X. though that is hardly a pure Java solution :)
Unfortunately, due to the way that the Container base class (which all swing components ultimately extend from) is implemented, no swing components can even be instantiated (let alone drawn) on a machine that is headless.
This is all because of the following block of static code in the Container class :
static {
/* ensure that the necessary native libraries are loaded */
Toolkit.loadLibraries();
if (!GraphicsEnvironment.isHeadless()) {
initIDs();
}
}
public class TestImage {
public static void main(String args[]) {
Object cols[] = {"this", "is", "a test"};
Object data[][] = {{"a","b","c"},{"d","e","f"},{"g","h","i"}};
JTable table = new JTable(data,cols);
JScrollPane jsp = new JScrollPane(table);
saveComponentAsImage(jsp);
}
public static void saveComponentAsImage(JComponent c) {
int w = c.getPreferredSize().width;
int h = c.getPreferredSize().height;
BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
Graphics g = image.getGraphics();
I was looking to do something like this a while back and worked great until I tried to run the web app on a headless machine. It was unable to draw components. have you also found this to be true?
-Lucas
very good article
2006-04-21 05:17:10 uhilger
[Reply | View]
I am big fan of Swing and was thinking of the same to bring Swing to the browser, however, I still have no solution for events, e.g. when I use a JTree to draw a tree image on the server, I search for a solution how to capture the user's click on a tree nde so that the server is messaged to send an updated tree image with the respective node expanded. I guess the only way to achieve this is an image map too? (when JavaScript is not to be used, that is)
Great use of LAF in action! This opens up so much possibilities - you can provide button pulsation by fetching a set of images from the server. Then you can make your own rollover effects bringing desktop into the browser. By the way, you don't need to use the toggle button. There's a whole bunch of properties right here that make button look active / change theme / use mixed themes / ...
It is a good practice to ...
2006-04-21 00:19:06 codemaster
[Reply | View]
Hi.
Great stuff but i think that you must say
Graphics2D grap = img.createGraphics();
grap.fillRect(0,0,img.getWidth(),img.getHeight());
grap.dispose();
instead of
Graphics2D grap = img.createGraphics();
grap.fillRect(0,0,img.getWidth(),img.getHeight());