1.5 Using Other Swing Features

«« Previous
Next »»

This lesson contains a collection of how-to pages to help you use miscellaneous Swing features.

➤ How to Integrate with the Desktop Class


Java™ Standard Edition version 6 narrows the gap between performance and integration of native applications and Java applications. Along with the new system tray functionality, splash screen support, and enhanced printing for JTables, Java SE version 6 provides the Desktop API (java.awt.Desktop) API, which allows Java applications to interact with default applications associated with specific file types on the host platform.

New functionality is provided by the Desktop class. The API arises from the JDesktop Integration Components (JDIC) project. The goal of the JDIC project is to make "Java technology-based applications first-class citizens" of the desktop, enabling seamless integration. JDIC provides Java applications with access to functionalities and facilities provided by the native desktop. Regarding the new Desktop API, this means that a Java application can perform the following operations:

Launch the host system's default browser with a specific Uniform Resource Identifier (URI)
Launch the host system's default email client
Launch applications to open, edit, or print files associated with those applications

Note: The Desktop API uses the host operating system's file associations to launch applications associated with specific file types. For example, if OpenDocument text (.odt) file extensions are associated with the OpenOffice Writer application, a Java application could launch OpenOffice Writer to open, edit, or even print files with that association. Depending on the host system, different applications may be associated with different actions. For example, if a particular file cannot be printed, check first whether its extension has a printing association on the given operating system.

Use the isDesktopSupported() method to determine whether the Desktop API is available. On the Solaris Operating System and the Linux platform, this API is dependent on Gnome libraries. If those libraries are unavailable, this method will return false. After determining that the Desktop API is supported, that is, the isDesktopSupported() returns true, the application can retrieve a Desktop instance using the static method getDesktop().

If an application runs in an environment without a keyboard, mouse, or monitor (a "headless" environment), the getDesktop() method throws a java.awt.HeadlessException.

Once retrieved, the Desktop instance allows an application to browse, mail, open, edit, or even print a file or URI, but only if the retrieved Desktop instance supports these activities. Each of these activities is called an action, and each is represented as a Desktop.Action enumeration instance:
  • BROWSE — Represents a browse action performed by the host's default browser.
  • MAIL — Represents a mail action performed by the host's default email client.
  • OPEN — Represents an open action performed by an application associated with opening a specific file type.
  • EDIT — Represents an edit action performed by an application associated with editing a specific file type
  • PRINT — Represents a print action performed by an application associated with printing a specific file type.
Different applications may be registered for these different actions even on the same file type. For example, the Firefox browser may be launched for the OPEN action, Emacs for the EDIT action, and yet a different application for the PRINT action. Your host desktop's associations are used to determine which application should be invoked. The ability to manipulate desktop file associations is not possible with the current version of the Desktop API in JDK 6, and those associations can be created or changed only with platform-dependent tools at this time.

The following example shows the capabilities mentioned above.

Try this:

1. Compile and run the example, consult the example index.
2. The DesktopDemo dialog box will appear.

Oracle Java Tutorials and Materials, Oracle Java Certifications

3. Enter an URI value into the URI text field, for example – https://docs.oracle.com/javase/tutorial.
4. Press the Launch Browser button.
5. Ensure that the default browser window opens and the Tutorial main page is loaded.
6. Change URI to an arbitrary value, press the Launch Browser button, and ensure that the web page you requested is loaded successfully.
7. Switch back to the DesktopDemo dialog box and enter a mail recipient name in the E-mail text field. You can also use the mailto scheme supporting CC, BCC, SUBJECT, and BODY fields, for example – duke@example.com?SUBJECT=Hello Duke!.
8. Press the Launch Mail button.
9. The compositing dialog box of the default email client will appear. Be sure that the To and Subject fields are as follows.

Oracle Java Tutorials and Materials, Oracle Java Certifications

10. You can continue to compose a message or try to enter different combinations of the mail schema in the E-mail field.
11. Switch back to the DesktopDemo dialog box and press the ellipsis button to choose any text file.
12. Select either Open, Edit, or Print to set the type of operation, then press the Launch Application button.
13. Be sure that operation completes correctly. Try other file formats, for example .odt, .html, .pdf. 

Note: If you try to edit .pdf file, the DesktopDemo returns the following message: Cannot perform the given operation to the <file name> file

The following code snippets provide more details on the DeskDemo application implementation. The DesktopDemo constructor disables the few components right after instantiating the UI and checks whether the Desktop API is available.

 public DesktopDemo() {
        // init all gui components
        initComponents();
        // disable buttons that launch browser, email client,
        // disable buttons that open, edit, print files
        disableActions();
        // before any Desktop APIs are used, first check whether the API is
        // supported by this particular VM on this particular host
        if (Desktop.isDesktopSupported()) {
            desktop = Desktop.getDesktop();
            // now enable buttons for actions that are supported.
            enableSupportedActions();
        }
        ...

    /**
     * Disable all graphical components until we know
     * whether their functionality is supported.
     */
    private void disableActions() {
        txtBrowserURI.setEnabled(false);
        btnLaunchBrowser.setEnabled(false);
        
        txtMailTo.setEnabled(false);
        btnLaunchEmail.setEnabled(false);
        
        rbEdit.setEnabled(false);
        rbOpen.setEnabled(false);
        rbPrint.setEnabled(false);

        txtFile.setEnabled(false);
        btnLaunchApplication.setEnabled(false);        
    }

   ...
Once a Desktop object is acquired, you can query the object to find out which specific actions are supported. If the Desktop object does not support specific actions, or if the Desktop API itself is unsupported, DesktopDemo simply keeps the affected graphical components disabled.

/**
     * Enable actions that are supported on this host.
     * The actions are the following: open browser, 
     * open email client, and open, edit, and print
     * files using their associated application.
     */
    private void enableSupportedActions() {
        if (desktop.isSupported(Desktop.Action.BROWSE)) {
            txtBrowserURI.setEnabled(true);
            btnLaunchBrowser.setEnabled(true);
        }
        
        if (desktop.isSupported(Desktop.Action.MAIL)) {
            txtMailTo.setEnabled(true);
            btnLaunchEmail.setEnabled(true);
        }

        if (desktop.isSupported(Desktop.Action.OPEN)) {
            rbOpen.setEnabled(true);
        }
        if (desktop.isSupported(Desktop.Action.EDIT)) {
            rbEdit.setEnabled(true);
        }
        if (desktop.isSupported(Desktop.Action.PRINT)) {
            rbPrint.setEnabled(true);
        }
        
        if (rbEdit.isEnabled() || rbOpen.isEnabled() || rbPrint.isEnabled()) {
            txtFile.setEnabled(true);
            btnLaunchApplication.setEnabled(true);
        }
    }

The browse(uri) method can throw a variety of exceptions, including a NullPointerException if the URI is null, and an UnsupportedOperationException if the BROWSE action is unsupported. This method can throw an IOException if the default browser or application cannot be found or launched, and a SecurityException if a security manager denies the invocation.

private void onLaunchBrowser(ActionEvent evt) {
        URI uri = null;
        try {
            uri = new URI(txtBrowserURI.getText());
            desktop.browse(uri);
        } catch(IOException ioe) {
            System.out.println("The system cannot find the " + uri + 
                " file specified");
            //ioe.printStackTrace();
        } catch(URISyntaxException use) {
            System.out.println("Illegal character in path");
            //use.printStackTrace();
        }
    }

Applications can launch the host's default email client, if that action is supported, by calling the mail(uriMailTo) method of this Desktop instance.

private void onLaunchMail(ActionEvent evt) {
        String mailTo = txtMailTo.getText();
        URI uriMailTo = null;
        try {
            if (mailTo.length() > 0) {
                uriMailTo = new URI("mailto", mailTo, null);
                desktop.mail(uriMailTo);
            } else {
                desktop.mail();
            }
        } catch(IOException ioe) {
            ioe.printStackTrace();
        } catch(URISyntaxException use) {
            use.printStackTrace();
        }
    }

Java applications can open, edit, and print files from their associated application using the open(), edit(), and print() methods of the Desktop class, respectively.

private void onLaunchDefaultApplication(ActionEvent evt) {
        String fileName = txtFile.getText();
        File file = new File(fileName);
        
        try {
            switch(action) {
                case OPEN:
                    desktop.open(file);
                    break;
                case EDIT:
                    desktop.edit(file);
                    break;
                case PRINT:
                    desktop.print(file);
                    break;
            }
        } catch (IOException ioe) {
            //ioe.printStackTrace();
            System.out.println("Cannot perform the given operation 
                to the " + file + " file");
        }
    }

The complete code for this demo is available in the DesktopDemo.java file.

import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.net.*;
import javax.swing.*;


public class DesktopDemo extends JFrame {
    
    JButton btnLaunchApplication = new JButton("Launch Application");
    JButton btnLaunchBrowser = new JButton("Launch Browser");
    JButton btnLaunchEmail = new JButton();
    JRadioButton rbEdit = new JRadioButton("Edit");
    JRadioButton rbOpen = new JRadioButton("Open", true);
    JRadioButton rbPrint = new JRadioButton("Print");
    JTextField txtBrowserURI = new JTextField();
    JTextField txtMailTo = new JTextField();
    JTextField txtFile = new JTextField();
    ButtonGroup bgAppAction = new ButtonGroup();
    JLabel lblMailRecipient = new JLabel("E-mail:");
    JLabel lblBrowserUri = new JLabel("URI:");
    JLabel lblFile = new JLabel("File:");
    JButton btnFile = new JButton("...");
    JLabel emptyLabel = new JLabel(" ");
    JPanel conLeft = new JPanel();
    JPanel conCenter = new JPanel();
    JPanel conRight = new JPanel();
    JFileChooser fc = new JFileChooser();
    File file;
    
    private Desktop desktop;
    private Desktop.Action action = Desktop.Action.OPEN;
    
    
    /**
     * Creates new form DesktopDemo
     */
    public DesktopDemo() {
        // init all gui components
        initComponents();
        // disable buttons that launch browser, email client,
        // disable buttons that open, edit, print files
        disableActions();
        // before any Desktop APIs are used, first check whether the API is
        // supported by this particular VM on this particular host
        if (Desktop.isDesktopSupported()) {
            desktop = Desktop.getDesktop();
            // now enable buttons for actions that are supported.
            enableSupportedActions();
        }
        loadFrameIcon();
        setResizable(false);
    }
    
    public static void main(String args[]) {
        /* Use an appropriate Look and Feel */
        try {
            //UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel");
            //UIManager.setLookAndFeel("com.sun.java.swing.plaf.gtk.GTKLookAndFeel");
            UIManager.setLookAndFeel("javax.swing.plaf.metal.MetalLookAndFeel");
        } catch (UnsupportedLookAndFeelException ex) {
            ex.printStackTrace();
        } catch (IllegalAccessException ex) {
            ex.printStackTrace();
        } catch (InstantiationException ex) {
            ex.printStackTrace();
        } catch (ClassNotFoundException ex) {
            ex.printStackTrace();
        }
        /* Turn off metal's use of bold fonts */
        UIManager.put("swing.boldMetal", Boolean.FALSE);
        //Schedule a job for the event-dispatching thread:
        //creating and showing this application's GUI.
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                new DesktopDemo().setVisible(true);
            }
        });
    }
    
    /** Create and show components
     */
    private void initComponents() {
        
        setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        setTitle("DesktopDemo");
        txtBrowserURI.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                onLaunchBrowser(null);
            }
        });
        
        btnLaunchBrowser.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent evt) {
                onLaunchBrowser(evt);
            }
        });
        
        
        txtMailTo.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                onLaunchMail(null);
            }
        });
        
        btnLaunchEmail.setText("Launch Mail");
        btnLaunchEmail.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent evt) {
                onLaunchMail(evt);
            }
        });
        
        
        txtFile.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                onLaunchDefaultApplication(null);
            }
        });
        
        rbOpen.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent evt) {
                onOpenAction(evt);
            }
        });
        
        
        rbEdit.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent evt) {
                onEditAction(evt);
            }
        });
        
        
        rbPrint.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent evt) {
                onPrintAction(evt);
            }
        });
        
        btnLaunchApplication.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent evt) {
                onLaunchDefaultApplication(evt);
            }
        });
        
        btnFile.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent evt) {
                onChooseFile(evt);
            }
        });
        
        Container conFrame = this.getContentPane();
        
        bgAppAction.add(rbOpen);
        bgAppAction.add(rbEdit);
        bgAppAction.add(rbPrint);
        
        // Components layouting
        
        GroupLayout layout = new GroupLayout(conFrame);
        conFrame.setLayout(layout);
        layout.setAutoCreateContainerGaps(true);
        layout.setAutoCreateGaps(true);
        
        GroupLayout.SequentialGroup majorHGroup = layout.createSequentialGroup();
        
        // Horizontal group
        
        GroupLayout.ParallelGroup lblHGroup =
                layout.createParallelGroup(GroupLayout.Alignment.LEADING);
        lblHGroup.addComponent(lblBrowserUri, GroupLayout.Alignment.TRAILING);
        lblHGroup.addComponent(lblMailRecipient, GroupLayout.Alignment.TRAILING);
        lblHGroup.addComponent(lblFile, GroupLayout.Alignment.TRAILING);
        
        GroupLayout.ParallelGroup txtFieldsHGroup =
                layout.createParallelGroup(GroupLayout.Alignment.LEADING);
        txtFieldsHGroup.addComponent(txtMailTo);
        txtFieldsHGroup.addComponent(txtBrowserURI);
        GroupLayout.SequentialGroup rbHGroup = layout.createSequentialGroup();
        rbHGroup.addComponent(rbOpen);
        rbHGroup.addComponent(rbEdit);
        rbHGroup.addComponent(rbPrint);
        txtFieldsHGroup.addGroup(rbHGroup);
        GroupLayout.SequentialGroup fileHGroup = layout.createSequentialGroup();
        fileHGroup.addComponent(txtFile);
        fileHGroup.addComponent(btnFile);
        txtFieldsHGroup.addGroup(fileHGroup);
        
        GroupLayout.ParallelGroup btnHGroup =
                layout.createParallelGroup(GroupLayout.Alignment.LEADING);
        btnHGroup.addComponent(btnLaunchBrowser);
        btnHGroup.addComponent(btnLaunchEmail);
        btnHGroup.addComponent(btnLaunchApplication);
        
        majorHGroup.addGroup(lblHGroup);
        majorHGroup.addGroup(txtFieldsHGroup);
        majorHGroup.addGroup(btnHGroup);
        
        layout.setHorizontalGroup(majorHGroup);
        
        // Vertical group
        
        GroupLayout.SequentialGroup majorVGroup = layout.createSequentialGroup();
        
        GroupLayout.ParallelGroup uriVGroup =
                layout.createParallelGroup(GroupLayout.Alignment.BASELINE);
        uriVGroup.addComponent(lblBrowserUri);
        uriVGroup.addComponent(txtBrowserURI);
        uriVGroup.addComponent(btnLaunchBrowser);
        
        GroupLayout.ParallelGroup mailVGroup =
                layout.createParallelGroup(GroupLayout.Alignment.BASELINE);
        mailVGroup.addComponent(lblMailRecipient);
        mailVGroup.addComponent(txtMailTo);
        mailVGroup.addComponent(btnLaunchEmail);
        
        GroupLayout.ParallelGroup rbVGroup =
                layout.createParallelGroup(GroupLayout.Alignment.BASELINE);
        rbVGroup.addComponent(rbOpen);
        rbVGroup.addComponent(rbEdit);
        rbVGroup.addComponent(rbPrint);
        
        GroupLayout.ParallelGroup fileVGroup =
                layout.createParallelGroup(GroupLayout.Alignment.BASELINE);
        fileVGroup.addComponent(lblFile);
        fileVGroup.addComponent(btnLaunchApplication);
        fileVGroup.addComponent(txtFile);
        fileVGroup.addComponent(btnFile);
        
        majorVGroup.addGroup(uriVGroup);
        majorVGroup.addGroup(mailVGroup);
        majorVGroup.addGroup(rbVGroup);
        majorVGroup.addGroup(fileVGroup);
        
        layout.setVerticalGroup(majorVGroup);
        
        pack();
    }
    
    /**
     * Load the "desktop" icon into our frame window.
     */
    private void loadFrameIcon() {
        URL imgUrl = null;
        ImageIcon imgIcon = null;
        
        imgUrl = DesktopDemo.class.getResource("images/desk32.gif");
        imgIcon = new ImageIcon(imgUrl);
        Image img = imgIcon.getImage();
        this.setIconImage(img);
    }
    
    /*
     * Set the Desktop.Action to PRINT before invoking
     * the default application.
     */
    private void onPrintAction(ActionEvent evt) {
        action = Desktop.Action.PRINT;
    }
    
    /**
     * Set the Desktop.Action to EDIT before invoking
     * the default application.
     */
    private void onEditAction(ActionEvent evt) {
        action = Desktop.Action.EDIT;
    }
    
    /**
     * Set the Desktop.Action to OPEN before invoking
     * the default application.
     */
    private void onOpenAction(ActionEvent evt) {
        action = Desktop.Action.OPEN;
    }
    
    /**
     * Launch the default application associated with a specific
     * filename using the preset Desktop.Action.
     *
     */
    private void onLaunchDefaultApplication(ActionEvent evt) {
        String fileName = txtFile.getText();
        File file = new File(fileName);
        
        try {
            switch(action) {
                case OPEN:
                    desktop.open(file);
                    break;
                case EDIT:
                    desktop.edit(file);
                    break;
                case PRINT:
                    desktop.print(file);
                    break;
            }
        } catch (IOException ioe) {
            //ioe.printStackTrace();
            System.out.println("Cannot perform the given operation to the " + file + " file");
        }
    }
    
    /**
     * Launch the default email client using the "mailto"
     * protocol and the text supplied by the user.
     *
     */
    private void onLaunchMail(ActionEvent evt) {
        String mailTo = txtMailTo.getText();
        URI uriMailTo = null;
        try {
            if (mailTo.length() > 0) {
                uriMailTo = new URI("mailto", mailTo, null);
                desktop.mail(uriMailTo);
            } else {
                desktop.mail();
            }
        } catch(IOException ioe) {
            ioe.printStackTrace();
        } catch(URISyntaxException use) {
            use.printStackTrace();
        }
    }
    
    /**
     * Launch the default browser with the text provided by the
     * user.
     *
     */
    private void onLaunchBrowser(ActionEvent evt) {
        URI uri = null;
        try {
            uri = new URI(txtBrowserURI.getText());
            desktop.browse(uri);
        } catch(IOException ioe) {
            System.out.println("The system cannot find the " + uri + " file specified");
            //ioe.printStackTrace();
        } catch(URISyntaxException use) {
            System.out.println("Illegal character in path");
            //use.printStackTrace();
        }
    }
    
    private void onChooseFile(ActionEvent evt) {
        if (evt.getSource() == btnFile) {
            int returnVal = fc.showOpenDialog(DesktopDemo.this);
            if (returnVal == JFileChooser.APPROVE_OPTION){
                file = fc.getSelectedFile();
                txtFile.setText(file.getAbsolutePath());
            }
        }
    }
    
    /**
     * Enable actions that are supported on this host.
     * The actions are: open browser, open email client, and
     * open, edit, and print files using their associated application
     */
    
    private void enableSupportedActions() {
        if (desktop.isSupported(Desktop.Action.BROWSE)) {
            txtBrowserURI.setEnabled(true);
            btnLaunchBrowser.setEnabled(true);
        }
        if (desktop.isSupported(Desktop.Action.MAIL)) {
            txtMailTo.setEnabled(true);
            btnLaunchEmail.setEnabled(true);
        }
        if (desktop.isSupported(Desktop.Action.OPEN)) {
            rbOpen.setEnabled(true);
        }
        if (desktop.isSupported(Desktop.Action.EDIT)) {
            rbEdit.setEnabled(true);
        }
        if (desktop.isSupported(Desktop.Action.PRINT)) {
            rbPrint.setEnabled(true);
        }
        if (rbEdit.isEnabled() || rbOpen.isEnabled() || rbPrint.isEnabled()) {
            txtFile.setEnabled(true);
            btnLaunchApplication.setEnabled(true);
            btnFile.setEnabled(true);
        }
    }
    
    /**
     * Disable all graphical components until we know
     * whether their functionality is supported.
     */
    private void disableActions() {
        txtBrowserURI.setEnabled(false);
        btnLaunchBrowser.setEnabled(false);
        
        txtMailTo.setEnabled(false);
        btnLaunchEmail.setEnabled(false);
        
        rbEdit.setEnabled(false);
        rbOpen.setEnabled(false);
        rbPrint.setEnabled(false);
        
        txtFile.setEnabled(false);
        btnLaunchApplication.setEnabled(false);
        btnFile.setEnabled(false);
    }
}

The Desktop API


The Desktop class allows Java applications to launch the native desktop applications that handle URIs or files.
 Method Purpose
