[an error occurred while processing this directive]

Page One
Page Two
The Databank
What Is Swing?
Special Report
IDE Roundup
Swing and the Web
Swing Text
Tech Topics
Friends
Tips and Tricks
The PLAF Papers
Call 911
The Archive
JFC Home
Download Swing
Swing API Docs
Download JDK
JDK Docs
Java Tutorial
 
The Swing Connection Friends

SWING IT!
Mailpuccino
: A Recipe for Brewing Cool Apps
In the JavaTM Programming Language


This article was written by Kong Eu Tak, an 18-year-old freelance programmer residing in Singapore. Eu Tak is the author of Mailpuccino, a Swing-based freeware e-mail client program written in the JavaTM programming language. Mailpuccino has been awarded a five-cow rating from the TUCOWS software site. It supports the SMTP, IMAP4, and POP3 protocols to send and download e-mail messages -- and has all the other features of a decent e-mail client. To try it out for yourself, download it from the Mailpuccino Homepage.


By Kong Eu Tak

Have you wished that an app you had created was as powerful as it looked -- or that it looked as powerful as it was? Now, thanks to Swing's pluggable look-and-feel design, it's easy to build apps that are as good as they look, and look as good as they are.

In this article, I will lead you through Mailpuccino, my award-winning Swing-based e-mail client program, and will reveal the secrets behind some of its interface gymnastics. Then you'll be able to write a cool, Swinging application -- just like I did!


Getting Started

Mailpuccino Splash Screen

When you start Mailpuccino, what do you see? Mailpuccino, splashed on your screen, of course!

The splash screen is truly a very important component of any application, especially for applications written in the JavaTM programming language, which have been accused in some quarters of having slow start-ups, delaying your gratification. Besides reducing the perceived startup time, a splash screen also gives you an excuse to display something really cool and hopefully awesome, thus obtaining a favorable first impression from the user.

And with Swing, creating a splash screen is so easy that there's really no excuse for any application not presenting one at startup time.


Creating a Splash Screen

Creating a splash screen isn't difficuilt at all; in fact, it is just a JFrame without the frame borders or a title bar. It is a Window.

To create a splash screen, just load a cool graphic into a JLabel instance, and dump it into your Window! Simple, isn't it? And if you want to display some initialization messages in your Window, just add those to the Window. Here is the source code for an example of a splash screen class very similar to the one I used in Mailpuccino:


import java.awt.*;
import javax.swing.*;
import javax.swing.border.*;

public class SplashScreen extends Window{
  JLabel StatusBar;

  // So that it can be run as an application for demonstration
  public static void main(String Args[]){
    SplashScreen Splash=
      new SplashScreen(new ImageIcon("Splash.jpg"));
    try{
      Splash.showStatus("Hi!");
      Thread.sleep(1500);
      Splash.showStatus("Splash Screens are cool!");
      Thread.sleep(1500);
      Splash.showStatus("Gives you something interesting...");
      Thread.sleep(1500);
      Splash.showStatus("... to look at...");
      Thread.sleep(1500);
      Splash.showStatus("... during startup!");
      Thread.sleep(1500);
      Splash.close();
    }catch(Exception e){e.printStackTrace();}
    System.exit(0);
  }

