Graphic Java 2: Mastering the Jfc, By Geary, 3Rd Edition, Volume 2: Swing
The first step in adding a Swing component to your application is preparing the Swing package for use. As long as you have installed SDK 1.2 or later, you don't have to take any special steps to use the Swing classes. If you're preparing an application to run with JDK 1.1, you'll need to put the swingall.jar file on the CLASSPATH so that the Swing components are available during compilation and at runtime. In your source code, you include the Swing package by adding an import statement: import javax.swing.*; Now you're ready to replace your Button objects with JButton objects. We'll also set up the application to take advantage of Swing's L&F capabilities; we've put another row of buttons at the bottom of the frame that let you select one of the standard L&Fs: // ToolbarFrame2.java // The Swing-ified button example // import java.awt.*; import java.awt.event.*; import javax.swing.*; public class ToolbarFrame2 extends Frame { // This time, let's use JButtons! JButton cutButton, copyButton, pasteButton; JButton javaButton, macButton, motifButton, winButton; public ToolbarFrame2( ) { super("Toolbar Example (Swing)"); setSize(450, 250); addWindowListener(new WindowAdapter( ) { public void windowClosing(WindowEvent e) { System.exit(0); } }); ActionListener printListener = new ActionListener( ) { public void actionPerformed(ActionEvent ae) { System.out.println(ae.getActionCommand( )); } }; // JPanel works similarly to Panel, so we'll use it. JPanel toolbar = new JPanel( ); toolbar.setLayout(new FlowLayout(FlowLayout.LEFT)); cutButton = new JButton("Cut"); cutButton.addActionListener(printListener); toolbar.add(cutButton); copyButton = new JButton("Copy"); copyButton.addActionListener(printListener); toolbar.add(copyButton); pasteButton = new JButton("Paste"); pasteButton.addActionListener(printListener); toolbar.add(pasteButton); add(toolbar, BorderLayout.NORTH); // Add the L&F controls. JPanel lnfPanel = new JPanel( ); LnFListener lnfListener = new LnFListener(this); macButton = new JButton("Mac"); macButton.addActionListener(lnfListener); lnfPanel.add(macButton); javaButton = new JButton("Metal"); javaButton.addActionListener(lnfListener); lnfPanel.add(javaButton); motifButton = new JButton("Motif"); motifButton.addActionListener(lnfListener); lnfPanel.add(motifButton); winButton = new JButton("Windows"); winButton.addActionListener(lnfListener); lnfPanel.add(winButton); add(lnfPanel, BorderLayout.SOUTH); } public static void main(String args[]) { ToolbarFrame2 tf2 = new ToolbarFrame2( ); tf2.setVisible(true); } } As you can see in Figure 2-2, the application is more or less the same. All we did was change Button to JButton and add four more JButtons for L&F selection. We update the application's L&F in the LnFListener class, which gets its events from the simple Swing buttons at the bottom of the application. Apart from figuring out which button was pressed, we must also force the L&F to change. That's pretty simple. The first step is setting the new L&F using the UIManager.setLookAndFeel( ) method. (This is the method that needs the correct name for the L&F we want.) Once the L&F is set, we want to make the change visible immediately, so we update the L&F for all of the components using the SwingUtilities.updateComponentTreeUI( ) method: // LnFListener.java // A listener that can change the L&F of a frame based on the actionCommand of an // ActionEvent object. Supported L&Fs are: Mac, Metal, Motif, and Windows. Not all // L&Fs will be available on a given machine. Notably, the Mac and Windows L&Fs work // only on their specific platforms. import java.awt.*; import java.awt.event.*; import javax.swing.*; public class LnFListener implements ActionListener { Frame frame; public LnFListener(Frame f) { frame = f; } public void actionPerformed(ActionEvent e) { String lnfName = null; if (e.getActionCommand( ).equals("Mac")) { lnfName = "com.apple.mrj.swing.MacLookAndFeel"; } else if (e.getActionCommand( ).equals("Metal")) { lnfName = "javax.swing.plaf.metal.MetalLookAndFeel"; } else if (e.getActionCommand( ).equals("Motif")) { lnfName = "com.sun.java.swing.plaf.motif.MotifLookAndFeel"; } else if (e.getActionCommand( ).equals("Windows")) { lnfName = "com.sun.java.swing.plaf.windows.WindowsLookAndFeel"; } else { System.err.println("Unrecognized L&F request action: " + e.getActionCommand( )); return; } try { UIManager.setLookAndFeel(lnfName); SwingUtilities.updateComponentTreeUI(frame); } catch (UnsupportedLookAndFeelException ex1) { System.err.println("Unsupported LookAndFeel: " + lnfName); } catch (ClassNotFoundException ex2) { System.err.println("LookAndFeel class not found: " + lnfName); } catch (InstantiationException ex3) { System.err.println("Could not load LookAndFeel: " + lnfName); } catch (IllegalAccessException ex4) { System.err.println("Cannot use LookAndFeel: " + lnfName); } } } With the JButton objects in place we get the application shown in Figure 2-2. Figure 2-2. The same application with JButtons for Cut, Copy, and Paste (in the Metal L&F) When we run the new version of the application, we still get ActionEvent objects from pressing the buttons, and the events are still delivered to the actionPerformed( ) method. OK, big deal. Now we have buttons that work just like before and don't look particularly great. So what? Well, for one thing, we can now take advantage of the new UI management capabilities of Swing components. Swing provides L&Fs that we can use with any of its components. If you press the Mac, Metal, Motif, or Windows button in this application, it switches from the current L&F to the appropriate version (if it's available on your system). Figure 2-3 shows the effect. Figure 2-3. JButtons using the Mac (left), Motif (right), and Windows (bottom) L&Fs Now we've got a bunch of JButtons. We're still using the old AWT Panel and Frame objects as containers for our applications. You can change them easily, too. Changing Panel to JPanel is as simple as updating the buttons: just do a global replace, and you're done. Updating Frame is a little more complex. Once you've replaced Frame with JFrame, you must also look at the calls to add( ) that put things in the JFrame. A JFrame has something in it called a "content pane"; when we add something to a JFrame, we usually want to add it to this content pane: getContentPane( ).add(something); // Formerly just add(something) With these changes, the JFrame and JPanel also change their appearance when you change the application's L&F. It may not be noticeable. But you'll also get the other new features that Swing gives you. We'll stick with the old Frame and Panel for now, but we'll use JFrame and JPanel later in this chapter and throughout the book. This is all very nice, but it's still not what we came for. We weren't interested in making minor changes in the way our buttons look, though that's a nice side effect. So let's get to those images! First, we need to create what the Swing components refer to as an Icon. You can get the details on icons in Chapter 4, but for now, just think of them as nicely self-contained images we can use inside just about any of the Swing components that can display normal text (such as labels, buttons, and menu items). We'll start out by adding an image to the text we're currently displaying in each button. We can use all of the graphics formats Java supports (GIF, JPEG, and others) with icons, including transparent and animated GIF-89a images. Here's the code to add images to each of our buttons: cutButton = new JButton("Cut", new ImageIcon("cut.gif")); cutButton.addActionListener(this); toolbar.add(cutButton); copyButton = new JButton("Copy", new ImageIcon("copy.gif")); copyButton.addActionListener(this); toolbar.add(copyButton); pasteButton = new JButton("Paste", new ImageIcon("paste.gif")); pasteButton.addActionListener(this); toolbar.add(pasteButton); This creates buttons with little icons to the left of the text. Any L&F can display the images. Figure 2-4 shows the result. Figure 2-4. Icon and text buttons in the Metal (left) and Mac (right) L&Fs Adding the icons hasn't changed anything. In particular, our action event handlers are exactly the same as they were with normal AWT buttons. But you probably see a problem developing. Our handler uses the buttons' text labels to decide which button was pressed. That's not a problem since our buttons still display some text. What happens if we throw that text out? How can we tell which button was pressed? First, let's look at the code to create an image-only button: copyButton = new JButton(new ImageIcon("copy.gif")); copyButton.addActionListener(this); toolbar.add(copyButton); If we do this for every button, the application looks like Figure 2-5. Figure 2-5. Icon-only JButtons in the Metal (left) and Windows (right) L&Fs Now let's look back at the event handler we use: public void actionPerformed(ActionEvent e) { System.out.println(e.getActionCommand( )); } This doesn't do much. Normally, you would need to distinguish between the various buttons or other components that report to this handler. Since we implement the ActionListener interface directly in the application class, we can use the simple route of checking the source of the event against the buttons we know we have. For example, we could differentiate the Cut, Copy, and Paste buttons like this: public void actionPerformed(ActionEvent ae) { if (ae.getSource( ) == cutButton) { System.out.println("Got Cut event"); } else if (ae.getSource( ) == copyButton) { System.out.println("Got Copy event"); } else if (ae.getSource( ) == pasteButton) { System.out.println("Got Paste event"); } } However, we don't always have the luxury of implementing the event handler directly in our application, and we might not want to pass around a huge list of button references to make it possible to write such code in other classes. Instead, you can use the actionCommand property of the Button class to distinguish your buttons from one another. The JButton class also implements this property, so we can just call setActionCommand( ) for each of the buttons and pass in a unique string that we can check in the actionPerformed( ) method regardless of which class that method sits in. Using the actionCommand property to distinguish a component works for components whose appearance might be changing for any of a variety of reasons. (For example, you might be writing an international application in which the text on the button changes depending on the user's native language.) Now, this is not the only or even the best way to handle events from our buttons, but it's a slightly more portable version of our simple application. Later, we'll look at the new Action interface to better support this type of event handling in a more object-oriented manner. For now, this code is easy to understand, even if it is a bit clunky. // ToolbarFrame4.java // The Swing-ified button example. The buttons in this toolbar all carry images // but no text. // import java.awt.*; import java.awt.event.*; import javax.swing.*; public class ToolbarFrame4 extends Frame { JButton cutButton, copyButton, pasteButton; JButton javaButton, macButton, motifButton, winButton; public ToolbarFrame4( ) { super("Toolbar Example (Swing no text)"); setSize(450, 250); addWindowListener(new WindowAdapter( ) { public void windowClosing(WindowEvent e) { System.exit(0); } }); // JPanel works much like Panel does, so we'll use it. JPanel toolbar = new JPanel( ); toolbar.setLayout(new FlowLayout(FlowLayout.LEFT)); CCPHandler handler = new CCPHandler( ); cutButton = new JButton(new ImageIcon("cut.gif")); cutButton.setActionCommand(CCPHandler.CUT); cutButton.addActionListener(handler); toolbar.add(cutButton); copyButton = new JButton(new ImageIcon("copy.gif")); copyButton.setActionCommand(CCPHandler.COPY); copyButton.addActionListener(handler); toolbar.add(copyButton); pasteButton = new JButton(new ImageIcon("paste.gif")); pasteButton.setActionCommand(CCPHandler.PASTE); pasteButton.addActionListener(handler); toolbar.add(pasteButton); add(toolbar, BorderLayout.NORTH); // Add the L&F controls. JPanel lnfPanel = new JPanel( ); LnFListener lnfListener = new LnFListener(this); macButton = new JButton("Mac"); macButton.addActionListener(lnfListener); lnfPanel.add(macButton); javaButton = new JButton("Metal"); javaButton.addActionListener(lnfListener); lnfPanel.add(javaButton); motifButton = new JButton("Motif"); motifButton.addActionListener(lnfListener); lnfPanel.add(motifButton); winButton = new JButton("Windows"); winButton.addActionListener(lnfListener); lnfPanel.add(winButton); add(lnfPanel, BorderLayout.SOUTH); } public static void main(String args[]) { ToolbarFrame4 tf4 = new ToolbarFrame4( ); tf4.setVisible(true); } } Here's the new event handler for this simple application. Notice that we set up some constants for the different actions we plan to take. We can now use these constants in the setActionCommand( ) call of any application whenever we're setting up Cut, Copy, or Paste buttons regardless of what we display on the screen for the buttons. We can now easily tell which action to take in the actionPerformed( ) method. However, you may still need to pass a reference to objects that contain the buttons because you will most likely need to take a real action when the user presses a button. We'll look at such a program a bit later in the chapter. // CCPHandler.java // A Cut, Copy, and Paste event handler. Nothing too fancy, just define some // constants that can be used to set the actionCommands on buttons. // import java.awt.event.*; public class CCPHandler implements ActionListener { public final static String CUT = "cut"; public final static String COPY = "copy"; public final static String PASTE = "paste"; public void actionPerformed(ActionEvent e) { String command = e.getActionCommand( ); if (command == CUT) { // We can do this since we're comparing constants. System.out.println("Got Cut event"); } else if (command == COPY) { System.out.println("Got Copy event"); } else if (command == PASTE) { System.out.println("Got Paste event"); } } } Finally, we should point out that although CCPHandler illustrates another way of handling button events, the Action mechanism introduced at the end of this chapter, and discussed in depth at the start of Chapter 3, is more powerful, object-oriented, and far more commonly used. |