isDesktopSupported() Tests whether this class is supported on the current platform. If it is supported, use getDesktop() to retrieve an instance.
getDesktop() Returns the Desktop instance of the current browser context. On some platforms the Desktop API may not be supported. Use the isDesktopSupported() method to determine if the current desktop is supported.
isSupported(Desktop.Action) Tests whether an action is supported on the current platform. Use the following constans of the Desktop.Action enum: BROWSE, EDIT, MAIL, OPEN, PRINT.
browse(URI) Launches the default browser to display a URI. If the default browser is not able to handle the specified URI, the application registered for handling URIs of the specified type is invoked. The application is determined from the protocol and path of the URI, as defined by the URI class.
mail(URI) Launches the mail composing window of the user default mail client, filling the message fields specified by a mailto: URI.
open(File) Launches the associated application to open a file.
edit(File) Launches the associated editor application and opens a file for editing.
print(File) Prints a file with the native desktop printing facility, using the associated application's print command.

Examples That Use Desktop API


The following table lists the example that uses the Desktop class integration.

ExampleWhere Described - Notes
DesktopDemoThis section - Launches the host system's default browser with the specified URI and default email client; launches an application to open, edit, or print a file.

➤ How to Create Translucent and Shaped Windows


As of the Java Platform, Standard Edition 6 (Java SE 6) Update 10 release, you can add translucent and shaped windows to your Swing applications. This page covers the following topics:

  • Supported Capabilities

This functionality, which is part of the public AWT package in the JDK 7 release, takes three forms, as follows:

You can create a window with uniform translucency, where each pixel has the same translucency (or alpha) value. The following screen capture shows a window with 45 percent translucency.

Oracle Java Tutorials and Materials, Oracle Java Certifications

You can create a window with per-pixel translucency, where each pixel has its own alpha value. With this feature you can, for example, create a window that fades away to nothing by defining a gradient in the alpha values. The following screen capture shows a window with gradient translucency from the top (fully translucent) to the bottom (fully opaque).

Oracle Java Tutorials and Materials, Oracle Java Certifications

You can create a window with any Shape object that you can define. Shaped windows can be opaque, or they can use uniform, or per-pixel, translucency. The following screen capture shows an oval-shaped window with 30 percent translucency.

Oracle Java Tutorials and Materials, Oracle Java Certifications

  • Determining a Platform's Capabilities

Not all platforms support all of these capabilities. An UnsupportedOperationException exception is thrown when code attempts to invoke the setShape or setOpacity methods on a platform that does not support these capabilities. Therefore, it is best practice to first check that the platform supports the capability that you want to implement. The GraphicsDevice class provides the isWindowTranslucencySupported(GraphicsDevice.WindowTranslucency) method that you can use for this purpose. You pass one of three enum values, defined in GraphicsDevice.WindowTranslucency, to this method:

  1. TRANSLUCENT – The underlying platform supports windows with uniform translucency, where each pixel has the same alpha value.
  2. PERPIXEL_TRANSLUCENT – The underlying platform supports windows with per-pixel translucency. This capability is required to implement windows that fade away.
  3. PERPIXEL_TRANSPARENT – The underlying platform supports shaped windows.
The GraphicsConfiguration class also provides the isTranslucencyCapable method to determine if PERPIXEL_TRANSLUCENT translucency is supported by the given GraphicsConfiguration object.

Version note: The translucent and shaped window API was first added to the Java SE 6 Update 10 release as a private API. This functionality was moved to the public AWT package in the JDK 7 release. This tutorial describes the API that is available in the JDK 7 release. See Java SE 6 Update 10 API for a mapping of the private API in the Java SE 6 Update 10 release to the public API in the JDK 7 release.

The following code shows how to check for all three capabilities:
import static java.awt.GraphicsDevice.WindowTranslucency.*;

// Determine what the default GraphicsDevice can support.
GraphicsEnvironment ge =
    GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice gd = ge.getDefaultScreenDevice();

boolean isUniformTranslucencySupported =
    gd.isWindowTranslucencySupported(TRANSLUCENT);
boolean isPerPixelTranslucencySupported =
    gd.isWindowTranslucencySupported(PERPIXEL_TRANSLUCENT);
boolean isShapedWindowSupported =
    gd.isWindowTranslucencySupported(PERPIXEL_TRANSPARENT);

Note: None of these capabilities work on windows in full-screen mode. Invoking any of the relevant methods while in full-screen mode causes an IllegalComponentStateException exception to be thrown.

  • How to Implement Uniform Translucency

You can create a window where each pixel has the same translucency by invoking the setOpacity(float) method in the Window class. The float argument passed to this method represents the translucency of the window and should be a value between 0 and 1, inclusive. The smaller the number, the more transparent the window. There is also a corresponding getOpacity method.
The TranslucentWindowDemo.java example creates a window that is 55 percent opaque (45 percent translucent). If the underlying platform does not support translucent windows, the example exits. The code relating to opacity is shown in bold.

import java.awt.*;
import javax.swing.*;
import static java.awt.GraphicsDevice.WindowTranslucency.*;

public class TranslucentWindowDemo extends JFrame {
    public TranslucentWindowDemo() {
        super("TranslucentWindow");
        setLayout(new GridBagLayout());

        setSize(300,200);
        setLocationRelativeTo(null);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        //Add a sample button.
        add(new JButton("I am a Button"));
    }

    public static void main(String[] args) {
        // Determine if the GraphicsDevice supports translucency.
        GraphicsEnvironment ge = 
            GraphicsEnvironment.getLocalGraphicsEnvironment();
        GraphicsDevice gd = ge.getDefaultScreenDevice();

        //If translucent windows aren't supported, exit.
        if (!gd.isWindowTranslucencySupported(TRANSLUCENT)) {
            System.err.println(
                "Translucency is not supported");
                System.exit(0);
        }
        
        JFrame.setDefaultLookAndFeelDecorated(true);

        // Create the GUI on the event-dispatching thread
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                TranslucentWindowDemo tw = new TranslucentWindowDemo();

                // Set the window to 55% opaque (45% translucent).
                tw.setOpacity(0.55f);

                // Display the window.
                tw.setVisible(true);
            }
        });
    }
}

Note that the button is also affected by the uniform translucency. Setting the opacity affects the whole window, including any components that the window contains.

  • How to Implement Per-Pixel Translucency

Creating a window that uses per-pixel translucency involves defining alpha values over the rectangular area that the window occupies. When a pixel's alpha value is zero, that pixel is fully transparent. When a pixel's alpha value is 255, that pixel is fully opaque. When a pixel's alpha value is 128, that pixel is 50 percent translucent, and so on. An easy way to create a smooth interpolation between alpha values is to use the GradientPaint class. The included example uses this approach.

Invoking setBackground(new Color(0,0,0,0)) on the window causes the software to use the alpha values to render per-pixel translucency. In fact, invoking setBackground(new Color(0,0,0,alpha), where alpha is less than 255, installs per-pixel transparency. So, if you invoke setBackground(new Color(0,0,0,128)) and do nothing else, the window is rendered with 50 percent translucency for each background pixel. However, if you are creating your own range of alpha values, you most likely will want an alpha value of 0.

While not prohibited by the public API, you will generally want to enable per-pixel translucency on undecorated windows. In most cases, using per-pixel translucency on decorated windows does not make sense. Doing so can disable the decorations, or cause other platform-dependent side effects.

To determine if a window is using per-pixel translucency, you can use the isOpaque method.

An example follows. First, here are the steps required to implement the example:

  1. Invoke setBackground(new Color(0,0,0,0)) on the window.
  2. Create a JPanel instance that overrides the paintComponent method.
  3. In the paintComponent method, create a GradientPaint instance.
  4. In the example, the top of the rectangle has an alpha value of 0 (the most transparent) and the bottom has an alpha value of 255 (the most opaque). The GradientPaint class smoothly interpolates the alpha values from the top to the bottom of the rectangle.
  5. Set the GradientPaint instance as the panel's paint method.
If the underlying platform does not support per-pixel translucency, this example exits. The code specifically relating to creating the gradient window is shown in bold.

import java.awt.*;
import javax.swing.*;
import static java.awt.GraphicsDevice.WindowTranslucency.*;

public class GradientTranslucentWindowDemo extends JFrame {
    public GradientTranslucentWindowDemo() {
        super("GradientTranslucentWindow");

        setBackground(new Color(0,0,0,0));
        setSize(new Dimension(300,200));
        setLocationRelativeTo(null);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        JPanel panel = new JPanel() {
            @Override
            protected void paintComponent(Graphics g) {
                if (g instanceof Graphics2D) {
                    final int R = 240;
                    final int G = 240;
                    final int B = 240;

                    Paint p =
                        new GradientPaint(0.0f, 0.0f, new Color(R, G, B, 0),
                            0.0f, getHeight(), new Color(R, G, B, 255), true);
                    Graphics2D g2d = (Graphics2D)g;
                    g2d.setPaint(p);
                    g2d.fillRect(0, 0, getWidth(), getHeight());
                }
            }
        };
        setContentPane(panel);
        setLayout(new GridBagLayout());
        add(new JButton("I am a Button"));
    }

    public static void main(String[] args) {
        // Determine what the GraphicsDevice can support.
        GraphicsEnvironment ge = 
            GraphicsEnvironment.getLocalGraphicsEnvironment();
        GraphicsDevice gd = ge.getDefaultScreenDevice();
        boolean isPerPixelTranslucencySupported = 
            gd.isWindowTranslucencySupported(PERPIXEL_TRANSLUCENT);

        //If translucent windows aren't supported, exit.
        if (!isPerPixelTranslucencySupported) {
            System.out.println(
                "Per-pixel translucency is not supported");
                System.exit(0);
        }

        JFrame.setDefaultLookAndFeelDecorated(true);

        // Create the GUI on the event-dispatching thread
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                GradientTranslucentWindowDemo gtw = new
                    GradientTranslucentWindowDemo();

                // Display the window.
                gtw.setVisible(true);
            }
        });
    }
}

Note that the button is not affected by the per-pixel translucency. Setting the per-pixel translucency affects the background pixels only. If you want a window that has a uniformly translucent effect on the background pixels only, you can invoke setBackground(new Color(0,0,0,alpha)) where alpha specifies your desired translucency.

  • How to Implement a Shaped Window


You can create a shaped window by invoking the setShape(Shape) method in the Window class. The Shape argument that is passed to the method determines how the window is clipped. When a shape is set on a window, the window decorations are not re-formed to the new shape, so setting a shape works best on undecorated windows.
The best practice for setting the window's shape is to invoke setShape in the componentResized method of the component event listener. This practice will ensure that the shape is correctly calculated for the actual size of the window. The following example uses this approach.

The ShapedWindowDemo.java example creates an oval-shaped window with 70 percent opacity. If the underlying platform does not support shaped windows, the example exits. If the underlying platform does not support translucency, the example uses a standard opaque window. You could modify this example to create a shaped window that also uses per-pixel translucency.

The code relating to shaping the window is shown in bold.

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.awt.geom.Ellipse2D;
import static java.awt.GraphicsDevice.WindowTranslucency.*;

public class ShapedWindowDemo extends JFrame {
    public ShapedWindowDemo() {
        super("ShapedWindow");
        setLayout(new GridBagLayout());

        // It is best practice to set the window's shape in
        // the componentResized method.  Then, if the window
        // changes size, the shape will be correctly recalculated.
        addComponentListener(new ComponentAdapter() {
            // Give the window an elliptical shape.
            // If the window is resized, the shape is recalculated here.
            @Override
            public void componentResized(ComponentEvent e) {
                setShape(new Ellipse2D.Double(0,0,getWidth(),getHeight()));
            }
        });

        setUndecorated(true);
        setSize(300,200);
        setLocationRelativeTo(null);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        add(new JButton("I am a Button"));
    }

    public static void main(String[] args) {
        // Determine what the GraphicsDevice can support.
        GraphicsEnvironment ge = 
            GraphicsEnvironment.getLocalGraphicsEnvironment();
        GraphicsDevice gd = ge.getDefaultScreenDevice();
        final boolean isTranslucencySupported = 
            gd.isWindowTranslucencySupported(TRANSLUCENT);

        //If shaped windows aren't supported, exit.
        if (!gd.isWindowTranslucencySupported(PERPIXEL_TRANSPARENT)) {
            System.err.println("Shaped windows are not supported");
            System.exit(0);
        }

        //If translucent windows aren't supported, 
        //create an opaque window.
        if (!isTranslucencySupported) {
            System.out.println(
                "Translucency is not supported, creating an opaque window");
        }

        // Create the GUI on the event-dispatching thread
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                ShapedWindowDemo sw = new ShapedWindowDemo();

                // Set the window to 70% translucency, if supported.
                if (isTranslucencySupported) {
                    sw.setOpacity(0.7f);
                }

                // Display the window.
                sw.setVisible(true);
            }
        });
    }
}

  • Java SE Release 6 Update 10 API

Method in Java SE 6 Update 10 JDK 7 Equivalent
AWTUtilities.isTranslucencySupported(Translucency) GraphicsDevice.isWindowTranslucencySupported(WindowTranslucency)
AWTUtilities.isTranslucencyCapable(GraphicsConfiguration) GraphicsConfiguration.isTranslucencyCapable()
AWTUtilities.setWindowOpacity(Window, float) Window.setOpacity(float)
AWTUtilities.setWindowShape(Window, Shape) Window.setShape(Shape)
AWTUtilities.setWindowOpaque(boolean) Window.setBackground(Color) Passing new Color(0,0,0,alpha) to this method, where alpha is less than 255, installs per-pixel translucency.


➤ How to Decorate Components with the JLayer Class


The JLayer class is a flexible and powerful decorator for Swing components. It enables you to draw on components and respond to component events without modifying the underlying component directly.

The JLayer class in Java SE 7 is similar in spirit to the JxLayer project project at java.net. The JLayer class was initially based on the JXLayer project, but its API evolved separately.


For a brief introduction to the material on this page, watch the following video.


This document describes examples that show the power of the JLayer class. Full source code is available.

  • Using the JLayer Class


The javax.swing.JLayer class is half of a team. The other half is the javax.swing.plaf.LayerUI class. Suppose you want to do some custom drawing atop a JButton object (decorate the JButton object). The component you want to decorate is the target.
  1. Create the target component.
  2. Create an instance of a LayerUI subclass to do the drawing.
  3. Create a JLayer object that wraps the target and the LayerUI object.
  4. Use the JLayer object in your user interface just as you would use the target component.
For example, to add an instance of a JPanel subclass to a JFrame object, you would do something similar to this:

JFrame f = new JFrame();

JPanel panel = createPanel();

f.add (panel);
To decorate the JPanel object, do something similar to this instead:

JFrame f = new JFrame();

JPanel panel = createPanel();
LayerUI<JPanel> layerUI = new MyLayerUISubclass();
JLayer<JPanel> jlayer = new JLayer<JPanel>(panel, layerUI);

f.add (jlayer);
Use generics to ensure that the JPanel object and the LayerUI object are for compatible types. In the previous example, both the JLayer object and the LayerUI object are used with the JPanel class.

The JLayer class is usually generified with the exact type of its view component, while the LayerUI class is designed to be used with JLayer classes of its generic parameter or any of its ancestors.

For example, a LayerUI<JComponent> object can be used with a JLayer<AbstractButton> object.

A LayerUI object is responsible for custom decoration and event handling for a JLayer object. When you create an instance of a LayerUI subclass, your custom behavior can be applicable to every JLayer object with an appropriate generic type. That is why the JLayer class is final; all custom behavior is encapsulated in your LayerUI subclass, so there is no need to make a JLayer subclass.

  • Using the LayerUI Class


The LayerUI class inherits most of its behavior from the ComponentUI class. Here are the most commonly overridden methods:
  1. The paint(Graphics g, JComponent c) method is called when the target component needs to be drawn. To render the component in the same way that Swing renders it, call the super.paint(g, c) method.
  2. The installUI(JComponent c) method is called when an instance of your LayerUI subclass is associated with a component. Perform any necessary initializations here. The component that is passed in is the corresponding JLayer object. Retrieve the target component with the JLayer class' getView() method.
  3. The uninstallUI(JComponent c) method is called when an instance of your LayerUI subclass is no longer associated with the given component. Clean up here if necessary.

  • Drawing on Components


To use the JLayer class, you need a good LayerUI subclass. The simplest kinds of LayerUI classes change how components are drawn. Here is one, for example, that paints a transparent color gradient on a component.

class WallpaperLayerUI extends LayerUI<JComponent> {
  @Override
  public void paint(Graphics g, JComponent c) {
    super.paint(g, c);

    Graphics2D g2 = (Graphics2D) g.create();

    int w = c.getWidth();
    int h = c.getHeight();
    g2.setComposite(AlphaComposite.getInstance(
            AlphaComposite.SRC_OVER, .5f));
    g2.setPaint(new GradientPaint(0, 0, Color.yellow, 0, h, Color.red));
    g2.fillRect(0, 0, w, h);

    g2.dispose();
  }
}

The paint() method is where the custom drawing takes place. The call to the super.paint() method draws the contents of the JPanel object. After setting up a 50% transparent composite, the color gradient is drawn.

After the LayerUI subclass is defined, using it is simple. Here is some source code that uses the WallpaperLayerUI class:

import java.awt.*;
import javax.swing.*;
import javax.swing.plaf.LayerUI;

public class Wallpaper {
  public static void main(String[] args) {
    javax.swing.SwingUtilities.invokeLater(new Runnable() {
      public void run() {
        createUI();
      }
    });
  }

  public static void createUI() {
    JFrame f = new JFrame("Wallpaper");
    
    JPanel panel = createPanel();
    LayerUI<JComponent> layerUI = new WallpaperLayerUI();
    JLayer<JComponent> jlayer = new JLayer<JComponent>(panel, layerUI);
    
    f.add (jlayer);
    
    f.setSize(300, 200);
    f.setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE);
    f.setLocationRelativeTo (null);
    f.setVisible (true);
  }

  private static JPanel createPanel() {
    JPanel p = new JPanel();

    ButtonGroup entreeGroup = new ButtonGroup();
    JRadioButton radioButton;
    p.add(radioButton = new JRadioButton("Beef", true));
    entreeGroup.add(radioButton);
    p.add(radioButton = new JRadioButton("Chicken"));
    entreeGroup.add(radioButton);
    p.add(radioButton = new JRadioButton("Vegetable"));
    entreeGroup.add(radioButton);

    p.add(new JCheckBox("Ketchup"));
    p.add(new JCheckBox("Mustard"));
    p.add(new JCheckBox("Pickles"));

    p.add(new JLabel("Special requests:"));
    p.add(new JTextField(20));

    JButton orderButton = new JButton("Place Order");
    p.add(orderButton);

    return p;
  }
}

Here is the result:

Oracle Java tutorials and Materials

Source code:


Wallpaper NetBeans Project
Wallpaper.java

The LayerUI class' paint() method gives you complete control over how a component is drawn. Here is another LayerUI subclass that shows how the entire contents of a panel can be modified using Java 2D image processing:

class BlurLayerUI extends LayerUI<JComponent> {
  private BufferedImage mOffscreenImage;
  private BufferedImageOp mOperation;

  public BlurLayerUI() {
    float ninth = 1.0f / 9.0f;
    float[] blurKernel = {
      ninth, ninth, ninth,
      ninth, ninth, ninth,
      ninth, ninth, ninth
    };
    mOperation = new ConvolveOp(
            new Kernel(3, 3, blurKernel),
            ConvolveOp.EDGE_NO_OP, null);
  }

  @Override
  public void paint (Graphics g, JComponent c) {
    int w = c.getWidth();
    int h = c.getHeight();

    if (w == 0 || h == 0) {
      return;
    }

    // Only create the offscreen image if the one we have
    // is the wrong size.
    if (mOffscreenImage == null ||
            mOffscreenImage.getWidth() != w ||
            mOffscreenImage.getHeight() != h) {
      mOffscreenImage = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
    }

    Graphics2D ig2 = mOffscreenImage.createGraphics();
    ig2.setClip(g.getClip());
    super.paint(ig2, c);
    ig2.dispose();

    Graphics2D g2 = (Graphics2D)g;
    g2.drawImage(mOffscreenImage, mOperation, 0, 0);
  }
}

In the paint() method, the panel is rendered into an offscreen image. The offscreen image is processed with a convolution operator, then drawn to the screen.

The entire user interface is still live, just blurry:

Oracle Java tutorials and Materials

Source code:

Myopia NetBeans Project
Myopia.java

  • Responding to Events

Your LayerUI subclass can also receive all of the events of its corresponding component. However, the JLayer instance must register its interest in specific types of events. This happens with the JLayer class' setLayerEventMask() method. Typically, however, this call is made from initialization performed in the LayerUI class' installUI() method.

For example, the following excerpt shows a portion of a LayerUI subclass that registers to receive mouse and mouse motion events.

public void installUI(JComponent c) {
  super.installUI(c);
  JLayer jlayer = (JLayer)c;
  jlayer.setLayerEventMask(
    AWTEvent.MOUSE_EVENT_MASK |
    AWTEvent.MOUSE_MOTION_EVENT_MASK
  );
}
All events going to your JLayer subclass get routed to an event handler method whose name matches the event type. For example, you can respond to mouse and mouse motion events by overriding corresponding methods:

protected void processMouseEvent(MouseEvent e, JLayer l) {
  // ...
}

protected void processMouseMotionEvent(MouseEvent e, JLayer l) {
  // ...
}
The following is a LayerUI subclass that draws a translucent circle wherever the mouse moves inside a panel.

