Develop dialog boxes and Swing apps faster I had been working at Sun Microsystems for just over a year when the mandate came from on high to switch to Java. In those days, you could become a Java expert in a scant few months, and we did, rewriting an existing C++ application in Java. Today, of course, it’s a different story—novice Java developers must learn a huge API, including the basics such as collections, JavaBeans, input/output (I/O), Swing, internationalization and localization, and more, not to mention ancillary topics like Ant, Java API for XML Parsing (JAXP), log4j, JavaServer Pages (JSP), JSP Standard Tag Library (JSTL), JavaServer Faces, Java Message Service (JMS), Enterprise JavaBeans (EJB), Java Data Objects (JDO), AspectJ…the list goes on and on. Just keeping up with the onslaught of new technologies is more than a full-time job.Object-oriented development helps manage complexity by encapsulating data and functionality in classes with well-defined interfaces, and Java lets you encapsulate classes in packages. But that encapsulation only goes so far; sometimes you have to delve into complex APIs to do even the simplest things, as EJB developers can attest.Although object-oriented development fails to reduce the complexity of learning a new API, we can use its facilities to implement a design pattern that does: the Façade pattern, where objects known as façades offer a simple interface to complex subsystems so you can do something without knowing the subsystem’s particulars. Let’s see how the Façade pattern works. Note: You can download the associated source code from Resources.The Façade patternIn Design Patterns, the authors describe the Façade pattern like this:Provide a unified interface to a set of interfaces in a subsystem. Façade defines a higher-level interface that makes the subsystem easier to use.Figure 1 shows the Façade design pattern’s class diagram. Java consists of high-powered APIs that let you do everything from developing killer graphical user interfaces (GUIs) to sending asynchronous messages between distributed objects. But those high-powered APIs can make it incredibly difficult to do the simplest things; for example, consider the “simple” GUI application that I discuss in “A Swing Application Façade” below.The Façade pattern is easy to understand: one class provides a simplified interface to a subsystem. Façades make the easy stuff easy; for the hard stuff, you must delve into the subsystem itself. But façades typically accommodate a high percentage of a subsystem’s use cases and, therefore, they are particularly valuable; so valuable in fact, that as we shall see in the “JOptionPane: Swing’s Façade for Dialogs” section below, complex subsystems such as Swing’s API for windows and dialog boxes often implement façades of their own that simplify their use.This installment of Java Design Patterns examines two uses of the Façade pattern. The first discusses a built-in Swing façade that simplifies the use of dialog boxes, and the second examines a custom façade implementation that makes it easy to throw together a Swing application. JOptionPane: Swing’s façade for dialogsSwing provides extensive facilities for windows and dialog boxes, from low-level functionality for creating, populating, and showing windows to high-powered features such as glass panes and choosers for selecting files and colors. Although you can do nearly anything with Swing windows and dialogs, it’s not an easy matter to create even the simplest dialogs from scratch; for example, consider Figure 2’s application that displays a home-grown message dialog.Figure 2’s application has a toolbar with two active buttons: if you click the left button, the application creates a message dialog from scratch and displays it; Example 1 lists the event handler for that button. Example 1. Implement a simple message dialog ... JDialog dialog = new JDialog(this, "Error!", false); ... newButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { // Top-left panel: JPanel topLeft = new JPanel(); topLeft.setLayout(new FlowLayout(FlowLayout.LEFT)); topLeft.add(new JLabel(new ImageIcon("../graphics/stopsign.jpg"))); // Top-right panel: JPanel topRight = new JPanel(); topRight.setLayout(new BorderLayout()); topRight.add(new JLabel("You must follow directions!", JLabel.CENTER), BorderLayout.EAST); // Top panel: JPanel top = new JPanel(); top.setLayout(new BorderLayout(15,0)); top.add(topLeft, BorderLayout.WEST); top.add(topRight, BorderLayout.EAST); // OK button: JButton button = new JButton("OK"); button.setDefaultCapable(true); getRootPane().setDefaultButton(button); button.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { dialog.hide(); } }); // Bottom panel: JPanel bottom = new JPanel(); bottom.setLayout(new BorderLayout()); bottom.add(button, BorderLayout.EAST); // Dialog panel: JPanel dialogPanel = new JPanel(); dialogPanel.setBorder(BorderFactory.createEmptyBorder(15,15,15,10)); dialogPanel.setLayout(new BorderLayout()); dialogPanel.add(top, BorderLayout.NORTH); dialogPanel.add(bottom, BorderLayout.SOUTH); Container cp = dialog.getContentPane(); cp.add(dialogPanel); dialog.setResizable(false); dialog.pack(); dialog.setLocationRelativeTo(TLDViewer.this); dialog.show(); } }); The preceding code uses five panels (instances of JPanel) to construct the dialog’s user interface; Figure 3 shows those panels.Figure 3. Layout for a dialogFigure 3’s panels are arranged in a hierarchy like this:dialogPanel top topLeft topRight bottom The dialogPanel contains the top and bottom panels, and the top panel contains the topLeft and topRight panels. Each panel in Figure 3 uses an appropriate layout manager in Example 1. I wanted the graphic left-justified, and the message and the button right-justified. I picked this configuration because it’s the same as message dialogs created by Swing’s dialog façade (see “Use JOptionPane” below for more information).Although it’s certainly not rocket science, Example 1 should convince you that creating dialog boxes from scratch is tedious work, which results in frequent trips to the Swing Javadocs for the average Java developer. Of course, wise developers would encapsulate that code in a class of its own for future reuse, instead of reimplementing the same functionality for future dialog boxes. Swing’s creators were even wiser: they implemented a class—JOptionPane—that pops up the most common types of dialog boxes. Let’s see how we can use JOptionPane to simplify dialog creation and display.Use JOptionPaneSwing’s JOptionPane, depicted in Figure 4, is a façade that creates different types of dialogs and displays them. Example 2 shows a rewritten action listener from Example 1 that uses JOptionPane to create a message dialog instead of creating it from scratch.Example 2. JOptionPane creates a simple message dialog ... openButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { JOptionPane.showMessageDialog(TLDViewer.this, "You must follow directions!", "Error!", JOptionPane.ERROR_MESSAGE); } }); ... Could any infomercial rival the amazing results you get from using JOptionPane? Compare Example 1 to Example 2 and decide which block of code you’d rather write the next time you need a message dialog. Thankfully, we have the Façade pattern. A Swing application façadeSwing is one of my all-time favorite GUI frameworks, but even after writing a 1,600-page book on the subject, I have to refer to the book and Javadocs almost every time I write a Swing application. Just getting a Swing application off the ground can require heavy lifting, especially if you follow best practices by internationalizing your application. This section shows you how to implement a façade that encapsulates much of the grunt work involved in setting up a Swing application.Figure 5’s application is a library for cataloging books, DVDs, and VHS tapes.The application’s features are unimportant to us, except for the fact that the application is internationalized and localized in English and French; Figure 6 shows the application localized for French. The library uses a façade—ApplicationSupport—I wrote so I could create Swing applications in a hurry. That façade provides the following methods:Launch application: public static void launch(final JFrame f, String title, final int x, final int y, final int w, int h)Menus and locale: public static JMenu addMenu(final JFrame f, String titleKey, String[] itstrong)public static Locale getLocale()Status area: public static JPanel getStatusArea()public static void showStatus(String s)Internationlization: public static String getResource(String key)public static String formatMessage(String patternKey, String[] params)Rather than show you the tedium involved in getting an application like the one in Figure 6 off the ground and then contrast it with the use of a façade, let’s save some time and proceed directly to the latter. Example 3 illustrates how Figure 5’s application uses the ApplicationSupport class. Example 3. Use a Swing application façadepublic class LibraryViewer extends JFrame { ... private static final String FILEMENU_TITLE = "app.frame.menus.file.title", FILEMENU_NEW = "app.frame.menus.file.new", FILEMENU_EXIT = "app.frame.menus.file.exit", EDITMENU_TITLE = "app.frame.menus.edit.title", EDITMENU_CUT = "app.frame.menus.edit.cut", EDITMENU_COPY = "app.frame.menus.edit.copy"; ... public static void main(String args[]) { ApplicationSupport.launch(new LibraryViewer(), ApplicationSupport.getResource("app.frame.title"), windowX, windowY, windowW, windowH); } public void clearStatusArea() { ApplicationSupport.showStatus(""); } public void selectDVD(boolean valueIsAdjusting) { ... ApplicationSupport.showStatus((String)table.getValueAt(table.getSelectedRow(), 0)); } private JMenu makeEditMenu() { return ApplicationSupport.addMenu(this, EDITMENU_TITLE, new String[] { EDITMENU_CUT, EDITMENU_COPY }); } private JMenu makeFileMenu() { JMenu fileMenu = ApplicationSupport.addMenu(this, FILEMENU_TITLE, new String[] {FILEMENU_NEW, FILEMENU_EXIT}); fileMenu.getItem(1).addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { System.exit(1); } }); } public void categoryChanged(JTabbedPane pane) { int selectedIndex = pane.getSelectedIndex(); if(selectedIndex >= 0) ApplicationSupport.showStatus(pane.getTitleAt(selectedIndex)); } private void makeCategoryTabs() { categoryPane.addTab(ApplicationSupport.getResource("app.books.tab"), booksPanel); categoryPane.addTab(ApplicationSupport.getResource("app.dvds.tab"), dvdPanel); categoryPane.addTab(ApplicationSupport.getResource("app.vhs.tab"), vhsPanel); } private void initializeTable() { String[] columnNames = new String[] { ApplicationSupport.getResource("dvd.title.column.heading"), ApplicationSupport.getResource("dvd.rating.column.heading"), ApplicationSupport.getResource("dvd.languages.column.heading"), ApplicationSupport.getResource("dvd.length.column.heading"), }; DefaultTableModel model = new LibraryTableModel(); for(int i=0; i < columnNames.length; ++i) model.addColumn(columnNames[i]); table.setModel(model); } private JComponent makeToolbar() { JToolBar toolbar = new JToolBar(); JButton newButton = new JButton(new ImageIcon(ApplicationSupport.getResource( TOOLBAR_NEW_ICON))); JButton openButton = new JButton(new ImageIcon(ApplicationSupport.getResource( TOOLBAR_OPEN_ICON))); JButton saveButton = new JButton(new ImageIcon(ApplicationSupport.getResource( TOOLBAR_SAVE_ICON))); JButton cutButton = new JButton(new ImageIcon(ApplicationSupport.getResource( TOOLBAR_CUT_ICON))); JButton copyButton = new JButton(new ImageIcon(ApplicationSupport.getResource( TOOLBAR_COPY_ICON))); JButton pasteButton = new JButton(new ImageIcon(ApplicationSupport.getResource( TOOLBAR_PASTE_ICON))); JButton redoButton = new JButton(new ImageIcon(ApplicationSupport.getResource( TOOLBAR_REDO_ICON))); ... return toolbar; } The preceding code uses ApplicationSupport.launch() to position and size the application’s window, set the window’s title, and set up a window listener that closes the application when the close box in the window is activated. The application uses the façade’s getResource() method to internationalize text displayed to the user based on key/value entries in a resource file. The application also uses the façade to display information in a status panel (created by the façade) and the façade’s addMenu() method to easily create a menu.Finally, the preceding application uses the ApplicationSupport and JOptionPane façades to create a localized dialog box, shown in Figure 7.Example 4 shows the code that displays Figure 7’s dialog. Thanks to façades, two lines of code are all it takes to display a localized dialog. Example 4. Use façades to create a localized dialog private void showErrorDialog(String missingFilename) { String msg = ApplicationSupport.formatMessage("error.missing.file", new String[] {missingFilename}); JOptionPane.showMessageDialog(LibraryViewer.this, msg, ApplicationSupport.getResource("error.missing.file.dialog.title"), JOptionPane.ERROR_MESSAGE); } The ApplicationSupport.formatMessage() method formats a pattern (the method’s first argument) given a set of arguments (the method’s second argument) to format a localized message with the given filename. Because you probably don’t format messages everyday, encapsulating that behavior in a façade is a great timesaver.Example 5 lists the ApplicationSupport class. Example 5. The ApplicationSupport façadepublic final class ApplicationSupport { static private final String PREFS_BUNDLE_BASENAME = "prefs"; static private final String BUNDLE_BASENAME = "app", PREFERRED_LOCALE_KEY = "locale"; static private final JPanel statusArea = new JPanel(); static private final JLabel status = new JLabel(); static private ResourceBundle preferences, resources; static private Locale locale; static { try { preferences = ResourceBundle.getBundle(PREFS_BUNDLE_BASENAME); locale = new Locale(preferences.getString(PREFERRED_LOCALE_KEY)); } catch(java.util.MissingResourceException ex) { System.err.println("ERROR: cannot find preferences properties file " + BUNDLE_BASENAME); } try { resources = ResourceBundle.getBundle(BUNDLE_BASENAME, locale); } catch(java.util.MissingResourceException ex) { System.err.println("ERROR: cannot find properties file for " + BUNDLE_BASENAME); } }; // Disallow direct instantiation private ApplicationSupport() {} public static void launch(final JFrame f, String title, final int x, final int y, final int w, int h) { f.setTitle(title); f.setBounds(x,y,w,h); f.setVisible(true); f.setResizable(true); status.setHorizontalAlignment(JLabel.LEFT); statusArea.setBorder(BorderFactory.createEtchedBorder()); statusArea.setLayout(new FlowLayout(FlowLayout.LEFT,0,0)); statusArea.add(status); f.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); f.addWindowListener(new WindowAdapter() { public void windowClosed(WindowEvent e) { System.exit(0); } }); } public static Locale getLocale() { return locale; } public static JMenu addMenu(final JFrame f, String titleKey, String[] itemKeys) { JMenuBar mb = f.getJMenuBar(); if(mb == null) { mb = new JMenuBar(); f.setJMenuBar(mb); } JMenu menu = new JMenu(ApplicationSupport.getResource(titleKey)); for(int i=0; i < itemKeys.length; ++i) { menu.add(new JMenuItem(ApplicationSupport.getResource(itemKeys[i]))); } mb.add(menu); return menu; } public static JPanel getStatusArea() { return statusArea; } public static void showStatus(String s) { status.setText(s); } public static String getResource(String key) { return (resources == null) ? null : resources.getString(key); } public static String formatMessage(String patternKey, String[] params) { String pattern = ApplicationSupport.getResource(patternKey); MessageFormat fmt = new MessageFormat(pattern); return fmt.format(params); } } All ApplicationSupport methods are static, which is common for a façade because often those methods are unrelated. The ApplicationSupport class can launch a Swing application, localize text from a resource bundle, display information in a status panel, and create localized menus. The ApplicationSource class makes it significantly easier to implement Swing applications.Fortunately for façadesPowerful APIs, which are plentiful in Java, are wonderful when you need a power tool, but for most basic uses, façades provide a simpler alternative that greatly eases application development. In this article, I’ve discussed a Swing built-in façade—the JOptionPane class—and a custom façade—ApplicationSupport—that make it much easier to create Swing applications. David Geary is the author of Core JSTL Mastering the JSP Standard Tag Library (Prentice Hall, 2002; ISBN: 0131001531), Advanced JavaServer Pages (Prentice Hall, 2001; ISBN: 0130307041), and the Graphic Java series (Prentice Hall). David has been developing object-oriented software with numerous object-oriented languages for 18 years. Since the GOF Design Patterns book was published in 1994, David has been an active proponent of design patterns, and has used and implemented design patterns in Smalltalk, C++, and Java. In 1997, David began working full-time as an author and occasional speaker and consultant. David is a member of the expert groups defining the JSP Standard Tag Library and JavaServer Faces, and is a contributor to the Apache Struts JSP framework. Software DevelopmentDesign PatternsJava