  // SplashScreen's constructor
  public SplashScreen(ImageIcon CoolPicture){
    super(new Frame());

    // Create a JPanel so we can use a BevelBorder
    JPanel PanelForBorder=new JPanel(new BorderLayout());
    PanelForBorder.setLayout(new BorderLayout());
    PanelForBorder.add(new JLabel(CoolPicture),
      BorderLayout.CENTER);
    PanelForBorder.add(
      StatusBar=new JLabel("...",SwingConstants.CENTER),
      BorderLayout.SOUTH);
    PanelForBorder.setBorder(new BevelBorder
(BevelBorder.RAISED)); add(PanelForBorder); pack(); // Plonk it on center of screen Dimension WindowSize=getSize(), ScreenSize=Toolkit.getDefaultToolkit().getScreenSize(); setBounds((ScreenSize.width-WindowSize.width)/2, (ScreenSize.height-WindowSize.height)/2,WindowSize.width, WindowSize.height); setVisible(true); } public void showStatus(String CurrentStatus){ try { // Update Splash-Screen's status bar in AWT thread SwingUtilities.invokeLater(new UpdateStatus(CurrentStatus)); }catch(Exception e){e.printStackTrace();} } public void close() { try { // Close and dispose Window in AWT thread SwingUtilities.invokeLater(new CloseSplashScreen()); }catch(Exception e){e.printStackTrace();} } class UpdateStatus implements Runnable{ String NewStatus; public UpdateStatus(String Status){NewStatus=Status;} public void run(){StatusBar.setText(NewStatus);} } class CloseSplashScreen implements Runnable{ public void run(){setVisible(false);dispose();} } }

Because the splash screen is loaded from the main application startup thread, you should be careful to ensure that any method calls to any Swing class's methods (like JLabel.setText()) are executed from the AWT event-dispatching thread. That's why the SwingUtilities.invokeLater() method is used. It ensures that the run() method in the Runnable object passed to it is called from the AWT event-dispatching thread! Here's a line to a source file you can download and compile to see the splash Screen running for yourself:

SplashScreen.java (can be run as an application)


The Reward

Finally, you are rewarded for your patience. Welcome to Mailpuccino:

Malpuccino main window
Mailpuccino

During the development of Mailpuccino, the user interface was always one of my top priorities. After all, no matter how powerful an application is, if all the rich functionality is not presented to the user in an accessible manner, it would only amount to added "bloat" in your app. Besides, who wants to work in an ugly-looking GUI? (A faceless command-line tool would be better . . .)

Now, let's get down to business. Much of Mailpuccino's use rinterface is constructed using rather rudimentary Swing programming, the usual stuff: JPanels, GridBagLayouts, JButtons, and so on. You get the idea. The point is, the Swing classes and the Swing API really place a lot of flexibility and power in your hands So with a little planning and creativity (and lots of tweaking, experimentation, and patience), it isn't too difficult to construct a handsome-looking Java app. You may be curious, though, to know how I created the sorted table of messages and placed the Ascending/Descending widget in the table headers.


Creating a Sorted JTable

Creating a sorted JTable like the one in Mailpuccino's main window is a relatively tedious affair, but once you get the hang of it, it isn't very hard. Before we delve into the details, however here's an applet demo to whet your appetite:

Run the Applet Demo

The source code presented later in this article is extracted from this demo. When the applet starts, go ahead -- press the button!

Here's the source code, in case you're interested:

  • Demo.java (an applet/application launcher for this example)
  • SortedJTable.java
  • Now, let's examine the features that a sorted JTable would have, and how each can be implemented:



    1. A widget in the table hjeaders to indicate Ascending/Descending sort

    To add the Ascending/Descending sort widget on the table headers, you will need to assign your own custom TableCellRenderer to the table headers (Remember: headers, not cells). How ? The only way to do it is to call the setHeaderRenderer() method in a TableColumn object. You can get a JTable object's TableColumn objects (one TableColumn for each column) from either the JTable object or the various methods in the JTable object's TableColumnModel object. Here's what our TableCellRenderer for the table headers looks like:

    class CustomHeaderRenderer implements TableCellRenderer{
      JLabel Renderer;
      ImageIcon Ascending,Descending;
      
      public CustomHeaderRenderer(){
        // Load the images that represent the widget
        Ascending=new ImageIcon("AscendSort.gif");
        Descending=new ImageIcon("DescendSort.gif");
    
        // Set up the Renderer
        Renderer=new JLabel();
        Renderer.setHorizontalAlignment(SwingConstants.CENTER);
        Renderer.setHorizontalTextPosition(SwingConstants.RIGHT);
        Renderer.setVerticalTextPosition(SwingConstants.CENTER);
        Renderer.setBorder(new EtchedBorder(EtchedBorder.LOWERED));
      }
        
      public Component getTableCellRendererComponent(JTable Table,
        Object Value,boolean Selected,
        boolean HasFocus,int Row,int Col){
        
        // Assign the column's name
        Renderer.setText(Value.toString());
          
        if (Table.convertColumnIndexToModel(Col)==ColumnToSort)
          Renderer.setIcon(AscendingSort ? Ascending : Descending); 
        else
          Renderer.setIcon(null);
          
        return Renderer;
      }
    }
    
    

    Note how the AscendSort.gif and DescendSort.gif images are loaded into ImageIcon objects in the constructor. Also, in the getTableCellRendererComponent() method, the if statement checks if the column to be rendered is the sorted column. If it is, the prgram puts either the ascending ImageIcon or the Descending ImageIcon object into the rendering JLabel. Then, Voila! You have the Ascending/Descending widget in the table headers.

    Here's the chunk of code from the SortedJTable class's constructor that assigns the CustomHeaderRenderer to the table headers:

    TableColumn Current;
    HeaderRenderer=new CustomHeaderRenderer();
    
    for (Enumeration Cols=getColumnModel().getColumns();
      Cols.hasMoreElements();){
      Current=(TableColumn)Cols.nextElement();
    
      // Assign our custom header-renderer to each column
      Current.setHeaderRenderer(HeaderRenderer);
    }
    

    NOTE: SortedJTable extends from JTable.


    2. Mouse-clickable table headers to allow the user to select the column to sort or to toggle Ascending/Descending sort on a column

    Obviously, what we need to achieve here is to respond to the user's mouse-clicks on the table's headers. How? As a matter of fact, all of a JTable object's table headers are represented by a lightweight component -- the JTableHeader object. This JTableHeader object can be obtained from the JTable object's getTableHeader() method. And finally, by adding a MouseListener to the JTableHeader object, we can respond to mouse-clicks on the JTable object's table headers! The following code shows how mouse clicks on the JTableHeader are handled by SortedJTable:

    public void mouseClicked(MouseEvent m){
      // Get the column which the user clicked
      int TargetCol=convertColumnIndexToModel(
        getTableHeader().columnAtPoint(m.getPoint()));
    
      if (TargetCol==ColumnToSort){
        // If it's already the current sorted column,
        // then just reverse the sorting order 
        AscendingSort=!AscendingSort;
    
      } else {
    
        ColumnToSort=TargetCol;
        AscendingSort=true;
      }
    
      // Sort the table all over again
      doSort();
      RealModel.fireChanged();
    
      // Repaint the table-header to reflect changes
      getTableHeader().repaint();
    }
    



    3. During sorting, a mechanism to compare between the rows in the relevant column

    Because our task is to create a sorted JTable, there must be a mechanism for comparing between two objects in a column to determine which of the two is "higher" than the other. Also, there must be a mechanism to map the sorted order of the rows to the actual order of the rows in the model.

    For the job of comparing objects, we can just write a simple interface with one method, which, when given two objects (of the same class), will return an integer value that indicates which object is "greater." With this interface, one comparator object can be created to handle each class of objects in the table. The interface is constructed like this:

    interface Comparator{
      public int compare(Object A,Object B);
    }
    

    We can use an array to map the sorted order of the rows to the actual order of the rows in the model. So, for example, the value in the first array index maps to the first sorted row (in the model). The diagram below illustrates this (again, remember that the array performs the mapping). Note that in the JTable shown below, the rows are sorted by the "Birthday" column.

    Mapping sorted rows to model

    See? It's as easy as 1-2-3, isn't it ?


    So Many Cool Things, So Little Time . . .

    There are at least a million and one more cool things you can implement with Swing, and I can't possibly cover them all in one article! But just for kicks, let's just take a look at some of the features of Mailpuccino's interface a little further, and see how Swing's cool look makes all the difference:

    Main Window with folders, tree view
    Main Window (with Folders-Tree View)
    Mail composition window
    Mail Composition Window
    Preferences window
    Preferences Window

    That should be enough to show you how Swing can be capitalized on to construct an infinite variety of pretty interfaces. Good luck in your own adventures with Swing, and have a Swinging good time! And (watch out, a plug is coming) why not download Mailpuccino and experience it first-hand, now!

     

    [an error occurred while processing this directive]