class SpotlightLayerUI extends LayerUI<JPanel> {
  private boolean mActive;
  private int mX, mY;

  @Override
  public void installUI(JComponent c) {
    super.installUI(c);
    JLayer jlayer = (JLayer)c;
    jlayer.setLayerEventMask(
      AWTEvent.MOUSE_EVENT_MASK |
      AWTEvent.MOUSE_MOTION_EVENT_MASK
    );
  }

  @Override
  public void uninstallUI(JComponent c) {
    JLayer jlayer = (JLayer)c;
    jlayer.setLayerEventMask(0);
    super.uninstallUI(c);
  }

  @Override
  public void paint (Graphics g, JComponent c) {
    Graphics2D g2 = (Graphics2D)g.create();

    // Paint the view.
    super.paint (g2, c);

    if (mActive) {
      // Create a radial gradient, transparent in the middle.
      java.awt.geom.Point2D center = new java.awt.geom.Point2D.Float(mX, mY);
      float radius = 72;
      float[] dist = {0.0f, 1.0f};
      Color[] colors = {new Color(0.0f, 0.0f, 0.0f, 0.0f), Color.BLACK};
      RadialGradientPaint p =
          new RadialGradientPaint(center, radius, dist, colors);
      g2.setPaint(p);
      g2.setComposite(AlphaComposite.getInstance(
          AlphaComposite.SRC_OVER, .6f));
      g2.fillRect(0, 0, c.getWidth(), c.getHeight());
    }

    g2.dispose();
  }

  @Override
  protected void processMouseEvent(MouseEvent e, JLayer l) {
    if (e.getID() == MouseEvent.MOUSE_ENTERED) mActive = true;
    if (e.getID() == MouseEvent.MOUSE_EXITED) mActive = false;
    l.repaint();
  }

  @Override
  protected void processMouseMotionEvent(MouseEvent e, JLayer l) {
    Point p = SwingUtilities.convertPoint(e.getComponent(), e.getPoint(), l);
    mX = p.x;
    mY = p.y;
    l.repaint();
  }
}

The mActive variable indicates whether or not the mouse is inside the coordinates of the panel. In the installUI() method, the setLayerEventMask() method is called to indicate the LayerUI subclass' interest in receiving mouse and mouse motion events.

In the processMouseEvent() method, the mActive flag is set depending on the position of the mouse. In the processMouseMotionEvent() method, the coordinates of the mouse movement are stored in the mX and mY member variables so that they can be used later in the paint() method.

The paint() method shows the default appearance of the panel, then overlays a radial gradient for a spotlight effect:

Oracle Java tutorials and Materials

Source code:

Diva NetBeans Project
Diva.java

  • Animating a Busy Indicator

This example is an animated busy indicator. It demonstrates animation in a LayerUI subclass and features a fade-in and fade-out. It is more complicated that the previous examples, but it is based on the same principle of defining a paint() method for custom drawing.

Click the Place Order button to see the busy indicator for 4 seconds. Notice how the panel is grayed out and the indicator spins. The elements of the indicator have varying levels of transparency.

The LayerUI subclass, the WaitLayerUI class, shows how to fire property change events to update the component. The WaitLayerUI class uses a Timer object to update its state 24 times a second. This happens in the timer's target method, the actionPerformed() method.

The actionPerformed() method uses the firePropertyChange() method to indicate that the internal state was updated. This triggers a call to the applyPropertyChange() method, which repaints the JLayer object:

Oracle Java tutorials and Materials

Source code:

TapTapTap NetBeans Project
TapTapTap.java

  • Validating Text Fields

The final example in this document shows how the JLayer class can be used to decorate text fields to show if they contain valid data. While the other examples use the JLayer class to wrap panels or general components, this example shows how to wrap a JFormattedTextField component specifically. It also demonstrates that a single LayerUI subclass implementation can be used for multiple JLayer instances.

The JLayer class is used to provide a visual indication for fields that have invalid data. When the ValidationLayerUI class paints the text field, it draws a red X if the field contents cannot be parsed. Here is an example:

Oracle Java tutorials and Materials

Source code:

FieldValidator NetBeans Project
FieldValidator.java

➤ How to Use Actions


An Action can be used to separate functionality and state from a component. For example, if you have two or more components that perform the same function, consider using an Action object to implement the function. An Action object is an action listener that provides not only action-event handling, but also centralized handling of the state of action-event-firing components such as tool bar buttons, menu items, common buttons, and text fields. The state that an action can handle includes text, icon, mnemonic, enabled, and selected status.

You typically attach an action to a component using the setAction method. Here's what happens when setAction is invoked on a component:
  • The component's state is updated to match the state of the Action. For example, if the Action's text and icon values were set, the component's text and icon are set to those values.
  • The Action object is registered as an action listener on the component.
  • If the state of the Action changes, the component's state is updated to match the Action. For example, if you change the enabled status of the action, all components it's attached to change their enabled states to match the action.

Here's an example of creating a tool-bar button and menu item that perform the same function:

Action leftAction = new LeftAction(); //LeftAction code is shown later
...
button = new JButton(leftAction)
...
menuItem = new JMenuItem(leftAction);

To create an Action object, you generally create a subclass of AbstractAction and then instantiate it. In your subclass, you must implement the actionPerformed method to react appropriately when the action event occurs. Here's an example of creating and instantiating an AbstractAction subclass:

leftAction = new LeftAction("Go left", anIcon,
             "This is the left button.",
             new Integer(KeyEvent.VK_L));
...
class LeftAction extends AbstractAction {
    public LeftAction(String text, ImageIcon icon,
                      String desc, Integer mnemonic) {
        super(text, icon);
        putValue(SHORT_DESCRIPTION, desc);
        putValue(MNEMONIC_KEY, mnemonic);
    }
    public void actionPerformed(ActionEvent e) {
        displayResult("Action for first button/menu item", e);
    }
}

When the action created by the preceding code is attached to a button and a menu item, the button and menu item display the text and icon associated with the action. The L character is used for mnemonics on the button and menu item, and their tool-tip text is set to the SHORT_DESCRIPTION string followed by a representation of the mnemonic key.

For example, we have provided a simple example, ActionDemo.java, which defines three actions. Each action is attached to a button and a menu item. Thanks to the mnemonic values set for each button's action, the key sequence Alt-L activates the left button, Alt-M the middle button, and Alt-R the right button. The tool tip for the left button displays This is the left button. Alt-L. All of this configuration occurs automatically, without the program making explicit calls to set the mnemonic or tool-tip text. As we'll show later, the program does make calls to set the button text, but only to avoid using the values already set by the actions.

Using Other Swing Features

Try this: 

1. Click the Launch button to run ActionDemo using Java™ Web Start (download JDK 7 or later).

2. Choose the top item from the left menu (Menu > Go left).
The text area displays some text identifying both the event source and the action listener that received the event.

3. Click the leftmost button in the tool bar.
The text area again displays information about the event. Note that although the source of the events is different, both events were detected by the same action listener: the Action object attached to the components.

4. Choose the top item from the Action State menu.
This disables the "Go left" Action object, which in turn disables its associated menu item and button.

Here is what the user sees when the "Go left" action is disabled:

Using Other Swing Features

Using Other Swing Features

Here's the code that disables the "Go left" action:

boolean selected = ...//true if the action should be enabled;
                      //false, otherwise
leftAction.setEnabled(selected);

After you create components using an Action, you might well need to customize them. For example, you might want to customize the appearance of one of the components by adding or deleting the icon or text. For example, ActionDemo.java has no icons in its menus, and no text in its buttons. Here's the code that accomplishes this:

menuItem = new JMenuItem();
menuItem.setAction(leftAction);
menuItem.setIcon(null); //arbitrarily chose not to use icon in menu
...
button = new JButton();
button.setAction(leftAction);
button.setText(""); //an icon-only button

We chose to create an icon-only button and a text-only menu item from the same action by setting the icon property to null and the text to an empty string. However, if a property of the Action changes, the widget may try to reset the icon and text from the Action again.

The Action API


The following tables list the commonly used Action constructors and methods. The API for using Action objects falls into three categories:
  • Components that Support set/getAction
  • Creating and Using an AbstractAction
  • Action Properties
Components that Support set/getAction

Class Purpose
AbstractButton
JComboBox
JTextField
These components and their subclasses may have an action directly assigned to them via setAction. For further information about components that are often associated with actions, see the sections on tool bar buttons, menu items, common buttons, and text fields. For details on which properties each component takes from the Action, see the API documentation for the relevant class's configurePropertiesFromAction method. Also refer to the buttonActions table.

Creating and Using an AbstractAction

 Constructor or Method  Purpose
AbstractAction()
AbstractAction(String)
AbstractAction(String, Icon)
Create an Action object. Through arguments, you can specify the text and icon to be used in the components that the action is attached to.
void setEnabled(boolean)
boolean isEnabled()
Set or get whether the components the action controls are enabled. Invoking setEnabled(false) disables all the components that the action controls. Similarly, invoking setEnabled(true) enables the action's components.
void putValue(String, Object)
Object getValue(String)
Set or get an object associated with a specified key. Used for setting and getting properties associated with an action.

Action Properties

This table defines the properties that can be set on an action. The second column lists which components automatically use the properties (and what method is specifically called). For example, setting the ACCELERATOR_KEY on an action that is then attached to a menu item, means that JMenuItem.setAccelerator(KeyStroke) is called automatically.

Property Auto-Applied to:
Class
(Method Called)
Purpose
 ACCELERATOR_KEY  JMenuItem
(setAccelerator)
 The KeyStroke to be used as the accelerator for the action. For a discussion of accelerators versus mnemonics.
 ACTION_COMMAND_KEY  AbstractButton, JCheckBox, JRadioButton
(setActionCommand)
 The command string associated with the ActionEvent.
 LONG_DESCRIPTION  None The longer description for the action. Can be used for context-sensitive help.
 MNEMONIC_KEY  AbstractButton, JMenuItem, JCheckBox, JRadioButton
(setMnemonic)
The mnemonic for the action. For a discussion of accelerators versus mnemonics
 NAME  AbstractButton, JMenuItem, JCheckBox, JRadioButton
(setText)
The name of the action. You can set this property when creating the action using the AbstractAction(String) or AbstractAction(String, Icon) constructors.
 SHORT_DESCRIPTION  AbstractButton, JCheckBox, JRadioButton
(setToolTipText)
The short description of the action.
 SMALL_ICON  AbstractButton, JMenuItem
(setIcon)
The icon for the action used in the tool bar or on a button. You can set this property when creating the action using the AbstractAction(name, icon) constructor.

Examples that Use Actions

The following examples use Action objects

ExampleWhere DescribedNotes
ActionDemoThis sectionUses actions to bind buttons and menu items to the same function.
TextComponentDemoText Component FeaturesUses text actions to create menu items for text editing commands, such as cut, copy, and paste, and to bind key strokes to caret movement. Also implements custom AbstractAction subclasses to implement undo and redo


➤ How to Use Swing Timers


A Swing timer (an instance of javax.swing.Timer) fires one or more action events after a specified delay. Do not confuse Swing timers with the general-purpose timer facility in the java.util package. This page describes only Swing timers.

In general, we recommend using Swing timers rather than general-purpose timers for GUI-related tasks because Swing timers all share the same, pre-existing timer thread and the GUI-related task automatically executes on the event-dispatch thread. However, you might use a general-purpose timer if you don't plan on touching the GUI from the timer, or need to perform lengthy processing.

You can use Swing timers in two ways:
  • To perform a task once, after a delay.
For example, the tool tip manager uses Swing timers to determine when to show a tool tip and when to hide it.
  • To perform a task repeatedly.
For example, you might perform animation or update a component that displays progress toward a goal.

Swing timers are very easy to use. When you create the timer, you specify an action listener to be notified when the timer "goes off". The actionPerformed method in this listener should contain the code for whatever task you need to be performed. When you create the timer, you also specify the number of milliseconds between timer firings. If you want the timer to go off only once, you can invoke setRepeats(false) on the timer. To start the timer, call its start method. To suspend it, call stop.

Note that the Swing timer's task is performed in the event dispatch thread. This means that the task can safely manipulate components, but it also means that the task should execute quickly. If the task might take a while to execute, then consider using a SwingWorker instead of or in addition to the timer. See Concurrency in Swing for instructions about using the SwingWorker class and information on using Swing components in multi-threaded programs.

Let's look at an example of using a timer to periodically update a component. The TumbleItem applet uses a timer to update its display at regular intervals. (To see this applet running, go to How to Make Applets. This applet begins by creating and starting a timer:

timer = new Timer(speed, this);
timer.setInitialDelay(pause);
timer.start(); 

The speed and pause variables represent applet parameters; as configured on the other page, these are 100 and 1900 respectively, so that the first timer event will occur in approximately 1.9 seconds, and recur every 0.1 seconds. By specifying this as the second argument to the Timer constructor, TumbleItem specifies that it is the action listener for timer events.

After starting the timer, TumbleItem begins loading a series of images in a background thread. Meanwhile, the timer events begin to occur, causing the actionPerformed method to execute:

public void actionPerformed(ActionEvent e) {
    //If still loading, can't animate.
    if (!worker.isDone()) {
        return;
    }

    loopslot++;

    if (loopslot >= nimgs) {
        loopslot = 0;
        off += offset;

        if (off < 0) {
            off = width - maxWidth;
        } else if (off + maxWidth > width) {
            off = 0;
        }
    }

    animator.repaint();

    if (loopslot == nimgs - 1) {
        timer.restart();
    }
}

Until the images are loaded, worker.isDone returns false, so timer events are effectively ignored. The first part of the event handling code simply sets values that are employed in the animation control's paintComponent method: loopslot (the index of the next graphic in the animation) and off (the horizontal offset of the next graphic).

Eventually, loopslot will reach the end of the image array and start over. When this happens, the code at the end of actionPerformed restarts the timer. Doing this causes a short delay before the animation sequence begins again.

➤ How to Support Assistive Technologies


You might be wondering what exactly assistive technologies are, and why you should care. Primarily, assistive technologies exist to enable people with permanent or temporary disabilities to use the computer. For example, if you get carpal tunnel syndrome, you can use assistive technologies to accomplish your work without using your hands.

Assistive technologies — voice interfaces, screen readers, alternate input devices, and so on — are useful not only for people with disabilities, but also for people using computers in non-office environments. For example, if you're stuck in a traffic jam, you might use assistive technologies to check your email, using only voice input and output. The information that enables assistive technologies can be used for other tools, as well, such as automated GUI testers and input devices such as touchscreens. Assistive technologies get information from components using the Accessibility API, which is defined in the javax.accessibility package.

Because support for the Accessibility API is built into the Swing components, your Swing program will probably work just fine with assistive technologies, even if you do nothing special. For example, assistive technologies can automatically get the text information that is set by the following lines of code:

JButton button = new JButton("I'm a Swing button!");
label = new JLabel(labelPrefix + "0    ");
label.setText(labelPrefix + numClicks);
JFrame frame = new JFrame("SwingApplication");

Assistive technologies can also grab the tool-tip text (if any) associated with a component and use it to describe the component to the user.

Making your program function smoothly with assistive technologies is easy to do and, in the United States, may be required by federal law.

The rest of this section covers these topics:

  • Rules for Supporting Accessibility

Here are a few things you can do to make your program work as well as possible with assistive technologies:
  • If a component doesn't display a short string (which serves as its default name), specify a name with the setAccessibleName method. You might want to do this for image-only buttons, panels that provide logical groupings, text areas, and so on.
  • Set tool tip text for components whenever it makes sense to do so. For example:
aJComponent.setToolTipText(
     "Clicking this component causes XYZ to happen.");
  • If you don't want to provide a tool tip for a component, use the setAccessibleDescription method to provide a description that assistive technologies can give the user. For example:
aJComponent.getAccessibleContext().
    setAccessibleDescription(
    "Clicking this component causes XYZ to happen.");
  • Specify keyboard alternatives wherever possible. Make sure you can use your program with only the keyboard. Try hiding your mouse! Note that if the focus is in an editable text component, you can use Shift-Tab to move focus to the next component.
  • Support for keyboard alternatives varies by component. Buttons support keyboard alternatives with the setMnemonic method. Menus inherit the button mnemonic support and also support accelerators, as described in Enabling Keyboard Operation. Other components can use key bindings to associate user typing with program actions.
  • Assign a textual description to all ImageIcon objects in your program. You can set this property by using either the setDescription method or one of the String forms of the ImageIcon constructors.
  • If a bunch of components form a logical group, try to put them into one container. For example, use a JPanel to contain all the radio buttons in a radio button group.
  • Whenever you have a label that describes another component, use the setLabelFor method so that assistive technologies can find the component that the label is associated with. This is especially important when the label displays a mnemonic for another component (such as a text field).
  • If you create a custom component, make sure it supports accessibility. In particular, be aware that subclasses of JComponent are not automatically accessible. Custom components that are descendants of other Swing components should override inherited accessibility information as necessary.
  • Use the examples provided with the accessibility utilities to test your program. Although the primary purpose of these examples is to show programmers how to use the Accessibility API when implementing assistive technologies, these examples are also quite useful for testing application programs for accessibility. Testing for Accessibility shows ScrollDemo running with Monkey, one of the accessibility utilities examples. Monkey shows the tree of accessible components in a program and lets you interact with those components.
  • Finally, don't break what you get for free! If your GUI has an inaccessible container — for example, your own subclass of Container or JComponent or any other container that doesn't implement the Accessible interface — any components inside that container become inaccessible.

  • Testing for Accessibility

The examples that come with the accessibility utilities can give you an idea of how accessible your program is. For instructions on getting these utilities, see the Java SE Desktop Accessibility home page. Follow the instructions in the accessibility utilities documentation for setting up the Java Virtual Machine (VM) to run one or more of the utilities automatically.

Let's use an accessibility utility to compare the original version of one of our demos to a version in which the rules for supporting accessibility have been applied. Here's a picture of a program called ScrollDemo.

Oracle Java Tutorials and Materials, Oracle Java Certifications

Try this:

1. Click the Launch button to run ScrollDemo using Java™ Web Start (download JDK 7 or later). Or, to compile and run the example yourself, consult the example index.

2. Next, click the Launch button to run AccessibleScrollDemo using Java™ Web Start (download JDK 7 or later). Or, to compile and run the example yourself, consult the example index

3. Compare the two versions side by side. The only noticeable difference is that the cm toggle button and the photograph have tool tips in the accessible version.

4. Now run the two versions under the accessibility utility called Monkey. Note that when the accessibility tools have been downloaded and configured in the accessibility.properties file, the Monkey window automatically comes up when you click on the Run ScrollDemo and AccessibleScrollDemo links (in steps 1 and 2).

If the Monkey window does not appear on startup, the problem may be that the accessibility.properties file is not present in the version of the VM being used by Java Web Start. You can change the VM you use by running the Java Web Start Application Manager and selecting File > Preferences > Java.

5. Note that when the Monkey window comes up you need to select File > Refresh Trees to see information appear under Accessible Tree. You can then expand the tree by successively clicking on the horizontal icons displayed by each folder icon. When the tree has been expanded, you can see detailed information for the various components. The custom components (rules and corners) that weren't accessible in the original version are accessible in the modified version. This can make quite a difference to assistive technologies.

Here's a snapshot of Monkey running on ScrollDemo:

Oracle Java Tutorials and Materials, Oracle Java Certifications

The left side of the split pane shows the actual component hierarchy for the program. The right side shows the accessible components in the hierarchy, which is what interests us.

The first thing to notice is that, even with no explicit support in ScrollDemo, Monkey is able to discover a lot of information about the various components in the program. Most of the components and their children appear in the tree. However, the names for most of the components are empty (null), which is rather unhelpful. The descriptions are also empty.

Further trouble comes with the program's custom components. The two rulers are inaccessible, so they are not included in the accessible tree. The viewports that contain the rulers are displayed as leaf nodes because they have no accessible children. The custom corners are also missing from the accessible tree.

Now here's a picture of the Monkey window for AccessibleScrollDemo:

Oracle Java Tutorials and Materials, Oracle Java Certifications

The rules are now listed as children of the viewports, and the corners are listed as children of the scroll pane. Furthermore, many of the components now have non-null names.

In the previous snapshot of Monkey, the Column Header item is selected. Monkey highlights the corresponding component in ScrollDemo program.

Oracle Java Tutorials and Materials, Oracle Java Certifications

When an item is selected, you can use Monkey's Panels menu to bring up one of four different panels that let you interact with the selected component. Choosing Panels > Accessibility API panel brings up a panel like the one shown in the following figure. This panel displays information available through methods defined in the AccessibleContext base class and the AccessibleComponent interface.

Oracle Java Tutorials and Materials, Oracle Java Certifications

Monkey has three other panels:
  • AccessibleAction: Shows the actions supported by an accessible component and lets you invoke the action. Works only with an accessible component whose context implements the AccessibleAction interface.
  • AccessibleSelection: Shows the current selection of an accessible component and lets you manipulate the selection. Works only with accessible component whose context implements the AccessibleSelection interface.
  • AccessibleHypertext: Shows any hyperlinks contained within an accessible component and lets you traverse them. Works only with accessible component whose context implements the AccessibleHypertext interface.
The accessibility utilities examples are handy as testing tools and can give you an idea of how accessible the components in your program are. However, even if your components behave well in Monkey or the other examples, they still might not be completely accessible because Monkey and the other examples exercise only certain portions of the Accessibility API.

The only true test of accessibility is to run your programs with real-world assistive technologies, however, you may find the following free and open source screen reader useful: NonVisual Desktop Access (NVDA).

  • Setting Accessible Names and Descriptions on Components

Giving your program's components accessible names and descriptions is one of the easiest and most important steps in making your program accessible. Following is a complete listing of the AccessibleScrollDemo constructor that creates the scroll pane and the custom components it uses. The boldface statements give components names and descriptions that assistive technologies can use.

public AccessibleScrollDemo() {
    // Get the image to use.
    ImageIcon bee = createImageIcon("images/flyingBee.jpg",
                      "Photograph of a flying bee.");

    // Create the row and column headers.
    columnView = new Rule(Rule.HORIZONTAL, true);
    if (bee != null) {
        columnView.setPreferredWidth(bee.getIconWidth());
    } else {
        columnView.setPreferredWidth(320);
    }
    columnView.getAccessibleContext().setAccessibleName("Column Header");
    columnView.getAccessibleContext().
            setAccessibleDescription("Displays horizontal ruler for " +
                                     "measuring scroll pane client.");
    rowView = new Rule(Rule.VERTICAL, true);
    if (bee != null) {
        rowView.setPreferredHeight(bee.getIconHeight());
    } else {
        rowView.setPreferredHeight(480);
    }
    rowView.getAccessibleContext().setAccessibleName("Row Header");
    rowView.getAccessibleContext().
            setAccessibleDescription("Displays vertical ruler for " +
                                     "measuring scroll pane client.");

    // Create the corners.
    JPanel buttonCorner = new JPanel();
    isMetric = new JToggleButton("cm", true);
    isMetric.setFont(new Font("SansSerif", Font.PLAIN, 11));
    isMetric.setMargin(new Insets(2,2,2,2));
    isMetric.addItemListener(this);
    isMetric.setToolTipText("Toggles rulers' unit of measure " +
                            "between inches and centimeters.");
    buttonCorner.add(isMetric); //Use the default FlowLayout
    buttonCorner.getAccessibleContext().
                 setAccessibleName("Upper Left Corner");

    String desc = "Fills the corner of a scroll pane " +
                  "with color for aesthetic reasons.";
    Corner lowerLeft = new Corner();
    lowerLeft.getAccessibleContext().
              setAccessibleName("Lower Left Corner");
    lowerLeft.getAccessibleContext().setAccessibleDescription(desc);

    Corner upperRight = new Corner();
    upperRight.getAccessibleContext().
               setAccessibleName("Upper Right Corner");
    upperRight.getAccessibleContext().setAccessibleDescription(desc);
    
    // Set up the scroll pane.
    picture = new ScrollablePicture(bee,
                                    columnView.getIncrement());
    picture.setToolTipText(bee.getDescription());
    picture.getAccessibleContext().setAccessibleName(
                                     "Scroll pane client");

    JScrollPane pictureScrollPane = new JScrollPane(picture);
    pictureScrollPane.setPreferredSize(new Dimension(300, 250));
    pictureScrollPane.setViewportBorder(
            BorderFactory.createLineBorder(Color.black));

    pictureScrollPane.setColumnHeaderView(columnView);
    pictureScrollPane.setRowHeaderView(rowView);

    // In theory, to support internationalization you would change
    // UPPER_LEFT_CORNER to UPPER_LEADING_CORNER,
    // LOWER_LEFT_CORNER to LOWER_LEADING_CORNER, and
    // UPPER_RIGHT_CORNER to UPPER_TRAILING_CORNER.  In practice,
    // bug #4467063 makes that impossible (at least in 1.4.0).
    pictureScrollPane.setCorner(JScrollPane.UPPER_LEFT_CORNER,
                                buttonCorner);
    pictureScrollPane.setCorner(JScrollPane.LOWER_LEFT_CORNER,
                                lowerLeft);
    pictureScrollPane.setCorner(JScrollPane.UPPER_RIGHT_CORNER,
                                upperRight);

    // Put it in this panel.
    setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
    add(pictureScrollPane);
    setBorder(BorderFactory.createEmptyBorder(20,20,20,20));
}
Often, the program sets a component's name and description directly through the component's accessible context. Other times, the program sets an accessible description indirectly with tool tips. In the case of the cm toggle button, the description is set automatically to the text on the button.

  • Concepts: How Accessibility Works

An object is accessible if it implements the Accessible interface. The Accessible interface defines just one method, getAccessibleContext, which returns an AccessibleContext object. The AccessibleContext object is an intermediary that contains the accessible information for an accessible object. The following figure shows how assistive technologies get the accessible context from an accessible object and query it for information:

Oracle Java Tutorials and Materials, Oracle Java Certifications

AccessibleContext is an abstract class that defines the minimum set of information an accessible object must provide about itself. The minimum set includes name, description, role, state set, and so on. To identify its accessible object as having particular capabilities, an accessible context can implement one or more of the interfaces as shown in the Accessible Interfaces table. For example, JButton implements AccessibleAction, AccessibleValue, AccessibleText, and AccessibleExtendedComponent. It is not necessary for JButton to implement AccessibleIcon because that is implemented by the ImageIcon attached to the button.

Because the JComponent class itself does not implement the Accessible interface, instances of its direct subclasses are not accessible. If you write a custom component that inherits directly from JComponent, you need to explicitly make it implement the Accessible interface. JComponent does have an accessible context, called AccessibleJComponent, that implements the AccessibleComponent interface and provides a minimal amount of accessible information. You can provide an accessible context for your custom components by creating a subclass of AccessibleJComponent and overriding important methods. Making Custom Components Accessible shows two examples of doing this.

All the other standard Swing components implement the Accessible interface and have an accessible context that implements one or more of the preceding interfaces as appropriate. The accessible contexts for Swing components are implemented as inner classes and have names of this style:

Component.AccessibleComponent

If you create a subclass of a standard Swing component and your subclass is substantially different from its superclass, then you should provide a custom accessible context for it. The easiest way is to create a subclass of the superclass's accessible context class and override methods as necessary. For example, if you create a JLabel subclass substantially different from JLabel, then your JLabel subclass should contain an inner class that extends AccessibleJLabel. The next section shows how to do so, using examples in which JComponent subclasses extend AccessibleJComponent.

  • Making Custom Components Accessible

The scroll demo program uses three custom component classes. ScrollablePicture is a subclass of JLabel, and Corner and Rule are both subclasses of JComponent.

The ScrollablePicture class relies completely on accessibility inherited from JLabel through JLabel.AccessibleJLabel. The code that creates an instance of ScrollablePicture sets the tool-tip text for the scrollable picture. The tool-tip text is used by the context as the component's accessible description. This behavior is provided by AccessibleJLabel.

The accessible version of the Corner class contains just enough code to make its instances accessible. We implemented accessibility support by adding the code shown in bold to the original version of Corner.

public class Corner extends JComponent implements Accessible {

    protected void paintComponent(Graphics g) {
        //Fill me with dirty brown/orange.
        g.setColor(new Color(230, 163, 4));
        g.fillRect(0, 0, getWidth(), getHeight());
    }

    public AccessibleContext getAccessibleContext() {
        if (accessibleContext == null) {
            accessibleContext = new AccessibleCorner();
        }
        return accessibleContext;
    }

    protected class AccessibleCorner extends AccessibleJComponent {
        //Inherit everything, override nothing.
    }
}

All of the accessibility provided by this class is inherited from AccessibleJComponent. This approach is fine for Corner because AccessibleJComponent provides a reasonable amount of default accessibility information and because corners are uninteresting: they exist only to take up a little bit of space onscreen. Other classes, such as Rule, need to provide customized information.

Rule provides an accessible context for itself in the same manner as Corner, but the context overrides two methods to provide details about the component's role and state:

protected class AccessibleRuler extends AccessibleJComponent {

    public AccessibleRole getAccessibleRole() {
        return AccessibleRuleRole.RULER;
    }

    public AccessibleStateSet getAccessibleStateSet() {
        AccessibleStateSet states =
            super.getAccessibleStateSet();
        if (orientation == VERTICAL) {
            states.add(AccessibleState.VERTICAL);
        } else {
            states.add(AccessibleState.HORIZONTAL);
        }
        if (isMetric) {
            states.add(AccessibleRulerState.CENTIMETERS);
        } else {
            states.add(AccessibleRulerState.INCHES);
        }
        return states;
    }
}

AccessibleRole is an enumeration of objects that identify roles that Swing components can play. It contains predefined roles such as label, button, and so on. The rulers in our example don't fit well into any of the predefined roles, so the program invents a new one in a subclass of AccessibleRole:

class AccessibleRuleRole extends AccessibleRole {
    public static final AccessibleRuleRole RULER
        = new AccessibleRuleRole("ruler");

    protected AccessibleRuleRole(String key) {
        super(key);
    }

    //Should really provide localizable versions of these names.
    public String toDisplayString(String resourceBundleName,
                                  Locale locale) {
        return key;
    }
}

Any component that has state can provide state information to assistive technologies by overriding the getAccessibleStateSet method. A rule has two sets of states: its orientation can be either vertical or horizontal, and its units of measure can be either centimeters or inches. AccessibleState is an enumeration of predefined states. This program uses its predefined states for vertical and horizontal orientation. Because AccessibleState contains nothing for centimeters and inches, the program makes a subclass to provide appropriate states:

class AccessibleRulerState extends AccessibleState {
    public static final AccessibleRulerState INCHES
        = new AccessibleRulerState("inches");
    public static final AccessibleRulerState CENTIMETERS
        = new AccessibleRulerState("centimeters");

    protected AccessibleRulerState(String key) {
        super(key);
    }

    //Should really provide localizable versions of these names.
    public String toDisplayString(String resourceBundleName,
                                  Locale locale) {
        return key;
    }
}

You've seen how to implement accessibility for two simple components, that exist only to paint themselves onscreen. Components that do more, such as responding to mouse or keyboard events, need to provide more elaborate accessible contexts. You can find examples of implementing accessible contexts by delving in the source code for the Swing components.

  • The Accessibility API

Method Purpose
isDesktopSupported() Tests whether this class is supported on the current platform. If it is supported, use getDesktop() to retrieve an instance.
getDesktop() Returns the Desktop instance of the current browser context. On some platforms the Desktop API may not be supported. Use the isDesktopSupported() method to determine if the current desktop is supported.
isSupported(Desktop.Action) Tests whether an action is supported on the current platform. Use the following constans of the Desktop.Action enum: BROWSE, EDIT, MAIL, OPEN, PRINT.
browse(URI) Launches the default browser to display a URI. If the default browser is not able to handle the specified URI, the application registered for handling URIs of the specified type is invoked. The application is determined from the protocol and path of the URI, as defined by the URI class.
mail(URI) Launches the mail composing window of the user default mail client, filling the message fields specified by a mailto: URI.
open(File) Launches the associated application to open a file.
edit(File) Launches the associated editor application and opens a file for editing.
print(File) print(File)

  • Examples that Use the Accessibility API

Example Where Described Notes
DesktopDemo This section Launches the host system's default browser with the specified URI and default email client; launches an application to open, edit, or print a file.

➤ How to Use the Focus Subsystem

Many components – even those primarily operated with the mouse, such as buttons – can be operated with the keyboard. For a key press to affect a component, the component must have the keyboard focus.

From the user's point of view, the component with the keyboard focus is generally prominent – with a dotted or black border, for example. The window containing the component is also more prominent than other windows onscreen. These visual cues let the user know to which component any typing will relate. Only one component at a time in the window system can have the keyboard focus.

Exactly how a window gains the focus depends on the windowing system. There is no foolproof way, across all platforms, to ensure that a window gains the focus. On some operating systems, such as Microsoft Windows, the front window usually becomes the focused window. In these cases, the Window.toFront method moves the window to the front, thereby giving it the focus. However, on other operating systems, such as Solaris™ Operating System, the window manager may choose the focused window based on cursor position, and in these cases the behavior of the Window.toFront method is different.

A component generally gains the focus when the user clicks it, or when the user tabs between components, or otherwise interacts with a component. A component can also be given the focus programmatically, such as when its containing frame or dialog-box is made visible. This code snippet shows how to give a particular component the focus every time the window gains the focus:

//Make textField get the focus whenever frame is activated.
frame.addWindowFocusListener(new WindowAdapter() {
    public void windowGainedFocus(WindowEvent e) {
        textField.requestFocusInWindow();
    }
});

If you want to ensure that a particular component gains the focus the first time a window is activated, you can call the requestFocusInWindow method on the component after the component has been realized, but before the frame is displayed. The following sample code shows how this operation can be done:

    //...Where initialization occurs...
    JFrame frame = new JFrame("Test");
    JPanel panel = new JPanel(new BorderLayout());

    //...Create a variety of components here...

    //Create the component that will have the initial focus.
    JButton button = new JButton("I am first");
    panel.add(button);
    frame.getContentPane().add(panel);  //Add it to the panel

    frame.pack();  //Realize the components.
    //This button will have the initial focus.
    button.requestFocusInWindow(); 
    frame.setVisible(true); //Display the window.

Alternatively, you can apply a custom FocusTraversalPolicy to the frame and call the getDefaultComponent method to determine which component will gain the focus.

The rest of this section covers the following topics:

  • Introduction to the Focus Subsystem

The focus subsystem is designed to do the right thing as invisibly as possible. In most cases it behaves in a reasonable manner, and if it does not you can tweak its behavior in various ways. Some common scenarios might include:

The ordering is right but the first component with the focus is not set. As shown in a code snippet in the preceding section, you can use the requestFocusInWindow method to set the focus on a component when the window becomes visible.
  1. The ordering is wrong. To fix this issue, you can change the containment hierarchy, you can change the order that the components are added to their containers, or you can create a custom focus traversal policy.
  2. A component must to be prevented from losing focus, or you need to check a value in a component before it loses focus. Input verification is a solution to this problem.
  3. A custom component is not getting the focus. To fix this issue, you need to make sure that it satisfies all the requirements outlined in Making a Custom Component Focusable.
The FocusConceptsDemo example illustrates a few concepts.

Oracle Java Certifications, Oracle Java Gudie, Oracle Java Tips

Try this: 

  1. Click the Launch button to run FocusConceptsDemo using Java™ Web Start (download JDK 7 or later). Alternatively, to compile and run the example yourself, consult the example index.
  2. If necessary, click the window to give it the focus.
  3. Move the focus from component to component using the Tab key.
  4. You will notice that when the focus moves into the text area, it stays in the text area.
  5. Move the focus out of the text area using Control-Tab.
  6. Move the focus in the opposite direction using Shift-Tab.
  7. Move the focus out of the text area in the opposite direction using Control-Shift-Tab.

The KeyboardFocusManager is a critical element of the focus subsystem. It manages state and initiates changes. The keyboard manager tracks the focus owner — the component that receives typing from the keyboard. The focused window is the window that contains the focus owner.

JWindow and focus: To use a JWindow component in your GUI, you should know that the JWindow component's owning frame must be visible in order for any components in the window to get the focus. By default, if you do not specify an owning frame for a JWindow component, an invisible owning frame is created for it. The result is that components in the JWindow component might not be able to get the focus. The solution is either to specify a visible owning frame when creating the JWindow component, or to use an undecorated JFrame component instead.

A focus cycle (or focus traversal cycle) is a set of components that share a common ancestor in the containment hierarchy. The focus cycle root is the container that is the root for a particular focus traversal cycle. By default, every JWindow and JInternalFrame component can be a focus cycle root. A focus cycle root can itself contain one or more focus cycle roots. The following Swing objects can be focus cycle roots: JApplet, JDesktopPane, JDialog, JEditorPane, JFrame, JInternalFrame, and JWindow. While it might appear that JTable and JTree objects are focus cycle roots, they are not.

A focus traversal policy determines the order in which a group of components are navigated. Swing provides the LayoutFocusTraversalPolicy class, which decides the order of navigation based on layout manager-dependent factors, such as size, location, and orientation of components. Within a focus cycle, components can be navigated in a forward or backward direction. In a hierarchy of focus cycle roots, upwards traversal takes the focus out of the current cycle into the parent cycle.

In most Look and Feel models, components are navigated using the Tab and Shift-Tab keys. These keys are the default focus traversal keys and can be changed programmatically. For example, you can add Enter as a forward focus traversal key with the following four lines of code:

Set forwardKeys = getFocusTraversalKeys(
    KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS);
Set newForwardKeys = new HashSet(forwardKeys);
newForwardKeys.add(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0));
setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS,
    newForwardKeys);

Tab shifts the focus in the forward direction. Shift-Tab moves the focus in the backward direction. For example, in FocusConceptsDemo, the first button has the initial focus. Tabbing moves the focus through the buttons into the text area. Additional tabbing moves the cursor within the text area but not out of the text area because, inside a text area, Tab is not a focus traversal key. However, Control-Tab moves the focus out of the text area and into the first text field. Likewise, Control-Shift-Tab moves the focus out of the text area and into the previous component. The Control key is used by convention to move the focus out of any component that treats Tab in a special way, such as JTable.

You have just received a brief introduction to the focus architecture.

  • Validating Input


A common requirement of GUI design is a component that restricts the user's input — for example, a text field that allows only numeric input in decimal format (money, for example) or a text field that allows only 5 digits for a zip code. An easy-to-use formatted text field component that allows input to be restricted to a variety of localizable formats. You can also specify a custom formatter for the text field, which can perform special checking such as determining whether values are not only formatted correctly, but also reasonable.

You can use an input verifier as an alternative to a custom formatter, or when you have a component that is not a text field. An input verifier allows you to reject specific values, such as a properly formatted but invalid zip code, or values outside of a desired range, for example a body temperature higher than 110°F. To use an input verifier, you create a subclass of InputVerifier, create an instance of your subclass, and set the instance as the input verifier for one or more components.

A component's input verifier is consulted whenever the component is about to lose the focus. If the component's value is not acceptable, the input verifier can take appropriate action, such as refusing to yield the focus on the component or replacing the user's input with the last valid value and then allowing the focus to transfer to the next component. However, InputVerifier is not called when the focus is transferred to another toplevel component.

The following two examples show mortgage calculators. One uses formatted text fields and the other uses input verification with standard text fields.

Oracle Java Certifications, Oracle Java Gudie, Oracle Java Tips

Try this: 

1. Click the Launch button to run the FormattedTextFieldDemo using Java™ Web Start (download JDK 7 or later). Alternatively, to compile and run the example yourself, consult the example index.

2. Click the Launch button to run the InputVerificationDemo using Java™ Web Start (download JDK 7 or later). Alternatively, to compile and run the example yourself, consult the example index.

3. Compare the two mortgage calculators side by side. You will see that the input verification demo specifies valid input values in the associated label for each editable text field. Try entering badly formatted values in both examples to observe behavior. Then try entering a properly formatted, but unacceptable value.

You can find the code for the Input Verification demo in InputVerificationDemo.java. Here is the code for the InputVerifier subclass, MyVerifier:

class MyVerifier extends InputVerifier
                 implements ActionListener {
    double MIN_AMOUNT = 10000.0;
    double MAX_AMOUNT = 10000000.0;
    double MIN_RATE = 0.0;
    int MIN_PERIOD = 1;
    int MAX_PERIOD = 40;

   public boolean shouldYieldFocus(JComponent input) {
        boolean inputOK = verify(input);
        makeItPretty(input);
        updatePayment();

        if (inputOK) {
            return true;
        } else {
            Toolkit.getDefaultToolkit().beep();
            return false;
        }
    }

    protected void updatePayment() {
        double amount = DEFAULT_AMOUNT;
        double rate = DEFAULT_RATE;
        int numPeriods = DEFAULT_PERIOD;
        double payment = 0.0;

        //Parse the values.
        try {
            amount = moneyFormat.parse(amountField.getText()).
                              doubleValue();
        } catch (ParseException pe) {pe.printStackTrace();}
        try {
            rate = percentFormat.parse(rateField.getText()).
                                 doubleValue();
        } catch (ParseException pe) {pe.printStackTrace();}
        try {
            numPeriods = decimalFormat.parse(numPeriodsField.getText()).
                              intValue();
        } catch (ParseException pe) {pe.printStackTrace();}

        //Calculate the result and update the GUI.
        payment = computePayment(amount, rate, numPeriods);
        paymentField.setText(paymentFormat.format(payment));
    }

    //This method checks input, but should cause no side effects.
    public boolean verify(JComponent input) {
        return checkField(input, false);
    }

    protected void makeItPretty(JComponent input) {
        checkField(input, true);
    }

    protected boolean checkField(JComponent input, boolean changeIt) {
        if (input == amountField) {
            return checkAmountField(changeIt);
        } else if (input == rateField) {
            return checkRateField(changeIt);
        } else if (input == numPeriodsField) {
            return checkNumPeriodsField(changeIt);
        } else {
            return true; //should not happen
        }
    }

    //Checks that the amount field is valid.  If it is valid,
    //it returns true; otherwise, returns false.  If the
    //change argument is true, this method sets the
    //value to the minimum or maximum value if necessary and
    // (even if not) sets it to the parsed number so that it
    // looks good -- no letters, for example.
    protected boolean checkAmountField(boolean change) {
        boolean wasValid = true;
        double amount = DEFAULT_AMOUNT;

        //Parse the value.
        try {
            amount = moneyFormat.parse(amountField.getText()).
                              doubleValue();
        } catch (ParseException pe) {
            pe.printStackTrace();
            wasValid = false;
        }

        //Value was invalid.
        if ((amount < MIN_AMOUNT) || (amount > MAX_AMOUNT)) {
            wasValid = false;
            if (change) {
                if (amount < MIN_AMOUNT) {
                    amount = MIN_AMOUNT;
                } else { // amount is greater than MAX_AMOUNT
                    amount = MAX_AMOUNT;
                }
            }
        }

        //Whether value was valid or not, format it nicely.
        if (change) {
            amountField.setText(moneyFormat.format(amount));
            amountField.selectAll();
        }

        return wasValid;
    }

    //Checks that the rate field is valid.  If it is valid,
    //it returns true; otherwise, returns false.  If the
    //change argument is true, this method reigns in the
    //value if necessary and (even if not) sets it to the
    //parsed number so that it looks good -- no letters,
    //for example.
    protected boolean checkRateField(boolean change) {
        ...//Similar to checkAmountField...
    }

    //Checks that the numPeriods field is valid.  If it is valid,
    //it returns true; otherwise, returns false.  If the
    //change argument is true, this method reigns in the
    //value if necessary and (even if not) sets it to the
    //parsed number so that it looks good -- no letters,
    //for example.
    protected boolean checkNumPeriodsField(boolean change) {
        ...//Similar to checkAmountField...
    }

    public void actionPerformed(ActionEvent e) {
        JTextField source = (JTextField)e.getSource();
        shouldYieldFocus(source); //ignore return value
        source.selectAll();
    }
}

Note that the verify method is implemented to detect invalid values but does nothing else. The verify method exists only to determine whether the input is valid — it should never bring up a dialog-box or cause any other side effects. The shouldYieldFocus method calls verify and, if a values is invalid, sets it to the minimum or maximum value. The shouldYieldFocus method is allowed to cause side effects, in this case, it always formats the text field and may also change its value. In our example, the shouldYieldFocus method always returns true so that the transfer of the focus is never actually prevented. This is just one way verification can be implemented. Find another version of this demo called InputVerificationDialogDemo that puts up a dialog-box when user input is invalid and requires the user to enter a legal value.

The input verifier is installed using the setInputVerifier method of the JComponent class. For example, the InputVerificationDemo has the following code:

private MyVerifier verifier = new MyVerifier();
...
amountField.setInputVerifier(verifier);
  • Making a Custom Component Focusable
For a component to gain the focus, it must satisfy three requirements: it must be visible, enabled, and focusable. An input map may also be given.

The TrackFocusDemo example defines the simple component Picture. Its constructor is shown below:

public Picture(Image image) {
    this.image = image;
    setFocusable(true);
    addMouseListener(this);
    addFocusListener(this);
}

The call to the setFocusable(true) method makes the component focusable. If you explicitly give your component key bindings in its WHEN_FOCUSED input map, you do not need to call the setFocusable method.

To visually show changes in the focus (by drawing a red border only when the component has the focus), Picture has a focus listener.

To gain the focus when the user clicks on the picture, the component has a mouse listener. The listener's mouseClicked method requests for the focus to be transferred to the picture. Here is the code:

public void mouseClicked(MouseEvent e) {
    //Since the user clicked on us, let us get focus!
    requestFocusInWindow();
}

  • Customizing Focus Traversal

The focus subsystem determines a default order that is applied when using the focus traversal keys (such as Tab) to navigate. The policy of a Swing application is determined by LayoutFocusTraversalPolicy . You can set a focus traversal policy on any Container by using the setFocusCycleRoot method. However, if the container is not a focus cycle root, it may have no apparent effect. Alternatively you can pass focus traversal policy providers to the FocusTraversalPolicy methods instead of focus cycle roots. Use the isFocusTraversalPolicyProvider() method to determine whether a Container is a focus traversal policy provider. Use the setFocusTraversalPolicyProvider() method to set a container for providing focus traversal policy.

The FocusTraversalDemo example demonstrates how to customize focus behavior.

Using Other Swing Features
Try this: 
  1. Click the Launch button to run FocusTraversalDemo using Java™ Web Start (download JDK 7 or later). Alternatively, to compile and run the example yourself, consult the example index.
  2. Click the window, if necessary, to give it the focus.
  3. Note the focus order as you tab through the components. The focus order was determined by the order that the components were added to the content pane. Note also that the check box never gets the focus; we removed it from the focus cycle.
  4. To move the focus out of the table, use Control-Tab or Control-Shift-Tab.
  5. Click the Custom FocusTraversalPolicy check box. This box installs a custom focus traversal policy on the frame.
  6. Try tabbing through the components again. Note that the focus order is now in left-to-right, top-down order.
The check box was removed from the focus cycle with this line of code:

togglePolicy.setFocusable(false);
Here is the application's custom FocusTraversalPolicy:

...
JTextField tf1 = new JTextField("Field 1");
JTextField tf2 = new JTextField("A Bigger Field 2");
JTextField tf3 = new JTextField("Field 3");
JTextField tf4 = new JTextField("A Bigger Field 4");
JTextField tf5 = new JTextField("Field 5");
JTextField tf6 = new JTextField("A Bigger Field 6");
JTable table = new JTable(4,3);
...
public FocusTraversalDemo() {
    super(new BorderLayout());

    JTextField tf1 = new JTextField("Field 1");
    JTextField tf2 = new JTextField("A Bigger Field 2");
    JTextField tf3 = new JTextField("Field 3");
    JTextField tf4 = new JTextField("A Bigger Field 4");
    JTextField tf5 = new JTextField("Field 5");
    JTextField tf6 = new JTextField("A Bigger Field 6");
    JTable table = new JTable(4,3);
    togglePolicy = new JCheckBox("Custom FocusTraversalPolicy");
    togglePolicy.setActionCommand("toggle");
    togglePolicy.addActionListener(this);
    togglePolicy.setFocusable(false);  //Remove it from the focus cycle.
    //Note that HTML is allowed and will break this run of text
    //across two lines.
    label = new JLabel("<html>Use Tab (or Shift-Tab) to navigate from component to component.<p>Control-Tab 
    (or Control-Shift-Tab) allows you to break out of the JTable.</html>");

    JPanel leftTextPanel = new JPanel(new GridLayout(3,2));
    leftTextPanel.add(tf1, BorderLayout.PAGE_START);
    leftTextPanel.add(tf3, BorderLayout.CENTER);
    leftTextPanel.add(tf5, BorderLayout.PAGE_END);
    leftTextPanel.setBorder(BorderFactory.createEmptyBorder(0,0,5,5));
    JPanel rightTextPanel = new JPanel(new GridLayout(3,2));
    rightTextPanel.add(tf2, BorderLayout.PAGE_START);
    rightTextPanel.add(tf4, BorderLayout.CENTER);
    rightTextPanel.add(tf6, BorderLayout.PAGE_END);
    rightTextPanel.setBorder(BorderFactory.createEmptyBorder(0,0,5,5));
    JPanel tablePanel = new JPanel(new GridLayout(0,1));
    tablePanel.add(table, BorderLayout.CENTER);
    tablePanel.setBorder(BorderFactory.createEtchedBorder());
    JPanel bottomPanel = new JPanel(new GridLayout(2,1));
    bottomPanel.add(togglePolicy, BorderLayout.PAGE_START);
    bottomPanel.add(label, BorderLayout.PAGE_END);

    add(leftTextPanel, BorderLayout.LINE_START);
    add(rightTextPanel, BorderLayout.CENTER);
    add(tablePanel, BorderLayout.LINE_END);
    add(bottomPanel, BorderLayout.PAGE_END);
    setBorder(BorderFactory.createEmptyBorder(20,20,20,20));
    Vector<Component> order = new Vector<Component>(7);
    order.add(tf1);
    order.add(tf2);
    order.add(tf3);
    order.add(tf4);
    order.add(tf5);
    order.add(tf6);
    order.add(table);
    newPolicy = new MyOwnFocusTraversalPolicy(order);
}

To use a custom FocusTraversalPolicy, implement the following code on any focus cycle root.

    MyOwnFocusTraversalPolicy newPolicy = new MyOwnFocusTraversalPolicy();
    frame.setFocusTraversalPolicy(newPolicy);

You can remove the custom focus traversal policy by setting the FocusTraversalPolicy to null, which will restore the default policy.
  • Tracking Focus Changes to Multiple Components
In some situations an application may need to track which component has the focus. This information might be used to dynamically update menus or perhaps a status bar. If you need to track the focus only on specific components, it may make sense to implement a focus event listener.

If a focus listener is not appropriate, you can instead register a PropertyChangeListener on the KeyboardFocusManager. The property change listener is notified of every change involving the focus, including changes to the focus owner, the focused window, and the default focus traversal policy.

The following example demonstrates tracking the focus owner by installing a property change listener on the keyboard focus manager.


Try this: 

1. Click the Launch button to run TrackFocusDemo using Java™ Web Start (download JDK 7 or later). Alternatively, to compile and run the example yourself, consult the example index.Launches the TrackFocusDemo application

2. If necessary, click the window to give it the focus.

3. The window shows six images, each of which is displayed by a Picture component. The Picture that has the focus is indicated with a red border. A label at the bottom of the window describes the Picture that has the focus.

4. Move the focus to another Picture by using Tab or Shift-Tab, or by clicking an image. Because a property change listener has been registered on the keyboard focus manager, the change in focus is detected and the label is updated appropriately.

You can view the demo's code in TrackFocusDemo.java. The custom component used for drawing the images can be found in Picture.java. Here is the code that defines and installs the property change listener:

KeyboardFocusManager focusManager =
    KeyboardFocusManager.getCurrentKeyboardFocusManager();
focusManager.addPropertyChangeListener(
    new PropertyChangeListener() {
        public void propertyChange(PropertyChangeEvent e) {
            String prop = e.getPropertyName();
            if (("focusOwner".equals(prop)) &&
                  ((e.getNewValue()) instanceof Picture)) {
                Component comp = (Component)e.getNewValue();
                String name = comp.getName();
                Integer num = new Integer(name);
                int index = num.intValue();
                if (index < 0 || index > comments.length) {
                    index = 0;
                }
                info.setText(comments[index]);
            }
        }
    }
);

The custom component, Picture, is responsible for drawing the image. All six components are defined in this manner:

pic1 = new Picture(createImageIcon("images/" +
            mayaString + ".gif", mayaString).getImage());
pic1.setName("1");
  • Timing Focus Transfers
Focus transfers are asynchronous. This quality can lead to some odd timing-related problems and assumptions, especially during automatic transfers of the focus. For example, imagine an application with a window containing a Start button, a Cancel button and a text field. The components are added in this order:
  1. Start button
  2. Text field
  3. Cancel button
When the application is launched, the LayoutFocusTraversalPolicy determines the focus traversal policy — in this case, it is the order that the components were added to their container. In this example, the desired behavior is that the Start button has the initial focus, and when the Start button is clicked, it is disabled, and then the Cancel button receives the focus. The correct way to implement this behavior would be to add the components to the container in the desired order or to create a custom focus traversal policy. If, for some reason, that is not possible, then you can implement this behavior with the following code snippet:

public void actionPerformed(ActionEvent e) {
    //This works.
    start.setEnabled(false);
    cancel.requestFocusInWindow();
}

As desired, the focus goes from the Start button to the Cancel button, rather than to the text field. But a different result would occur if the same methods were called in the opposite order as follows:

public void actionPerformed(ActionEvent e) {
    //This does not work.
    cancel.requestFocusInWindow();
    start.setEnabled(false);
}

In this case, the focus is requested on the Cancel button before it has left the Start button. The call to the requestFocusInWindow method initiates the focus transfer, but it does not immediately move the focus to the Cancel button. When the Start button is disabled, the focus is transferred to the next component (so there is always a component with the focus) and, in this case, it would then move the focus to the text field, not to the Cancel button.

There are several situations in which you need to make focus requests after all other changes that might affect the focus applies to:
  1. Hiding the focus owner.
  2. Making the focus owner non-focusable.
  3. Calling the removeNotify method on the focus owner.
  4. Doing any of the above operations to the container of the focus owner, or causing changes to the focus policy so that the container no longer accepts the component as the focus owner.
  5. Disposing of the top-level window that contains the focus owner.
  • The Focus API
The following tables list the commonly used constructors and methods related to focus. The focus API falls into four categories:
  1. Useful Methods for Components
  2. Creating and Using a Custom FocusTraversalPolicy
  3. Input Verification API
  4. KeyboardFocusManager Properties
Useful Methods for Components
Method (in Component) Purpose
isFocusOwner() Returns true if the component is the focus owner.
setRequestFocusEnabled(boolean)
isRequestFocusEnabled()
(in JComponent)
Sets or checks on whether this component should get the focus. Setting the setRequestFocusEnabled to false typically prevents mouse clicks from giving the component the focus, while still allowing keyboard navigation to give the component the focus. This method applies only to components that receive mouse events. For example, you can use this method on a JButton, but not on a JPanel. If you write a custom component it is up to you to honor this property. This method is recommended over the setFocusable method and will allow your program to work better for users employing assistive technologies.
setFocusable(boolean)
isFocusable()
Sets or gets the focusable state of the component. A component must be focusable in order to gain the focus. When a component has been removed from the focus cycle with setFocusable(false), it can no longer be navigated with the keyboard. The setRequestFocusEnabled method is recommended so that your program can be run by users employing assistive technologies.
requestFocusInWindow() Requests that this component should get the focus. The component's window must be the current focused window. For this request to be granted a subclass of JComponent must be visible, enabled, and focusable, and have an input map for this request to be granted. It should not be assumed that the component has the focus until it fires a FOCUS_GAINED event. This method is preferred to the requestFocus method, which is platform-dependent.
setFocusTraversalKeys(int, Set)
getFocusTraversalKeys(int)
areFocusTraversalKeysSet(int)
(in java.awt.Container)
Sets or gets the focus traversal keys for a particular direction or determines whether any focus traversal keys have been explicitly set on this container. If no focus traversal keys have been set, they are inherited from an ancestor or from the keyboard focus manager. Focus traversal keys can be set for the following directions: KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS KeyboardFocusManager.UP_CYCLE_TRAVERSAL_KEYS, or KeyboardFocusManager.DOWN_CYCLE_TRAVERSAL_KEYS. If you set the UP_CYCLE_TRAVERSAL_KEYS or the DOWN_CYCLE_TRAVERSAL_KEYS, you must also invoke setImplicitDownCycleTraversal(false) on the focus traversal policy.

Creating and Using a Custom FocusTraversalPolicy
Class or Method Purpose
LayoutFocusTraversalPolicy The class that, by default, determines the focus traversal policy for Swing components.
getComponentAfter(Container, Component) Given the component that is passed as input, returns the component that should next have the focus.
getComponentBefore(Container, Component) Given the component that is passed as input, returns the component that should have the focus before this component. The method is used for backward tabbing.
getDefaultComponent(Container)
(in javax.swing.SortingFocusTraversalPolicy)
Returns the component that should have the default focus.
getFirstComponent(Container) Returns the first component in the traversal cycle.
getInitialComponent(Container) Returns the component that should receive the focus when a window is made visible for the first time.
getLastComponent(Container) Returns the last component in the traversal cycle.
setFocusTraversalPolicy(FocusTraversalPolicy)
getFocusTraversalPolicy(FocusTraversalPolicy)
(in java.awt.Container)
Sets or gets the focus traversal policy or determines if a policy has been set. Note that setting a focus traversal policy on a container that is not the focus cycle root may have no apparent effect. A value of null means that a policy has not been explicitly set. If no policy has been set, a policy is inherited from the parent focus cycle root.
isFocusCycleRoot()
setFocusCycleRoot(boolean)
(in java.awt.Container)
Checks or sets whether a container is the root of a focus traversal cycle.
isFocusTraversalPolicyProvider()
setFocusTraversalPolicyProvider(boolean)
(in java.awt.Container)
Checks or sets whether a container will be used to provide focus traversal policy.

Input Verification API
Class or Method Purpose
InputVerifier Abstract class that allows input validation via the focus mechanism. When an attempt is made to shift the focus from a component containing an input verifier, the focus is not relinquished until the verifier is satisfied.
shouldYieldFocus(JComponent)
(in InputVerifier)
When a component has an input verifier, this method is called by the system to determine whether the focus can leave this component. This method may cause side effects, such as bringing up a dialog-box. If this method returns false, the focus remains on the component passed in to the method.
verify(JComponent)
(in InputVerifier)
You need to override this method to check that the component's input is valid. It should return true if valid, otherwise return false. This method should not cause any side effects, such as bringing up a dialog-box. This method is called by shouldYieldFocus.
setInputVerifier(inputVerifier)
getInputVerifier()
(in JComponent)
Sets or gets the input verifier assigned to the component. By default, components have no input verifier.
setVerifyInputWhenFocusTarget(boolean)
getVerifyInputWhenFocusTarget()
(in JComponent)
Sets or gets whether the input verifier for the current focus owner is called before this component requests the focus. The default is true. This method should be set to false for components, such as a Cancel button or a scroll bar, that should receive the focus even if input is invalid.

KeyboardFocusManager Properties

This table defines the bound properties for KeyboardFocusManager. A listener can be registered for these properties by calling addPropertyChangeListener.

Property Purpose
focusOwner The component that currently receives key events.
permanentFocusOwner The component that most recently received a permanent FOCUS_GAINED event. Typically the same as focusOwner, unless a temporary focus change is currently in effect.
focusedWindow The window that is or that contains the focus owner.
activeWindow The component must always be either a Frame or a Dialog. The active window is either the focused window, or the first frame or dialog-box that is an owner of the focused window.
defaultFocusTraversalPolicy The default focus traversal policy, which can be set by the setFocusTraversalPolicy method of the Container class.
forwardDefaultFocusTraversalKeys  The set of default focus keys for a forward traversal. For multi-line text components, these keys default to Control-Tab. For all other components, these keys default to Tab and Control-Tab.
backwardDefaultFocusTraversalKeys The set of default focus keys for a backward traversal. For multi-line text components these keys default to Control-Shift-Tab. For all other components these keys default to Shift-Tab and Control-Shift-Tab.
upCycleDefaultFocusTraversalKeys The set of default focus keys for an up cycle. These keys are null, by default, for Swing components. If you set these keys on the KeyboardFocusManager, or if you set the downCycleFocusTraversalKeys on a focus cycle root, you must also invoke the setImplicitDownCycleTraversal(false) method on the focus traversal policy.
downCycleDefaultFocusTraversalKeys The set of default focus keys for a down cycle. These keys are null, by default, for Swing components. If you set these keys on the KeyboardFocusManager, or if you set the upCycleFocusTraversalKeys on a focus cycle root, you must also invoke the setImplicitDownCycleTraversal(false) method on the focus traversal policy.
currentFocusCycleRoot The container that is the current focus cycle root.

  • Focus Examples
Example Where Described Notes
FocusConceptsDemo This section Demonstrates basic default focus behavior.
FocusTraversalDemo This section Demonstrates how to override the default focus order.
TrackFocusDemo This section Demonstrates how to use a PropertyChangeListener to track the focus owner. Also implements a custom focusable component.
InputVerificationDemo This section Demonstrates how to implement an InputVerifier to validate user input.
InputVerificationDialogDemo This section Demonstrates how to implement an InputVerifier that puts up a dialog-box when user input is invalid.
FocusEventDemo How to Write a Focus Listener Reports all focus events that occur on several components to demonstrate the circumstances under which focus events are fired.

➤ How to Use Key Bindings

The JComponent class supports key bindings as a way of responding to individual keys typed by a user. Here are some examples of when key bindings are appropriate:
  • You're creating a custom component and want to support keyboard access to it.
  • For example, you might want the component to react when it has the focus and the user presses the Space key.
  • You want to override the behavior of an existing key binding.
  • For example, if your application normally reacts to presses of the F2 key in a particular way, you might want it to perform a different action or ignore the key press.
  • You want to provide a new key binding for an existing action.
  • For example, you might feel strongly that Control-Shift-Insert should perform a paste operation.
You often don't need to use key bindings directly. They're used behind the scenes by mnemonics (supported by all buttons and by tabbed panes as well as by JLabel) and accelerators (supported by menu items). You can find coverage of mnemonics and accelerators in the section Enabling Keyboard Operation.

An alternative to key bindings is using key listeners. Key listeners have their place as a low-level interface to keyboard input, but for responding to individual keys key bindings are more appropriate and tend to result in more easily maintained code. Key listeners are also difficult if the key binding is to be active when the component doesn't have focus. Some of the advantages of key bindings are they're somewhat self documenting, take the containment hierarchy into account, encourage reusable chunks of code (Action objects), and allow actions to be easily removed, customized, or shared. Also, they make it easy to change the key to which an action is bound. Another advantage of Actions is that they have an enabled state which provides an easy way to disable the action without having to track which component it is attached to.

The rest of this section gives you the details you need to use key bindings:

  • How Key Bindings Work

The key binding support provided by JComponent relies on the InputMap and ActionMap classes. An input map binds key strokes to action names, and an action map specifies the action corresponding to each action name. Technically, you don't need to use action names in the maps; you can use any object as the "key" into the maps. By convention, however, you use a string that names an action.

Each InputMap/ActionMap has a parent that typically comes from the UI. Any time the look and feel is changed, the parent is reset. In this way, any bindings specified by the developer are never lost on look and feel changes.

Each JComponent has one action map and three input maps. The input maps correspond to the following focus situations:

JComponent.WHEN_FOCUSED

The component has the keyboard focus. The WHEN_FOCUSED input map is typically used when the component has no children. For example, buttons bind the Space key using the WHEN_FOCUSED map.
These bindings are only effective when the component has focus.

JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT

The component contains (or is) the component that has the focus. This input map is commonly used for a composite component — a component whose implementation depends on child components. For example, JTables make all their bindings using WHEN_ANCESTOR_OF_FOCUSED_COMPONENT so that if the user is editing, the up-arrow key (for example) still changes the selected cell.

JComponent.WHEN_IN_FOCUSED_WINDOW

The component's window either has the focus or contains the component that has the focus. This input map is commonly used for mnemonics or accelerators, which need to be active regardless of where focus is in the window.

When the user types a key, the JComponent key event processing code searches through one or more input maps to find a valid binding for the key. When it finds a binding, it looks up the corresponding action in the action map. If the action is enabled, the binding is valid and the action is executed. If it's disabled, the search for a valid binding continues.

If more than one binding exists for the key, only the first valid one found is used. Input maps are checked in this order:
  1. The focused component's WHEN_FOCUSED input map.
  2. The focused component's WHEN_ANCESTOR_OF_FOCUSED_COMPONENT input map.
  3. The WHEN_ANCESTOR_OF_FOCUSED_COMPONENT input maps of the focused component's parent, and then its parent's parent, and so on, continuing up the containment hierarchy. Note: Input maps for disabled components are skipped.
  4. The WHEN_IN_FOCUSED_WINDOW input maps of all the enabled components in the focused window are searched. Because the order of searching the components is unpredictable, avoid duplicate WHEN_IN_FOCUSED_WINDOW bindings!
Let's consider what happens in two typical key binding cases: a button reacting to the Space key, and a frame with a default button reacting to the Enter key.

In the first case, assume the user presses the Space key while a JButton has the keyboard focus. First, the button's key listeners are notified of the event. Assuming none of the key listeners consumes the event (by invoking the consume method on the KeyEvent) the button's WHEN_FOCUSED input map is consulted. A binding is found because JButton uses that input map to bind Space to an action name. The action name is looked up in the button's action map, and the actionPerformed method of the action is invoked. The KeyEvent is consumed, and processing stops.

In the second case, assume the Enter key is pressed while the focus is anywhere inside a frame that has a default button (set using the JRootPane setDefaultButton method). Whatever the focused component is, its key listeners are first notified. Assuming none of them consumes the key event the focused component's WHEN_FOCUSED input map is consulted. If it has no binding for the key or the Action bound to the key is disabled, the focused component's WHEN_ANCESTOR_OF_FOCUSED_COMPONENT input map is consulted and then (if no binding is found or the Action bound to the key is disabled) the WHEN_ANCESTOR_OF_FOCUSED_COMPONENT input maps of each of the component's ancestors in the containment hierarchy. Eventually, the root pane's WHEN_ANCESTOR_OF_FOCUSED_COMPONENT input map is searched. Since that input map has a valid binding for Enter, the action is executed, causing the default button to be clicked.

  • How to Make and Remove Key Bindings

Here is an example of specifying that a component should react to the F2 key:

component.getInputMap().put(KeyStroke.getKeyStroke("F2"),
                            "doSomething");
component.getActionMap().put("doSomething",
                             anAction);
//where anAction is a javax.swing.Action

As the preceding code shows, to get a component's action map you use the getActionMap method (inherited from JComponent). To get an input map, you can use the getInputMap(int) method, where the integer is one of the JComponent.WHEN_*FOCUSED* constants shown in the preceding list. Or, in the usual case where the constant is JComponent.WHEN_FOCUSED, you can just use getInputMap with no arguments.

To add an entry to one of the maps, use the put method. You specify a key using a KeyStroke object, which you can get using the KeyStroke.getKeyStroke(String) method. You can find examples of creating an Action (to put in an action map) in How to Use Actions.

Here's a slightly more complex example that specifies that a component should react to the Space key as if the user clicked the mouse.

component.getInputMap().put(KeyStroke.getKeyStroke("SPACE"),
                            "pressed");
component.getInputMap().put(KeyStroke.getKeyStroke("released SPACE"),
                            "released");
component.getActionMap().put("pressed",
                             pressedAction);
component.getActionMap().put("released",
                             releasedAction);
//where pressedAction and releasedAction are javax.swing.Action objects

To make a component ignore a key that it normally responds to, you can use the special action name "none". For example, the following code makes a component ignore the F2 key.

component.getInputMap().put(KeyStroke.getKeyStroke("F2"),
                            "none");
Note: 

The preceding code doesn't prevent the relevant WHEN_ANCESTOR_OF_FOCUSED_COMPONENT and WHEN_IN_FOCUSED_WINDOW input maps from being searched for an F2 key binding. To prevent this search, you must use a valid action instead of "none". For example:

Action doNothing = new AbstractAction() {
    public void actionPerformed(ActionEvent e) {
        //do nothing
    }
};
component.getInputMap().put(KeyStroke.getKeyStroke("F2"),
                            "doNothing");
component.getActionMap().put("doNothing",
                             doNothing);
  • The Key Binding API
The following tables list the commonly used API for key bindings. Also see the API table Creating and Using an Action, in the section How to Use Actions.

1. Creating and Using InputMaps

Method Purpose
InputMap getInputMap() InputMap getInputMap(int) (in JComponent) Get one of the input maps for the component. The arguments can be one of these JComponent constants: WHEN_FOCUSED, WHEN_IN_FOCUSED_WINDOW, or WHEN_ANCESTOR_OF_FOCUSED_COMPONENT. The no-argument method gets the WHEN_FOCUSED input map.
void put(KeyStroke, Object) (in InputMap) Set the action name associated with the specified key stroke. If the second argument is null, this method removes the binding for the key stroke. To make the key stroke be ignored, use "none" as the second argument.
static KeyStroke getKeyStroke(String) (in KeyStroke) Get the object specifying a particular user keyboard activity. Typical arguments are "alt shift X", "INSERT", and "typed a". See the KeyStroke API documentation for full details and for other forms of the getKeyStroke method.

2. Creating and Using ActionMaps

MethodPurpose
ActionMap getActionMap() (in JComponent)Get the object that maps names into actions for the component.
void put(Object, Action) (in ActionMap) Set the action associated with the specified name. If the second argument is null, this method removes the binding for the name
  • Examples that Use Key Bindings
Example Where Described Notes
TableFTFEditDemo How to Use Tables The IntegerEditor class registers a key binding on a formatted text field to validate the input when the user presses the Enter key.
TextComponentDemo Text Component Features Key bindings are registered on a text pane to navigate through the text when the user presses the Control-B, Control-F, Control-P, and Control-N keys.

➤How to Use Modality in Dialogs

Java™ SE 6 has resolved modality issues that arose in earlier versions of the platform. The new modality model enables the developer to scope, or limit, a dialog box's modality blocking.

Before proceding with the new modality model, review the following terms:
  • Dialog box — A top-level pop-up window with a title and a border that typically takes some form of input from the user. A dialog box can be modal or modeless. For more information about dialog boxes.
  • Modal dialog box — A dialog box that blocks input to some other top-level windows in the application, except for windows created with the dialog box as their owner. The modal dialog box captures the window focus until it is closed, usually in response to a button press.
  • Modeless dialog box — A dialog box that enables you to operate with other windows while this dialog box is shown.
In Java SE 6 the behavior of both modal and modeless dialog boxes has been changed so that they always appear on top of both not only of their parent windows and of all blocked windows as well.

The following modality types are supported in Java SE 6:
  • Modeless type — A modeless dialog box does not block any other window while it is visible.
  • Document-modal type — A document-modal dialog box blocks all windows from the same document, except windows from its child hierarchy. In this context, a document is a hierarchy of windows that share a common ancestor, called the document root, which is the closest ancestor window without an owner.
  • Application-modal type — An application-modal dialog box blocks all windows from the same application, except windows from its child hierarchy. If several applets are launched in a browser environment, the browser is allowed to treat them either as separate applications or as a single application. This behavior is implementation-dependent.
  • Toolkit-modal type — A toolkit-modal dialog box blocks all windows that run in the same toolkit, except windows from its child hierarchy. If several applets are launched, all of them run with the same toolkit. Hence, a toolkit-modal dialog box shown from an applet may affect other applets and all windows of the browser instance that embeds the Java runtime environment for this toolkit.
Additionally, you can set up the modality exclusion mode:
  • Exclusion mode — Any top-level window can be marked not to be blocked by modal dialogs. This property enables you to set up the modal exclusion mode. The Dialog.ModalExclusionType enum specifies the possible modal exclusion types.
Note : The new modality model does not implement a system modality, which blocks all applications (including Java applications) that are displayed on the desktop while a modal dialog box is active.

The ModalityDemo example demonstrates the first three of the four modality types mentioned above.

Oracle Java Tutorials and Materials, Java Certifications
This figure has been reduced to fit on the page. 
Click the image to view it at its natural size.

Try this: 

1. Click the Launch button to run ModalityDemo using Java™ Web Start (download JDK 7 or later). Alternatively, to compile and run the example yourself, consult the example index.

2. The following dialog boxes will appear:
    • Book 1 (parent frame)
    • Book 2 (parent frame)
    • Feedback (parent frame)
    • Classics (excluded frame)
3. Switch to the Book 1 frame and choose the Biography title for the book, then select OK.

4. The Biography title will be displayed in the title of the dialog box. Enter the name, for example - “John”, into the text field.

5. Switch to the Book 1 frame and change the title to Funny Tale, then select OK. Since the dialog box for entering the name is modeless, you can easily switch to its parent frame.

Note : The modeless dialog box title has been changed to Funny Tale.

6. Select OK in the modeless dialog box.

7. The Funny tale document-modal dialog box appears.

8. Type some text in the text field. Notice that it is signed by the name you entered in the modeless dialog box.

9. Switch to the modeless dialog box and try to change your name. You will not be able to do so, because the document-modal dialog box blocks all windows in its parent hierarchy.

10. Perform the same sequence of operations (steps 3 - 9) for the Book 2 parent frame.

11. Try switching to different dialog boxes. You will notice that you can switch either to the Classics frame or to the Feedback frame as well as to the dialog box of either the Book 1 frame or the Book 2 frame.

12. Switch to the Feedback parent frame. Select Rate Yourself.

13. The confirmation dialog box will appear. Try switching to different dialog boxes. You are only enabled to switch to the Classics dialog box because the standard confirmation dialog box is an application-modal dialog box and it blocks all windows from the same application. However, you will notice that you can select your favorite classical author in the Classics frame. This frame has been created by using the APPLICATION_EXCLUDE modality exclusion type, which prevents all top-level windows from being blocked by any application-modal dialog boxes.

The following code snippet shows how to create dialog boxes of different modality types:

//The Book 1 parent frame
f1 = new JFrame("Book 1 (parent frame)");

...

//The modeless dialog box
d2 = new JDialog(f1);

...
        
//The document-modal dialog box
d3 = new JDialog(d2, "", Dialog.ModalityType.DOCUMENT_MODAL);

...

        //The Book2 parent frame
f4 = new JFrame("Book 2 (parent frame)");

...

//The modeless dialog box
d5 = new JDialog(f4);

...

//The document-modality dialog box
d6 = new JDialog(d5, "", Dialog.ModalityType.DOCUMENT_MODAL);
        
...

//The excluded frame
f7 = new JFrame("Classics (excluded frame)");
f7.setModalityExclusionType(Dialog.ModalExclusionType.APPLICATION_EXCLUDED);
        
...

//The Feedback parent frame and Confirm Dialog box
f8 = new JFrame("Feedback (parent frame)");
...

JButton b8 = new JButton("Rate yourself");
b8.addActionListener(new ActionListener() {
    public void actionPerformed(ActionEvent e) {
        JOptionPane.showConfirmationDialog(null,
                                           "I really like my book",
                                           "Question (application-modal dialog)", 
                                           JOptionPane.Yes_NO_OPTION,
                                           JOptionPane.QUESTION_MESSAGE); 
    }
});

The ModalityDemo.java file.

package misc;

/*
 * ModalityDemo.java
 *
 */

import java.awt.*;
import java.awt.event.*;
import java.awt.font.*;

import javax.swing.*;

public class ModalityDemo {
    
    // first document: frame, modeless dialog, document-modal dialog
    private JFrame f1;
    private JDialog d2;
    private JDialog d3;
    
    // second document: frame, modeless dialog, document-modal dialog
    private JFrame f4;
    private JDialog d5;
    private JDialog d6;
    
    // third document: modal excluded frame
    private JFrame f7;
    
    // fourth document: frame, file dialog (application-modal)
    private JFrame f8;
    
    // radiobuttons in f1
    JRadioButton rb11, rb12, rb13;
    // text field in d2
    JTextField tf2;
    
    // label in d3
    JLabel l3;
    
    // radiobuttons in f4
    JRadioButton rb41, rb42, rb43;
    // text field in d5
    JTextField tf5;
    
    // label in d6
    JLabel l6;
    
    // radiobuttons in f7
    JRadioButton rb71, rb72, rb73;
   
    public static void main(String[] args) {
        /* Use an appropriate Look and Feel */
        try {
            //UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel");
            UIManager.setLookAndFeel("javax.swing.plaf.metal.MetalLookAndFeel");
        } catch (UnsupportedLookAndFeelException ex) {
            ex.printStackTrace();
        } catch (IllegalAccessException ex) {
            ex.printStackTrace();
        } catch (InstantiationException ex) {
            ex.printStackTrace();
        } catch (ClassNotFoundException ex) {
            ex.printStackTrace();
        }
        /* Turn off metal's use of bold fonts */
        UIManager.put("swing.boldMetal", Boolean.FALSE);
        
        //Schedule a job for the event-dispatching thread:
        //creating and showing this application's GUI.
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                ModalityDemo md = new ModalityDemo();
                md.createAndShowGUI();
                md.start();
            }
        });
    }
    
    //start frames
    private void start() {
        f1.setVisible(true);
        f4.setVisible(true);
        f7.setVisible(true);
        f8.setVisible(true);
    }
    
    private static WindowListener closeWindow = new WindowAdapter() {
        public void windowClosing(WindowEvent e) {
            e.getWindow().dispose();
        }
    };
    
    /**
     * Create the GUI and show it.  For thread safety,
     * this method is invoked from the
     * event-dispatching thread.
     */
    private void createAndShowGUI(){
        GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
        GraphicsDevice gd = ge.getDefaultScreenDevice();
        GraphicsConfiguration gc = gd.getDefaultConfiguration();
        Insets ins = Toolkit.getDefaultToolkit().getScreenInsets(gc);
        int sw = gc.getBounds().width - ins.left - ins.right;
        int sh = gc.getBounds().height - ins.top - ins.bottom;
        
        // first document
        
        // frame f1
        
        f1 = new JFrame("Book 1 (parent frame)");
        f1.setBounds(32, 32, 300, 200);
        f1.addWindowListener(closeWindow);
        // create radio buttons
        rb11 = new JRadioButton("Biography", true);
        rb12 = new JRadioButton("Funny tale", false);
        rb13 = new JRadioButton("Sonnets", false);
        // place radio buttons into a single group
        ButtonGroup bg1 = new ButtonGroup();
        bg1.add(rb11);
        bg1.add(rb12);
        bg1.add(rb13);
        JButton b1 = new JButton("OK");
        b1.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                // get label of selected radiobutton
                String title = null;
                if (rb11.isSelected()) {
                    title = rb11.getText();
                } else if (rb12.isSelected()) {
                    title = rb12.getText();
                } else {
                    title = rb13.getText();
                }
                // prepend radio button label to dialogs' titles
                d2.setTitle(title + " (modeless dialog)");
                d3.setTitle(title + " (document-modal dialog)");
                d2.setVisible(true);
            }
        });
        Container cp1 = f1.getContentPane();
        // create three containers to improve layouting
        cp1.setLayout(new GridLayout(1, 3));
        // an empty container
        Container cp11 = new Container();
        // a container to layout components
        Container cp12 = new Container();
        // an empty container
        Container cp13 = new Container();
        // add a button into a separate panel
        JPanel p1 = new JPanel();
        p1.setLayout(new FlowLayout());
        p1.add(b1);
        // add radio buttons and the OK button one after another into a single column
        cp12.setLayout(new GridLayout(4, 1));
        cp12.add(rb11);
        cp12.add(rb12);
        cp12.add(rb13);
        cp12.add(p1);
        // add three containers
        cp1.add(cp11);
        cp1.add(cp12);
        cp1.add(cp13);
        
        // dialog d2
        
        d2 = new JDialog(f1);
        d2.setBounds(132, 132, 300, 200);
        d2.addWindowListener(closeWindow);
        JLabel l2 = new JLabel("Enter your name: ");
        l2.setHorizontalAlignment(SwingConstants.CENTER);
        tf2 = new JTextField(12);
        JButton b2 = new JButton("OK");
        b2.setHorizontalAlignment(SwingConstants.CENTER);
        b2.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                //pass a name into the document modal dialog
                l3.setText("by " + tf2.getText());
                d3.setVisible(true);
            }
        });
        Container cp2 = d2.getContentPane();
        // add label, text field and button one after another into a single column
        cp2.setLayout(new BorderLayout());
        cp2.add(l2, BorderLayout.NORTH);
        cp2.add(tf2, BorderLayout.CENTER);
        JPanel p2 = new JPanel();
        p2.setLayout(new FlowLayout());
        p2.add(b2);
        cp2.add(p2, BorderLayout.SOUTH);
                
        // dialog d3
        
        d3 = new JDialog(d2, "", Dialog.ModalityType.DOCUMENT_MODAL);
        d3.setBounds(232, 232, 300, 200);
        d3.addWindowListener(closeWindow);
        JTextArea ta3 = new JTextArea();
        l3 = new JLabel();
        l3.setHorizontalAlignment(SwingConstants.RIGHT);
        Container cp3 = d3.getContentPane();
        cp3.setLayout(new BorderLayout());
        cp3.add(new JScrollPane(ta3), BorderLayout.CENTER);
        JPanel p3 = new JPanel();
        p3.setLayout(new FlowLayout(FlowLayout.RIGHT));
        p3.add(l3);
        cp3.add(p3, BorderLayout.SOUTH);
        
        // second document
        
        // frame f4
        
        f4 = new JFrame("Book 2 (parent frame)");
        f4.setBounds(sw - 300 - 32, 32, 300, 200);
        f4.addWindowListener(closeWindow);
        // create radio buttons
        rb41 = new JRadioButton("Biography", true);
        rb42 = new JRadioButton("Funny tale", false);
        rb43 = new JRadioButton("Sonnets", false);
        // place radio buttons into a single group
        ButtonGroup bg4 = new ButtonGroup();
        bg4.add(rb41);
        bg4.add(rb42);
        bg4.add(rb43);
        JButton b4 = new JButton("OK");
        b4.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                // get label of selected radiobutton
                String title = null;
                if (rb41.isSelected()) {
                    title = rb41.getText();
                } else if (rb42.isSelected()) {
                    title = rb42.getText();
                } else {
                    title = rb43.getText();
                }
                // prepend radiobutton label to dialogs' titles
                d5.setTitle(title + " (modeless dialog)");
                d6.setTitle(title + " (document-modal dialog)");
                d5.setVisible(true);
            }
        });
        Container cp4 = f4.getContentPane();
        // create three containers to improve layouting
        cp4.setLayout(new GridLayout(1, 3));
        Container cp41 = new Container();
        Container cp42 = new Container();
        Container cp43 = new Container();
        // add the button into a separate panel
        JPanel p4 = new JPanel();
        p4.setLayout(new FlowLayout());
        p4.add(b4);
        // add radiobuttons and the OK button one after another into a single column
        cp42.setLayout(new GridLayout(4, 1));
        cp42.add(rb41);
        cp42.add(rb42);
        cp42.add(rb43);
        cp42.add(p4);
        //add three containers
        cp4.add(cp41);
        cp4.add(cp42);
        cp4.add(cp43);
        
        // dialog d5
        
        d5 = new JDialog(f4);
        d5.setBounds(sw - 400 - 32, 132, 300, 200);
        d5.addWindowListener(closeWindow);
        JLabel l5 = new JLabel("Enter your name: ");
        l5.setHorizontalAlignment(SwingConstants.CENTER);
        tf5 = new JTextField(12);
        tf5.setHorizontalAlignment(SwingConstants.CENTER);
        JButton b5 = new JButton("OK");
        b5.setHorizontalAlignment(SwingConstants.CENTER);
        b5.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                //pass a name into the document modal dialog
                l6.setText("by " + tf5.getText());
                d6.setVisible(true);
            }
        });
        Container cp5 = d5.getContentPane();
        // add label, text field and button one after another into a single column
        cp5.setLayout(new BorderLayout());
        cp5.add(l5, BorderLayout.NORTH);
        cp5.add(tf5, BorderLayout.CENTER);
        JPanel p5 = new JPanel();
        p5.setLayout(new FlowLayout());
        p5.add(b5);
        cp5.add(p5, BorderLayout.SOUTH);
        
        // dialog d6
        
        d6 = new JDialog(d5, "", Dialog.ModalityType.DOCUMENT_MODAL);
        d6.setBounds(sw - 500 - 32, 232, 300, 200);
        d6.addWindowListener(closeWindow);
        JTextArea ta6 = new JTextArea();
        l6 = new JLabel();
        l6.setHorizontalAlignment(SwingConstants.RIGHT);
        Container cp6 = d6.getContentPane();
        cp6.setLayout(new BorderLayout());
        cp6.add(new JScrollPane(ta6), BorderLayout.CENTER);
        JPanel p6 = new JPanel();
        p6.setLayout(new FlowLayout(FlowLayout.RIGHT));
        p6.add(l6);
        cp6.add(p6, BorderLayout.SOUTH);
                
        // third document
        
        // frame f7
        
        f7 = new JFrame("Classics (excluded frame)");
        f7.setModalExclusionType(Dialog.ModalExclusionType.APPLICATION_EXCLUDE);
        f7.setBounds(32, sh - 200 - 32, 300, 200);
        f7.addWindowListener(closeWindow);
        JLabel l7 = new JLabel("Famous writers: ");
        l7.setHorizontalAlignment(SwingConstants.CENTER);
        // create radio buttons
        rb71 = new JRadioButton("Burns", true);
        rb72 = new JRadioButton("Dickens", false);
        rb73 = new JRadioButton("Twain", false);
        // place radio buttons into a single group
        ButtonGroup bg7 = new ButtonGroup();
        bg7.add(rb71);
        bg7.add(rb72);
        bg7.add(rb73);
        Container cp7 = f7.getContentPane();
        // create three containers to improve layouting
        cp7.setLayout(new GridLayout(1, 3));
        Container cp71 = new Container();
        Container cp72 = new Container();
        Container cp73 = new Container();
        // add the label into a separate panel
        JPanel p7 = new JPanel();
        p7.setLayout(new FlowLayout());
        p7.add(l7);
        // add a label and radio buttons one after another into a single column
        cp72.setLayout(new GridLayout(4, 1));
        cp72.add(p7);
        cp72.add(rb71);
        cp72.add(rb72);
        cp72.add(rb73);
        // add three containers
        cp7.add(cp71);
        cp7.add(cp72);
        cp7.add(cp73);
        
        // fourth document
        
        // frame f8
        
        f8 = new JFrame("Feedback (parent frame)");
        f8.setBounds(sw - 300 - 32, sh - 200 - 32, 300, 200);
        f8.addWindowListener(closeWindow);
        JButton b8 = new JButton("Rate yourself");
        b8.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                JOptionPane.showConfirmDialog(null,
                        "I really like my book",
                        "Question (application-modal dialog)",
                        JOptionPane.YES_NO_OPTION,
                        JOptionPane.QUESTION_MESSAGE);
            }
        });
        Container cp8 = f8.getContentPane();
        cp8.setLayout(new FlowLayout(FlowLayout.CENTER, 8, 8));
        cp8.add(b8);
    }
}

In Java SE 6 you can create a document-modal dialog box without a parent. Because the Dialog class is a subclass of the Window class, a Dialog instance automatically becomes the root of the document if it has no owner. Thus, if such a dialog box is document-modal, its scope of blocking is empty, and it behaves as if it were a modeless dialog box.

The Modality API


The JDialog class constructors enable you to create dialog boxes of various modality types.

Constructor Purpose
JDialog(Dialog owner) Creates a modeless dialog box with the specified Dialog owner but without a title.
JDialog(Dialog owner, boolean modal) Creates a dialog box with the specified Dialog owner and modality.
JDialog(Dialog owner, String title) Creates a modeless dialog box with the specified Dialog owner and title.
JDialog(Dialog owner, String title, boolean modal) Creates a dialog box with the specified Dialog owner, title, and modality.
JDialog(Dialog owner, String title, boolean modal, GraphicsConfiguration gc) Creates a dialog box with the specified Dialog owner, title, modality, and graphics configuration.
JDialog(Frame owner) Creates a modeless dialog box without a title with the specified Frame owner. If the value for the owner is null, a shared, hidden frame will be set as the owner of the dialog box.
JDialog(Window owner, String title, Dialog.ModalityType modalityType) Creates a dialog box with the specified Window owner, title, and modality.

The following table lists methods inherited from the java.awt.Dialog class.

MethodPurpose
getModalityTypeReturns the modality type for this dialog box.
setModalityTypeSets the modality type for this dialog box.

Examples That Use Modality API


The following table lists the example that uses modality in dialogs.

ExampleWhere Described-Notes
ModalityDemoThis section - Creates dialog boxes of different modality types, demonstrates scope blocking for those types.

➤ How to Print Tables

The JTable class provides support for printing tables. The JTable printing API includes methods that allow you to implement both basic and advanced printing tasks. For common printing tasks, when you need to simply print a table, use the print method directly. The print method has several forms with various argument sets. This method prepares your table, gets a corresponding Printable object, and sends it to a printer.

If the default implementation of the Printable object does not meet your needs, you can customize the printing layout by overriding the getPrintable method to wrap the default Printable or even replace it altogether.

The easiest way to print your table is to call the print method without parameters. See the code example below.

try {
    boolean complete = table.print();
    if (complete) {
        /* show a success message  */
        ...
    } else {
        /*show a message indicating that printing was cancelled */
        ...
    }
} catch (PrinterException pe) {
    /* Printing failed, report to the user */
    ...
}

When you call the print method with no parameters, a print dialog is displayed, and then your table is printed interactively in the FIT_WIDTH mode without a header or a footer. The code example below shows the print method signature with the complete set of arguments.

boolean complete = table.print(JTable.PrintMode printMode,
                               MessageFormat headerFormat,
                               MessageFormat footerFormat, 
                               boolean showPrintDialog,
                               PrintRequestAttributeSet attr,
                               boolean interactive,
                               PrintService service);

When you call the print method with all arguments, you explicitly choose printing features such as a printing mode, a header and a footer text, printing attributes, a destination print service, and also whether to show a print dialog or not, and whether to print interactively or non-interactively. To decide which parameters suit your needs best, see the description of available features below.

The JTable printing API provides the following features:

➥ Printing Interactively or Non-interactively

In interactive mode a progress dialog with an abort option is shown for the duration of printing. Here is a sample of a progress dialog.

Oracle Java Tutorials and Materials, Oracle Java Materials, Oracle Java Certifications, Oracle Java Guide

This dialog enables the user to keep track of printing progress. The progress dialog is modal, which means that while it is shown on the screen, the user cannot interact with the table. It is important that your table remain unchanged while it is being printed, otherwise the printing behavior will be undefined. Nevertheless, printing interactively does not block other developer's code from changing the table. For example, there is another thread that posts updates using the SwingUtilities.invokeLater method. Therefore, to ensure correct printing behavior, you should be sure that your own code refrains from modifying the table during printing.

Alternatively, you can print your table non-interactively. In this mode, printing begins immediately on the event dispatch thread and completely blocks any events to be processed. On the one hand, this mode securely keeps the table against any changes until printing is done. On the other hand, this mode completely deprives the user of any interaction with the GUI. That is why printing non-interactively can only be recommended when printing from applications with non-visible GUI.

➥ Displaying a Print Dialog

You can display a standard print dialog which allows the user to do the following:
  • Select a printer
  • Specify number of copies
  • Change printing attributes
  • Cancel printing before it has been started
  • Start printing
Oracle Java Tutorials and Materials, Oracle Java Materials, Oracle Java Certifications, Oracle Java Guide

You may notice that the print dialog does not specify the total number of pages in the printout. This is because the table printing implementation uses the Printable API and the total number of pages is not known ahead of printing time.

➥ Adding a Header or a Footer (or Both) to a Printing Layout

Headers and footers are provided by MessageFormat parameters. These parameters allow the header and footer to be localized. Read the documentation for the MessageFormat class, as some characters, such as single quotes, are special and need to be avoided. Both headers and footers are centered. You can insert a page number by using {0}.

MessageFormat footer = new MessageFormat("Page - {0}");

Since the total number of pages in the output is not known before printing time, there is no way to specify a numbering format like "Page 1 of 5".

➥ Selecting a Printing Mode

Printing modes are responsible for scaling the output and spreading it across pages. You can print your table in one of the following modes:
  • PrintMode.NORMAL
  • PrintMode.FIT_WIDTH
In the NORMAL mode a table is printed at its current size. If columns do not fit a page, they spread across additional pages according to the table's ComponentOrientation. In the FIT_WIDTH mode a table has a smaller size, if necessary, to fit all columns on each page. Note that both width and height are scaled to provide an output of the same aspect ratio. In both modes rows spread across multiple pages sequentially with as many rows on a page as possible.

➥ Automatic Layout and Pagination

With the use of the JTable printing API you do not need to take care of layout and pagination. You only need to specify appropriate parameters to the print method such as printing mode and footer text format (if you want to insert the page number in the footer). As demonstrated earlier, you can specify the page number in your footer by including "{0}" in the string given to the MessageFormat footer parameter. In the printed output, {0} will be replaced by the current page number.

Table Printing Examples

Let us look at an example called TablePrintDemo1. The entire code for this program can be found in TablePrintDemo1.java. This demo's rich GUI is built automatically by the NetBeans IDE GUI builder. Here is a picture of the TablePrintDemo1 application.

Oracle Java Tutorials and Materials, Oracle Java Materials, Oracle Java Certifications, Oracle Java Guide

Try this: 
  1. Click the Launch button to run TablePrintDemo1 using Java™ Web Start (download JDK 7 or later). Alternatively, to compile and run the example yourself, consult the example index.
  2. Each checkbox in the bottom part of the application window has a tool tip. Hold the cursor over a checkbox to find out its purpose.
  3. Edit the text in the Header or Footer checkboxes or both to provide a different header or footer.
  4. Clear the Header or Footer checkboxes or both to turn the header or footer off.
  5. Clear the Show print dialog checkbox to turn the print dialog off.
  6. Clear the Fit width to printed page checkbox to select printing in the NORMAL mode.
  7. Clear the Interactive (Show status dialog) checkbox to turn the print dialog off.
  8. Click the Print button to print the table according to the selected options.
Whenever a web-launched application tries to print, Java Web Start pops up a security dialog asking the user for permission to print. To proceed with printing, the user has to accept the request.

Note when you clear the Interactive checkbox, a message appears that warns the user about the disadvantage of printing non-interactively. You can find the printing code in the PrintGradesTable method. When called, this method first obtains the set of selected options from the GUI components and then calls the print method as follows.

boolean complete = gradesTable.print(mode, header, footer,
                                     showPrintDialog, null,
                                     interactive, null);

The value returned by the print method is then used to show either the success message or the message saying that the user cancelled printing.

Another important feature is the table printing API's use of table renderers. By using the table's renderers, the API provides a printed output that looks like the table on the screen. Look at the last column of the table on the screen. It contains custom images denoting the passed or failed status of each student. Now look at the printed result. You can see that the check and X marks look the same.

Here is a picture of the TablePrintDemo1 printed result in the FIT_WIDTH mode.

Oracle Java Tutorials and Materials, Oracle Java Materials, Oracle Java Certifications, Oracle Java Guide
This figure has been reduced to fit on the page. 
Click the image to view it at its natural size.

TablePrintDemo2 Example

The TablePrintDemo2 example is based on the previous demo and has an identical interface. The only difference is in the printed output. If you look at the TablePrintDemo1's printed result more attentively, you may notice that the check and X marks are fuzzy. The TablePrintDemo2 example shows how to customize the table to make the images more distinguishable in the table printout. In this demo, the overridden getTableCellRendererComponent method finds out whether the table is being printed and returns clearer black and white images. If the table is not being printed, it returns colored images that you can see on the screen.

Click the Launch button to run TablePrintDemo2 using Java™ Web Start (download JDK 7 or later). Alternatively, to compile and run the example yourself, consult the example index.

The isPaintingForPrint method defined in the JComponent class allows us to customize what we print compared with what we see on the screen. The code of the custom cell renderer, taken from TablePrintDemo2.java, is listed below. This code chooses which images to use depending on the value returned by the isPaintingForPrint method.

    /**
     * A custom cell renderer that extends TablePrinteDemo1's renderer, to instead
     * use clearer black and white versions of the icons when printing.
     */
    protected static class BWPassedColumnRenderer extends PassedColumnRenderer {
            public Component getTableCellRendererComponent(JTable table,
                                                           Object value,
                                                           boolean isSelected,
                                                           boolean hasFocus,
                                                           int row,
                                                           int column) {

            super.getTableCellRendererComponent(table, value, isSelected,
                                                hasFocus, row, column);

            /* if we're currently printing, use the black and white icons */
            if (table.isPaintingForPrint()) {
                boolean status = (Boolean)value;
                setIcon(status ? passedIconBW : failedIconBW);
            } /* otherwise, the superclass (colored) icons are used */

            return this;
        }
    }

Here is a picture of the TablePrintDemo2 printed result in the FIT_WIDTH mode.

Oracle Java Tutorials and Materials, Oracle Java Materials, Oracle Java Certifications, Oracle Java Guide
This figure has been reduced to fit on the page. 
Click the image to view it at its natural size.

TablePrintDemo3 Example

The TablePrintDemo3 example is based on the two previous demos. This example shows how to provide a customized Printable implementation by wrapping the default Printable with extra decoration. This demo has a similar interface but the Header and Footer checkboxes are disabled since the customized printable object will provide its own header and footer.

Click the Launch button to run TablePrintDemo3 using Java™ Web Start (download JDK 7 or later). Alternatively, to compile and run the example yourself, consult the example index.

This example prints the table inside an image of a clipboard. Here is a picture of the printed result in the FIT_WIDTH mode.

Oracle Java Tutorials and Materials, Oracle Java Materials, Oracle Java Certifications, Oracle Java Guide
This figure has been reduced to fit on the page. 
Click the image to view it at its natural size.

The entire code for this program can be found in TablePrintDemo3.java. In this demo, a custom subclass of the JTable class is used called FancyPrintingJTable. This FancyPrintingJTable class overrides the getPrintable method to return a custom printable object that wraps the default printable with its own decorations and header and footer. Here is the implementation of the getPrintable method.

public Printable getPrintable(PrintMode printMode,
                              MessageFormat headerFormat,
                              MessageFormat footerFormat) {

     MessageFormat pageNumber = new MessageFormat("- {0} -");

     /* Fetch the default printable */
     Printable delegate = super.getPrintable(printMode, null, pageNumber);

     /* Return a fancy printable that wraps the default */
     return new FancyPrintable(delegate);
}

The FancyPrintable class is responsible for wrapping the default printable object into another printable object and setting up the clipboard image. When an instance of this class is instantiated, it loads the images needed to assemble the clipboard image, calculates the area required for the clipboard image, calculates the shrunken area for the table, prints the table into the smaller area, and assembles and prints the clipboard image.

Pay attention to the flexibility of the code that assembles the clipboard image with respect to the page size. The code takes into account the actual page dimensions and puts together the auxiliary images, stretching some of them as necessary so that the final clipboard image fits the actual page size. The picture below shows the auxiliary images and indicates how those images form the final output.

Oracle Java Tutorials and Materials, Oracle Java Materials, Oracle Java Certifications, Oracle Java Guide
This figure has been reduced to fit on the page. 
Click the image to view it at its natural size.

The Table Printing API

This section lists methods defined in the JTable class that allow you to print tables.

Method Purpose
boolean print()
boolean print(printMode)
boolean print(printMode, MessageFormat, MessageFormat)
boolean print(printMode, MessageFormat, MessageFormat, boolean, PrintRequestAttributeSet, boolean)
boolean print(printMode, MessageFormat, MessageFormat, boolean, PrintRequestAttributeSet, boolean, PrintService)
When called without arguments, displays a print dialog, and then prints this table interactively in the FIT_WIDTH mode without a header or a footer text. Returns true if the user continued printing and false if the user cancelled printing.
When called with a full set of arguments, prints this table according to the specified arguments. The first argument specifies the printing mode. Two MessageFormat arguments specify header and footer text. The first boolean argument defines whether to show a print dialog or not. Another boolean argument specifies whether to print interactively or not. With two other arguments you can specify printing attributes and a print service.
Whenever a PrintService argument is omitted, the default printer will be used.
Printable getPrintable(PrintMode, MessageFormat, MessageFormat) Returns a Printable for printing a table. Override this method to get a customized Printable object. You can wrap one Printable object into another to get various layouts.

Examples That Use Table Printing

This table lists examples that use table printing and points to where those examples are described.

Example Where Described Notes
TablePrintDemo How to Use Tables Demonstrates basic features in table printing such as displaying a print dialogue, and then printing interactively in the FIT_WIDTH mode with a page number as a header.
TablePrintDemo1 This page Demostrates the basics of table printing and provides a rich GUI. Allows the user to specify a header or a footer text, select the printing mode, turn the print dialog on or off, and select printing interactively or non-interactively.
TablePrintDemo2 This page Based on the TablePrintDemo1, this example has an identical interface. This demo shows how to customize the table so that the printed result looks differently compared to the table being shown on the screen.
TablePrintDemo3 This page  This demo shows advanced table printing features such as wrapping the default table printable into another printable to get a different layout.

➤ How to Print Text

The JTextComponent class provides support for printing text documents. The JTextComponent API includes methods that allow you to implement both basic and advanced printing tasks. Supported formats include HTML, RTF, and plain text. For common printing tasks such as simply printing a text document, use the print method directly. The print method has several forms with various argument sets. This method prepares your text document, gets a corresponding Printable object, and sends it to a printer.

If the default implementation of the Printable object does not meet your needs, you can customize the printing layout by overriding the getPrintable method to wrap the default Printable or even replace it altogether.

The easiest way to print your text component is to call the print method without parameters. See the code example below.

try {
    boolean complete = textComponent.print();
    if (complete) {
        /* show a success message  */
        ...
    } else {
        /*show a message indicating that printing was cancelled */
        ...
    }
} catch (PrinterException pe) {
    /* Printing failed, report to the user */
    ...
}

When you call the print method with no parameters, a print dialog is displayed, and then your text component is printed interactively without a header or a footer. The code example below shows the print method signature with the complete set of arguments.

boolean complete = textComponent.print(MessageFormat headerFormat,
                                       MessageFormat footerFormat, 
                                       boolean showPrintDialog,
                                       PrintService service
                                       PrintRequestAttributeSet attributes,
                                       boolean interactive);

When you call the print method with all arguments, you explicitly choose printing features such as header and footer text, printing attributes, a destination print service, and also whether to show a print dialog or not, and whether to print interactively or non-interactively. To decide which parameters suit your needs best, see the description of available features below.

The JTextComponent printing API provides the following features:

➥ Printing Interactively or Non-interactively

In interactive mode a progress dialog with an abort option is shown for the duration of printing. Here is a sample of a progress dialog.


This dialog allows the user to keep track of printing progress. The progress dialog is modal when the print method is called on the event dispatch thread and non-modal otherwise. It is important that your document remain unchanged while being printed, otherwise the printing behavior is undefined. The print method ensures that your document will not be changed and disables the component for the duration of printing.

If you call the print method on the event dispatch thread in non-interactive mode, then all events including repaints will be blocked. That is why printing non-interactively on EDT is only recommended for applications with non-visible GUI.

➥ Displaying a Print Dialog

You can display a standard print dialog which allows the user to do the following:
  • Select a printer
  • Specify number of copies
  • Change printing attributes
  • Cancel printing before it has been started
  • Start printing

You may notice that the print dialog does not specify the total number of pages in the printout. This is because the text printing implementation uses the Printable API and the total number of pages is not known before printing time.

➥ Adding a Header or a Footer (or Both) to a Printing Layout

Headers and footers are provided by MessageFormat parameters. These parameters allow the header and footer to be localized. Read the documentation for the MessageFormat class as characters such as single quotes are special and need to be avoided. Both headers and footers are centered. You can insert a page number by using {0}.

MessageFormat footer = new MessageFormat("Page - {0}");

Since the total number of pages in the output is not known before printing time, there is no way to specify a numbering format like "Page 1 of 5".

➥ Automatic Layout and Pagination

With the use of the JTextComponent printing API you do not need to take care of layout and pagination. Both layout and pagination are done automatically. The document content is formatted to fit the page size and spreads across multiple pages. You only need to specify an appropriate footer text format to the print method if you want to insert a page number in the footer. As demonstrated earlier, you can specify the page number in your footer by including "{0}" in the string given to the MessageFormat footer parameter. In the printed output, {0} will be replaced by the current page number.

➥ Text Area Printing Example

Let us look at an example called TextAreaPrintingDemo. The main feature of this demo is printing a text document either on the event dispatch thread or on a background thread depending on the user's choice. This demo displays a text area, allows to select several printing features, and prints the text area's content according to the selected options. The entire code for this program can be found in TextAreaPrintingDemo.java. This demo's rich GUI is built in the NetBeans IDE GUI builder. Here is a picture of the TextAreaPrintingDemo application.


Try this: 
  1. Click the Launch button to run TextAreaPrintingDemo using Java™ Web Start (download JDK 7 or later). Alternatively, to compile and run the example yourself, consult the example index.
  2. Edit the text in the Header or Footer checkboxes or both to provide a different header or footer.
  3. Clear the Show Progress Dialog checkbox if you want to print without displaying a progress dialog, which means printing non-interactively. Note that you will not be able to cancel printing once it has been started.
  4. Clear the Print in Background checkbox to select printing on the event dispatch thread. Note that printing on EDT non-interactively will make your application unresponsive — interaction with your application will be blocked for the duration of the printing process.
  5. Click the Print button to print the text area's content according to the selected options.
Whenever a web-launched application tries to print, Java Web Start opens up a security dialog asking the user for permission to print unless this permission has already been granted in the system settings. To proceed with printing the user has to accept the request.

An action listener is registered for the Print button. As the user clicks the Print button the actionPerformed method calls the print method, which initiates a printing task. The printing task is a SwingWorker object. The code example below shows how the PrintingTask class is implemented.

private class PrintingTask extends SwingWorker<Object, Object> {
    private final MessageFormat headerFormat;
    private final MessageFormat footerFormat;
    private final boolean interactive;
    private volatile boolean complete = false;
    private volatile String message;
        
    public PrintingTask(MessageFormat header, MessageFormat footer,
                        boolean interactive) {
        this.headerFormat = header;
        this.footerFormat = footer;
        this.interactive = interactive;
    }
        
    @Override
    protected Object doInBackground() {
        try {
            complete = text.print(headerFormat, footerFormat,
                    true, null, null, interactive);
            message = "Printing " + (complete ? "complete" : "canceled");
        } catch (PrinterException ex) {
            message = "Sorry, a printer error occurred";
        } catch (SecurityException ex) {
            message =
                "Sorry, cannot access the printer due to security reasons";
        }
        return null;
    }
        
    @Override
    protected void done() {
        message(!complete, message);
    }
}

The code example below shows how the print method obtains the set of selected options from the GUI components, then creates an instance of the PrintingTask class, and performs printing.

private void print(java.awt.event.ActionEvent evt) {
        MessageFormat header = createFormat(headerField);
        MessageFormat footer = createFormat(footerField);
        boolean interactive = interactiveCheck.isSelected();
        boolean background = backgroundCheck.isSelected();

        PrintingTask task = new PrintingTask(header, footer, interactive);
        if (background) {
            task.execute();
        } else {
            task.run()
        }
    }

The code in bold illustrates how PrintingTask's methods are invoked depending on the background parameter's value. Whenever the user prefers to print on a background thread, the execute method is called, which schedules the printing task for the execution on a background thread. Otherwise the run method performs the printing task on EDT.

Since printing large documents is a time-consuming task, it is recommended to perform printing on a background thread.

Text Batch Printing Example

The TextBatchPrintingDemo example illustrates printing non-visible HTML text documents on background threads. When launched, this demo displays a page with a list of URLs. You can visit an HTML page, add the displayed page to the print list, and once you select all pages that you need, you can print them all at once on background threads. The entire code for this program can be found in TextBatchPrintingDemo.java. Here is a picture of the TextBatchPrintingDemo application.


Try this: 
  1. Click the Launch button to run TextBatchPrintingDemo using Java™ Web Start (download JDK 7 or later). Alternatively, to compile and run the example yourself, consult the example index.
  2. Click on any link to view the corresponding HTML page.
  3. Press ALT+A or choose File > Add Page menu item to add the displayed page to a print list shown on the right.
  4. Press ALT+H or choose File > Home Page menu item to return to the demo's home page.
  5. Add as many pages to the print list as you need.
  6. Press ALT+C or choose File > Clear Selected menu item if you need to clear the print list and build at again.
  7. Press ALT+P or choose File > Print Selected menu item to print the selected pages.
  8. Press ALT+Q or choose File > Quit menu item to quit the application.
You can find the printing code in the printSelectedPages method. When called, this method first obtains the amount of pages selected for printing. The code example below shows how the printSelectedPages method creates a Runnable object for each page and then prints the current page on a separate thread.

for (int i = 0; i < n; i++) {
    final PageItem item = (PageItem) pages.getElementAt(i);
    // This method is called from EDT.  Printing is a time-consuming
    // task, so it should be done outside EDT, in a separate thread.
    Runnable printTask = new Runnable() {
        public void run() {
            try {
                item.print(
                        // Two "false" args mean "no print dialog" and
                        // "non-interactive" (ie, batch-mode printing).
                                null, null, false, printService, null, false);
            } catch (PrinterException pe) {
                JOptionPane.showMessageDialog(null,
                        "Error printing " + item.getPage() + "\n" + pe,
                        "Print Error", JOptionPane.WARNING_MESSAGE);
            }
        }
    };
    new Thread(printTask).start();

Text Printing API

This section lists methods defined in the JTextComponent class that allow you to print text documents.

Method Purpose
boolean print()
boolean print(MessageFormat, MessageFormat)
boolean print(MessageFormat, MessageFormat, boolean, PrintRequestAttributeSet, boolean, PrintService)
When called without arguments, displays a print dialog, and then prints this text component interactively without a header or a footer text. Returns true if the user continued printing and false if the user cancelled printing.
When called with the two MessageFormat arguments, displays a print dialog, and then prints this text component interactively with the specified header and footer text.
When called with a full set of arguments, prints this text component according to the specified arguments. The two MessageFormat arguments specify header and footer text. The first boolean argument defines whether to show a print dialog or not. Another boolean argument specifies whether to print interactively or not. With two other arguments you can specify printing attributes and a print service.
Whenever a PrintService argument is omitted, the default printer will be used.
Printable getPrintable(MessageFormat, MessageFormat) Returns a Printable object for printing your text component. Override this method to get a customized Printable object. You can wrap one Printable object into another in order to obtain complex reports and documents.

Examples That Use Text Printing

This table lists examples that use text printing and points to where those examples are described.

Example Where Described Notes
TextAreaPrintingDemo This page Demonstrates the basics of text printing and provides a rich GUI. Allows the user to specify header or footer text, turn the print dialog on or off, select printing interactively or non-interactively, and then print according to the selected options.
TextBatchPrintingDemo This page This demo displays a text component with a list of URLs, allows the user to view HTML pages, add them to the print list, and print all selected pages at once on background threads.

◉ How to Create a Splash Screen

Almost all modern applications have a splash screen. Typically splash screens are used for the following purposes:
  • Advertising a product
  • Indicating to the user that the application is launching during long startup times
  • Providing information that is only needed once per visit
Java Foundation Classes, both Swing and Abstract Windowing Toolkit (AWT), enable a developer to create splash screens in Java technology applications. However, because the main purpose of a splash screen is to provide the user with feedback about the application's startup, the delay between the application's startup and the moment when the splash screen pops up should be minimal. Before the splash screen can pop up, the application has to load and initialize the Java™ Virtual Machine (JVM), AWT, Swing, and sometimes application-dependent libraries as well. The resulting delay of several seconds has made the use of a Java™ technology-based splash screen less than desirable.

Fortunately, Java™ SE 6 provides a solution that allows the application to display the splash screen much earlier, even before the virtual machine starts. A Java application launcher is able to decode an image and display it in a simple non-decorated window.

The splash screen can display any gif, png, or jpeg image, with transparency, translucency, and animation. The figure below represents an example of the Java application splash screen developed as an animated gif file.

Using Other Swing Features

The SplashScreen class is used to close the splash screen, change the splash-screen image, obtain the image position or size, and paint in the splash screen. An application cannot create an instance of this class. Only a single instance created within this class can exist, and this instance can be obtained using the getSplashScreen() static method. If the application has not created the splash screen at startup through the command-line or manifest-file option, the getSplashScreen method returns null.

Typically, a developer wants to keep the splash-screen image on the screen and display something over the image. The splash-screen window has an overlay surface with an alpha channel, and this surface can be accessed with a traditional Graphics2D interface.

The following code snippet shows how to obtain a SplashScreen object, then how to create a graphics context with the createGraphics() method:

...
        final SplashScreen splash = SplashScreen.getSplashScreen();
        if (splash == null) {
            System.out.println("SplashScreen.getSplashScreen() returned null");
            return;
        }
        Graphics2D g = splash.createGraphics();
        if (g == null) {
            System.out.println("g is null");
            return;
        }
...

Find the demo's complete code in the SplashDemo.java file.

package misc;

/*
 * SplashDemo.java
 *
 */

import java.awt.*;
import java.awt.event.*;

public class SplashDemo extends Frame implements ActionListener {
    static void renderSplashFrame(Graphics2D g, int frame) {
        final String[] comps = {"foo", "bar", "baz"};
        g.setComposite(AlphaComposite.Clear);
        g.fillRect(120,140,200,40);
        g.setPaintMode();
        g.setColor(Color.BLACK);
        g.drawString("Loading "+comps[(frame/5)%3]+"...", 120, 150);
    }
    public SplashDemo() {
        super("SplashScreen demo");
        setSize(300, 200);
        setLayout(new BorderLayout());
        Menu m1 = new Menu("File");
        MenuItem mi1 = new MenuItem("Exit");
        m1.add(mi1);
        mi1.addActionListener(this);
        this.addWindowListener(closeWindow);

        MenuBar mb = new MenuBar();
        setMenuBar(mb);
        mb.add(m1);
        final SplashScreen splash = SplashScreen.getSplashScreen();
        if (splash == null) {
            System.out.println("SplashScreen.getSplashScreen() returned null");
            return;
        }
        Graphics2D g = splash.createGraphics();
        if (g == null) {
            System.out.println("g is null");
            return;
        }
        for(int i=0; i<100; i++) {
            renderSplashFrame(g, i);
            splash.update();
            try {
                Thread.sleep(90);
            }
            catch(InterruptedException e) {
            }
        }
        splash.close();
        setVisible(true);
        toFront();
    }
    public void actionPerformed(ActionEvent ae) {
        System.exit(0);
    }
    
    private static WindowListener closeWindow = new WindowAdapter(){
        public void windowClosing(WindowEvent e){
            e.getWindow().dispose();
        }
    };
    
    public static void main (String args[]) {
        SplashDemo test = new SplashDemo();
    }
}

Note: 

The SplashDemo application uses fixed coordinates to display overlay information. These coordinates are image-dependent and calculated individually for each splash screen.

The native splash screen can be displayed in the following ways:
  • Command-line argument
  • Java™ Archive (JAR) file with the specified manifest option

How to Use the Command-Line Argument to Display a Splash Screen


To display a splash screen from the command line use the -splash: command-line argument. This argument is a Java application launcher option that displays a splash screen:

java -splash:<file name> <class name>

Try this: 

1. Compile the SplashDemo.java file.

2. Save the splash.gif image in the images directory.

3. Run the application from the command line with the following arguments:
java -splash:images/splash.gif SplashDemo

4. Wait until the splash screen has been completely displayed.

5. The application window appears. To close the window choose File|Exit from the pop-up menu or click the X.

6. Change the splash screen name to a non-existent image, for example, nnn.gif. Run the application as follows:
java -splash:images/nnn.gif SplashDemo

7. You will see the following output string:
SplashScreen.getSplashScreen() returned null

How to Use a JAR File to Display Splash Screen


If your application is packaged in a JAR file, you can use the SplashScreen-Image option in a manifest file to show a splash screen. Place the image in the JAR file and specify the path in the option as follows:

Manifest-Version: 1.0
Main-Class: <class name>
SplashScreen-Image: <image name>

Try this: 

1. Compile the SplashDemo.java file.

2. Save the splash.gif image in the images directory.

3. Prepare the splashmanifest.mf file as follows:
Manifest-Version: 1.0
Main-Class: SplashDemo
SplashScreen-Image: images/splash.gif

4. Create a JAR file using the following command:
jar cmf splashmanifest.mf splashDemo.jar SplashDemo*.class images/splash.gif

For more information about JAR files, see Using JAR Files in the Packaging Programs in JAR Files page.

5. Run the application:
java -jar splashDemo.jar

6. Wait until the splash screen has been completly displayed.

7. The application window appears. To close the window choose File|Exit from the pop-up menu or click the X.

The Splash Screen API


The SplashScreen class cannot be used to create the splash screen. Only a single instance created within this class can exist.

Method Purpose
getSplashScreen() Returns the SplashScreen object used for Java startup splash screen control.
createGraphics() Creates a graphics context (as a Graphics2D object) for the splash screen overlay image, which allows you to draw over the splash screen.
getBounds() Returns the bounds of the splash screen window as a Rectangle.
close() Closes the splash screen and releases all associated resources.

Example That Uses the SplashScreen API


The following table lists the example that uses splash screen.

MethodWhere Described - Notes
SplashDemoThis section - Shows a splash screen before opening the application window.

◉ How to Use the System Tray

The system tray is a specialized area of the desktop where users can access currently running programs. This area may be referred to differently on various operating systems. On Microsoft Windows, the system tray is referred to as the Taskbar Status Area, while on the GNU Network Object Model Environment (GNOME) Desktop it is referred to as the Notification Area. On K Desktop Environment (KDE) this area is referred to as the System Tray. However, on each system the tray area is shared by all applications running on the desktop.

The java.awt.SystemTray class introduced in Java™ SE version 6 represents the system tray for a desktop. The system tray can be accessed by calling the static SystemTray.getSystemTray() method. Before calling this method, use the static method isSupported() to check that the system tray is supported. If the system tray is not supported on this platform, the isSupported() method returns false. If the application attempts to call the getSystemTray() method in such a case, the method will throw a java.lang.UnsupportedOperationException.

An application cannot create an instance of the SystemTray class. Only a single instance created within this class can exist, and this instance can be obtained using the getSystemTray() method.

The system tray contains one or more tray icons which are added to the tray using the add(java.awt.TrayIcon) method. They can be removed when they are no longer needed with the remove(java.awt.TrayIcon) method.

Note: The add() method can throw an AWTException if the operating system or the Java runtime determines that the icon cannot be added to the system tray. For example, an AWTException will be thrown by X-Window desktops if the system tray does not exist on the desktop.
The TrayIcon class functionality goes beyond the icon that is displayed in the tray. It also includes a text tooltip, a pop-up menu, ballon messages, and a set of listeners associated with it. A TrayIcon object generates various mouse events and supports the addition of corresponding listeners to receive notification of these events. The TrayIcon class processes some of the events itself. For example, by default, when a right-click is performed on the tray icon, it displays the specified pop-up menu. When a double-click is performed, the TrayIcon object generates an ActionEvent to launch an application. When the mouse pointer hovers over the tray icon, the tooltip is displayed. The icon image is automatically resized to fit the space allocated for the image on the tray.


The following demo, developed using the AWT package, demonstrates the features of SystemTray and TrayIcon classes.

Using Other Swing Features

Unfortunately, the current implementation of the TrayIcon class provides limited support of the Swing pop-up menu (the JPopupMenu class) and does not enable an application to use all of the capabilities of the javax.swing package. The workaround proposal for this issue is described in the Bug Database.

Try this: 

1. Place the bulb.gif image file in the image directory. Compile and run the example, consult the example index.

2. The tray icon will appear in the system tray.

Using Other Swing Features

3. Double-click the tray icon to launch the corresponding application. The dialog box will be displayed.

4. Hover the mouse pointer over the tray icon and click the right mouse button. The pop-up menu appears.

5. Select the Set auto size checkbox menu item. Notice that the icon appearance is changed as follows.

6. Select the Set tooltip checkbox menu item. Hover the mouse pointer over the tray icon. The tooltip appears.

7. Choose the About menu item. The dialog box appears. Close the dialog box.

8. Choose any of the Display submenu items. Each of these items displays a message dialog box of a particular type: error, warning, info, or standard.

9. Use the Exit menu item to quit the application.

The following code snippet shows how to add a tray icon to the system tray and apply a pop-up menu:
...
        //Check the SystemTray is supported
        if (!SystemTray.isSupported()) {
            System.out.println("SystemTray is not supported");
            return;
        }
        final PopupMenu popup = new PopupMenu();
        final TrayIcon trayIcon =
                new TrayIcon(createImage("images/bulb.gif", "tray icon"));
        final SystemTray tray = SystemTray.getSystemTray();
       
        // Create a pop-up menu components
        MenuItem aboutItem = new MenuItem("About");
        CheckboxMenuItem cb1 = new CheckboxMenuItem("Set auto size");
        CheckboxMenuItem cb2 = new CheckboxMenuItem("Set tooltip");
        Menu displayMenu = new Menu("Display");
        MenuItem errorItem = new MenuItem("Error");
        MenuItem warningItem = new MenuItem("Warning");
        MenuItem infoItem = new MenuItem("Info");
        MenuItem noneItem = new MenuItem("None");
        MenuItem exitItem = new MenuItem("Exit");
       
        //Add components to pop-up menu
        popup.add(aboutItem);
        popup.addSeparator();
        popup.add(cb1);
        popup.add(cb2);
        popup.addSeparator();
        popup.add(displayMenu);
        displayMenu.add(errorItem);
        displayMenu.add(warningItem);
        displayMenu.add(infoItem);
        displayMenu.add(noneItem);
        popup.add(exitItem);
       
        trayIcon.setPopupMenu(popup);
       
        try {
            tray.add(trayIcon);
        } catch (AWTException e) {
            System.out.println("TrayIcon could not be added.");
        }
...

The complete code for this demo is available in the TrayIconDemo.java file. This demo also uses the bulb.gif image file.

Removing the current limitations on applying Swing components will enable developers to add such components as JMenuItem (with image), JRadioButtonMenuItem, and JCheckBoxMenuItem.

The SystemTray API


Only a single instance created within SystemTray class can exist.

Method Purpose
add Adds a tray icon to the system tray. The tray icon becomes visible in the system tray once it is added. The order in which icons are displayed in a tray is not specified — it is platform- and implementation-dependent.
getSystemTray Gets the SystemTray instance that represents the desktop's tray area. This method always returns the same instance per application. On some platforms the system tray may not be supported. Use the isSupported() method to check if the system tray is supported.
isSupported Returns information as to whether the system tray is supported on the current platform. In addition to displaying the tray icon, minimal system tray support includes either a pop-up menu (see the TrayIcon.setPopupMenu(PopupMenu) method) or an action event (see the TrayIcon.addActionListener(ActionListener)).

The TrayIcon API


A TrayIcon object represents a tray icon that can be added to the system tray. A TrayIcon object can have a tooltip (text), an image, a pop-up menu, and a set of listeners associated with it.

Method Purpose
setImageAutoSize Sets the auto-size property. Auto-size determines whether the tray image is automatically sized to fit the space allocated for the image on the tray. By default, the auto-size property is set to false.
setPopupMenu Sets the pop-up menu for this TrayIcon object. If pop-up is null, no pop-up menu will be associated with this TrayIcon object.
setToolTip Sets the tooltip string for this TrayIcon object. The tooltip is displayed automatically when the mouse hovers over the icon. Setting the tooltip to null removes any tooltip text. When displayed, the tooltip string may be truncated on some platforms; the number of characters that may be displayed is platform-dependent.

Examples That Use the SystemTray API


The following table lists the example that uses tray icons added to the system tray.

ExampleWhere Describe - Notes
TrayIconDemoThis section - Creates the tray icon in the system tray, adds a pop-up menu to the tray icon.

◉ Solving Common Problems Using Other Swing Features

Problem: My application is not showing the look and feel I have requested via UIManager.setLookAndFeel.

You probably either set the look and feel to an invalid look and feel or set it after the UI manager loaded the default look and feel. If you are sure that the look and feel you specified is valid and setting the look and feel is the first thing your program does (at the top of its main method, for example), check whether you have a static field that references a Swing class. This reference can cause the default look and feel to be loaded if none has been specified.

Problem: Why is not my component getting the focus?
  • Is it a custom component (for example, a direct subclass of JComponent) that you created? If so, you may need to give your component an input map and mouse listener.
  • Is the component inside of a JWindow object? The focus system requires a JWindow's owning frame to be visible for any components in the JWindow object to get the focus. By default, if you do not specify an owning frame for a JWindow object, an invisible owning frame is created for it. The solution is to either specify a visible and focusable owning frame when creating the JWindow object or to use JDialog or JFrame objects instead.
Problem: Why cannot my dialog receive the event generated when the user hits the Escape key?

If your dialog contains a text field, it may be consuming the event.
  • If you want to get the Escape event regardless of whether a component consumes it, you should use a KeyEventDispatcher.
  • If you want to get the Escape event only if a component has not consumed it, then register a key binding on any JComponent component in the JDialog object, using the WHEN_IN_FOCUSED_WINDOW input map. 
Problem: Why I cannot apply Swing components to a tray icon? Current implementation of the TrayIcon class supports the PopupMenu component, but not its Swing counterpart JPopupMenu. This limitation narrows capabilities to employ additional Swing features, for example, menu icons.
  • A new JTrayIcon class will be created to eliminate this inconvenience. Until then, use AWT components to add a menu item, checkbox menu item, or submenu.
If you do not find your problem in this section, consult Solving Common Component Problems.

«« Previous
Next »»