This content was uploaded by our users and we assume good faith they have the permission to share this book. If you own the copyright to this book and it is wrongfully on our website, we offer a simple DMCA procedure to remove your content from our site. Start by pressing the button below!
■JOHN ZUKOWSKI has been involved with the Java platform since it was just called Java, pushing ten years now. He currently writes a monthly column for Sun’s Core Java Technologies Tech Tips (http://java.sun.com/developer/ JDCTechTips/) and IBM’s developerWorks (http://www-136.ibm.com/ developerworks/java/). He has contributed content to numerous other sites, including jGuru (http://www.jguru.com), DevX (http://www.devx.com/, Intel (http://www.intel.com/), and JavaWorld (http://www.javaworld.com/). He is the author of many other popular titles on Java, including Java AWT Reference (O’Reilly and Associates), Mastering Java 2 (Sybex), Borland’s JBuilder: No Experience Required (Sybex), Learn Java with JBuilder 6 (Apress), Java Collections (Apress), and Definitive Guide to Swing for Java 2 (Apress).
xix
About the Technical Reviewers T
his book was technically reviewed by Daren Klamer, David Vittor, Hido Hasimbegovic, Charlie Castaneda, and Robert Castaneda, who are all part of the CustomWare Asia Pacific team working on numerous Java and integration-based projects in Australia and the Asia Pacific region. Their web site is http://www.customware.net.
xxi
Acknowledgments T
his book has been a long time coming, with various starts and stops, and getting sidetracked a few times along the way. Now that it is all done, I need to thank those who helped. For starters, I want to thank everyone at Apress who hung in there and had patience when dealing with me throughout the project, especially project manager Beth Christmas, who I’m sure I drove nuts at times, and editor Steve Anglin, who kept nudging me along. On the production side, I’d like to thank Marilyn Smith for all the input and updates, Ellie Fountain for her hard work at ensuring little changes got done right, and, of course, my technical reviewer Rob Castaneda and the team at CustomWare for all the input on my rough work. Congrats on that marriage thing. Some of the images used in the sample programs were made by Deb Felts, who ran a web site called the Image Addict’s Attic. The site doesn’t seem to be online any more, but the images are used with permission and she does retain copyright on them. Sun also maintains the Java Look and Feel Graphics Repository at http://java.sun.com/developer/techDocs/hi/repository/, with its own set of images to be used for Java applications. For all the readers out there, thanks for asking me to do the update. Without your continued support, you wouldn’t be holding this book in your hands. For their continued encouragement along the way, I’d like to personally thank the following: Joe Sam Shirah, thanks for doing that long drive to visit while I was in Florida for the conference; my Aunt Mary Hamfeldt, congrats on your first grandchild; our Realtor Nancy Moore, thanks for putting up with us for so long; Miguel Muniz, thanks for all the bug reports at SavaJe; Matthew B. Doar, thanks for JDiff (http://www.jdiff.org/), a great little Java doclet for reporting API differences. Happy tenth birthday, Duke and Java. I am forever grateful to my wife, Lisa, for her support, and our dog , Jaeger, for his playfulness. Thanks to Dad, too. Good luck at the casinos.
xxiii
Introduction W
elcome to Learn Java 5.0 Swing in a Nutshell for Dummies in 21 Days. Since the beginning of Java time (1995), the component libraries have been actively evolving. What began as a small set of nine AWT components, plus menus and containers, has grown to a more complete and complex set of around 50 Swing components—all just to create graphical user interfaces (GUIs) for your Java client-side programs. That’s where this book comes in. Its purpose is to make your life easier in creating those GUIs. Earlier editions of this book took the approach that if the class wasn’t found in the javax.swing package, it wasn’t covered in the book. This third edition takes a more complete view of creating GUIs. For instance, instead of just describing the Swing layout managers, there is also material on the AWT layout managers, since you’re likely to be using them. The first edition of this book was written for a mix of the Java 1.1 and 1.2 developer. The second edition hit the 1.3 platform. This edition is wholly for the 5.0 developer. Almost all the programs will not work on a 1.4 platform, though with a little tweaking, they can be made to do so. In this book, you’ll find a tutorial-like approach to learning about the Swing libraries and related capabilities. It is not an API reference book, nor is it a primer that describes how to install the Java Development Kit (JDK), compile your programs, or run them. If you need help in those areas, consider using an integrated development environment (IDE)—such as IntelliJ IDEA, Eclipse, or Borland’s JBuilder—or get one of Apress’s other books, such as Beginning Java Objects, by Jacquie Barker. Is this book for you? If you are new to the Java platform, you might want to start with a more introductory text first, before jumping on the Swing bandwagon. On the other hand, if you’ve been working with Java for a while and have decided it’s time to start using the Swing component set, you’ll find this book extremely useful. With this book, you won’t have to drudge through the countless Swing classes for a way to accomplish that impossible task. You’ll become much more productive more quickly, and you’ll be able to make the most of the many reusable components and techniques available with Swing.
Book Structure This book can be read from cover to cover, but it doesn’t have to be done that way. It’s true that later sections of the book assume you’ve absorbed knowledge from the earlier sections. However, if you want to find something on a topic covered in a later chapter, you don’t need to read all the chapters that precede it first. If you come across something that’s unfamiliar to you, you can always go back to the earlier chapter or search the index to locate the information you need.
xxv
xxvi
■I N T R O D U C T I O N
The contents of this book are grouped into three logical sections: Chapters 1 through 4 provide general knowledge that will prove to be useful as you read through the remainder of the book. In Chapter 1, you’ll find an overview of the Swing component set. Chapter 2 details event handling with the Swing component set. It describes the delegation-based event model and focus management policies used by Swing. In Chapter 3, you’ll learn about the Model-View-Controller (MVC) architecture. You can avoid using MVC if you wish, but to take full advantage of everything that Swing has to offer, it helps to have a good grasp of MVC concepts. In Chapter 4, you’ll find the beginning coverage of the specific Swing components. All Swing components share many of the same attributes, and in Chapter 4, you’ll learn the foundation for those common behaviors. In Chapters 5 through 15, you’ll discover the many aspects of the reusable Swing components. You’ll find out about menus, toolbars, borders, high-level containers, pop-up dialogs, layout managers, advanced Swing containers, bounded range components, toggle components, list model components, spinners, and text components. Most of what you’ll want to accomplish with the Swing libraries is discussed in these chapters. In Chapters 16 through 22, some of the more advanced Swing topics are covered. These tend to be the areas that even the experienced developers find the most confusing. Chapter 16 goes beyond the basics of text component handling found in Chapter 15. Chapters 17 and 18 deal with the Swing tree and table components. These components allow you to display hierarchical or tabular data. In Chapter 19, you’ll learn about drag-and-drop support in Swing. Chapter 20 explores how to customize the appearance of your application. Because the Swing libraries are completely Java-based, if you don’t like the way something is done or how it appears, you can change it. In Chapter 21, you’ll learn about the undo framework, which offers undo and redo support for your applications. Finally, in Chapter 22, you finish off with a look into the accessibility framework offered by Swing, such as support for screen readers and magnifying glasses to help those needing assistive technologies. The Appendix contains a list of about 1,000 settable properties the user interface manager employs to configure the appearance of the Swing components for the current look and feel. The Swing components manage various defaults, such as colors and fonts applied to components, so you don’t need to subclass a component in order to customize its appearance. Appendix A gathers all of the property settings listed throughout the chapters into one comprehensive list for easy reference.
Support You can head to many places online to get technical support for Swing and answers to general Java questions. Here’s a list of some of the more useful places around: • The Java Ranch at http://www.javaranch.com/ offers forums for just about everything in the Big Moose Saloon. • Java Forums at http://forums.java.sun.com/ are Sun’s online forums for Java development issues.
■I N T R O D U C T I O N
• developerWorks at http://www.ibm.com/developerworks/java/ is the IBM’s developer community for Java with forums and tutorials. • jGuru at http://www.jguru.com offers a series of FAQs and forums for finding answers. • Marcus Green’s Java Certification Exam Discussion Forum at http://www.jchq.net/ discus/ provides support for those going the certification route. While I would love to be able to answer all reader questions, I get swamped with e-mail and real-life responsibilities. Please consider using these resources to get help.
About Java Java is one of 13,000 islands that makes up Indonesia, whose capital is Jakarta. It is home to about 120 million people with an area about 50,000 square miles (132,000 square kilometers). While on the island, you can hear traditional music such as gamelan or angklung and enjoy Java’s main export, a coffee that is considered spicy and full-bodied, with a strong, slightly acidic flavor. The island also has a dangerous volcano named Merapi, which makes up part of the Pacific “Ring of Fire.” In 1891, on the island, Eugene Dubois discovered fossils from Pithecanthropus erectus, better known as Java man (homo javanensis). For more information, see http://encyclopedia.lockergnome.com/s/b/Java_(island).
xxvii
CHAPTER 1 ■■■
Swing Overview A
ccording to Encyclopedia Britannica, Swing was a popular music in the United States, circa 1930–1945. Okay, maybe not in the Java sense. Instead, on May 23, 1995, John Gage, then director of the Science Office for Sun, introduced Java to the world. With its birth came something called the Abstract Window Toolkit, or AWT. In turn, with AWT came native widgets, and with this early native widget set came . . . trouble. The original component set that came with the Java platform, AWT, was dependent on too many idiosyncrasies of the underlying platform. Instead of providing a mature-looking component set, Java offered the lowest common denominator version. If a feature wasn’t available on all Java platforms, it wasn’t available on any Java platform. And then you had to deal with all the browser/platform differences. Each Java runtime environment relied on how the component set was connected with the underlying platform-specific native widget set. If there were issues with the connection, first, they were specific to the platform (and/or browser) and second, you had to code around these problems so your programs could be write-once, run anywhere (WORA), the Java mantra of the time. As Java technologies became more popular, users realized AWT was extremely slow and unreliable, and you couldn’t really do much with the provided components. Very few of them were available, and you couldn’t use them in a visual programming environment. So, new technologies were introduced, such as just-in-time (JIT) compilers to improve performance and, with Borland’s help, JavaBeans for a component-based development. With these new technologies came more and more widget sets, for the AWT component set itself was very basic. So, applet download times grew and grew, because these new widget sets weren’t part of the core Java platform, and Java archive (JAR) files were introduced to improve delivery time. Eventually, each of the major browser vendors added its favorite component library to its virtual machine—AFC, IFC, and WFC, to name just a few. Yet all the libraries used different design models, and there were no true cross-browser standards. Eventually, Sun Microsystems teamed up with Netscape Communication and other partners to create yet another library called the Java Foundation Classes, or JFC. Part of JFC is something called the Swing component set. This Swing component set is what this book is all about.
1
2
CHAPTER 1 ■ SWING OVERVIEW
■Note Later technologies were introduced to help people use the Swing components within a browser and with web-based application delivery. These include the Java Plug-in (http://java.sun.com/products/ plugin/) and Java Web Start (http://java.sun.com/products/javawebstart/). Alternatives to Swing, like the SWT component set with Eclipse (http://www.eclipse.org/swt/), have also been created. These are not discussed here.
This chapter will familiarize you with the various Swing pieces. For starters, there is the component set. Without these, there is no Swing. Next, you’ll peek at the world of event handling and layout management common to both AWT and Swing components. After that, you’ll take a quick look at the undo/redo framework available within the Swing architecture. Then you’ll explore the SwingSet2 demonstration provided with the Java 2 Platform Standard Edition 5.0 Development Kit (JDK 5.0) so that you can see some of the capabilities. Lastly, I’ll point out where in the book all these capabilities are discussed in detail.
Getting to Know the Swing Components The book will serve as a guide to development using the Swing component set. Over the course of its pages, you’ll look at every package in the javax.swing package hierarchy, as shown in Figure 1-1.
Figure 1-1. The Swing package hierarchy
■Note The javax.swing.plaf package contains several subpackages and related packages, some of which are located outside the javax.swing package hierarchy. Plaf stands for pluggable look and feel— a Swing concept that will be described more fully in Chapter 20.
CHAPTER 1 ■ SWING OVERVIEW
The Swing component set is one big group of components. While the JDK 5.0 release didn’t add any new Swing components to the mix, logically, you can think of them as those with duplicate components within AWT and those without.
AWT Component Replacements The Swing component set was originally created because the basic AWT components that came with the original version of the Java libraries were insufficient for real-world, forms-based applications. All the basic components were there, but the existing set was too small and far too restrictive. For instance, you couldn’t even put an image on a button. To alleviate this situation, the Swing component set offers replacements for each of the AWT components. The Swing components support all the capabilities of the original set and offer a whole lot more besides. As such, you should never need to deal with any of the basic AWT components.
■Note Although the Swing components replace the AWT components, you’ll still need to understand several basic AWT concepts, such as layout managers, event handling, and drawing support. In addition, you’ll need to grasp the concept that all of Swing is built on top of the core AWT libraries.
The basic distinction between the Swing and equivalent AWT components is, in most cases, the Swing component class names begin with a J and the AWT ones don’t. Swing’s JButton is a replacement for the AWT Button component. One exception is the JComboBox, which replaces the AWT Choice component. At the application programming interface (API) level, the Swing components are almost always a superset of the features the AWT components support. While they support additional capabilities, the basic AWT capabilities are there for everything but the JList component, whose API is completely unlike that of the AWT List component. Table 1-1 maps the original AWT components to their replacement Swing components.
Table 1-1. AWT to Swing Component Mapping
AWT Component
Nearest Swing Replacement
Button
JButton
Canvas
JPanel
Checkbox
JCheckBox
Checkbox in CheckboxGroup
JRadioButton in ButtonGroup
Choice
JComboBox
Component
JComponent
Container
JPanel
Label
JLabel
List
JList
3
4
CHAPTER 1 ■ SWING OVERVIEW
Table 1-1. AWT to Swing Component Mapping (Continued)
AWT Component
Nearest Swing Replacement
Menu
JMenu
MenuBar
JMenuBar
MenuItem
JMenuItem
Panel
JPanel
PopupMenu
JPopupMenu
Scrollbar
JScrollBar
ScrollPane
JScrollPane
TextArea
JTextArea
TextField
JTextField
■Note For most people, the fact that the Swing components replace AWT components is irrelevant. Just treat the Swing components as an independent component set, and you’ll be perfectly okay.
To help you understand how to use the Swing components, you’ll examine each of the components in this book. For instance, Chapter 4 looks at how the JButton component works, with just a single line of text as its label, like an AWT Button, but adds capabilities, such as using image icons on buttons and working with multiple lines of text. To find out where each component is discussed in this book, see the “Swing Component to Chapter Mapping” section later in this chapter. In addition to replacing each of the basic components, the Swing component set has a replacement for the higher-level window objects. Although the only change in most of the components’ names is the beginning J, you’ll discover in Chapter 8 how the high-level container objects are much different in the Swing world. Swing’s replacement for the old FileDialog object differs even more and is discussed in Chapter 9. Table 1-2 maps the high-level window objects from the AWT component world to the Swing universe.
Table 1-2. AWT to Swing Window Mapping
AWT Window
Nearest Swing Replacement
Applet
JApplet
Dialog
JDialog
FileDialog
JFileChooser
Frame
JFrame
Window
JWindow
CHAPTER 1 ■ SWING OVERVIEW
Whereas the AWT components rely on the user’s operating system to provide the actual component to a Java program, Swing components are all controlled from within the Java runtime. The AWT approach is called either the heavyweight or the peered approach; most Swing components are lightweight or peerless. You’ll explore the basics of this approach in Chapter 4 with the JComponent. Additional features for customizing the look and feel of components are discussed in Chapter 20.
Non-AWT Upgraded Components In addition to offering replacements for all the basic AWT components, the Swing component set includes twice as many new components.
■Note If you’re new to Java, just think of all of these components—both the AWT component replacements and those that were not in the AWT—as one big set of components, versus two distinct sets.
Here’s a look at those components that didn’t originate in the AWT world: • JPasswordField: This specialized text field is for password entry, as shown in Figure 1-2. You cannot use cut or copy operations within the component, but you can paste text into it.
Figure 1-2. The Swing JPasswordField • JEditorPane and JTextPane: These two components provide support for displaying and editing multiple-attributed content, such as an HTML and RTF viewer. Figure 1-3 shows a JEditorPane component.
Figure 1-3. The Swing JEditorPane
5
6
CHAPTER 1 ■ SWING OVERVIEW
• JSpinner: This component, shown in Figure 1-4, provides selection from an ordered set of predefined values, offering arrows to scroll through the next and previous choices. The predefined values can be an array of strings, a sequential set of numbers, or a date.
Figure 1-4. The Swing JSpinner • JToggleButton: This component offers a button that stays depressed when selected. In the example shown in Figure 1-5, the North, East, and South buttons are depressed.
Figure 1-5. The Swing JToggleButton • JSlider: This component is like the Scrollbar component of AWT (or JScrollBar in the Swing component set). However, its purpose in Swing is for user input. It offers various clues to help the user choose a value. Figure 1-6 shows an example of a JSlider component.
Figure 1-6. The Swing JSlider • JProgressBar: This component allows the user to visually see the progress of an activity. Some options available include showing static text or percentage done, as shown in Figure 1-7.
Figure 1-7. The Swing JProgressBar
CHAPTER 1 ■ SWING OVERVIEW
• JFormattedTextField: This component provides for the input of formatted text, like numeric values, phone numbers, dates, or social security numbers. Figure 1-8 shows two examples of this component.
Figure 1-8. The Swing JFormattedTextField • JTable: This component provides for the display of two-dimensional row and column information, such as stock quotes, as in the example shown in Figure 1-9.
Figure 1-9. The Swing JTable • JTree: This component supports the display of hierarchical data. Figure 1-10 shows an example of a JTree component.
Figure 1-10. The Swing JTree • JToolTip: Through this component, all Swing components support pop-up text for offering useful tips. Figure 1-11 shows an example of a JToolTip component added to a JSlider.
7
8
CHAPTER 1 ■ SWING OVERVIEW
Figure 1-11. The Swing JToolTip • JToolBar: This container offers a draggable toolbar to be included within any program window, as shown in Figure 1-12.
Figure 1-12. The Swing JToolBar • JRadioButtonMenuItem: This component is an addition to the set of menu components. With it, you can have radio buttons on a menu for mutually exclusive choices, as shown in the example in Figure 1-13. There’s also a JCheckBoxMenuItem component, for when you don’t need mutually exclusive choices.
Figure 1-13. The Swing JRadioButtonMenuItem • JSeparator: The menu’s separator bar is now its own component and can be used outside of menus, too, as shown in Figure 1-14.
Figure 1-14. The Swing JSeparator • JDesktopPane and JInternalFrame: This pair of components allows you to develop applications using the familiar Windows Multiple Document Interface (MDI). Figure 1-15 shows an example.
CHAPTER 1 ■ SWING OVERVIEW
Figure 1-15. The Swing JDesktopPane and JInternalFrame • JOptionPane: This component allows you to easily create pop-up windows with varied content, as shown in Figure 1-16.
Figure 1-16. The Swing JOptionPane • JColorChooser: This component is for choosing a color, with different views available to select the color, as shown in Figure 1-17.
Figure 1-17. The Swing JColorChooser
9
10
CHAPTER 1 ■ SWING OVERVIEW
• JSplitPane: This container allows you to place multiple components in a window. It also allows the user control over how much of each component is visible. Figure 1-18 shows an example of a JSplitPane.
Figure 1-18. The Swing JSplitPane • JTabbedPane: This component is like a container whose layout manager is CardLayout (discussed in Chapter 10), with labeled tabs automatically provided to allow the user to swap cards. This provides you with the familiar property-sheet motif, as shown in Figure 1-19.
Figure 1-19. The Swing JTabbedPane You’ll learn about all of these components throughout this book. Refer to the “Swing Component to Chapter Mapping” section later in this chapter to see where each component is covered.
Event Handling and Layout Management To use the Swing components successfully, you must understand the underlying parts of the original AWT component set. For instance, the Swing components all support the delegationbased event model, which was introduced with JDK 1.1 and is supported by the AWT 1.1 component set. In addition, layout managers control screen layout.
CHAPTER 1 ■ SWING OVERVIEW
■Note The Swing components don’t support the original JDK 1.0 event model. They no longer use the public boolean handleEvent(Event) method and all its helper methods. If you need to convert an AWT program that uses the JDK 1.0 event model to one that uses the Swing components, you’ll need to convert the program to use the delegation-based event model, in addition to changing the component set.
Although directly porting old Java AWT programs (or programmers!) to Swing programs is done most easily by continuing to use the delegation-based event model, this solution is rarely the best one. Besides supporting the delegation-based event model, the Swing components provide other, more efficient ways of dealing with events for components. In Chapter 2, you’ll explore the delegation-based event model and look at the other ways of managing event handling. In addition to the delegation-based event-handling support, the Swing components use the Model-View-Controller (MVC) design to separate their user interfaces from their underlying data models. Using the MVC architecture provides yet another way of event handling with a Swing component. While MVC might be new to most developers, the basic constructs use the delegation-based event model. MVC provides the optimal way of working with the Swing components. You’ll find an overview of the MVC architecture in Chapter 3. Besides all the support for extended event handling with the Swing classes, these classes share the need to use a layout manager for positioning components on the screen. In addition to using the layout managers that come with AWT, you can use other layout managers that come with the Swing classes. In Chapter 10, you’ll learn about both the AWT and Swing layout managers.
Undo Framework Situated within the javax.swing class hierarchy are the javax.swing.undo classes. These classes offer a framework for supporting undo and redo capabilities within Java programs. Instead of creating the basic framework yourself, the framework is provided as part of the Swing classes. Although the undo classes don’t use anything directly outside their package, the Swing text components use the undo functionality. Chapter 21 provides a detailed explanation of undo.
SwingSet Demonstration As part of the demo/jfc directory with the Java 2 platform, you have available a Swing demonstration program called SwingSet2. This program provides a quick preview of the majority of the Swing capabilities. All the source code is included, so if you see something you like and are interested in learning how it was done, just dig through the code to find the appropriate lines. With the Java 2 platform, you start up this demonstration from the SwingSet2 directory with the java -jar SwingSet2.jar command. After starting the SwingSet2 demonstration, you see the opening screen, as shown in Figure 1-20.
11
12
CHAPTER 1 ■ SWING OVERVIEW
Figure 1-20. SwingSet2 startup screen Choose the different buttons and tabs to see many of the features supported by the Swing components.
Swing Component to Chapter Mapping The Swing packages contain many classes and components. To help you find where all the different components are discussed, Table 1-3 provides a handy reference (with the components listed alphabetically).
CHAPTER 1 ■ SWING OVERVIEW
Table 1-3. Mapping of Swing Components to Chapters in This Book
Swing Component
Chapter
Box
11
JApplet
8
JButton
4
JCheckBox
5
JCheckBoxMenuItem
6
JColorChooser
9
JComboBox
13
JComponent
4
JDesktopPane
8
JDialog
8
JEditorPane
15
JFileChooser
9
JFormattedTextField
15
JFrame
8
JInternalFrame
8
JLabel
4
JLayeredPane
8
JList
13
JMenu
6
JMenuBar
6
JMenuItem
6
JOptionPane
9
JPanel
4
JPasswordField
15
JPopupMenu
6
JProgressBar
12
JRadioButton
5
JRadioButtonMenuItem
6
13
14
CHAPTER 1 ■ SWING OVERVIEW
Table 1-3. Mapping of Swing Components to Chapters in This Book (Continued)
Swing Component
Chapter
JRootPane
8
JScrollBar
12
JScrollPane
11
JSeparator
6
JSlider
12
JSpinner
14
JSplitPane
11
JTabbedPane
11
JTable
18
JTextArea
15
JTextField
15
JTextPane
15
JToggleButton
5
JToolBar
6
JToolTip
4
JTree
17
JViewport
11
JWindow
8
In addition to information about using the different components, the following chapters feature a table for each component that lists the JavaBeans properties defined by that component. Each table notes whether a property has a setter (setPropertyName(newValue)), a getter (getPropertyName()), or an isPropertyName() method defined by the class, and whether a property is bound (you can listen for a PropertyChangeEvent). In these property tables, inherited properties aren’t listed, so even though a property for a component is listed as write-only, the parent class might still provide a getter method. As an example, Table 1-4 shows the property table for the JScrollBar component.
Table 1-4. JScrollBar Properties
Property Name
Data Type
Access
accessibleContext
AccessibleContext
Read-only
adjustmentListeners
AdjustmentListener[ ]
Read-only
blockIncrement
int
Read-write bound
enabled
boolean
Write-only
CHAPTER 1 ■ SWING OVERVIEW
Table 1-4. JScrollBar Properties (Continued)
Property Name
Data Type
Access
maximum
int
Read-write
maximumSize
Dimension
Read-only
minimum
int
Read-write
minimumSize
Dimension
Read-only
model
BoundedRangeModel
Read-write bound
orientation
int
Read-write bound
UI
ScrollBarUI
Read-write bound
UIClassID
String
Read-only
unitIncrement
int
Read-write bound
value
int
Read-write bound
valueIsAdjusting
boolean
Read-write bound
visibleAmount
int
Read-write
Besides the property tables, you’ll find information about important aspects of each component and the techniques for using them.
■Note This book is not intended to be an API reference, nor does it cover everything about each component. For the lesser-used aspects of a component, see the online javadoc documentation.
Summary This chapter provided a brief overview of what will be covered in this book, such as the many essential parts of the Swing component set you need to understand in order to use Swing components. The combined set of javax.swing packages is larger than the entire first JDK, if not the first two. In Chapter 2, you’ll explore how to deal with the many aspects of event handling using the Swing components. In addition to reviewing the delegation-based event model, you’ll look at different ways you can deal with events when using Swing components and get a grasp of the focus traversal policies involved with Swing.
15
CHAPTER 2 ■■■
Event Handling with the Swing Component Set C
hapter 1 provided a brief overview of the Swing component set. In this chapter, you will start to look at the details of one aspect of using Swing components: event handling. When working with the Swing component set, the delegation-based event-handling mechanism is available, but you can also take advantage of several additional ways to respond to user-initiated actions (as well as to programmatic events). In this chapter, you’ll explore all these event-handling response mechanisms. You’ll also learn how Swing manages input focus and some techniques for controlling how focus is handled. As you explore event-handling capabilities, you will start to look at some actual Swing components. In this chapter, you will be using the components in the simplest manner possible. Feel free to first read up on the components covered in later chapters of this book, and then come back to this chapter for a general discussion of event handling. The later chapters of this book also contain specific details on event handling for each component.
Delegation-Based Event Handling Sun Microsystems introduced the delegation-based event-handling mechanism into the Java libraries with the release of JDK 1.1 and JavaBeans. Although the Java 1.0 libraries included the Observer–Observable pair of objects that followed the Observer behavioral design pattern, this wasn’t an adequate long-term solution for user-interface programming. (The Java 1.0 containment event-handling mechanism was even worse.)
Event Delegation Model The delegation-based event-handling mechanism is a specialized form of the Observer design pattern. The Observer pattern is used when an Observer wants to know when a watched object’s state changes and what that state change is. In the case of the delegation-based eventhandling mechanism, instead of the Observer listening for a state change, the Observer listens for events to happen.
17
18
CHAPTER 2 ■ EVENT HANDLING WITH THE SWING COMPONENT SET
Figure 2-1 shows the structure of the modified Observer pattern as it relates to the specific classes within the Java libraries for event handling. The generic Subject participant in the pattern manages a list (or lists) of generic Observer objects for each event that the subject can generate. The Observer objects in the list must provide a specific interface through which the Subject participant can notify them. When an event that the Observer objects are interested in happens within the Subject participant, all the registered Observer objects are notified. In the Java world, the specific interface for the Observer objects to implement must extend the java.util.EventListener interface. The specific event the Subject participant must create needs to extend the java.util.EventObject class.
Figure 2-1. The modified Observer pattern To make this a little clearer, let’s take a second look at the delegation-based event-handling mechanism without all the design pattern terms. GUI components (and JavaBeans) manage lists of listeners with a pair of methods for each listener type: addXXXListener() and removeXXXListener(). When an event happens within the subject component, the component notifies all registered listeners of the event. Any observer class interested in such an event needs to register with the component an implementer of the appropriate interface. Then each implementation is notified when the event happens. Figure 2-2 illustrates this sequence.
■Note Some users like to call the event delegation model a publish-subscribe model, in which components publish a set of available listeners for subscription, and others can subscribe to them.
CHAPTER 2 ■ EVENT HANDLING WITH THE SWING COMPONENT SET
Figure 2-2. Event delegation sequence diagram
Event Listeners As Observers Using event listeners to handle an event is a three-step process: 1. Define a class that implements the appropriate listener interface (this includes providing implementations for all the methods of the interface). 2. Create an instance of this listener. 3. Register this listener to the component whose events you’re interested in. Let’s take a look at the three specific steps for creating a simple button that responds to selection by printing a message.
Defining the Listener To set up event handling for a selectable button, you need to create an ActionListener, because the JButton generates ActionEvent objects when selected. class AnActionListener implements ActionListener { public void actionPerformed(ActionEvent actionEvent) { System.out.println("I was selected."); } }
19
20
CHAPTER 2 ■ EVENT HANDLING WITH THE SWING COMPONENT SET
■Note Part of the problem of creating responsive user interfaces is figuring out which event listener to associate with a component to get the appropriate response for the event you’re interested in. For the most part, this process becomes more natural with practice. Until then, you can examine the different component APIs for a pair of add/remove listener methods, or reference the appropriate component material in this book.
Creating an Instance of the Listener Next, you simply create an instance of the listener you just defined. ActionListener actionListener = new AnActionListener(); If you use anonymous inner classes for event listeners, you can combine steps 1 and 2: ActionListener actionListener = new ActionListener() { public void actionPerformed(ActionEvent actionEvent) { System.out.println("I was selected."); } };
Registering the Listener with a Component Once you’ve created the listener, you can associate it with the appropriate component. Assuming the JButton has already been created with a reference stored in the variable button, this would merely entail calling the button’s addActionListener() method: button.addActionListener(actionListener); If the class that you’re currently defining is the class that implements the event listener interface, you don’t need to create a separate instance of the listener. You just need to associate your class as the listener for the component. The following source demonstrates this: public class YourClass implements ActionListener { ... // Other code for your class public void actionPerformed(ActionEvent actionEvent) { System.out.println("I was selected."); } // Code within some method JButton button = new JButton(...); button.addActionListener(this); // More code within some method } Using event handlers such as creating a listener and associating it to a component is the basic way to respond to events with the Swing components. The specifics of which listener works with which component are covered in later chapters, when each component is described. In the following sections, you’ll learn about some additional ways to respond to events.
CHAPTER 2 ■ EVENT HANDLING WITH THE SWING COMPONENT SET
■Tip Personally, I don’t like the approach of just associating a class as the event listener, because it doesn’t scale well when the situation gets more complicated. For instance, as soon as you add another button onto the screen and want the same event listener to handle its selection, the actionPerformed() method must figure out which button triggered the event before it can respond. Although creating a separate event listener for each component adds another class to the set of deliverables, creating separate listeners is more maintainable than sharing a listener across multiple components. In addition, most integrated development environment (IDE) tools, such as Borland’s JBuilder, can automatically create the listener objects as separate classes.
Multithreaded Swing Event Handling To increase their efficiency and decrease the complexity, all Swing components were designed to not be thread-safe. Although this might sound scary, it simply means that all access to Swing components needs to be done from a single thread—the event-dispatch thread. If you are unsure that you’re in a particular thread, you can ask the EventQueue class with its public static boolean isDispatchThread() method or the SwingUtilities class with its public static boolean isEventDispatchThread() method. The latter just acts as a proxy to the former.
■Note Earlier versions of this book showed one particular way of creating Swing programs. They were wrong. It was thought that accessing invisible (unrealized) components from outside the event-dispatch thread was okay. However, that’s not true. Doing something with a Swing component can trigger a reaction within the component, and that other action would be done on the event-dispatch thread, violating the singlethreaded access.
With the help of the EventQueue class, you create Runnable objects to execute on the eventdispatch thread to properly access components. If you need to execute a task on the event-dispatch thread, but you don’t need any results and don’t care exactly when the task finishes, you can use the public static void invokeLater(Runnable runnable) method of EventQueue. If, on the other hand, you can’t continue with what you’re doing until the task completes and returns a value, you can use the public static void invokeAndWait(Runnable runnable) method of EventQueue. The code to get the value is left up to you and is not the return value to the invokeAndWait() method.
■Caution The invokeAndWait(Runnable) method can throw an InterruptedException or an InvocationTargetException.
21
22
CHAPTER 2 ■ EVENT HANDLING WITH THE SWING COMPONENT SET
To demonstrate the proper way to create a Swing-based program, Listing 2-1 shows the source for a selectable button. Listing 2-1. Swing Application Framework import javax.swing.*; import java.awt.*; import java.awt.event.*; public class ButtonSample { public static void main(String args[]) { Runnable runner = new Runnable() { public void run() { JFrame frame = new JFrame("Button Sample"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); JButton button = new JButton("Select Me"); // Define ActionListener ActionListener actionListener = new ActionListener() { public void actionPerformed(ActionEvent actionEvent) { System.out.println("I was selected."); } }; // Attach listeners button.addActionListener(actionListener); frame.add(button, BorderLayout.SOUTH); frame.setSize(300, 100); frame.setVisible(true); } }; EventQueue.invokeLater(runner); } } This code produces the button shown in Figure 2-3.
Figure 2-3. Button sample First, let’s look at the invokeLater() method. It requires a Runnable object as its argument. You just create a Runnable object and pass it along to the invokeLater() method. Some time after the current event dispatching is done, this Runnable object will execute.
CHAPTER 2 ■ EVENT HANDLING WITH THE SWING COMPONENT SET
Runnable runnable = new Runnable() { public void run() { // Do work to be done } } EventQueue.invokeLater(runnable); If you want your Swing GUI creation to be thread-safe, you should follow this pattern with all of your Swing code. If you need to access the command-line arguments, just add the final keyword to the argument declaration: public static void main(final String args[]). This may seem like overkill for a simple example like this, but it does ensure the thread safety of your program, making sure that all Swing component access is done from the event-dispatch thread. (However, calls to repaint(), revalidate(), and invalidate() don’t need to be done from the event-dispatch thread.)
■Note In addition to the invokeLater() and invokeAndWait() methods of the EventQueue class, there are wrapper methods of the same name in the SwingUtilities class. Since the SwingUtilities calls just redirect the calls on to the EventQueue class, you should avoid the extra layer of indirection and access EventQueue directly. These wrapper methods were created for an early Swing version, prior to the existence of the EventQueue class.
One additional line from Listing 2-1 requires some extra explanation: frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); By default, if you click the little X in the title bar of the window shown in Figure 2-3, the application does not close; instead, the frame is made invisible. Setting the default close operation to JFrame.EXIT_ON_CLOSE, as in Listing 2-1, causes the application to exit if the user clicks the X. You’ll learn more about this behavior in Chapter 8, which explores the JFrame class.
Using SwingUtilities for Mouse Button Identification The Swing component set includes a utility class called SwingUtilities that provides a collection of generic helper methods. You will look at this class periodically throughout this book when a particular set of methods for this class seems useful. For the button example in Listing 2-1, the methods of interest are related to determining which mouse button has been selected. The MouseInputListener interface consists of seven methods: mouseClicked(MouseEvent), mouseEntered(MouseEvent), mouseExited(MouseEvent), mousePressed(MouseEvent), and mouseReleased(MouseEvent) from MouseListener; and mouseDragged(MouseEvent) and mouseMoved(MouseEvent) from MouseMotionListener. If you need to determine which buttons on the mouse were selected (or released) when the event happened, check the modifiers property of MouseEvent and compare it to various mask-setting constants of the InputEvent class. For instance, to check if a middle mouse button is pressed for a mouse press event, you could use the following code in your mouse listener’s mousePressed() method:
23
24
CHAPTER 2 ■ EVENT HANDLING WITH THE SWING COMPONENT SET
public void mousePressed(MouseEvent mouseEvent) { int modifiers = mouseEvent.getModifiers(); if ((modifiers & InputEvent.BUTTON2_MASK) == InputEvent.BUTTON2_MASK) { System.out.println("Middle button pressed."); } } Although this works fine and dandy, the SwingUtilities class has three methods to make this process much simpler: SwingUtilities.isLeftMouseButton(MouseEvent mouseEvent) SwingUtilities.isMiddleMouseButton(MouseEvent mouseEvent) SwingUtilities.isRightMouseButton(MouseEvent mouseEvent) Now, instead of needing to manually get the modifiers and compare them against the mask, you can simply ask the SwingUtilities, as follows: if (SwingUtilities.isMiddleMouseButton(mouseEvent)) { System.out.println("Middle button released."); } This makes your code much more readable and easier to maintain. Listing 2-2 contains an updated ButtonSample that adds another listener to detect which mouse button was pressed. Listing 2-2. Button Sample with Mouse Button Detection import javax.swing.*; import java.awt.*; import java.awt.event.*; public class ButtonSample { public static void main(String args[]) { Runnable runner = new Runnable() { public void run() { JFrame frame = new JFrame("Button Sample"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); JButton button = new JButton("Select Me"); // Define ActionListener ActionListener actionListener = new ActionListener() { public void actionPerformed(ActionEvent actionEvent) { System.out.println("I was selected."); } };
CHAPTER 2 ■ EVENT HANDLING WITH THE SWING COMPONENT SET
// Define MouseListener MouseListener mouseListener = new MouseAdapter() { public void mousePressed(MouseEvent mouseEvent) { int modifiers = mouseEvent.getModifiers(); if ((modifiers & InputEvent.BUTTON1_MASK) == InputEvent.BUTTON1_MASK) { System.out.println("Left button pressed."); } if ((modifiers & InputEvent.BUTTON2_MASK) == InputEvent.BUTTON2_MASK) { System.out.println("Middle button pressed."); } if ((modifiers & InputEvent.BUTTON3_MASK) == InputEvent.BUTTON3_MASK) { System.out.println("Right button pressed."); } } public void mouseReleased(MouseEvent mouseEvent) { if (SwingUtilities.isLeftMouseButton(mouseEvent)) { System.out.println("Left button released."); } if (SwingUtilities.isMiddleMouseButton(mouseEvent)) { System.out.println("Middle button released."); } if (SwingUtilities.isRightMouseButton(mouseEvent)) { System.out.println("Right button released."); } System.out.println(); } }; // Attach listeners button.addActionListener(actionListener); button.addMouseListener(mouseListener); frame.add(button, BorderLayout.SOUTH); frame.setSize(300, 100); frame.setVisible(true); } }; EventQueue.invokeLater(runner); } }
25
26
CHAPTER 2 ■ EVENT HANDLING WITH THE SWING COMPONENT SET
Using Property Change Listeners As Observers Besides the basic event-delegation mechanism, the JavaBeans framework introduced yet another incarnation of the Observer design pattern, this time through the property change listener. The PropertyChangeListener implementation is a truer representation of the Observer pattern. Each Observer watches for changes to an attribute of the Subject. The Observer is then notified of the new state when changed in the Subject. Figure 2-4 shows the structure of this Observer pattern as it relates to the specific classes within the JavaBeans libraries for property change handling. In this particular case, the observable Subject has a set of add/remove property change listener methods and a property (or properties) whose state is being watched.
Figure 2-4. The property change listener Observer pattern With a PropertyChangeListener, the registered set of listeners is managed within the PropertyChangeSupport class. When the watched property value changes, this support class notifies any registered listeners of the new and old property state values.
■Note Although PropertyChangeListener observers are registered at the class level, not all properties of the class might be bound. A property is bound when a change to the property causes the registered listeners to be notified. In addition, although the JavaBeans framework introduced the concept of property change listeners in JDK 1.1, none of the properties of the AWT components were bound, although this changed for the Component class in the 1.2 release. The Swing components have many of their properties bound. To find out which ones are bound, see the property tables for each Swing component that appear in later chapters of this book.
CHAPTER 2 ■ EVENT HANDLING WITH THE SWING COMPONENT SET
By registering PropertyChangeListener objects with the various components that support this type of listener, you can reduce the amount of source code you must generate after the initial listening setup. For instance, the background color of a Swing component is bound, meaning someone can register a PropertyChangeListener to a component to be notified when the background setting changes. When the value of the background property for that component changes, anyone listening is notified, allowing an Observer to change its background color to the new setting. Therefore, if you want all the components of your program to have the same background color, you can register them all with one component. Then, when that single component changes its background color, all the other components will be notified of the change and will modify their backgrounds to the new setting.
■Note Although you can use a PropertyChangeListener to “share” a common property setting among components, you can also map the property of a subject to a different property of the Observer.
The program in Listing 2-3 demonstrates using a PropertyChangeListener. It creates two buttons. When either button is selected, the background of the selected button is changed to some random color. The second button is listening for property changes within the first button. When the background color changes for the first button, the background color of the second button is changed to that new value. The first button isn’t listening for property changes for the second button. Therefore, when the second button is selected, changing its background color, this change doesn’t propagate back to the first button. Listing 2-3. Property Change Listener Sample import import import import import
public class BoundSample { public static void main(String args[]) { Runnable runner = new Runnable() { public void run() { JFrame frame = new JFrame("Button Sample"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); final JButton button1 = new JButton("Select Me"); final JButton button2 = new JButton("No Select Me"); final Random random = new Random();
27
28
CHAPTER 2 ■ EVENT HANDLING WITH THE SWING COMPONENT SET
// Define ActionListener ActionListener actionListener = new ActionListener() { public void actionPerformed(ActionEvent actionEvent) { JButton button = (JButton)actionEvent.getSource(); int red = random.nextInt(255); int green = random.nextInt(255); int blue = random.nextInt(255); button.setBackground(new Color(red, green, blue)); } }; // Define PropertyChangeListener PropertyChangeListener propertyChangeListener = new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent propertyChangeEvent) { String property = propertyChangeEvent.getPropertyName(); if ("background".equals(property)) { button2.setBackground((Color)propertyChangeEvent.getNewValue()); } } }; // Attach listeners button1.addActionListener(actionListener); button1.addPropertyChangeListener(propertyChangeListener); button2.addActionListener(actionListener); frame.add(button1, BorderLayout.NORTH); frame.add(button2, BorderLayout.SOUTH); frame.setSize(300, 100); frame.setVisible(true); } }; EventQueue.invokeLater(runner); } } Although this example causes only a color change from button selection, imagine if the background color of the first button could be changed from a couple of hundred different places other than the one action listener! Without a property change listener, each of those places would be required to also change the background color of the second button. With the property change listener, it’s only necessary to modify the background color of the primary object—the first button, in this case. The change would then automatically propagate to the other components. The Swing library also uses the ChangeEvent/ChangeListener pair to signify state changes. Although similar to the PropertyChangeEvent/PropertyChangeListener pair, the ChangeEvent doesn’t carry with it the new and old data value settings. You can think of it as a lighter-weight version of a property change listener. The ChangeEvent is useful when more than one property value changes, because ChangeEvent doesn’t need to package the changes.
CHAPTER 2 ■ EVENT HANDLING WITH THE SWING COMPONENT SET
■Tip The Swing components use the SwingPropertyChangeSupport class, instead of the PropertyChangeSupport class, to manage and notify their PropertyChangeListener list. The Swing version, SwingPropertyChangeSupport, isn’t thread-safe, but it is faster and takes up less memory.
Assuming it is accessed from only the event-dispatch thread, the lack of thread safety is irrelevant.
Managing Listener Lists If you’re creating your own components and want those components to fire off events, you need to maintain a list of listeners to be notified. If the listener list is for AWT events (found in java.awt.event), you can use the AWTEventMulticaster class for help with list management. Prior to the Swing libraries, if the event wasn’t a predefined AWT event type, you had to manage this list of listeners yourself. With the help of the EventListenerList class in the javax.swing.event package, you no longer need to manually manage the listener list and worry about thread safety. And, if you ever need to get the list of listeners, you can ask a Component with public EventListener[ ] getListeners(Class listenerType), or one of the type-specific methods like the getActionListeners() method of JButton. This allows you to remove listeners from an internally managed list, which helps with garbage collection.
AWTEventMulticaster Class Whether you realize it or not, the AWTEventMulticaster class is used by each and every AWT component to manage event listener lists. The class implements all the AWT event listeners (ActionListener, AdjustmentListener, ComponentListener, ContainerListener, FocusListener, HierarchyBoundsListener, HierarchyListener, InputMethodListener, ItemListener, KeyListener, MouseListener, MouseMotionListener, MouseWheelListener, TextListener, WindowFocusListener, WindowListener, and WindowStateListener). Whenever you call a component’s method to add or remove a listener, the AWTEventMulticaster is used for support. If you want to create your own component and manage a list of listeners for one of these AWT event/listener pairs, you can use the AWTEventMulticaster. As an example, let’s look at how to create a generic component that generates an ActionEvent object whenever a key is pressed within the component. The component uses the public static String getKeyText (int keyCode) method of KeyEvent to convert the key code to its appropriate text string and passes this string back as the action command for the ActionEvent. Because the component is meant to serve as the source for ActionListener observers, it needs a pair of add/remove methods to handle the registration of listeners. This is where the AWTEventMulticaster comes in, because it will manage the adding and removing of listeners from your listener list variable: private ActionListener actionListenerList = null; public void addActionListener(ActionListener actionListener) { actionListenerList = AWTEventMulticaster.add( actionListenerList, actionListener); } public void removeActionListener(ActionListener actionListener) { actionListenerList = AWTEventMulticaster.remove( actionListenerList, actionListener); }
29
30
CHAPTER 2 ■ EVENT HANDLING WITH THE SWING COMPONENT SET
The remainder of the class definition describes how to handle the internal events. An internal KeyListener needs to be registered in order to send keystrokes to an ActionListener. In addition, the component must be able to get the input focus; otherwise, all keystrokes will go to other components. The complete class definition is shown in Listing 2-4. The line of source code for notification of the listener list is in boldface. That one line notifies all the registered listeners. Listing 2-4. Managing Listener Lists with AWTEventMulticaster import java.awt.*; import java.awt.event.*; import javax.swing.*; public class KeyTextComponent extends JComponent { private ActionListener actionListenerList = null; public KeyTextComponent() { setBackground(Color.CYAN); KeyListener internalKeyListener = new KeyAdapter() { public void keyPressed(KeyEvent keyEvent) { if (actionListenerList != null) { int keyCode = keyEvent.getKeyCode(); String keyText = KeyEvent.getKeyText(keyCode); ActionEvent actionEvent = new ActionEvent( this, ActionEvent.ACTION_PERFORMED, keyText); actionListenerList.actionPerformed(actionEvent); } } }; MouseListener internalMouseListener = new MouseAdapter() { public void mousePressed(MouseEvent mouseEvent) { requestFocusInWindow(); } }; addKeyListener(internalKeyListener); addMouseListener(internalMouseListener); } public void addActionListener(ActionListener actionListener) { actionListenerList = AWTEventMulticaster.add( actionListenerList, actionListener); }
CHAPTER 2 ■ EVENT HANDLING WITH THE SWING COMPONENT SET
public void removeActionListener(ActionListener actionListener) { actionListenerList = AWTEventMulticaster.remove( actionListenerList, actionListener); } public boolean isFocusable() { return true; } } Figure 2-5 shows the component in use. The top portion of the figure is the component, and the bottom is a text field. An ActionListener is registered with the KeyTextComponent that updates the text field in order to display the text string for the key pressed.
Figure 2-5. Demonstrating the KeyTextComponent The source code for the example shown in Figure 2-5 follows in Listing 2-5. Listing 2-5. Sample Program with an AWTEventMulticaster Component import java.awt.*; import java.awt.event.*; import javax.swing.*; public class KeyTextTester { public static void main(String args[]) { Runnable runner = new Runnable() { public void run() { JFrame frame = new JFrame("Key Text Sample"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); KeyTextComponent keyTextComponent = new KeyTextComponent(); final JTextField textField = new JTextField(); ActionListener actionListener = new ActionListener() { public void actionPerformed(ActionEvent actionEvent) { String keyText = actionEvent.getActionCommand(); textField.setText(keyText); } };
31
32
CHAPTER 2 ■ EVENT HANDLING WITH THE SWING COMPONENT SET
EventListenerList Class Although the AWTEventMulticaster class is easy to use, it doesn’t work for managing lists of custom event listeners or any of the Swing event listeners found in javax.swing.event. You could create a custom extension of the class for each type of event listener list you need to manage (not practical), or you could just store the list in a data structure such as a Vector or LinkedList. Although using a Vector or LinkedList works satisfactorily, when you use this method, you need to worry about synchronization issues. If you don’t program the list management properly, the listener notification may happen with the wrong set of listeners. To help simplify this situation, the Swing component library includes a special event-listener support class, EventListenerList. One instance of the class can manage all the different types of event listeners for a component. To demonstrate the class usage, let’s see how the previous example can be rewritten to use EventListenerList instead of AWTEventMulticaster. Note that in this particular example, using the AWTEventMulticaster class is actually the simpler solution. However, imagine a similar situation in which the event listener isn’t one of the predefined AWT event listeners or if you need to maintain multiple listener lists. The adding and removing of listeners is similar to the technique used with the AWTEventMulticaster in the previous example. You need to create a variable of the appropriate type—this time EventListenerList—as well as define add and remove listener methods. One key difference between the two approaches is that the initial EventListenerList is non-null, whereas the other starts off being null. A reference to an empty EventListenerList must be created to start. This removes the need for several checks for a null list variable later. The adding and removing of listeners is also slightly different. Because an EventListenerList can manage a list of listeners of any type, when you add or remove the listener, you must provide the class type for the listener being acted on. EventListenerList actionListenerList = new EventListenerList(); public void addActionListener(ActionListener actionListener) { actionListenerList.add(ActionListener.class, actionListener); } public void removeActionListener(ActionListener actionListener) { actionListenerList.remove(ActionListener.class, actionListener); }
CHAPTER 2 ■ EVENT HANDLING WITH THE SWING COMPONENT SET
This leaves only the notification of the listeners to be handled. No generic method exists in the class to notify all the listeners of a particular type that an event has happened, so you must create the code yourself. A call to the following code (fireActionPerformed(actionEvent)) will replace the one line of boldfaced source code: (actionListenerList.actionPerformed(actionEvent) from the previous example. The code gets a copy of all the listeners of a particular type from the list as an array (in a thread-safe manner). You then need to loop through the list and notify the appropriate listeners. protected void fireActionPerformed(ActionEvent actionEvent) { EventListener listenerList[] = actionListenerList.getListeners(ActionListener.class); for (int i=0, n=listenerList.length; i
java.awt.*; java.awt.event.*; javax.swing.*; javax.swing.event.*; java.util.EventListener;
public class KeyTextComponent2 extends JComponent { private EventListenerList actionListenerList = new EventListenerList(); public KeyTextComponent2() { setBackground(Color.CYAN); KeyListener internalKeyListener = new KeyAdapter() { public void keyPressed(KeyEvent keyEvent) { if (actionListenerList != null) { int keyCode = keyEvent.getKeyCode(); String keyText = KeyEvent.getKeyText(keyCode); ActionEvent actionEvent = new ActionEvent( this, ActionEvent.ACTION_PERFORMED, keyText); fireActionPerformed(actionEvent); } } };
33
34
CHAPTER 2 ■ EVENT HANDLING WITH THE SWING COMPONENT SET
MouseListener internalMouseListener = new MouseAdapter() { public void mousePressed(MouseEvent mouseEvent) { requestFocusInWindow(); } }; addKeyListener(internalKeyListener); addMouseListener(internalMouseListener); } public void addActionListener(ActionListener actionListener) { actionListenerList.add(ActionListener.class, actionListener); } public void removeActionListener(ActionListener actionListener) { actionListenerList.remove(ActionListener.class, actionListener); } protected void fireActionPerformed(ActionEvent actionEvent) { EventListener listenerList[] = actionListenerList.getListeners(ActionListener.class); for (int i=0, n=listenerList.length; i
Timer Class In addition to the invokeAndWait() and invokeLater() methods of EventQueue, you can use the Timer class to create actions to be executed on the event-dispatch thread. A Timer provides a way of notifying an ActionListener after a predefined number of milliseconds. The timer can repeatedly notify the listeners, or just call them once.
Creating Timer Objects Following is the single constructor for creating a Timer that specifies the millisecond delay time between calls to the ActionListener: public Timer(int delay, ActionListener actionListener); // 1 second interval Timer timer = new Timer(1000, anActionListener);
CHAPTER 2 ■ EVENT HANDLING WITH THE SWING COMPONENT SET
Using Timer Objects After a Timer object has been created, you need to start() it. Once the Timer is started, the ActionListener will be notified after the given number of milliseconds. If the system is busy, the delay could be longer, but it won’t be shorter. If there comes a time when you want to stop a Timer, call its stop() method. The Timer also has a restart() method, which calls stop() and start(), restarting the delay period. To demonstrate, Listing 2-7 defines an ActionListener that simply prints a message. You then create a Timer to call this listener every half second. After creating the timer, you need to start it. Listing 2-7. Swing Timer Sample import javax.swing.*; import java.awt.*; import java.awt.event.*; public class TimerSample { public static void main(String args[]) { Runnable runner = new Runnable() { public void run() { ActionListener actionListener = new ActionListener() { public void actionPerformed(ActionEvent actionEvent) { System.out.println("Hello World Timer"); } }; Timer timer = new Timer(500, actionListener); timer.start(); } }; EventQueue.invokeLater(runner); } }
■Note A Timer doesn’t start up the AWT event-dispatch thread on its own.
Timer Properties Table 2-1 lists the six properties of Timer. Four allow you to customize the behavior of the timer. running tells you if a timer has been started but not stopped, and actionListeners gets you the list of action listeners.
35
36
CHAPTER 2 ■ EVENT HANDLING WITH THE SWING COMPONENT SET
Table 2-1. Timer Properties
Property Name
Data Type
Access
actionListeners
ActionListener[ ]
Read-only
coalesce
boolean
Read-write
delay
int
Read-write
initialDelay
int
Read-write
repeats
boolean
Read-write
running
boolean
Read-only
The delay property is the same as the constructor argument. If you change the delay of a running timer, the new delay won’t be used until the existing delay runs out. The initialDelay property allows you to have another startup delay besides the periodic delay after the first execution. For instance, if you don’t want to initially do a task for an hour, but then want to do it every 15 minutes thereafter, you need to change the initialDelay setting before you start the timer. By default, the initialDelay and delay properties are set to the same setting in the constructor. The repeats property is true by default, which results in a repeating timer. When false, the timer notifies action listeners only once. You then need to restart() the timer to trigger the listener again. Nonrepeating timers are good for onetime notifications that need to happen after a triggering event. The coalesce property allows for a busy system to throw away notifications that haven’t happened yet when a new event needs to be fired to the registered ActionListener objects. By default, the coalesce value is true. This means if a timer runs every 500 milliseconds, but its system is bogged down and doesn’t respond for a whole 2 seconds, the timer needs to send only one message, rather than also sending the missing ones. If the setting were false, four messages would still need to be sent. In addition to the properties just listed, you can turn on log messages with the following line of code: Timer.setLogTimers(true); Log messages are good for actions that lack a visual element, allowing you to see when something happens.
■Tip The java.util.Timer class works in a fashion similar to the javax.swing.Timer class, except that it doesn’t run the scheduled task in the event-dispatch thread. In addition, it supports executing tasks at a fixed rate, versus after a fixed delay. The latter scheme permits the repeat rate to drift between executions if the event-dispatch thread is busy.
CHAPTER 2 ■ EVENT HANDLING WITH THE SWING COMPONENT SET
Swing-Specific Event Handling Keeping in mind that the Swing components are built on top of the AWT libraries, the Swing component library has several improved capabilities to make event handling much easier. The capabilities improve on several of AWT’s core event-handling features, from basic action listening to focus management. To simplify event handling, the Swing library extends the original ActionListener interface with the Action interface to store visual attributes with the event handler. This allows the creation of event handlers independent of visual components. Then, when the Action is later associated with a component, the component automatically gets information (such as a button label) directly from the event handler. This includes notification of updates for the label when the Action is modified. The AbstractAction and TextAction classes are implementations of this concept. The Swing library also adds a KeyStroke class that allows you to more easily respond to key events. Instead of watching all key events for a specific key, you can tell a component that when a specific keystroke sequence is pressed, it must respond with a particular action. These keystroke-to-action mappings are stored in a combination of InputMap and ActionMap objects. The InputMap is specifically a ComponentInputMap when the component’s window has the focus. The Swing text components can use these more readily to store the mapping of keystrokes to actions with the help of the Keymap interface. The mappings for the TextAction support are described in more detail in Chapter 16, along with the remainder of the text event-handling capabilities. The KeyboardFocusManager and DefaultKeyboardFocusManager, along with the help of the FocusTraversalPolicy and its implementations, manage the focus subsystem. The InputVerifier helps, too, for validation of user input. These are discussed in the “Swing Focus Management” section later in this chapter.
Action Interface The Action interface is an extension to the ActionListener interface that’s very flexible for defining shared event handlers independent of the components that act as the triggering agents. The interface implements ActionListener and defines a lookup table data structure whose keys act as bound properties. Then, when an Action is associated with a component, these display properties are automatically carried over to it. The following is the interface definition: public interface Action implements ActionListener { // Constants public final static String ACCELERATOR_KEY; public final static String ACTION_COMMAND_KEY; public final static String DEFAULT; public final static String LONG_DESCRIPTION; public final static String MNEMONIC_KEY; public final static String NAME; public final static String SHORT_DESCRIPTION; public final static String SMALL_ICON;
37
38
CHAPTER 2 ■ EVENT HANDLING WITH THE SWING COMPONENT SET
// Listeners public void addPropertyChangeListener(PropertyChangeListener listener); public void removePropertyChangeListener(PropertyChangeListener listener); // Properties public boolean isEnabled(); public void setEnabled(boolean newValue); // Other methods public Object getValue(String key); public void putValue(String key, Object value); } Because Action is merely an interface, the Swing libraries offer a class to implement the interface. That class is AbstractAction.
AbstractAction Class The AbstractAction class provides a default implementation of the Action interface. This is where the bound property behavior is implemented.
Using Actions Once you define an AbstractAction by subclassing and providing a public void actionPerformed (ActionEvent actionEvent) method, you can then pass it along to some special Swing components. JButton, JCheckBox, JRadioButton, JToggleButton, JMenuItem, JCheckBoxMenuItem, and JRadioButtonMenuItem provide constructors for creating the components from actions, whereas the Swing text components have their own built-in support for Action objects through their Keymap, InputMap, and ActionMap. When the component with the associated Action is added to the respective Swing container, selection triggers the calling of the actionPerformed(ActionEvent actionEvent) method of the Action. The display of the component is defined by the property elements added to the internal data structure. To demonstrate, Listing 2-8 presents an Action with a “Print” label and an image icon. When this is activated, a “Hello, World” message is printed. Listing 2-8. Action Usage Example import java.awt.event.*; import javax.swing.*; public class PrintHelloAction extends AbstractAction { private static final Icon printIcon = new ImageIcon("Print.gif"); PrintHelloAction() { super("Print", printIcon); putValue(Action.SHORT_DESCRIPTION, "Hello, World"); } public void actionPerformed(ActionEvent actionEvent) { System.out.println("Hello, World"); } }
CHAPTER 2 ■ EVENT HANDLING WITH THE SWING COMPONENT SET
Once the Action has been defined, you can create the Action and associate it with as many other components as you want. Action printAction = new PrintHelloAction(); menu.add(new JMenuItem(printAction)); toolbar.add(new JButton(printAction)); After the Action has been associated with the various objects, if you find that you need to modify the properties of the Action, you need to change the setting in only one place. Because the properties are all bound, they propagate to any component that uses the Action. For instance, disabling the Action (printAction.setEnabled(false)) will disable the JMenuItem and JButton created on the JMenu and JToolBar, respectively. In contrast, changing the name of the Action with printAction.putValue(Action.NAME, "Hello, World") changes the text label of the associated components. Figure 2-6 shows what the PrintHelloAction might look like on a JToolBar and a JMenu. Selectable buttons are provided to enable or disable the Action, as well as to change its name.
Figure 2-6. The PrintHelloAction in use The complete source code for this example follows in Listing 2-9. Don’t worry just yet about the specifics of creating toolbars and menu bars. They’ll be discussed in more detail in Chapter 6. Listing 2-9. PrintHelloAction Example import java.awt.*; import java.awt.event.*; import javax.swing.*; public class ActionTester { public static void main(String args[]) { Runnable runner = new Runnable() { public void run() { JFrame frame = new JFrame("Action Sample"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); final Action printAction = new PrintHelloAction(); JMenuBar menuBar = new JMenuBar();
39
40
CHAPTER 2 ■ EVENT HANDLING WITH THE SWING COMPONENT SET
JMenu menu = new JMenu("File"); menuBar.add(menu); menu.add(new JMenuItem(printAction)); JToolBar toolbar = new JToolBar(); toolbar.add(new JButton(printAction)); JButton enableButton = new JButton("Enable"); ActionListener enableActionListener = new ActionListener() { public void actionPerformed(ActionEvent actionEvent) { printAction.setEnabled(true); } }; enableButton.addActionListener(enableActionListener); JButton disableButton = new JButton("Disable"); ActionListener disableActionListener = new ActionListener() { public void actionPerformed(ActionEvent actionEvent) { printAction.setEnabled(false); } }; disableButton.addActionListener(disableActionListener); JButton relabelButton = new JButton("Relabel"); ActionListener relabelActionListener = new ActionListener() { public void actionPerformed(ActionEvent actionEvent) { printAction.putValue(Action.NAME, "Hello, World"); } }; relabelButton.addActionListener(relabelActionListener); JPanel buttonPanel = new JPanel(); buttonPanel.add(enableButton); buttonPanel.add(disableButton); buttonPanel.add(relabelButton); frame.setJMenuBar(menuBar); frame.add(toolbar, BorderLayout.SOUTH); frame.add(buttonPanel, BorderLayout.NORTH); frame.setSize(300, 200); frame.setVisible(true); } }; EventQueue.invokeLater(runner); } }
CHAPTER 2 ■ EVENT HANDLING WITH THE SWING COMPONENT SET
AbstractAction Properties As Table 2-2 shows, the AbstractAction class has three available properties.
Table 2-2. AbstractAction Properties
Property Name
Data Type
Access
enabled
boolean
Read-write bound
keys
Object [ ]
Read-only
propertyChangeListeners
PropertyChangeListener[ ]
Read-only
The remainder of the bound properties are placed in the lookup table with putValue (String key, Object value). Getting the current keys property setting allows you to find out which ones can be set en masse, instead of asking for each one individually. Table 2-3 describes the predefined set of Action constants that can be used as the key. You can also add your own constants, to look up later when the action happens.
Table 2-3. AbstractAction Lookup Property Keys
Constant
Description
NAME
Action name, used as button label
SMALL_ICON
Icon for the Action, used as button label
SHORT_DESCRIPTION
Short description of the Action; could be used as tooltip text, but not by default
LONG_DESCRIPTION
Long description of the Action; could be used for accessibility (see Chapter 22)
ACCELERATOR
KeyStroke string; can be used as the accelerator for the Action
ACTION_COMMAND_KEY
InputMap key; maps to the Action in the ActionMap of the associated JComponent
MNEMONIC_KEY
Key code; can be used as mnemonic for action
DEFAULT
Unused constant that could be used for your own property
Once a property has been placed in the lookup table, you can get it with public Object getValue(String key). It works similarly to the java.util.Hashtable class or java.util.Map interface, with one distinction: if you try to put a key/value pair into the table with a null value, the table removes the key, if it’s present.
KeyStroke Class The KeyStroke class and the inputMap and actionMap properties of a specific JComponent provide a simple replacement for registering KeyListener objects to components and watching for
41
42
CHAPTER 2 ■ EVENT HANDLING WITH THE SWING COMPONENT SET
specific keys to be pressed. The KeyStroke class allows you to define a single combination of keystrokes, such as Shift-Ctrl-P or F4. You can then activate the keystroke by registering it with a component and telling the keystroke what to do when the component recognizes it, causing the ActionListener to be notified. Before finding out how to create keystrokes, let’s look at the different conditions that can be activated and thus added to different input maps. Three conditions can activate a registered keystroke, and there are four constants in JComponent to help. The fourth is for an undefined state. The four available constants are listed in Table 2-4.
Table 2-4. Keystroke Registration Conditions
Constant
Description
WHEN_FOCUSED
Activates the keystroke when the actual component has the input focus
WHEN_IN_FOCUSED_WINDOW
Activates the keystroke when the window that the component is in has the input focus
WHEN_ANCESTOR_OF_FOCUSED_COMPONENT
Activates the keystroke when pressed in the component or a container of the component
UNDEFINED_CONDITION
For when no condition is defined
■Note In the special instance in which the keystrokes are supposed to be active only when the component is in the focused window, the InputMap is actually a ComponentInputMap.
Creating a KeyStroke The KeyStroke class is a subclass of AWTKeyStroke and has no public constructor. You create a keystroke by using one of the following methods: public static KeyStroke public static KeyStroke public static KeyStroke public static KeyStroke boolean onKeyRelease) public static KeyStroke
getKeyStroke(char keyChar) getKeyStroke(String representation) getKeyStroke(int keyCode, int modifiers) getKeyStroke(int keyCode, int modifiers, getKeyStrokeForEvent(KeyEvent keyEvent)
The first version in this list, public static KeyStroke getKeyStroke(char keyChar), allows you to create a keystroke from a char variable, such as Z. KeyStroke space = KeyStroke.getKeyStroke('Z');
CHAPTER 2 ■ EVENT HANDLING WITH THE SWING COMPONENT SET
■Note I prefer to avoid using a char variable to create a keystroke, because you don’t know whether to specify an uppercase or lowercase letter. There is also an outdated, or deprecated, version of this method that adds a boolean onKeyRelease argument. This, too, should be avoided.
The public static KeyStroke getKeyStroke(String representation) version is the most interesting of the lot. It allows you to specify a keystroke as a text string, such as "control F4". The set of modifiers to the string are shift, control, meta, alt, button1, button2, and button3, and multiple modifiers can be specified. The remainder of the string comes from one of the many VK_* constants of the KeyEvent class. For example, the following defines a keystroke for Ctrl-Alt-7: KeyStroke controlAlt7 = KeyStroke.getKeyStroke("control alt 7"); The public static KeyStroke getKeyStroke(int keyCode, int modifiers) and public static KeyStroke getKeyStroke(int keyCode, int modifiers, boolean onKeyRelease) methods are the most straightforward. They allow you to directly specify the VK_* key constant and the InputEvent masks for the modifiers (or zero for no modifiers). When not specified, onKeyRelease is false. KeyStroke enter = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0, true); KeyStroke shiftF4 = KeyStroke.getKeyStroke(KeyEvent.VK_F4, InputEvent.SHIFT_MASK); The last version listed, public static KeyStroke getKeyStrokeForEvent(KeyEvent keyEvent), maps a specific KeyEvent directly to a KeyStroke. This is useful when you want to allow a user to supply the keystroke to activate an event. You ask the user to press a key for the event, and then register the KeyEvent so that the next time it happens, the event is activated. KeyStroke fromKeyEvent = KeyStroke.getKeyStrokeForEvent(keyEvent);
Registering a KeyStroke After you’ve created the keystroke, you need to register it with a component. When you register a keystroke with a component, you provide an Action to call when pressed (or released). Registration involves providing a mapping from keystroke to Action. First, you get the appropriate InputMap for the component based on the focus activation condition (from Table 2-4) with getInputMap(condition). If no condition is provided, WHEN_FOCUSED is assumed. You then add a mapping from keystroke to text string in the InputMap: component.getInputMap().put(keystroke, string) If you know the action string for an existing action, you can use that; otherwise, you define the string. You then work with the ActionMap to map that string to an Action: component.getActionMap.put(string, action)
43
44
CHAPTER 2 ■ EVENT HANDLING WITH THE SWING COMPONENT SET
You can share actions between components by sharing ActionMap instances. The example in Listing 2-10 creates four buttons, each with a different keystroke registered to it and possibly a different focus-activation condition, as listed in Table 2-4. The button label signifies the keystroke-activation conditions. The Action simply prints out a message and the activating button label. Listing 2-10. KeyStroke Listening import javax.swing.*; import java.awt.*; import java.awt.event.*; public class KeyStrokeSample { private static final String ACTION_KEY = "theAction"; public static void main(String args[]) { Runnable runner = new Runnable() { public void run() { JFrame frame = new JFrame("KeyStroke Sample"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); JButton buttonA = new JButton("
FOCUSED control alt 7"); JButton buttonB = new JButton("
FOCUS/RELEASE VK_ENTER"); JButton buttonC = new JButton("
ANCESTOR VK_F4+SHIFT_MASK"); JButton buttonD = new JButton("
■Tip For text components, you can get the Keymap and bind an Action to a KeyStroke in one step with addActionForKeyStroke(KeyStroke, Action).
Figure 2-7 shows what the running program looks like.
Figure 2-7. KeyStroke listening example
45
46
CHAPTER 2 ■ EVENT HANDLING WITH THE SWING COMPONENT SET
Using Mnemonics and Accelerators The Swing libraries also use KeyStroke objects for several internal functions. Two such functions are component mnemonics and accelerators, which work as follows: • In a component mnemonic, one character in a label appears underlined. When that character is pressed along with a platform-specific hotkey combination, the component is activated. For instance, pressing Alt-A in the window shown in Figure 2-8 would select the About button on a Windows XP platform. • A menu accelerator activates a menu item when it is not visible. For instance, pressing Ctrl-P would select the Print menu item in the window shown in Figure 2-8 when the File menu isn’t visible.
Figure 2-8. Mnemonics and menu shortcuts You’ll learn more about mnemonics and accelerators in Chapter 6.
Swing Focus Management The term focus refers to when a component acquires the input focus. When a component has the input focus, it serves as the source for all key events, such as text input. In addition, certain components have some visual markings to indicate that they have the input focus, as shown in Figure 2-9. When certain components have the input focus, you can trigger selection with a keyboard key (usually the spacebar or Enter key), in addition to selection with a mouse. For instance, with a button, pressing the spacebar activates it.
Figure 2-9. A JButton showing it has input focus
CHAPTER 2 ■ EVENT HANDLING WITH THE SWING COMPONENT SET
■Note The focus subsystem had a major overhaul with the 1.4 release of J2SE. All the older guts are still present, but should be avoided. The older stuff didn’t work well and was very buggy. Sun’s fix was to essentially throw everything away and start over, but old APIs are still present. In your quest to work with the focus subsystem, learn to use only the updated APIs, not the older ones. Classes like javax.swing.FocusManager and javax.swing.DefaultFocusManager are completely obsolete now.
An important concept in focus management is the focus cycle, which maps the focus traversal order for the closed set of components within a specific Container. The following classes are also major players in focus management: • FocusTraversalPolicy: A java.awt class that defines the algorithm used to determine the next and previous focusable components. • KeyboardFocusManager: A java.awt class that acts as the controller for keyboard navigation and focus changes. To request a focus change, you tell the manager to change focusable components; you don’t request focus on a particular component. You can find out when the Swing component gets the input focus by registering a FocusListener. The listener allows you to find out when a component gains or loses focus, which component lost focus when another component gained it, and which component got focus when another component lost focus. Additionally, a temporary focus change can happen for something like a pop-up menu. The component that lost focus will receive it again when the menu goes down. The installed focus traversal policy describes how to move between the focusable components of a window. By default, the next component is defined by the order in which components are added to a container, as shown in Figure 2-10. For Swing applications, this focus traversal starts at the top left of the figure and goes across each row and down to the bottom right. This is the default policy, LayoutFocusTraversalPolicy. When all the components are in the same container, this traversal order is called a focus cycle and can be limited to remain within that container.
■Note A user can press Tab or Shift-Tab to move forward or backward through the components in a container, thus transferring the input focus.
47
48
CHAPTER 2 ■ EVENT HANDLING WITH THE SWING COMPONENT SET
Figure 2-10. Default focus ordering
Moving the Focus As an example of some basic capabilities, let’s look at how to create two listeners to handle input focus: a MouseListener that moves the input focus to a component when the mouse enters its space, and an ActionListener that transfers the input focus to the next component. The MouseListener merely needs to call requestFocusInWindow() when the mouse enters the component. import java.awt.*; import java.awt.event.*; public class MouseEnterFocusMover extends MouseAdapter { public void mouseEntered(MouseEvent mouseEvent) { Component component = mouseEvent.getComponent(); if (!component.hasFocus()) { component.requestFocusInWindow(); } } } For the ActionListener, you need to call the focusNextComponent() method for the KeyboardFocusManager. import java.awt.*; import java.awt.event.*;
CHAPTER 2 ■ EVENT HANDLING WITH THE SWING COMPONENT SET
public class ActionFocusMover implements ActionListener { public void actionPerformed(ActionEvent actionEvent) { KeyboardFocusManager manager = KeyboardFocusManager.getCurrentKeyboardFocusManager(); manager.focusNextComponent(); } } The ActionFocusMover and MouseEnterFocusMover show two different ways of programmatically moving focus around. The ActionFocusMover uses the KeyboardFocusManager for traversal. In MouseEnterFocusMover, the call to requestFocusInWindow() says that you would like for the suggested component to get focus for the window of the application. However, getting focus can be turned off. If the component isn’t focusable, either because the default setting of the focusable property is false or you called component.setFocusable(false), then the component will be skipped over and the next component after it gets focus; the component is removed from the tab focus cycle. (Think of a scrollbar that isn’t in the focus cycle, but is draggable to change a setting.) The program in Listing 2-11 uses the two event handlers for moving focus around. It creates a 3×3 grid of buttons, in which each button has an attached mouse listener and a focus listener. The even buttons are selectable but aren’t focusable. Listing 2-11. Focus Traversal Sample import javax.swing.*; import java.awt.*; import java.awt.event.*; public class FocusSample { public static void main(String args[]) { Runnable runner = new Runnable() { public void run() { JFrame frame = new JFrame("Focus Sample"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); ActionListener actionListener = new ActionFocusMover(); MouseListener mouseListener = new MouseEnterFocusMover(); frame.setLayout(new GridLayout(3,3)); for (int i=1; i<10; i++) { JButton button = new JButton(Integer.toString(i)); button.addActionListener(actionListener); button.addMouseListener(mouseListener); if ((i%2) != 0) { // odd - enabled by default button.setFocusable(false); } frame.add(button); }
49
50
CHAPTER 2 ■ EVENT HANDLING WITH THE SWING COMPONENT SET
frame.setSize(300, 200); frame.setVisible(true); } }; EventQueue.invokeLater(runner); } } Figure 2-11 shows the main window of the program.
Figure 2-11. Focus management example
Examining Focus Cycles One customization option available at the Swing container level is the focus cycle. Remember that the focus cycle for a container is a map of the focus traversal order for the closed set of components. You can limit the focus cycle to stay within the bounds of a container by setting the focusCycleRoot property to be true, thus restricting the focus traversal from going beyond an inner container. Then, when the Tab key is pressed within the last component of the container, the focus cycle will wrap back to the first component in the container, instead of moving the input focus to the first component outside the container. When Shift-Tab is pressed in the first component, it wraps to the last component of the container, instead of to the prior component in the outer container. Figure 2-12 illustrates how the focus ordering would look if you placed the middle three buttons from Figure 2-10 within a container restricted in this way. In this cycle, you cannot get to the first component on the third row by pressing the Tab key to move forward. To be able to tab into the second row container, you need to set the focusTraversalPolicyProvider property to true. Otherwise, while the panel will keep the traversal policy within the second row, tabbing will never get you into the third row.
CHAPTER 2 ■ EVENT HANDLING WITH THE SWING COMPONENT SET
Figure 2-12. Restrictive focus cycle The program in Listing 2-12 demonstrates the behavior illustrated in Figure 2-12. The on-screen program will look just like Figure 2-11; it just behaves differently. Listing 2-12. Restricting the Focus Cycle import javax.swing.*; import java.awt.*; import java.awt.event.*; public class FocusCycleSample { public static void main(String args[]) { Runnable runner = new Runnable() { public void run() { JFrame frame = new JFrame("Focus Cycle Sample"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setLayout(new GridBagLayout()); GridBagConstraints constraints = new GridBagConstraints(); constraints.weightx = 1.0; constraints.weighty = 1.0; constraints.gridwidth = 1; constraints.gridheight = 1; constraints.fill = GridBagConstraints.BOTH;
51
52
CHAPTER 2 ■ EVENT HANDLING WITH THE SWING COMPONENT SET
// Row One constraints.gridy=0; for (int i=0; i<3; i++) { JButton button = new JButton("" + i); constraints.gridx=i; frame.add(button, constraints); } // Row Two JPanel panel = new JPanel(); panel.setFocusCycleRoot(true); panel.setFocusTraversalPolicyProvider(true); panel.setLayout(new GridLayout(1,3)); for (int i=0; i<3; i++) { JButton button = new JButton("" + (i+3)); panel.add(button); } constraints.gridx=0; constraints.gridy=1; constraints.gridwidth=3; frame.add(panel, constraints); // Row Three constraints.gridy=2; constraints.gridwidth=1; for (int i=0; i<3; i++) { JButton button = new JButton("" + (i+6)); constraints.gridx=i; frame.add(button, constraints); } frame.setSize(300, 200); frame.setVisible(true); } }; EventQueue.invokeLater(runner); } }
FocusTraversalPolicy Class The FocusTraversalPolicy is responsible for determining the focus traversal order. It allows you to specify the next and previous components in the order. This class offers six methods for controlling traversal order:
CHAPTER 2 ■ EVENT HANDLING WITH THE SWING COMPONENT SET
• getComponentAfter(Container aContainer, Component aComponent) • getComponentBefore(Container aContainer, Component aComponent) • getDefaultComponent(Container aContainer) • getFirstComponent(Container aContainer) • getInitialComponent(Window window) • getLastComponent(Container aContainer) Swing provides five predefined traversal policies, as listed in Table 2-5. By picking the right traversal policy for your application, or rolling your own, you can determine how users will navigate around the screens.
Table 2-5. Predefined Traversal Policies
Policy
Description
ContainerOrderFocusTraversalPolicy
The components are traversed in the order they are added to their container. The component must be visible, displayable, enabled, and focusable to be part of the focus cycle.
DefaultFocusTraversalPolicy
The default policy for AWT programs, this extends ContainerOrderFocusTraversalPolicy to check with the component peer (the operating system) if the component hasn’t explicitly set focusability. The focusability of a peer depends on the Java runtime implementation.
InternalFrameFocusTraversalPolicy
Special policy for JInternalFrame, with behavior to determine initial focusable component based on the default component of the frame.
SortingFocusTraversalPolicy
Here, you provide a Comparator to the policy constructor to define the focus cycle order.
LayoutFocusTraversalPolicy
The default policy for Swing programs, this takes into account geometric settings of components (height, width, position), and then goes top down, left to right to determine navigation order. The topdown, left-right order is determined by the current ComponentOrientation setting for your locale. For instance, Hebrew would be in right-left order instead.
To demonstrate, the program in Listing 2-13 reverses the functionality of Tab and ShiftTab. When you run the program, it looks the same as the screen shown earlier in Figure 2-11, with the 3×3 set of buttons. However, with this version, the initial focus starts on the 9 button, and pressing Tab takes you to 8, then 7, and so on. Shift-Tab goes in the other, more normal, order.
53
54
CHAPTER 2 ■ EVENT HANDLING WITH THE SWING COMPONENT SET
public class NextComponentSample { public static void main(String args[]) { Runnable runner = new Runnable() { public void run() { JFrame frame = new JFrame("Reverse Sample"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setLayout(new GridLayout(3,3)); // for (int i=1; i<10; i++) { for (int i=9; i>0; i--) { JButton button = new JButton(Integer.toString(i)); frame.add(button, 0); } final Container contentPane = frame.getContentPane(); Comparator comp = new Comparator() { public int compare(Component c1, Component c2) { Component comps[] = contentPane.getComponents(); List list = Arrays.asList(comps); int first = list.indexOf(c1); int second = list.indexOf(c2); return second - first; } }; FocusTraversalPolicy policy = new SortingFocusTraversalPolicy(comp); frame.setFocusTraversalPolicy(policy); frame.setSize(300, 200); frame.setVisible(true); } }; EventQueue.invokeLater(runner); } }
CHAPTER 2 ■ EVENT HANDLING WITH THE SWING COMPONENT SET
KeyboardFocusManager Class The abstract KeyboardFocusManager class in the AWT library serves as the control mechanism framework for the input focus behavior of Swing components. The DefaultKeyboardFocusManager is the concrete implementation. The focus manager allows you to both programmatically discover who currently has the input focus and to change it. The component with the current input focus is called the focus owner. This is accessible via the focusOwner property of KeyboardFocusManager. You can also discover the focusedWindow and activeWindow properties. The focused window is the window containing the focus owner, and the active window is either the focused window or the frame or dialog containing the focus owner. The simple concept of moving to the previous or next component is supported in many different ways. First, you can use the shortcut API methods of Component and Container: • Component.transferFocus() • Component.transferFocusBackward() • Component.transferFocusUpCycle() • Container.transferFocusDownCycle() The first two methods request focus to move to the next or previous component, respectively. The up and down cycle methods request that you move up out of the current focus cycle or down into the next cycle. The following methods map directly to methods of the KeyboardFocusManager: • focusNextComponent() • focusPreviousComponent() • upFocusCycle() • downFocusCycle() A second set of the same four methods accepts a second parameter of a Component. If the component isn’t specified, these methods change the focused component based on the current focus owner. If a component is provided, the change is based on that component. Tab and Shift-Tab are used for keyboard focus traversal because they are defined as the default focus traversal keys for most, if not all, components. To define your own traversal keys, you can replace or append to a key set via the setFocusTraversalKeys() method of Component. Different sets are available for forward, backward, and up-cycle, as specified by the FORWARD_ TRAVERSAL_KEYS, BACKWARD_TRAVERSAL_KEYS, and UP_CYCLE_TRAVERSAL_KEYS constants of KeyboardFocusManager. You can set and get key sets for each. For instance, to add the F3 key as an up-cycle key for a component, use the following code: Set set = component.getFocusTraversalKeys( KeyboardFocusManager.UP_CYCLE_TRAVERSAL_KEYS); KeyStroke stroke = KeyStroket.getKeyStroke("F3"); set.add(stroke); component.setFocusTraversalKeys(KeyboardFocusManager.UP_CYCLE_TRAVERSAL_KEYS, set);
55
56
CHAPTER 2 ■ EVENT HANDLING WITH THE SWING COMPONENT SET
Verifying Input During Focus Traversal Swing offers the abstract InputVerifier class for component-level verification during focus traversal with any JComponent. Just subclass InputVerifier and provide your own public boolean verify(JComponent) method to verify the contents of the component. Listing 2-14 provides a simple numeric text field verification example, showing three text fields, of which only two have verification. Unless fields one and three are valid, you can’t tab out of them. Listing 2-14. Numeric Input Verifier import java.awt.*; import java.awt.event.*; import javax.swing.*; public class VerifierSample { public static void main(String args[]) { Runnable runner = new Runnable() { public void run() { JFrame frame = new JFrame("Verifier Sample"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); JTextField textField1 = new JTextField(); JTextField textField2 = new JTextField(); JTextField textField3 = new JTextField(); InputVerifier verifier = new InputVerifier() { public boolean verify(JComponent comp) { boolean returnValue; JTextField textField = (JTextField)comp; try { Integer.parseInt(textField.getText()); returnValue = true; } catch (NumberFormatException e) { returnValue = false; } return returnValue; } }; textField1.setInputVerifier(verifier); textField3.setInputVerifier(verifier); frame.add(textField1, BorderLayout.NORTH); frame.add(textField2, BorderLayout.CENTER); frame.add(textField3, BorderLayout.SOUTH); frame.setSize(300, 100); frame.setVisible(true); }
CHAPTER 2 ■ EVENT HANDLING WITH THE SWING COMPONENT SET
}; EventQueue.invokeLater(runner); } }
■Tip To make sure that cancel-type buttons get the input focus no matter what when using InputVerifier, use the setVerifyInputWhenFocusTarget(false) method with the component.
Summary In this chapter, you looked at the many ways of dealing with event handling when using Swing components. Because Swing components are built on top of AWT components, you can use the delegation-based event-handling mechanism common with those components. You then learned about the multithreading limitations of the Swing components and how to get around them with the invokeAndWait() and invokeLater() methods of EventQueue. You also explored how the Swing components use the JavaBeans PropertyChangeListener approach for notification of bound property changes. Besides exploring the similarities between the Swing components and AWT components, you also looked at several of the new features that the Swing library offers. You explored the Action interface and how it can simplify complex user-interface development by completely separating the event-handling task from the visual component. You looked at the technique for registering KeyStroke objects to components to simplify listening for key events. Finally, you explored Swing’s focus management capabilities and how to customize the focus cycle and use the FocusTraversalPolicy and KeyboardFocusManager, as well as validating input with the InputVerifier. In Chapter 3, you’ll meet the Model-View-Controller (MVC) architecture of the Swing component set. You’ll learn how MVC can make your user interface development efforts much easier.
57
CHAPTER 3 ■■■
The Model-View-Controller Architecture C
hapter 2 explored how to deal with event producers and consumers with regard to Swing components. We looked at how event handling with Swing components goes beyond the event-handling capabilities of the original AWT components. In this chapter, we will take the Swing component design one step further to examine what is called the Model-View-Controller (MVC) architecture.
Understanding the Flow of MVC First introduced in Smalltalk in the late 1980s, the MVC architecture is a special form of the Observer pattern described in Chapter 2. The model part of the MVC holds the state of a component and serves as the Subject. The view part of the MVC serves as the Observer of the Subject to display the model’s state. The view creates the controller, which defines how the user interface reacts to user input.
MVC Communication Figure 3-1 shows how the MVC elements communicate—in this case, with Swing’s multiline text component, the JTextArea. In MVC terms, the JTextArea serves as the view part within the MVC architecture. Displayed within the component is a Document, which is the model for the JTextArea. The Document stores the state information for the JTextArea, such as the text contents. Within the JTextArea is the controller, in the form of an InputMap. It maps keyboard input to commands in an ActionMap, and those commands are mapped to TextAction objects, which can modify the Document. When the modification happens, the Document creates a DocumentEvent and sends it back to the JTextArea.
59
60
CHAPTER 3 ■ THE MODEL-VIEW-CONTROLLER ARCHITECTURE
Figure 3-1. MVC communication mechanism
UI Delegates for Swing Components This example demonstrates an important aspect of the MVC architecture within the Swing world. Complex interactions need to happen between the view and the controller. The Swing design combines these two elements into a delegate object to simplify the overall design. This results in each Swing component having a UI delegate that is in charge of rendering the current state of the component and dealing with user input events. Sometimes, the user events result in changes to the view that don’t affect the model. For instance, the cursor position is an attribute of the view. The model doesn’t care about the position of the cursor, only the text contents. User input that affects the cursor position isn’t passed along to the model. At other times, user input that affects the contents of the Document (for example, pressing the Backspace key) is passed along. Pressing the Backspace key results in a character being removed from the model. Because of this tight coupling, each Swing component has a UI delegate. To demonstrate, Figure 3-2 shows the makeup of the JTextArea, with respect to the model and UI delegate. The UI delegate for the JTextArea starts with the TextUI interface, with its basic implementation in the BasicTextUI class. In turn, this is specialized with the BasicTextAreaUI for the JTextArea. The BasicTextAreaUI creates a view that is either a PlainView or a WrappedPlainView. On the model side, things are much simpler. The Document interface is implemented by the AbstractDocument class, which is further specialized by the PlainDocument. The text components will be explained more fully in Chapters 15 and 16. As the diagram in Figure 3-2 demonstrates, much is involved in working with the text components. In most cases, you don’t need to deal with the specifics to the degree shown in this figure. However, all of these classes are working behind the scenes. The UI-delegate part of the MVC architecture will be discussed further in Chapter 20, when we explore how to customize delegates.
CHAPTER 3 ■ THE MODEL-VIEW-CONTROLLER ARCHITECTURE
Figure 3-2. The JTextArea MVC architecture
Sharing Data Models Because data models store only the state information, you can share a model across multiple components. Then each component view can be used to modify the model. In the case of Figure 3-3, three different JTextArea components are used to modify one Document model. If a user modifies the contents of one JTextArea, the model is changed, causing the other text areas to automatically reflect the updated document state. It isn’t necessary for any Document view to manually notify others sharing the model.
61
62
CHAPTER 3 ■ THE MODEL-VIEW-CONTROLLER ARCHITECTURE
Figure 3-3. Sharing MVC data models Sharing of a data model can be done in either one of two ways: • You can create the data model apart from any component and tell each component to use the data model. • You can create one component first, get the model from the first component, and then share it with the other components. Listing 3-1 demonstrates how to share a data model using the latter technique. Listing 3-1. Sharing an MVC Model import java.awt.*; import javax.swing.*; import javax.swing.text.*; public class ShareModel { public static void main (String args[]) { Runnable runner = new Runnable() { public void run() { JFrame frame = new JFrame("Sharing Sample"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); Container content = frame.getContentPane(); JTextArea textarea1 = new JTextArea(); Document document = textarea1.getDocument(); JTextArea textarea2 = new JTextArea(document); JTextArea textarea3 = new JTextArea(document); content.setLayout(new BoxLayout(content, BoxLayout.Y_AXIS)); content.add(new JScrollPane(textarea1));
CHAPTER 3 ■ THE MODEL-VIEW-CONTROLLER ARCHITECTURE
content.add(new JScrollPane(textarea2)); content.add(new JScrollPane(textarea3)); frame.setSize (300, 400); frame.setVisible (true); } }; EventQueue.invokeLater(runner); } } Figure 3-4 shows how this program might look after editing the shared document. Notice that the three text areas are capable of viewing (or modifying) different areas of the document. They aren’t limited to adding text only at the end, for instance. This is because each text area manages the position and cursor separately. The position and cursor are attributes of the view, not the model.
Figure 3-4. Sharing a document between JTextArea components
Understanding the Predefined Data Models When working with Swing components, it’s helpful to understand the data models behind each of the components because the data models store their state. Understanding the data model for each component helps you to separate the parts of the component that are visual (and thus part of the view) from those that are logical (and thus part of the data model). For example, by understanding this separation, you can see why the cursor position within a JTextArea isn’t part of the data model, but rather is part of the view. Table 3-1 provides a complete listing of the Swing components, the interface that describes the data model for each component, as well as the specific implementations. If a component isn’t listed, that component inherits its data model from its parent class, most likely
63
64
CHAPTER 3 ■ THE MODEL-VIEW-CONTROLLER ARCHITECTURE
AbstractButton. In addition, in some cases, multiple interfaces are used to describe a component, because the data is stored in one model and the selection of the data is in a second model. In the case of the JComboBox, the MutableComboBoxModel interface extends from ComboBoxModel. No predefined class implements the ComboBoxModel interface without also implementing the MutableComboBoxModel interface.
When directly accessing the model of a Swing component, if you change the model, all registered views are automatically notified. This, in turn, causes the views to revalidate themselves to ensure that the components display their proper current states. This automatic propagation of state changes is one reason why MVC has become so popular. In addition, using the MVC architecture helps programs become more maintainable as they change over time and their complexity grows. No longer will you need to worry about losing state information if you change visual component libraries!
Summary This chapter provided a quick look at how the Swing components use a modified MVC architecture. You explored what makes up this modified architecture and how one particular component, the JTextArea, maps into this architecture. In addition, the chapter discussed the sharing of data models between components and listed all the data models for the different Swing components. In Chapter 4, you’ll start to look at the individual components that make up the Swing component library. In addition, you’ll explore the Swing component class hierarchy as you examine the base JComponent component from the Swing library.
65
CHAPTER 4 ■■■
Core Swing Components I
n Chapter 3, you received a quick introduction to the Model-View-Controller (MVC) pattern used by the components of the JFC/Swing project. In this chapter, you’ll begin to explore how to use the key parts of the many available components. All Swing components start with the JComponent class. Although some parts of the Swing libraries aren’t rooted with the JComponent class, all the components share JComponent as the common parent class at some level of their ancestry. It’s with this JComponent class that common behavior and properties are defined. In this chapter, you’ll look at common functionality such as component painting, customization, tooltips, and sizing. As far as specific JComponent descendent classes are concerned, you’ll specifically look at the JLabel, JButton, and JPanel, three of the more commonly used Swing component classes. They require an understanding of the Icon interface for displaying images within components, as well as of the ImageIcon class for when using predefined images and the GrayFilter class for support. In addition, you’ll look at the AbstractButton class, which serves as the parent class to the JButton. The data model shared by all AbstractButton subclasses is the ButtonModel interface; you’ll explore that and the specific implementation class, the DefaultButtonModel.
JComponent Class The JComponent class serves as the abstract root class from which all Swing components descend. The JComponent class has 42 descendent subclasses, each of which inherits much of the JComponent functionality. Figure 4-1 shows this hierarchy. Although the JComponent class serves as the common root class for all Swing components, many classes in the libraries for the Swing project descend from classes other than JComponent. Those include all the high-level container objects such as JFrame, JApplet, and JInternalFrame; all the MVC-related classes; event-handling–related interfaces and classes; and much more. All of these will be discussed in later chapters. Although all Swing components extend JComponent, the JComponent class extends the AWT Container class, which, in turn, extends from the AWT Component class. This means that many aspects of the JComponent are shared with both the AWT Component and Container classes.
67
68
CHAPTER 4 ■ CORE SWING COMPONENTS
Figure 4-1. JComponent class hierarchy diagram
■Note JComponent extends from the Container class, but most of the JComponent subclasses aren’t themselves containers of other components. To see if a particular Swing component is truly a container, check the BeanInfo for the class to see if the isContainer property is set to true. To get the BeanInfo for a class, ask the Introspector.
CHAPTER 4 ■ CORE SWING COMPONENTS
Component Pieces The JComponent class defines many aspects of AWT components that go above and beyond the capabilities of the original AWT component set. This includes customized painting behavior and several different ways to customize display settings, such as colors, fonts, and any other client-side settings.
Painting JComponent Objects Because the Swing JComponent class extends from the Container class, the basic AWT painting model is followed: All painting is done through the paint() method, and the repaint() method is used to trigger updates. However, many tasks are done differently. The JComponent class optimizes many aspects of painting for improved performance and extensibility. In addition, the RepaintManager class is available to customize painting behavior even further.
■Note The public void update(Graphics g) method, inherited from Component, is never invoked on Swing components.
To improve painting performance and extensibility, the JComponent splits the painting operation into three tasks. The public void paint(Graphics g) method is subdivided into three separate protected method calls. In the order called, they are paintComponent(g), paintBorder(g), and paintChildren(g), with the Graphics argument passed through from the original paint() call. The component itself is first painted through paintComponent(g). If you want to customize the painting of a Swing component, you override paintComponent() instead of paint(). Unless you want to completely replace all the painting, you would call super.paintComponent() first, as shown here, to get the default paintComponent() behavior. public class MyComponent extends JPanel { protected void paintComponent(Graphics g) { super.paintComponent(g); // Customize after calling super.paintComponent(g) } ... }
■Note When running a program that uses Swing components in Java 5.0, the Graphics argument passed to the paint() method and on to paintComponent() is technically a Graphics2D argument. Therefore, after casting the Graphics argument to a Graphics2D object, you could use the Java 2D capabilities of the platform, as you would when defining a drawing Stroke, Shape, or AffineTransform.
69
70
CHAPTER 4 ■ CORE SWING COMPONENTS
The paintBorder() and paintChildren() methods tend not to be overridden. The paintBorder() method draws a border around the component, a concept described more fully in Chapter 7. The paintChildren() method draws the components within the Swing container object, if any are present. To optimize painting, the JComponent class provides three additional painting properties: opaque, optimizedDrawingEnabled, and doubleBuffered. These work as follows: • Opacity: The opaque property for a JComponent defines whether a component is transparent. When transparent, the container of the JComponent must paint the background behind the component. To improve performance, you can leave the JComponent opaque and let the JComponent draw its own background, instead of relying on the container to draw the covered background. • Optimization: The optimizedDrawingEnabled property determines whether immediate children can overlap. If children cannot overlap, the repaint time is reduced considerably. By default, optimized drawing is enabled for most Swing components, except for JDesktopPane, JLayeredPane, and JViewport. • Double buffering: By default, all Swing components double buffer their drawing operations into a buffer shared by the complete container hierarchy; that is, all the components within a window (or subclass). This greatly improves painting performance, because when double buffering is enabled (with the doubleBuffered property), there is only a single screen update drawn.
■Note For synchronous painting, you can call one of the public void paintImmediately() methods. (Arguments are either a Rectangle or its parts—position and dimensions.) However, you’ll rarely need to call this directly unless your program has real-time painting requirements.
The public void revalidate() method of JComponent also offers painting support. When this method is called, the high-level container of the component validates itself. This is unlike the AWT approach requiring a direct call to the revalidate() method of that high-level component. The last aspect of the Swing component painting enhancements is the RepaintManager class.
RepaintManager Class The RepaintManager class is responsible for ensuring the efficiency of repaint requests on the currently displayed Swing components, making sure the smallest “dirty” region of the screen is updated when a region becomes invalid. Although rarely customized, RepaintManager is public and provides a static installation routine to use a custom manager: public static void setCurrentManager(RepaintManager manager). To get the current manager, just ask with public static void currentManager (JComponent). The argument is usually null, unless you’ve customized the manager to provide component-level support. Once you have the manager, one thing you can do is get the offscreen buffer for a component as an Image. Because the buffer is what is eventually shown on
CHAPTER 4 ■ CORE SWING COMPONENTS
the screen, this effectively allows you to do a screen dump of the inside of a window (or any JComponent). Component comp = ... RepaintManager manager = RepaintManager.currentManager(null); Image htmlImage = manager.getOffscreenBuffer(comp, comp.getWidth(), comp.getHeight()); // or Image volatileImage = manager.getVolatileOffscreenBuffer(comp, comp.getWidth(), comp.getHeight()); Table 4-1 shows the two properties of RepaintManager. They allow you to disable double buffering for all drawing operations of a component (hierarchy) and to set the maximum double buffer size, which defaults to the end user’s screen size.
Table 4-1. RepaintManager Properties
Property Name
Data Type
Access
doubleBufferingEnabled
boolean
Read-write
doubleBufferMaximumSize
Dimension
Read-write
■Tip To globally disable double-buffered drawing, call RepaintManager.currentManager(aComponent). setDoubleBufferingEnabled(false).
Although it’s rarely done, providing your own RepaintManager subclass does allow you to customize the mechanism of painting dirty regions of the screen, or at least track when the painting is finished. Overriding any of the following four methods allows you to customize the mechanisms: public synchronized void addDirtyRegion(JComponent component, int x, int y, int width, int height) public Rectangle getDirtyRegion(JComponent component) public void markCompletelyClean(JComponent component) public void markCompletelyDirty(JComponent component)
UIDefaults Class The UIDefaults class represents a lookup table containing the display settings installed for the current look and feel, such as which font to use within a JList, as well as what color or icon should be displayed within a JTree node. The use of UIDefaults will be detailed in Chapter 20 with the coverage of Java’s pluggable look and feel architecture. Here, you will get a brief introduction to the UIDefaults table.
71
72
CHAPTER 4 ■ CORE SWING COMPONENTS
Whenever you create a component, the component automatically asks the UIManager to look in the UIDefaults table for the current settings for that component. Most color- and fontrelated component settings, as well as some others not related to colors and fonts, are configurable. If you don’t like a particular setting, you can simply change it by updating the appropriate entry in the UIDefaults lookup table.
■Note All predefined resource settings in the UIDefaults table implement the UIResource interface, which allows the components to monitor which settings have been customized just by looking for those settings that don’t implement the interface.
First, you need to know the name of the UIDefaults setting you want to change. You can find the setting names in Appendix A of this book, which contains a complete alphabetical listing of all known settings for the predefined look and feel types in J2SE 5.0. (These differ a little from release to release.) In addition, included with the description of each component is a table containing the UIResource-related property elements. (To find the specific component section in the book, consult the table of contents or the index.) Once you know the name of a setting, you can store a new setting with the public static void put(Object key, Object value) method of UIManager, where key is the key string. For instance, the following code will change the default background color of newly created buttons to black and the foreground color to red: UIManager.put("Button.background", Color.BLACK); UIManager.put("Button.foreground", Color.RED);
Fetching UIResource Properties If you’re creating your own components, or just need to find out the current value setting, you can ask the UIManager. Although the public static Object get(Object key) method is the most generic, it requires you to cast the return value to the appropriate class type. Alternatively, you could use one of the more specific getXXX() methods, which does the casting for you, to return the appropriate type: public public public public public public public public public public
boolean getBoolean(Object key) Border getBorder(Object key) Color getColor(Object key) Dimension getDimension(Object key) Font getFont(Object key) Icon getIcon(Object key) Insets getInsets(Object key) int getInt(Object key) String getString(Object key) ComponentUI getUI(JComponent target)
There is a second set of overloaded methods that accept a second argument for the Locale.
CHAPTER 4 ■ CORE SWING COMPONENTS
■Note You can also work with the UIDefaults directly, by calling the public static UIDefaults getDefaults() method of UIManager.
Client Properties In addition to the UIManager maintaining a table of key/value pair settings, each instance of every component can manage its own set of key/value pairs. This is useful for maintaining aspects of a component that may be specific to a particular look and feel, or for maintaining data associated with a component without requiring the definition of new classes or methods to store such data. public final void putClientProperty(Object key, Object value) public final Object getClientProperty(Object key)
■Note Calling putClientProperty() with a value of null causes the key to be removed from the client property table.
For instance, the JTree class has a property with the Metal look and feel for configuring the line style for connecting or displaying nodes within a JTree. Because the setting is specific to one look and feel, it doesn’t make sense to add something to the tree API. Instead, you set the property by calling the following on a particular tree instance: tree.putClientProperty("JTree.lineStyle", "None") Then, when the look and feel is the default Metal, lines will connect the nodes of the tree. If another look and feel is installed, the client property will be ignored. Figure 4-2 shows a tree with and without lines.
Figure 4-2. A JTree, with and without angled lines
73
74
CHAPTER 4 ■ CORE SWING COMPONENTS
■Note The list of client properties is probably one of the least documented aspects of Swing. Chapter 20 lists the available properties I was able to determine. Also, while Metal is the default look and feel, what you see is called Ocean. Ocean is a theme of the Metal look and feel and makes Metal look a bit flashier.
JComponent Properties You’ve seen some of the pieces shared by the different JComponent subclasses. Now it’s time to look at the JavaBeans properties. Table 4-2 shows the complete list of properties defined by JComponent, including those inherited through the AWT Container and Component classes.
Table 4-2. JComponent Properties
Property Name
Data Type
Component Access
Container Access
JComponent Access
accessibleContext
AccessibleContext
Read-only
N/A
Read-only
actionMap
ActionMap
N/A
N/A
Read-write
alignmentX
float
Read-only
Read-only Read-write
alignmentY
float
Read-only
Read-only Read-write
ancestorListeners
AncestorListener[ ]
N/A
N/A
Read-only
autoscrolls
boolean
N/A
N/A
Read-write
background
Color
Read-write bound
N/A
Write-only
backgroundSet
boolean
Read-only
N/A
N/A
border
Border
N/A
N/A
Read-write bound
bounds
Rectangle
Read-write
N/A
N/A
colorModel
ColorModel
Read-only
N/A
N/A
componentCount
int
N/A
Read-only N/A
componentListeners
ComponentListener[ ]
Read-only
N/A
N/A
componentOrientation
ComponentOrientation
Read-write bound
N/A
N/A
componentPopupMenu
JPopupMenu
N/A
N/A
Read-write
components
Component[ ]
N/A
Read-only N/A
containerListeners
ContainerListener[ ]
N/A
Read-only N/A
cursor
Cursor
Read-write
N/A
N/A
cursorSet
boolean
Read-only
N/A
N/A
debugGraphicsOptions
int
N/A
N/A
Read-write
displayable
boolean
Read-only
N/A
N/A
CHAPTER 4 ■ CORE SWING COMPONENTS
75
Table 4-2. JComponent Properties (Continued)
Property Name
Data Type
Component Access
Container Access
JComponent Access
doubleBuffered
boolean
Read-only
N/A
Read-write
dropTarget
DropTarget
Read-write
N/A
N/A
enabled
boolean
Read-write
N/A
Write-only bound
focusable
boolean
Read-write bound
N/A
N/A
focusCycleRoot
boolean
N/A
Read-write N/A bound
focusCycleRootAncestor
Container
Read-only
N/A
N/A
focusListeners
FocusListener[ ]
Read-only
N/A
N/A
focusOwner
boolean
Read-only
N/A
N/A
focusTraversalKeysEnabled
boolean
Read-write bound
N/A
N/A
focusTraversalPolicy
FocusTraversalPolicy
N/A
Read-write N/A bound
focusTraversalPolicyProvider
boolean
N/A
Read-write N/A bound
focusTraversalPolicySet
boolean
N/A
Read-only N/A
font
Font
Read-write bound
Write-only Write-only
fontSet
boolean
Read-only
N/A
N/A
foreground
Color
Read-write bound
N/A
Write-only
foregroundSet
boolean
Read-only
N/A
N/A
graphics
Graphics
Read-only
N/A
Read-only
graphicsConfiguration
GraphicsConfiguration
Read-only
N/A
N/A
height
int
Read-only
N/A
Read-only
hierarchyBoundsListeners
HierarchyBoundsListener[ ]
Read-only
N/A
N/A
hierarchyListeners
HierarchyListener[ ]
Read-only
N/A
N/A
ignoreRepaint
boolean
Read-write
N/A
N/A
inheritsPopupMenu
boolean
N/A
N/A
Read-write
inputContext
InputContext
Read-only
N/A
N/A
inputMap
InputMap
N/A
N/A
Read-only
inputMethodListeners
InputMethodListener[ ]
Read-only
N/A
N/A
inputMethodRequests
InputMethodRequests
Read-only
N/A
N/A
76
CHAPTER 4 ■ CORE SWING COMPONENTS
Table 4-2. JComponent Properties (Continued)
Property Name
Data Type
Component Access
Container Access
JComponent Access
inputVerifier
InputVerifier
N/A
N/A
Read-write bound
insets
Insets
N/A
Read-only Read-only
keyListeners
KeyListener[ ]
Read-only
N/A
layout
LayoutManager
N/A
Read-write N/A
lightweight
boolean
Read-only
N/A
N/A
locale
Locale
Read-write bound
N/A
N/A
location
Point
Read-write
N/A
N/A
locationOnScreen
Point
Read-only
N/A
N/A
maximumSize
Dimension
Read-write bound
Read-only Read-write
maximumSizeSet
boolean
Read-only
N/A
minimumSize
Dimension
Read-write bound
Read-only Read-write
minimumSizeSet
boolean
Read-only
N/A
N/A
mouseListeners
MouseListener[ ]
Read-only
N/A
N/A
mouseMotionListeners
MouseMotionListener[ ]
Read-only
N/A
N/A
mousePosition
Point
Read-only
N/A
N/A
mouseWheelListeners
MouseWheelListener
Read-only
N/A
N/A
name
String
Read-write bound
N/A
N/A
opaque
boolean
Read-only
N/A
Read-write bound
optimizedDrawingEnabled
boolean
N/A
N/A
Read-only
paintingTile
boolean
N/A
N/A
Read-only
parent
Container
Read-only
N/A
N/A
preferredSize
Dimension
Read-write bound
Read-only Read-write
preferredSizeSet
boolean
Read-only
N/A
N/A
propertyChangeListeners
PropertyChangeListener[ ]
Read-only
N/A
N/A
registeredKeyStrokes
KeyStroke[ ]
N/A
N/A
Read-only
requestFocusEnabled
boolean
N/A
N/A
Read-write
rootPane
JRootPane
N/A
N/A
Read-only
N/A
N/A
CHAPTER 4 ■ CORE SWING COMPONENTS
77
Table 4-2. JComponent Properties (Continued)
Property Name
Data Type
Component Access
Container Access
JComponent Access
showing
boolean
Read-only
N/A
N/A
size
Dimension
Read-write
N/A
N/A
toolkit
Toolkit
Read-only
N/A
N/A
tooltipText
String
N/A
N/A
Read-write
topLevelAncestor
Container
N/A
N/A
Read-only
transferHandler
TransferHandler
N/A
N/A
Read-write bound
treeLock
Object
Read-only
N/A
N/A
uiClassID
String
N/A
N/A
Read-only
valid
boolean
Read-only
N/A
N/A
validateRoot
boolean
N/A
N/A
Read-only
verifyInputWhenFocusTarget
boolean
N/A
N/A
Read-write bound
vetoableChangeListeners
VetoableChangeListener[ ]
N/A
N/A
Read-only
visible
boolean
Read-write
N/A
Write-only
visibleRect
Rectangle
N/A
N/A
Read-only
width
int
Read-only
N/A
Read-only
x
int
Read-only
N/A
Read-only
y
int
Read-only
N/A
Read-only
■Note Additionally, there’s a read-only class property defined at the Object level, the parent of the Component class.
Including the properties from the parent hierarchy, approximately 92 properties of JComponent exist. As that number indicates, the JComponent class is extremely well suited for visual development. There are roughly ten categories of JComponent properties, as described in the following sections.
Position-Oriented Properties The x and y properties define the location of the component relative to its parent. The locationOnScreen is just another location for the component, this time relative to the screen’s origin (the upper-left corner). The width and height properties define the size of the component. The visibleRect property describes the part of the component visible within the topLevelAncestor, whereas the bounds property defines the component’s area, whether visible or not.
78
CHAPTER 4 ■ CORE SWING COMPONENTS
Component-Set-Oriented Properties The components and componentCount properties enable you to find out what the children components are of the particular JComponent. For each component in the components property array, the current component would be its parent. In addition to determining a component’s parent, you can find out its rootPane or topLevelAncestor.
Focus-Oriented Properties The focusable, focusCycleRoot, focusCycleRootAncestor, focusOwner, focusTraversalKeysEnabled, focusTraversalPolicy, focusTraversalPolicyProvider, focusTraversablePolicySet, requestFocusEnabled, verifyInputWhenFocusTarget, and inputVerifier properties define the set of focus-oriented properties. These properties control the focus behavior of JComponent and were discussed in Chapter 2.
Layout-Oriented Properties The alignmentX, alignmentY, componentOrientation, layout, maximumSize, minimumSize, preferredSize, maximumSizeSet, minimumSizeSet, and preferredSizeSet properties are used to help with layout management.
Painting Support Properties The background and foreground properties describe the current drawing colors. The font property describes the text style to draw. The backgroundSet, foregroundSet, and fontSet properties describe if the properties are explicitly set. The insets and border properties are intermixed to describe the drawing of a border around a component. The graphics property permits realtime drawing, although the paintImmediately() method might now suffice. To improve performance, there are the opaque (false is transparent), doubleBuffered, ignoreRepaint, and optimizedDrawingEnabled properties. The colorModel and paintingTile properties store intermediate drawing information. The graphicsConfiguration property adds support for virtual devices. debugGraphicsOption allows you to slow down the drawing of your component if you can’t figure out why it’s not painted properly. The debugGraphicsOption property is set to one or more of the settings shown in Table 4-3.
Table 4-3. DebugGraphics Settings
DebugGraphics Settings
Description
DebugGraphics.BUFFERED_OPTION
Causes a window to pop up, displaying the drawing of the double-buffered image
DebugGraphics.FLASH_OPTION
Causes the drawing to be done more slowly, flashing between steps
DebugGraphics.LOG_OPTION
Causes a message to be printed to the screen as each step is done
DebugGraphics.NONE_OPTION
Disables all options
CHAPTER 4 ■ CORE SWING COMPONENTS
You can combine multiple DebugGraphics settings with the bitwise OR (|) operator, as in this example: JComponent component = new ...(); component.setDebugGraphicsOptions(DebugGraphics.BUFFERED_OPTION | DebugGraphics.FLASH_OPTION | DebugGraphics.LOG_OPTION);
Internationalization Support Properties The inputContext, inputMethodRequests, and locale properties help when creating multilingual operations.
State Support Properties To get state information about a component, all you have to do is ask; there’s much you can discover. The autoscrolls property lets you place a component within a JViewport and it automatically scrolls when dragged. The validateRoot property is used when revalidate() has been called and returns true when the current component is at the point it should stop. The remaining seven properties are self-explanatory: displayable, dropTarget, enabled, lightweight, showing, valid, and visible.
Event Support Properties The registeredKeyStrokes, inputMap, and actionMap properties allow you to register keystroke responses with a window. All the getXXXListeners() methods allow you to get the current set of listeners for a particular listener type.
Pop-Up Support Properties There are two types of pop-ups associated with a component: tooltips and pop-up menus. The toolTipText property is set to display pop-up support text over a component. The componentPopupMenu and inheritsPopupMenu properties are related to automatically showing pop-up menus associated with the component. The mousePosition property helps to position these.
Other Properties The remaining properties don’t seem to have any kind of logical grouping. The accessibleContext property is for support with the javax.accessibility package. The cursor property lets you change the cursor to one of the available cursors, where cursorSet is used to recognize when the property is explicitly set. The toolkit property encapsulates platform-specific behaviors for accessing system resources. The transferHandler property is there for drag-and-drop support. The name property gives you the means to recognize a particular instance of a class. The treelock property is the component tree-synchronization locking resource. The uiClassID property is new; it allows subclasses to return the appropriate class ID for their specific instance.
79
80
CHAPTER 4 ■ CORE SWING COMPONENTS
Handling JComponent Events There are many different types of events that all JComponent subclasses share. Most of these come from parent classes, like Component and Container. First, you’ll explore the use of PropertyChangeListener, which is inherited from Container. Then you’ll look at the use of two event-handling capabilities shared by all JComponent subclasses: VetoableChangeListener and AncestorListener. Finally, you’ll see the complete set of listeners inherited from Component.
Listening to Component Events with a PropertyChangeListener The JComponent class has several component bound properties, directly and indirectly. By binding a PropertyChangeListener to the component, you can listen for particular JComponent property changes, and then respond accordingly. public interface PropertyChangeListener extends EventListener { public void propertyChange(PropertyChangeEvent propertyChangeEvent); } To demonstrate, the PropertyChangeListener in Listing 4-1 demonstrates the behavior you might need when listening for changes to an Action type property within a JButton component. The property that changes determines which if block is executed. Listing 4-1. Watching for Changes to a JButton import java.beans.*; import javax.swing.*; public class ActionChangedListener implements PropertyChangeListener { private JButton button; public ActionChangedListener(JButton button) { this.button = button; } public void propertyChange(PropertyChangeEvent e) { String propertyName = e.getPropertyName(); if (e.getPropertyName().equals(Action.NAME)) { String text = (String)e.getNewValue(); button.setText(text); button.repaint(); } else if (propertyName.equals("enabled")) { Boolean enabledState = (Boolean)e.getNewValue(); button.setEnabled(enabledState.booleanValue()); button.repaint();
■Note You can bind a PropertyChangeListener to a specific property by adding the listener with addPropertyChangeListener(String propertyName, PropertyChangeListener listener). This allows your listener to avoid having to check for the specific property that changed.
Listening to JComponent Events with a VetoableChangeListener The VetoableChangeListener is another JavaBeans listener that Swing components use. It works with constrained properties, whereas the PropertyChangeListener works with only bound properties. A key difference between the two is that the public void vetoableChange(PropertyChangeEvent propertyChangeEvent) method can throw a PropertyVetoException if the listener doesn’t like the requested change. public interface VetoableChangeListener extends EventListener { public void vetoableChange(PropertyChangeEvent propertyChangeEvent) throws PropertyVetoException; }
■Note Only one Swing class, JInternalFrame, has constrained properties. The listener is meant primarily for programmers to use with their own newly created components.
Listening to JComponent Events with an AncestorListener You can use an AncestorListener to find out when a component moves, is made visible, or is made invisible. It’s useful if you permit your users to customize their screens by moving components around and possibly removing components from the screens. public interface AncestorListener extends EventListener { public void ancestorAdded(AncestorEvent ancestorEvent); public void ancestorMoved(AncestorEvent ancestorEvent); public void ancestorRemoved(AncestorEvent ancestorEvent); }
81
82
CHAPTER 4 ■ CORE SWING COMPONENTS
To demonstrate, Listing 4-2 associates an AncestorListener with the root pane of a JFrame. You’ll see the messages Removed, Added, and Moved when the program first starts up. In addition, you’ll see Moved messages when you drag the frame around. Listing 4-2. Listening for Ancestor Events import java.awt.*; import javax.swing.*; import javax.swing.event.*; public class AncestorSampler { public static void main (String args[]) { Runnable runner = new Runnable() { public void run() { JFrame frame = new JFrame("Ancestor Sampler"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); AncestorListener ancestorListener = new AncestorListener() { public void ancestorAdded(AncestorEvent ancestorEvent) { System.out.println ("Added"); } public void ancestorMoved(AncestorEvent ancestorEvent) { System.out.println ("Moved"); } public void ancestorRemoved(AncestorEvent ancestorEvent) { System.out.println ("Removed"); } }; frame.getRootPane().addAncestorListener(ancestorListener); frame.setSize(300, 200); frame.setVisible(true); frame.getRootPane().setVisible(false); frame.getRootPane().setVisible(true); } }; EventQueue.invokeLater(runner); } }
Listening to Inherited Events of a JComponent In addition to the ability to listen for an instance of an AncestorEvent or PropertyChangeEvent with a JComponent, the JComponent inherits the ability to listen to many other events from its Container and Component superclasses.
CHAPTER 4 ■ CORE SWING COMPONENTS
Table 4-4 lists ten event listeners. You may find yourself using the JComponent listener interfaces quite a bit, but the older ones work, too. Use the ones most appropriate for the task at hand.
JToolTip Class The Swing components support the ability to display brief pop-up messages when the cursor rests over them. The class used to display pop-up messages is JToolTip.
Creating a JToolTip Calling the public void setToolTipText(String text) method of JComponent automatically causes the creation of a JToolTip instance when the mouse rests over a component with the installed pop-up message. You don’t normally call the JToolTip constructor directly. There’s only one constructor, and it’s of the no-argument variety. Tooltip text is normally one line long. However, if the text string begins with (in any case), then the contents can be any HTML 3.2 formatted text. For instance, the following line causes the pop-up message shown in Figure 4-3: component.setToolTipText("Tooltip Message");
Figure 4-3. HTML-based tooltip text
Creating Customized JToolTip Objects You can easily customize the display characteristics for all pop-up messages by setting UIResource elements for JToolTip, as shown in the “Customizing a JToolTip Look and Feel” section later in this chapter. The JComponent class defines an easy way for you to customize the display characteristics of the tooltip when it’s placed over a specific component. Simply subclass the component you want to customize and override its inherited public JToolTip createToolTip() method. The createToolTip() method is called when the ToolTipManager has determined that it’s time to display the pop-up message. To customize the pop-up tooltip appearance, just override the method and customize the JToolTip returned from the inherited method. For instance, the following source demonstrates the setting of a custom coloration for the tooltip for a JButton, as shown in Figure 4-4. JButton b = new JButton("Hello, World") { public JToolTip createToolTip() { JToolTip tip = super.createToolTip(); tip.setBackground(Color.YELLOW); tip.setForeground(Color.RED); return tip; } };
CHAPTER 4 ■ CORE SWING COMPONENTS
Figure 4-4. Tooltip text displayed with custom colors After the JToolTip has been created, you can configure the inherited JComponent properties or any of the properties specific to JToolTip, as shown in Table 4-5. Table 4-5. JToolTip Properties
Property Name
Data Type
Access
accessibleContext
AccessibleContext
Read-only
component
JComponent
Read-write
tipText
String
Read-write
UI
ToolTipUI
Read-only
UIClassID
String
Read-only
Displaying Positional Tooltip Text Swing components can even support the display of different tooltip text, depending on where the mouse pointer is located. This requires overriding the public boolean contains(int x, int y) method, which originates from the Component class. For instance, after enhancing the customized JButton created in the previous section (Figure 4-4), the tooltip text will differ, depending on whether or not the mouse pointer is within 50 pixels from the left edge of the component. JButton button = new JButton("Hello, World") { public JToolTip createToolTip() { JToolTip tip = super.createToolTip(); tip.setBackground(Color.YELLOW); tip.setForeground(Color.RED); return tip; } public boolean contains(int x, int y) { if (x < 50) { setToolTipText("Got Green Eggs?"); } else { setToolTipText("Got Ham?"); } return super.contains(x, y); } };
85
86
CHAPTER 4 ■ CORE SWING COMPONENTS
Customizing a JToolTip Look and Feel Each installable Swing look and feel provides a different JToolTip appearance and a set of default UIResource value settings. Figure 4-5 shows the appearance of the JToolTip component for the preinstalled set of look and feel types: Motif, Windows, and Ocean.
Motif
Windows
Ocean
Figure 4-5. JToolTip under different look and feel types The available set of UIResource-related properties for a JToolTip is shown in Table 4-6. For the JToolTip component, there are nine different properties.
Table 4-6. JToolTip UIResource Elements
Property String
Object Type
ToolTip.background
Color
ToolTip.backgroundInactive
Color
ToolTip.border
Border
ToolTip.borderInactive
Color
ToolTip.font
Font
ToolTip.foreground
Color
ToolTip.foregroundInactive
Color
ToolTip.hideAccelerator
Boolean
ToolTipUI
String
As noted earlier in this chapter, the JToolTip class supports the display of arbitrary HTML content. This permits the display of multiple-column and multiple-row input.
ToolTipManager Class Although the JToolTip is something of a passive object, in the sense that the JComponent creates and shows the JToolTip on its own, there are many more configurable aspects of its usage. However, these configurable aspects are the responsibility of the class that manages tooltips, not the JToolTip itself. The class that manages tooltip usage is aptly named ToolTipManager. With the Singleton design pattern, no constructor for ToolTipManager exists. Instead, you have access to the current manager through the static sharedInstance() method of ToolTipManager.
CHAPTER 4 ■ CORE SWING COMPONENTS
ToolTipManager Properties Once you have accessed the shared instance of ToolTipManager, you can customize when and if tooltip text appears. As Table 4-7 shows, there are five configurable properties.
Table 4-7. ToolTipManager Properties
Property Name
Data Type
Access
dismissDelay
int
Read-write
enabled
boolean
Read-write
initialDelay
int
Read-write
lightWeightPopupEnabled
boolean
Read-write
reshowDelay
int
Read-only
Initially, tooltips are enabled, but you can disable them with ToolTipManager. sharedInstance().setEnabled(false). This allows you to always associate tooltips with components, while letting the end user enable and disable them when desired. There are three timing-oriented properties: initialDelay, dismissDelay, and reshowDelay. They all measure time in milliseconds. The initialDelay property is the number of milliseconds the user must rest the mouse inside the component before the appropriate tooltip text appears. The dismissDelay specifies the length of time the text appears while the mouse remains motionless; if the user moves the mouse, it also causes the text to disappear. The reshowDelay determines how long a user must remain outside a component before reentry would cause the pop-up text to reappear. The lightWeightPopupEnabled property is used to determine the pop-up window type to hold the tooltip text. If the property is true and the pop-up text fits entirely within the bounds of the top-level window, the text appears within a Swing JPanel. If this property is false and the pop-up text fits entirely within the bounds of the top-level window, the text appears within an AWT Panel. If part of the text wouldn’t appear within the top-level window no matter what the property setting is, the pop-up text would appear within a Window. Although not properties of ToolTipManager, two other methods of ToolTipManager are worth mentioning: public void registerComponent(JComponent component) public void unregisterComponent(JComponent component) When you call the setToolTipText() method of JComponent, this causes the component to register itself with the ToolTipManager. There are times, however, when you need to register a component directly. This is necessary when the display of part of a component is left to another renderer. With JTree, for instance, a TreeCellRenderer displays each node of the tree. When the renderer displays the tooltip text, you “register” the JTree and tell the renderer what text to display.
87
88
CHAPTER 4 ■ CORE SWING COMPONENTS
JTree tree = new JTree(...); ToolTipManager.sharedInstance().registerComponent(tree); TreeCellRenderer renderer = new ATreeCellRenderer(...); tree.setCellRenderer(renderer); ... public class ATreeCellRenderer implements TreeCellRenderer { ... public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) { ... renderer.setToolTipText("Some Tip"); return renderer; } }
■Note If this sounds confusing, don’t worry. We’ll revisit the JTree in Chapter 17.
JLabel Class The first real Swing component to examine closely is the simplest, the JLabel. The JLabel serves as the replacement component for the AWT Label but it can do much more. Whereas the AWT Label is limited to a single line of text, the Swing JLabel can have text, images, or both. The text can be a single line of text or HTML. In addition JLabel can support different enabled and disabled images. Figure 4-6 shows some sample JLabel components.
Figure 4-6. Sample JLabel components
■Note A JLabel subclass is used as the default renderer for each of the JList, JComboBox, JTable, and JTree components.
CHAPTER 4 ■ CORE SWING COMPONENTS
Creating a JLabel There are six constructors for JLabel: public JLabel() JLabel label = new JLabel(); public JLabel(Icon image) Icon icon = new ImageIcon("dog.jpg"); JLabel label = new JLabel(icon); public JLabel(Icon image, int horizontalAlignment) Icon icon = new ImageIcon("dog.jpg"); JLabel label = new JLabel(icon, JLabel.RIGHT); public JLabel(String text) JLabel label = new JLabel("Dog"); public JLabel(String text, int horizontalAlignment) JLabel label = new JLabel("Dog", JLabel.RIGHT); public JLabel(String text, Icon icon, int horizontalAlignment) Icon icon = new ImageIcon("dog.jpg"); JLabel label = new JLabel("Dog", icon, JLabel.RIGHT); With the constructors for JLabel, you can customize any of three properties of the JLabel: text, icon, or horizontalAlignment. By default, the text and icon properties are empty, whereas the initial horizontalAlignment property setting depends on the constructor arguments. These settings can be any of JLabel.LEFT, JLabel.CENTER, or JLabel.RIGHT. In most cases, not specifying the horizontalAlignment setting results in a left-aligned label. However, if only the initial icon is specified, then the default alignment is centered.
JLabel Properties Table 4-8 shows the 14 properties of JLabel. They allow you to customize the content, position, and (in a limited sense) the behavior of the JLabel.
Table 4-8. JLabel Properties
Property Name
Data Type
Access
accessibleContext
AccessibleContext
Read-only
disabledIcon
Icon
Read-write bound
displayedMnemonic
char
Read-write bound
displayedMnemonicIndex
int
Read-write bound
horizontalAlignment
int
Read-write bound
horizontalTextPosition
int
Read-write bound
89
90
CHAPTER 4 ■ CORE SWING COMPONENTS
Table 4-8. JLabel Properties (Continued)
Property Name
Data Type
Access
icon
Icon
Read-write bound
iconTextGap
int
Read-write bound
labelFor
Component
Read-write bound
text
String
Read-write bound
UI
LabelUI
Read-write
UIClassID
String
Read-only
verticalAlignment
int
Read-write bound
verticalTextPosition
int
Read-write bound
The content of the JLabel is the text and its associated image. Displaying an image within a JLabel will be discussed in the “Interface Icon” section later in this chapter. However, you can display different icons, depending on whether the JLabel is enabled or disabled. By default, the icon is a grayscaled version of the enabled icon, if the enabled icon comes from an Image object (ImageIcon, as described later in the chapter). If the enabled icon doesn’t come from an Image, there’s no icon when JLabel is disabled, unless manually specified. The position of the contents of the JLabel is described by four different properties: horizontalAlignment, horizontalTextPosition, verticalAlignment, and verticalTextPosition. The horizontalAlignment and verticalAlignment properties describe the position of the contents of the JLabel within the container in which it’s placed.
■Note Alignments have an effect only if there’s extra space for the layout manager to position the component. If you’re using a layout manager such as FlowLayout, which sizes components to their preferred size, these settings will effectively be ignored.
The horizontal position can be any of the JLabel constants LEFT, RIGHT, or CENTER. The vertical position can be TOP, BOTTOM, or CENTER. Figure 4-7 shows various alignment settings, with the label reflecting the alignments. The text position properties reflect where the text is positioned relative to the icon when both are present. The properties can be set to the same constants as the alignment constants. Figure 4-8 shows various text position settings, with each label reflecting the setting.
■Note The constants for the different positions come from the SwingConstants interface that the JLabel class implements.
CHAPTER 4 ■ CORE SWING COMPONENTS
Figure 4-7. Various JLabel alignments
Figure 4-8. Various JLabel text positions
JLabel Event Handling No event-handling capabilities are specific to the JLabel. Besides the event-handling capabilities inherited through JComponent, the closest thing there is for event handling with the JLabel is the combined usage of the displayedMnemonic, displayedMnemonicIndex, and labelFor properties. When the displayedMnemonic and labelFor properties are set, pressing the keystroke specified by the mnemonic, along with the platform-specific hotkey (usually Alt), causes the input focus to shift to the component associated with the labelFor property. This can be helpful when a component doesn’t have its own manner of displaying a mnemonic setting, such as with all the text input components. Here is an example, which results in the display shown in Figure 4-9: JLabel label = new JLabel("Username"); JTextField textField = new JTextField(); label.setDisplayedMnemonic(KeyEvent.VK_U); label.setLabelFor(textField);
Figure 4-9. Using a JLabel to display the mnemonic for another component
91
92
CHAPTER 4 ■ CORE SWING COMPONENTS
The displayedMnemonicIndex property adds the ability for the mnemonic highlighted to not be the first instance of mnemonic in the label’s text. The index you specify represents the position in the text, not the instance of the mnemonic. To highlight the second e in Username, you would specify an index of 7: label.setDisplayedMnemonicIndex(7).
■Note The component setting of the labelFor property is stored as a client property of the JLabel with the LABELED_BY_PROPERTY key constant. The setting is used for accessibility purposes.
Customizing a JLabel Look and Feel Each installable Swing look and feel provides a different JLabel appearance and set of default UIResource value settings. Although appearances differ based on the current look and feel, the differences are minimal within the preinstalled set of look and feel types. Table 4-9 shows the available set of UIResource-related properties for a JLabel. There are eight different properties for the JLabel component. Table 4-9. JLabel UIResource Elements
Property String
Object Type
Label.actionMap
ActionMap
Label.background
Color
Label.border
Border
Label.disabledForeground
Color
Label.disabledShadow
Color
Label.font
Font
Label.foreground
Color
LabelUI
String
Interface Icon The Icon interface is used to associate glyphs with various components. A glyph (like a symbol on a highway sign that conveys information nonverbally, such as “winding road ahead!”) can be a simple drawing or a GIF image loaded from disk with the ImageIcon class. The interface contains two properties describing the size and a method to paint the glyph. public interface Icon { // Properties public int getIconHeight(); public int getIconWidth(); // Other methods public void paintIcon(Component c, Graphics g, int x, int y); }
CHAPTER 4 ■ CORE SWING COMPONENTS
Creating an Icon Creating an Icon is as simple as implementing the interface. All you need to do is specify the size of the icon and what to draw. Listing 4-3 shows one such Icon implementation. The icon is a diamond-shaped glyph in which the size, color, and filled-status are all configurable.
■Tip In implementing the paintIcon() method of the Icon interface, translate the drawing coordinates of the graphics context based on the x and y position passed in, and then translate them back when the drawing is done. This greatly simplifies the different drawing operations.
Listing 4-3. Reusable Diamond Icon Definition import javax.swing.*; import java.awt.*; public class DiamondIcon implements Icon { private Color color; private boolean selected; private int width; private int height; private Polygon poly; private static final int DEFAULT_WIDTH = 10; private static final int DEFAULT_HEIGHT = 10; public DiamondIcon(Color color) { this(color, true, DEFAULT_WIDTH, DEFAULT_HEIGHT); } public DiamondIcon(Color color, boolean selected) { this(color, selected, DEFAULT_WIDTH, DEFAULT_HEIGHT); } public DiamondIcon(Color color, boolean selected, int width, int height) { this.color = color; this.selected = selected; this.width = width; this.height = height; initPolygon(); } private void initPolygon() { poly = new Polygon(); int halfWidth = width/2; int halfHeight = height/2; poly.addPoint(0, halfHeight); poly.addPoint(halfWidth, 0);
93
94
CHAPTER 4 ■ CORE SWING COMPONENTS
poly.addPoint(width, halfHeight); poly.addPoint(halfWidth, height); } public int getIconHeight() { return height; } public int getIconWidth() { return width; } public void paintIcon(Component c, Graphics g, int x, int y) { g.setColor(color); g.translate(x, y); if (selected) { g.fillPolygon(poly); } else { g.drawPolygon(poly); } g.translate(-x, -y); } }
Using an Icon Once you have your Icon implementation, using the Icon is as simple as finding a component with an appropriate property. For example, here’s the icon with a JLabel: Icon icon = new DiamondIcon(Color.RED, true, 25, 25); JLabel label = new JLabel(icon); Figure 4-10 shows what such a label might look like.
Figure 4-10. Using an Icon in a JLabel
ImageIcon Class The ImageIcon class presents an implementation of the Icon interface for creating glyphs from AWT Image objects, whether from memory (a byte[ ]), off a disk (a file name), or over the network (a URL). Unlike with regular Image objects, the loading of an ImageIcon is immediately started when the ImageIcon is created, though it might not be fully loaded when used. In addition,
CHAPTER 4 ■ CORE SWING COMPONENTS
unlike Image objects, ImageIcon objects are serializable so that they can be easily used by JavaBean components.
Creating an ImageIcon There are nine constructors for an ImageIcon: public ImageIcon() Icon icon = new ImageIcon(); icon.setImage(anImage); public ImageIcon(Image image) Icon icon = new ImageIcon(anImage); public ImageIcon(String filename) Icon icon = new ImageIcon(filename); public ImageIcon(URL location) Icon icon = new ImageIcon(url); public ImageIcon(byte imageData[]) Icon icon = new ImageIcon(aByteArray); public ImageIcon(Image image, String description) Icon icon = new ImageIcon(anImage, "Duke"); public ImageIcon(String filename, String description) Icon icon = new ImageIcon(filename, filename); public ImageIcon(URL location, String description) Icon icon = new ImageIcon(url, location.getFile()); public ImageIcon(byte imageData[], String description) Icon icon = new ImageIcon(aByteArray, "Duke"); The no-argument version creates an uninitialized version (empty). The remaining eight offer the ability to create an ImageIcon from an Image, byte array, file name String, or URL, with or without a description.
Using an ImageIcon Using an ImageIcon is as simple as using an Icon: just create the ImageIcon and associate it with a component. Icon icon = new ImageIcon("Warn.gif"); JLabel label3 = new JLabel("Warning", icon, JLabel.CENTER)
95
96
CHAPTER 4 ■ CORE SWING COMPONENTS
ImageIcon Properties Table 4-10 shows the six properties of ImageIcon. The height and width of the ImageIcon are the height and width of the actual Image object. The imageLoadStatus property represents the results of the loading of the ImageIcon from the hidden MediaTracker, either MediaTracker.ABORTED, MediaTracker.ERRORED, or MediaTracker.COMPLETE.
Table 4-10. ImageIcon Properties
Property Name
Data Type
Access
description
String
Read-write
iconHeight
int
Read-only
iconWidth
int
Read-only
image
Image
Read-write
imageLoadStatus
int
Read-only
imageObserver
ImageObserver
Read-write
Sometimes, it’s useful to use an ImageIcon to load an Image, and then just ask for the Image object from the Icon. ImageIcon imageIcon = new ImageIcon(...); Image image = imageIcon.getImage(); There is one major problem with using ImageIcon objects: They don’t work when the image and class file using the icon are both loaded in a JAR (Java archive) file, unless you explicitly specify the full URL for the file within the JAR (jar:http://www.example.com/directory/ foo.jar!/com/example/image.gif). You can’t just specify the file name as a String and let the ImageIcon find the file. You must manually get the image data first, and then pass the data along to the ImageIcon constructor. To help with loading images outside JAR files, Listing 4-4 shows an ImageLoader class that provides a public static Image getImage(Class relativeClass, String filename) method. You specify both the base class where the image file relative is found and the file name for the image file. Then you just need to pass the Image object returned to the constructor of ImageIcon. Listing 4-4. Image Loading Support Class import java.awt.*; import java.io.*; public final class ImageLoader { private ImageLoader() { }
CHAPTER 4 ■ CORE SWING COMPONENTS
public static Image getImage(Class relativeClass, String filename) { Image returnValue = null; InputStream is = relativeClass.getResourceAsStream(filename); if (is != null) { BufferedInputStream bis = new BufferedInputStream(is); ByteArrayOutputStream baos = new ByteArrayOutputStream(); try { int ch; while ((ch = bis.read()) != -1) { baos.write(ch); } returnValue = Toolkit.getDefaultToolkit().createImage(baos.toByteArray()); } catch (IOException exception) { System.err.println("Error loading: " + filename); } } return returnValue; } } Here’s how you use the helper class: Image warnImage = ImageLoader.getImage(LabelJarSample.class, "Warn.gif"); Icon warnIcon = new ImageIcon(warnImage); JLabel label2 = new JLabel(warnIcon);
■Tip Keep in mind that the Java platform supports GIF89A animated images.
GrayFilter Class One additional class worth mentioning here is GrayFilter. Many of the Swing component classes rely on this class to create a disabled version of an Image to be used as an Icon. The components use the class automatically, but there might be times when you need an AWT ImageFilter that does grayscales. You can convert an Image from normal to grayed out with a call to the one useful method of the class: public static Image createDisabledImage (Image image). Image normalImage = ... Image grayImage = GrayFilter.createDisabledImage(normalImage) You can now use the grayed-out image as the Icon on a component: Icon warningIcon = new ImageIcon(grayImage); JLabel warningLabel = new JLabel(warningIcon);
97
98
CHAPTER 4 ■ CORE SWING COMPONENTS
AbstractButton Class The AbstractButton class is an important Swing class that works behind the scenes as the parent class of all the Swing button components, as shown at the top of Figure 4-1. The JButton, described in the “JButton Class” section later in this chapter, is the simplest of the subclasses. The remaining subclasses are described in later chapters. Each of the AbstractButton subclasses uses the ButtonModel interface to store their data model. The DefaultButtonModel class is the default implementation used. In addition, you can group any set of AbstractButton objects into a ButtonGroup. Although this grouping is most natural with the JRadioButton and JRadioButtonMenuItem components, any of the AbstractButton subclasses will work.
AbstractButton Properties Table 4-11 lists the 32 properties (with mnemonic listed twice) of AbstractButton shared by all its subclasses. They allow you to customize the appearance of all the buttons.
Table 4-11. AbstractButton Properties
Property Name
Data Type
Access
action
Action
Read-write bound
actionCommand
String
Read-write
actionListeners
ActionListener[ ]
Read-only
borderPainted
boolean
Read-write bound
changeListeners
ChangeListener[ ]
Read-only
contentAreaFilled
boolean
Read-write bound
disabledIcon
Icon
Read-write bound
disabledSelectedIcon
Icon
Read-write bound
displayedMnemonicIndex
int
Read-write bound
enabled
boolean
Write-only
focusPainted
boolean
Read-write bound
horizontalAlignment
int
Read-write bound
horizontalTextPosition
int
Read-write bound
icon
Icon
Read-write bound
iconTextGap
int
Read-write bound
itemListeners
ItemListener[ ]
Read-only
layout
LayoutManager
Write-only
margin
Insets
Read-write bound
mnemonic
char
Read-write bound
CHAPTER 4 ■ CORE SWING COMPONENTS
Table 4-11. AbstractButton Properties (Continued)
Property Name
Data Type
Access
mnemonic
int
Write-only
model
ButtonModel
Read-write bound
multiClickThreshhold
long
Read-write
pressedIcon
Icon
Read-write bound
rolloverEnabled
boolean
Read-write bound
rolloverIcon
Icon
Read-write bound
rolloverSelectedIcon
Icon
Read-write bound
selected
boolean
Read-write
selectedIcon
Icon
Read-write bound
selectedObjects
Object[ ]
Read-only
text
String
Read-write bound
UI
ButtonUI
Read-write
verticalAlignment
int
Read-write bound
verticalTextPosition
int
Read-write bound
■Note AbstractButton has a deprecated label property. You should use the equivalent text property instead.
One property worth mentioning is multiClickThreshhold. This property represents a time, in milliseconds. If a button is selected with a mouse multiple times within this time period, additional action events won’t be generated. By default, the value is zero, meaning each press generates an event. To avoid accidental duplicate submissions from happening in important dialogs, set this value to some reasonable level above zero.
■Tip Keep in mind that all AbstractButton children can use HTML with its text property to display HTML content within the label. Just prefix the property setting with the string .
ButtonModel/Class DefaultButtonModel Interface The ButtonModel interface is used to describe the current state of the AbstractButton component. In addition, it describes the set of event listeners objects that are supported by all the different AbstractButton children. Its definition follows:
99
100
CHAPTER 4 ■ CORE SWING COMPONENTS
public interface ButtonModel extends ItemSelectable { // Properties public String getActionCommand(); public void setActionCommand(String newValue); public boolean isArmed(); public void setArmed(boolean newValue); public boolean isEnabled(); public void setEnabled(boolean newValue); public void setGroup(ButtonGroup newValue); public int getMnemonic(); public void setMnemonic(int newValue); public boolean isPressed(); public void setPressed(boolean newValue); public boolean isRollover(); public void setRollover(boolean newValue); public boolean isSelected(); public void setSelected(boolean newValue); // Listeners public void addActionListener(ActionListener listener); public void removeActionListener(ActionListener listener); public void addChangeListener(ChangeListener listener); public void removeChangeListener(ChangeListener listener); public void addItemListener(ItemListener listener); public void removeItemListener(ItemListener listener); } The specific implementation of ButtonModel you’ll use, unless you create your own, is the DefaultButtonModel class. The DefaultButtonModel class defines all the event registration methods for the different event listeners and manages the button state and grouping within a ButtonGroup. Its set of nine properties is shown in Table 4-12. They all come from the ButtonGroup interface, except selectedObjects, which is new to the DefaultButtonModel class, but more useful to the JToggleButton.ToggleButtonModel, which is discussed in Chapter 5. Table 4-12. DefaultButtonModel Properties
Property Name
Data Type
Access
actionCommand
String
Read-write
armed
boolean
Read-write
enabled
boolean
Read-write
group
ButtonGroup
Read-write
mnemonic
int
Read-write
pressed
boolean
Read-write
rollover
boolean
Read-write
selected
boolean
Read-write
selectedObjects
Object[ ]
Read-only
CHAPTER 4 ■ CORE SWING COMPONENTS
Most of the time, you don’t access the ButtonModel directly. Instead, the components that use the ButtonModel wrap their property calls to update the model’s properties.
■Note The DefaultButtonModel also lets you get the listeners for a specific type with public EventListener[ ] getListeners(Class listenerType).
Understanding AbstractButton Mnemonics A mnemonic is a special keyboard accelerator that when pressed causes a particular action to happen. In the case of the JLabel discussed earlier in the “JLabel Class” section, pressing the displayed mnemonic causes the associated component to get the input focus. In the case of an AbstractButton, pressing the mnemonic for a button causes its selection. The actual pressing of the mnemonic requires the pressing of a look-and-feel–specific hotkey (the key tends to be the Alt key). So, if the mnemonic for a button were the B key, you would need to press Alt-B to activate the button with the B-key mnemonic. When the button is activated, registered listeners will be notified of appropriate state changes. For instance, with the JButton, all ActionListener objects would be notified. If the mnemonic key is part of the text label for the button, you’ll see the character underlined. This does depend on the current look and feel and could be displayed differently. In addition, if the mnemonic isn’t part of the text label, there will not be a visual indicator for selecting the particular mnemonic key, unless the look and feel shows it in the tooltip text. Figure 4-11 shows two buttons: one with a W-key mnemonic, and the other with an H-key mnemonic. The left button has a label with W in its contents, so it shows the first W underlined. The second component doesn’t benefit from this behavior on the button, but in the Ocean look and feel, identifies it only if the tooltip text is set and shown.
Figure 4-11. AbstractButton mnemonics To assign a mnemonic to an abstract button, you can use either one of the setMnemonic() methods. One accepts a char argument and the other an int. Personally, I prefer the int variety, in which the value is one of the many VK_* constants from the KeyEvent class. You can also specify the mnemonic by position via the displayedMnemonicIndex property. AbstractButton button1 = new JButton("Warning"); button1.setMnemonic(KeyEvent.VK_W); content.add(button1);
101
102
CHAPTER 4 ■ CORE SWING COMPONENTS
Understanding AbstractButton Icons AbstractButton has seven specific icon properties. The natural or default icon is the icon property. It is used for all cases unless a different icon is specified or there is a default behavior provided by the component. The selectedIcon property is the icon used when the button is selected. The pressedIcon is used when the button is pressed. Which of these two icons is used depends on the component, because a JButton is pressed but not selected, whereas a JCheckBox is selected but not pressed. The disabledIcon and disabledSelectedIcon properties are used when the button has been disabled with setEnabled(false). By default, if the icon is an ImageIcon, a grayscaled version of the icon will be used. The remaining two icon properties, rolloverIcon and rolloverSelectedIcon, allow you to display different icons when the mouse moves over the button (and rolloverEnabled is true).
Understanding Internal AbstractButton Positioning The horizontalAlignment, horizontalTextPosition, verticalAlignment, and verticalTextPosition properties share the same settings and behavior as the JLabel class. They’re listed in Table 4-13. Table 4-13. AbstractButton Position Constants
Position Property
Available Settings
horizontalAlignment
LEFT, CENTER, RIGHT
horizontalTextPosition
LEFT, CENTER, RIGHT
verticalAlignment
TOP, CENTER, BOTTOM
verticalTextPosition
TOP, CENTER, BOTTOM
Handling AbstractButton Events Although you do not create AbstractButton instances directly, you do create subclasses. All of them share a common set of event-handling capabilities. You can register PropertyChangeListener, ActionListener, ItemListener, and ChangeListener objects with abstract buttons. The PropertyChangeListener object will be discussed here, and the remaining objects listed will be discussed in later chapters, with the appropriate components. Like the JComponent class, the AbstractButton component supports the registering of PropertyChangeListener objects to detect when bound properties of an instance of the class change. Unlike the JComponent class, the AbstractButton component provides the following set of class constants to signify the different property changes: • BORDER_PAINTED_CHANGED_PROPERTY • CONTENT_AREA_FILLED_CHANGED_PROPERTY • DISABLED_ICON_CHANGED_PROPERTY • DISABLED_SELECTED_ICON_CHANGED_PROPERTY • FOCUS_PAINTED_CHANGED_PROPERTY
CHAPTER 4 ■ CORE SWING COMPONENTS
• HORIZONTAL_ALIGNMENT_CHANGED_PROPERTY • HORIZONTAL_TEXT_POSITION_CHANGED_PROPERTY • ICON_CHANGED_PROPERTY • MARGIN_CHANGED_PROPERTY • MNEMONIC_CHANGED_PROPERTY • MODEL_CHANGED_PROPERTY • PRESSED_ICON_CHANGED_PROPERTY • ROLLOVER_ENABLED_CHANGED_PROPERTY • ROLLOVER_ICON_CHANGED_PROPERTY • ROLLOVER_SELECTED_ICON_CHANGED_PROPERTY • SELECTED_ICON_CHANGED_PROPERTY • TEXT_CHANGED_PROPERTY • VERTICAL_ALIGNMENT_CHANGED_PROPERTY • VERTICAL_TEXT_POSITION_CHANGED_PROPERTY Therefore, instead of hard-coding specific text strings, you can create a PropertyChangeListener that uses these constants, as shown in Listing 4-5. Listing 4-5. Base PropertyChangeListener for AbstractButton import javax.swing.*; import java.beans.*; public class AbstractButtonPropertyChangeListener implements PropertyChangeListener { public void propertyChange(PropertyChangeEvent e) { String propertyName = e.getPropertyName(); if (e.getPropertyName().equals(AbstractButton.TEXT_CHANGED_PROPERTY)) { String newText = (String) e.getNewValue(); String oldText = (String) e.getOldValue(); System.out.println(oldText + " changed to " + newText); } else if (e.getPropertyName().equals(AbstractButton.ICON_CHANGED_PROPERTY)) { Icon icon = (Icon) e.getNewValue(); if (icon instanceof ImageIcon) { System.out.println("New icon is an image"); } } } }
103
104
CHAPTER 4 ■ CORE SWING COMPONENTS
JButton Class The JButton component is the basic AbstractButton component that can be selected. It supports text, images, and HTML-based labels, as shown in Figure 4-12.
Figure 4-12. Sample JButton components
Creating a JButton The JButton class has five constructors: public JButton() JButton button = new JButton(); public JButton(Icon image) Icon icon = new ImageIcon("dog.jpg"); JButton button = new JButton(icon); public JButton(String text) JButton button = new JButton("Dog"); public JButton(String text, Icon icon) Icon icon = new ImageIcon("dog.jpg"); JButton button = new JButton("Dog", icon); public JButton(Action action) Action action = ...; JButton button = new JButton(action); You can create a button with or without a text label or icon. The icon represents the default or selected icon property from AbstractButton.
■Note Creating a JButton from an Action initializes the text label, icon, enabled status, and tooltip text. In addition, the ActionListener of the Action will be notified upon button selection.
CHAPTER 4 ■ CORE SWING COMPONENTS
JButton Properties The JButton component doesn’t add much to the AbstractButton. As Table 4-14 shows, of the four properties of JButton, the only new behavior added is enabling the button to be the default.
Table 4-14. JButton Properties
Property Name
Data Type
Access
accessibleContext
AccessibleContext
Read-only
defaultButton
boolean
Read-only
defaultCapable
boolean
Read-write bound
UIClassID
String
Read-only
The default button tends to be drawn with a different and darker border than the remaining buttons. When a button is the default, pressing the Enter key while in the top-level window causes the button to be selected. This works only as long as the component with the input focus, such as a text component or another button, doesn’t consume the Enter key. Because the defaultButton property is read-only, how (you might be asking) do you set a button as the default? All top-level Swing windows contain a JRootPane, to be described in Chapter 8. You tell this JRootPane which button is the default by setting its defaultButton property. Only buttons whose defaultCapable property is true can be configured to be the default. Figure 4-13 shows the top-right button set as the default.
Figure 4-13. Setting a default button Listing 4-6 demonstrates setting the default button component, as well as using a basic JButton. If the default button appearance doesn’t seem that obvious in Figure 4-13, wait until the JOptionPane is described in Chapter 9, where the difference in appearance will be more obvious. Figure 4-13 uses a 2-by-2 GridLayout for the screen. The extra two arguments to the constructor represent gaps to help make the default button’s appearance more obvious.
105
106
CHAPTER 4 ■ CORE SWING COMPONENTS
Listing 4-6. Configuring a Default Button import javax.swing.*; import java.awt.*; import java.awt.event.*; public class DefaultButton { public static void main(String args[]) { Runnable runner = new Runnable() { public void run() { JFrame frame = new JFrame("DefaultButton"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setLayout(new GridLayout(2, 2, 10, 10)); JButton button1 = new JButton("Text Button"); button1.setMnemonic(KeyEvent.VK_B); frame.add(button1); Icon warnIcon = new ImageIcon("Warn.gif"); JButton button2 = new JButton(warnIcon); frame.add(button2); JButton button3 = new JButton("Warning", warnIcon); frame.add(button3); String htmlButton = "<sup>HTML <sub><em>Button " + "Multi-line"; JButton button4 = new JButton(htmlButton); frame.add(button4); JRootPane rootPane = frame.getRootPane(); rootPane.setDefaultButton(button2); frame.setSize(300, 200); frame.setVisible(true); } }; EventQueue.invokeLater(runner); } }
Handling JButton Events The JButton component itself has no specific event-handling capabilities. They’re all inherited from AbstractButton. Although you can listen for change events, item events, and property change events, the most helpful listener with the JButton is the ActionListener.
CHAPTER 4 ■ CORE SWING COMPONENTS
When the JButton component is selected, all registered ActionListener objects are notified. When the button is selected, an ActionEvent is passed to each listener. This event passes along the actionCommand property of the button to help identify which button was selected when a shared listener is used across multiple components. If the actionCommand property hasn’t been explicitly set, the current text property is passed along instead. The explicit use of the actionCommand property is helpful with localization. Because the text property of the JButton is what the user sees, you as the handler of the button selection event listener cannot rely on a localized text label for determining which button was selected. So, while the text property can be localized so that a Yes button in English can say Sí in a Spanish version, if you explicitly set the actionCommand to be the "Yes" string, then no matter which language the user is running in, the actionCommand will remain "Yes" and not take on the localized text property setting. Listing 4-7 adds the event-handling capabilities to the default button example in Listing 4-6 (see Figure 4-13). Notice that the default button behavior works properly: press Enter from any component, and button 2 (the default) will be activated. Listing 4-7. Watching Button Selection Events import javax.swing.*; import java.awt.*; import java.awt.event.*; public class ActionButtonSample { public static void main(String args[]) { Runnable runner = new Runnable() { public void run() { JFrame frame = new JFrame("DefaultButton"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); ActionListener actionListener = new ActionListener() { public void actionPerformed(ActionEvent actionEvent) { String command = actionEvent.getActionCommand(); System.out.println("Selected: " + command); } }; frame.setLayout(new GridLayout(2, 2, 10, 10)); JButton button1 = new JButton("Text Button"); button1.setMnemonic(KeyEvent.VK_B); button1.setActionCommand("First"); button1.addActionListener(actionListener); frame.add(button1);
Customizing a JButton Look and Feel Each installable Swing look and feel provides a different JButton appearance and set of default UIResource value settings. Figure 4-14 shows the appearance of the JButton component for the preinstalled set of look and feel types: Motif, Windows, and Ocean.
Motif
Windows
Figure 4-14. JButton under different look and feel types
Ocean
CHAPTER 4 ■ CORE SWING COMPONENTS
The available set of UIResource-related properties for a JButton is shown in Table 4-15. For the JButton component, there are 34 different properties.
Table 4-15. JButton UIResource Elements
Property String
Object Type
Button.actionMap
ActionMap
Button.background
Color
Button.border
Border
Button.contentAreaFilled
Boolean
Button.darkShadow
Color
Button.dashedRectGapHeight
Integer
Button.dashedRectGapWidth
Integer
Button.dashedRectGapX
Integer
Button.dashedRectGapY
Integer
Button.defaultButtonFollowsFocus
Boolean
Button.disabledForeground
Color
Button.disabledGrayRange
Integer[ ]
Button.disabledShadow
Color
Button.disabledText
Color
Button.disabledToolBarBorderBackground
Color
Button.focus
Color
Button.focusInputMap
InputMap
Button.font
Font
Button.foreground
Color
Button.gradient
List
Button.highlight
Color
Button.icon
Icon
Button.iconTextGap
Integer
Button.light
Color
Button.margin
Insets
Button.rollover
Boolean
Button.rolloverIconType
String
Button.select
Color
Button.shadow
Color
Button.showMnemonics
Boolean
109
110
CHAPTER 4 ■ CORE SWING COMPONENTS
Table 4-15. JButton UIResource Elements (Continued)
Property String
Object Type
Button.textIconGap
Integer
Button.textShiftOffset
Integer
Button.toolBarBorderBackground
Color
ButtonUI
String
JPanel Class The last of the basic Swing components is the JPanel component. The JPanel component serves as both a general-purpose container object, replacing the AWT Panel container, and a replacement for the Canvas component, for those times when you need a drawable Swing component area.
Creating a JPanel There are four constructors for JPanel: public JPanel() JPanel panel = new JPanel(); public JPanel(boolean isDoubleBuffered) JPanel panel = new JPanel(false); public JPanel(LayoutManager manager) JPanel panel = new JPanel(new GridLayout(2,2)); public JPanel(LayoutManager manager, boolean isDoubleBuffered) JPanel panel = new JPanel(new GridLayout(2,2), false); With the constructors, you can either change the default layout manager from FlowLayout or change the default double buffering that is performed from true to false.
Using a JPanel You can use JPanel as your general-purpose container or as a base class for a new component. For the general-purpose container, the procedure is simple: Just create the panel, set its layout manager if necessary, and add components using the add() method. JPanel panel = new JPanel(); JButton okButton = new JButton("OK"); panel.add(okButton); JButton cancelButton = new JButton("Cancel"); panel.add(cancelButton);
CHAPTER 4 ■ CORE SWING COMPONENTS
When you want to create a new component, subclass JPanel and override the public void paintComponent(Graphics g) method. Although you can subclass JComponent directly, it seems more appropriate to subclass JPanel. Listing 4-8 demonstrates a simple component that draws an oval to fit the size of the component; it also includes a test driver. Listing 4-8. Oval Panel Component import java.awt.*; import javax.swing.*; public class OvalPanel extends JPanel { Color color; public OvalPanel() { this(Color.black); } public OvalPanel(Color color) { this.color = color; } public void paintComponent(Graphics g) { int width = getWidth(); int height = getHeight(); g.setColor(color); g.drawOval(0, 0, width, height); } public static void main(String args[]) { Runnable runner = new Runnable() { public void run() { JFrame frame = new JFrame("Oval Sample"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setLayout(new GridLayout(2, 2)); Color colors[] = {Color.RED, Color.BLUE, Color.GREEN, Color.YELLOW}; for (int i=0; i<4; i++) { OvalPanel panel = new OvalPanel(colors[i]); frame.add(panel); } frame.setSize(300, 200); frame.setVisible(true); } }; EventQueue.invokeLater(runner); } }
111
112
CHAPTER 4 ■ CORE SWING COMPONENTS
Figure 4-15 shows the test driver program results.
Figure 4-15. The new OvalPanel component
■Note By default, JPanel components are opaque. This differs from JComponent, whose opacity property setting by default is false. A false setting for opacity means the component is transparent.
Customizing a JPanel Look and Feel The available set of UIResource-related properties for a JPanel is shown in Table 4-16. For the JPanel component, there are five different properties. These settings may have an effect on the components within the panel.
Table 4-16. JPanel UIResource Elements
Property String
Object Type
Panel.background
Color
Panel.border
Border
Panel.font
Font
Panel.foreground
Color
PanelUI
String
Summary In this chapter, you explored the root of all Swing components: the JComponent class. From there, you looked at some of the common elements of all components, such as tooltips, as well as specific components such as JLabel. You also learned how to put glyphs (nonverbal images) on components with the help of the Icon interface and the ImageIcon class, and the GrayFilter image filter for disabled icons.
CHAPTER 4 ■ CORE SWING COMPONENTS
You also learned about the AbstractButton component, which serves as the root component for all Swing button objects. You looked at its data model interface, ButtonModel, and the default implementation of this interface, DefaultButtonModel. Next, you looked at the JButton class, which is the simplest of the AbstractButton implementations. And lastly, you looked at the JPanel as the basic Swing container object. In Chapter 5, you’ll start to dig into some of the more complex AbstractButton implementations: the toggle buttons.
113
CHAPTER 5 ■■■
Toggle Buttons N
ow that you’ve seen the capabilities of the relatively simple Swing components JLabel and JButton, it’s time to take a look at more active components, specifically those that can be toggled. These so-called toggleable components—JToggleButton, JCheckBox, and JRadioButton—provide the means for your users to select from among a set of options. These options are either on or off, or enabled or disabled. When presented in a ButtonGroup, only one of the options in the group can be selected at a time. To deal with this selection state, the components share a common data model with ToggleButtonModel. Let’s take a look at the data model, the components’ grouping mechanism with ButtonGroup, and the individual components.
ToggleButtonModel Class The JToggleButton.ToggleButtonModel class is a public inner class of JToggleButton. The class customizes the behavior of the DefaultButtonModel class, which, in turn, is an implementation of the ButtonModel interface. The customization affects the data models of all AbstractButton components in the same ButtonGroup—a class explored next. In short, a ButtonGroup is a logical grouping of AbstractButton components. At any one time, only one of the AbstractButton components in the ButtonGroup can have the selected property of its data model set to true. The remaining ones must be false. This does not mean that only one selected component in the group can exist at a time. If multiple components in a ButtonGroup share a ButtonModel, multiple selected components in the group can exist. If no components share a model, at most, the user can select one component in the group. Once the user has selected that one component, the user cannot interactively deselect the selection. However, programmatically, you can deselect all group elements. The definition of JToggleButton.ToggleButtonModel follows. public class ToggleButtonModel extends DefaultButtonModel { // Constructors public ToggleButtonModel(); // Properties public boolean isSelected(); public void setPressed(boolean newValue); public void setSelected(boolean newvalue); } 115
116
CHAPTER 5 ■ TOGGLE BUTTONS
The ToggleButtonModel class defines the default data model for both the JToggleButton and its subclasses JCheckBox and JRadioButton, described in this chapter, as well as the JCheckBoxMenuItem and JRadioButtonMenuItem classes described in Chapter 6.
■Note Internally, Swing’s HTML viewer component uses the ToggleButtonModel for its check box and radio button input form elements.
ButtonGroup Class Before describing the ButtonGroup class, let’s demonstrate its usage. The program shown in Listing 5-1 creates objects that use the ToggleButtonModel and places them into a single group. As the program demonstrates, in addition to adding the components into the screen’s container, you must add each component to the same ButtonGroup. This results in a pair of add() method calls for each component. Furthermore, the container for the button group tends to place components in a single column and to label the grouping for the user with a titled border, though neither of these treatments are required. Figure 5-1 shows the output of the program. Listing 5-1. Odd Collection of Button Components import javax.swing.*; import javax.swing.border.*; import java.awt.*; public class AButtonGroup { public static void main(String args[]) { Runnable runner = new Runnable() { public void run() { JFrame frame = new JFrame("Button Group"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); JPanel panel = new JPanel(new GridLayout(0, 1)); Border border = BorderFactory.createTitledBorder("Examples"); panel.setBorder(border); ButtonGroup group = new ButtonGroup(); AbstractButton abstract1 = new JToggleButton("Toggle Button"); panel.add(abstract1); group.add(abstract1); AbstractButton abstract2 = new JRadioButton("Radio Button"); panel.add(abstract2); group.add(abstract2);
CHAPTER 5 ■ TOGGLE BUTTONS
AbstractButton abstract3 = new JCheckBox("Check Box"); panel.add(abstract3); group.add(abstract3); AbstractButton abstract4 = new JRadioButtonMenuItem("Radio Button Menu Item"); panel.add(abstract4); group.add(abstract4); AbstractButton abstract5 = new JCheckBoxMenuItem("Check Box Menu Item"); panel.add(abstract5); group.add(abstract5); frame.add(panel, BorderLayout.CENTER); frame.setSize(300, 200); frame.setVisible(true); } }; EventQueue.invokeLater(runner); } }
Figure 5-1. ButtonGroup/ToggleButtonModel example As previously stated, the ButtonGroup class represents a logical grouping of AbstractButton components. The ButtonGroup is not a visual component; therefore, there’s nothing visual on screen when a ButtonGroup is used. Any AbstractButton component can be added to the grouping with public void add(AbstractButton abstractButton). Although any AbstractButton component can belong to a ButtonGroup, only when the data model for the component is ToggleButtonModel will the grouping have any effect. The result of having a component with a data model of ToggleButtonModel in a ButtonGroup is that after the component is selected, the ButtonGroup deselects any currently selected component in the group.
■Note Technically speaking, the model doesn’t need to be ToggleButtonModel as long as the custom model exhibits the same behavior of limiting the number of selected component models to one.
117
118
CHAPTER 5 ■ TOGGLE BUTTONS
Although the add() method is typically the only ButtonGroup method you’ll ever need, the following class definition shows that it’s not the only method of ButtonGroup in existence: public class ButtonGroup implements Serializable { // Constructor public ButtonGroup(); // Properties public int getButtonCount(); public Enumeration getElements(); public ButtonModel getSelection(); // Other methods public void add(AbstractButton aButton); public boolean isSelected(ButtonModel theModel) ; public void remove(AbstractButton aButton); public void setSelected(ButtonModel theModel, boolean newValue); } One interesting thing the class definition shows is that given a ButtonGroup, you cannot directly find out the selected AbstractButton. You can directly ask only which ButtonModel is selected. However, getElements() returns an Enumeration of all the AbstractButton elements in the group. You can then loop through all the buttons to find the selected one (or ones) by using code similar to the following: Enumeration elements = group.getElements(); while (elements.hasMoreElements()) { AbstractButton button = (AbstractButton)elements.nextElement(); if (button.isSelected()) { System.out.println("The winner is: " + button.getText()); break; // Don't break if sharing models -- could show multiple buttons selected } } The other interesting method of ButtonGroup is setSelected(). The two arguments of the method are a ButtonModel and a boolean. If the boolean value is false, the selection request is ignored. If the ButtonModel isn’t the model for a button in the ButtonGroup, then the ButtonGroup deselects the currently selected model, causing no buttons in the group to be selected. The proper usage of the method is to call the method with a model of a component in the group and a new state of true. For example, if aButton is an AbstractButton and aGroup is the ButtonGroup, then the method call would look like aGroup.setSelected(aButton.getModel(), true).
■Note If you add a selected button to a ButtonGroup that already has a previously selected button, the previous button retains its state and the newly added button loses its selection.
Now, let’s look at the various components whose data model is the ToggleButtonModel.
CHAPTER 5 ■ TOGGLE BUTTONS
JToggleButton Class The JToggleButton is the first of the toggleable components. It’s discussed first because it’s the parent class of the two other components that are not menu-oriented: JCheckBox and JRadioButton. The JToggleButton is like a JButton that stays depressed when selected, instead of bouncing back to an unselected state. To deselect the selected component, you must reselect it. JToggleButton isn’t a commonly used component, but you might find it useful on a toolbar, such as in Microsoft Word (for paragraph alignment, among other instances) or in a file dialog box, as shown in the upper-right corner of Figure 5-2. *4OGGLE"UTTON
Figure 5-2. Sample JToggleButton components from file chooser Defining the JToggleButton structure are two objects that customize the AbstractButton parent class: ToggleButtonModel and ToggleButtonUI. The ToggleButtonModel class represents a customized ButtonModel data model for the component, whereas ToggleButtonUI is the user interface delegate. Now that you know about the different pieces of a JToggleButton, let’s find out how to use them.
Creating JToggleButton Components Eight constructors are available for JToggleButton: public JToggleButton() JToggleButton aToggleButton = new JToggleButton(); public JToggleButton(Icon icon) JToggleButton aToggleButton = new JToggleButton(new DiamondIcon(Color.PINK))
119
120
CHAPTER 5 ■ TOGGLE BUTTONS
public JToggleButton(Icon icon, boolean selected) JToggleButton aToggleButton = new JToggleButton(new DiamondIcon(Color.PINK), true); public JToggleButton(String text) JToggleButton aToggleButton = new JToggleButton("Sicilian"); public JToggleButton(String text, boolean selected) JToggleButton aToggleButton = new JToggleButton("Thin Crust", true); public JToggleButton(String text, Icon icon) JToggleButton aToggleButton = new JToggleButton("Thick Crust", new DiamondIcon(Color.PINK)); public JToggleButton(String text, Icon icon, boolean selected) JToggleButton aToggleButton = new JToggleButton("Stuffed Crust", new DiamondIcon(Color.PINK), true); public JToggleButton(Action action) Action action = ...; JToggleButton aToggleButton = new JToggleButton(action); Each allows you to customize one or more of the label, icon, or initial selection state. Unless specified otherwise, the label is empty with no text or icon, and the button initially is not selected.
■Note Surprisingly, Swing lacks a constructor that accepts only an initial state of a boolean setting. Lacking this constructor, you need to create a JToggleButton with the no-argument constructor variety, and then call setSelected(boolean newValue) directly or work with an Action.
JToggleButton Properties After creating a JToggleButton, you can modify each of its many properties. Although there are about 100 inherited properties, Table 5-1 shows only the two introduced with JToggleButton. The remaining properties come from AbstractButton, JComponent, Container, and Component.
Table 5-1. JToggleButton Properties
Property Name
Data Type
Access
accessibleContext
AccessibleContext
Read-only
UIClassID
String
Read-only
You can change one or more of the text, icon, or selected properties set in the constructor, as well as any of the other AbstractButton properties described in Chapter 4. You configure
CHAPTER 5 ■ TOGGLE BUTTONS
the primary three properties with the appropriate getter and setter methods: get/setText(), get/setIcon(), and is/setSelected(), or setAction(action). The other properties have corresponding getter and setter methods. The more visual configurable options of JToggleButton (and its subclasses) include the various icons for the different states of the button. Besides the standard icon, you can display a different icon when the button is selected, among other state changes. However, if you’re changing icons based on the currently selected state, then JToggleButton probably isn’t the most appropriate component to use. You should use one of its subclasses, JCheckBox or JRadioButton, explored later in this chapter.
■Note Keep in mind that the JButton component ignores the selectedIcon property.
Handling JToggleButton Selection Events After configuring a JToggleButton, you can handle selection events in one of three ways: with an ActionListener, an ItemListener, or a ChangeListener. This is in addition to providing an Action to the constructor, which would be notified like an ActionListener.
Listening to JToggleButton Events with an ActionListener If you’re interested only in what happens when a user selects or deselects the JToggleButton, you can attach an ActionListener to the component. After the user selects the button, the component notifies any registered ActionListener objects. Unfortunately, this isn’t the desired behavior, because you must then actively determine the state of the button so that you can respond appropriately for selecting or deselecting. To find out the selected state, you must get the model for the event source, and then ask for its selection state, as the following sample ActionListener source shows: ActionListener actionListener = new ActionListener() { public void actionPerformed(ActionEvent actionEvent) { AbstractButton abstractButton = (AbstractButton)actionEvent.getSource(); boolean selected = abstractButton.getModel().isSelected(); System.out.println("Action - selected=" + selected + "\ n"); } };
Listening to JToggleButton Events with an ItemListener The better listener to attach to a JToggleButton is the ItemListener. The ItemEvent passed to the itemStateChanged() method of ItemListener includes the current selection state of the button. This allows you to respond appropriately, without needing to search for the current button state. To demonstrate, the following ItemListener reports the state of a selected ItemEventgenerating component:
121
122
CHAPTER 5 ■ TOGGLE BUTTONS
ItemListener itemListener = new ItemListener() { public void itemStateChanged(ItemEvent itemEvent) { int state = itemEvent.getStateChange(); if (state == ItemEvent.SELECTED) { System.out.println("Selected"); } else { System.out.println("Deselected"); } } };
Listening to JToggleButton Events with a ChangeListener Attaching a ChangeListener to a JToggleButton provides even more flexibility. Any attached listener will be notified of the data model changes for the button, corresponding to changes in its armed, pressed, and selected properties. Listening for notification from the three listeners— ActionListener, ItemListener, and ChangeListener—allows you to react seven different times. Figure 5-3 shows the sequencing of the ButtonModel property changes, and when the model notifies each of the listeners.
Figure 5-3. JToggleButton notification sequencing diagram To demonstrate the ChangeListener notifications, the following code fragment defines a ChangeListener that reports the state changes to the three properties of the button model: ChangeListener changeListener = new ChangeListener() { public void stateChanged(ChangeEvent changeEvent) { AbstractButton abstractButton = (AbstractButton)changeEvent.getSource(); ButtonModel buttonModel = abstractButton.getModel(); boolean armed = buttonModel.isArmed(); boolean pressed = buttonModel.isPressed(); boolean selected = buttonModel.isSelected(); System.out.println("Changed: " + armed + "/" + pressed + "/" + selected); } };
CHAPTER 5 ■ TOGGLE BUTTONS
After you attach the ChangeListener to a JToggleButton and select the component by pressing and releasing the mouse over the component, the following output results: Changed: Changed: Changed: Changed: Changed:
With all three listeners attached to the same button, notification of registered ItemListener objects would happen after the selected property changes—in other words, between lines 3 and 4. Listing 5-2 demonstrates all three listeners attached to the same JToggleButton. With regard to the registered ActionListener objects, notification happens after releasing the button, but before the armed state changes to false, falling between lines 4 and 5. Listing 5-2. Listening for Toggle Selection import import import import
// Define ItemListener ItemListener itemListener = new ItemListener() { public void itemStateChanged(ItemEvent itemEvent) { int state = itemEvent.getStateChange(); if (state == ItemEvent.SELECTED) { System.out.println("Selected"); } else { System.out.println("Deselected"); } } }; // Attach Listeners toggleButton.addActionListener(actionListener); toggleButton.addChangeListener(changeListener); toggleButton.addItemListener(itemListener); frame.add(toggleButton, BorderLayout.NORTH); frame.setSize(300, 125); frame.setVisible(true); } }; EventQueue.invokeLater(runner); } }
Customizing a JToggleButton Look and Feel Each installable Swing look and feel provides a different JToggleButton appearance and set of default UIResource values. Figure 5-4 shows the appearance of the JToggleButton component for the preinstalled set of look and feel types: Motif, Windows, and Ocean. As the button labels might indicate, the first button is selected, the second has the input focus (and isn’t selected), and the third button isn’t selected.
-OTIF
7INDOWS
/CEAN
Figure 5-4. JToggleButton under different look and feel types
CHAPTER 5 ■ TOGGLE BUTTONS
The available set of UIResource-related properties for a JToggleButton is shown in Table 5-2. The JToggleButton component has 17 different properties.
Table 5-2. JToggleButton UIResource Elements
Property String
Object Type
ToggleButton.background
Color
ToggleButton.border
Border
ToggleButton.darkShadow
Color
ToggleButton.disabledText
Color
ToggleButton.focus
Color
ToggleButton.focusInputMap
Object[ ]
ToggleButton.font
Font
ToggleButton.foreground
Color
ToggleButton.gradient
List
ToggleButton.highlight
Color
ToggleButton.light
Color
ToggleButton.margin
Insets
ToggleButton.select
Color
ToggleButton.shadow
Color
ToggleButton.textIconGap
Integer
ToggleButton.textShiftOffset
Integer
ToggleButtonUI
String
JCheckBox Class The JCheckBox class represents the toggle component that, by default, displays a check box icon next to the text label for a two-state option. The check box icon uses an optional check mark to show the current state of the object, instead of keeping the button depressed, as with the JToggleButton. With the JCheckBox, the icon shows the state of the object, whereas with the JToggleButton, the icon is part of the label and isn’t usually used to show state information. With the exception of the UI-related differences between JCheckBox and JToggleButton, the two components are identical. Figure 5-5 demonstrates how check box components might appear in a pizza-ordering application.
125
126
CHAPTER 5 ■ TOGGLE BUTTONS
Figure 5-5. Sample JCheckBox components The JCheckBox is made up of several pieces. Like JToggleButton, the JCheckBox uses a ToggleButtonModel to represent its data model. The user interface delegate is CheckBoxUI. Although the ButtonGroup is available to group together check boxes, it isn’t normally appropriate. When multiple JCheckBox components are within a ButtonGroup, they behave like JRadioButton components but look like JCheckBox components. Because of this visual irregularity, you shouldn’t put JCheckBox components into a ButtonGroup. Now that you’ve seen the different pieces of a JCheckBox, let’s find out how to use them.
Creating JCheckBox Components Eight constructors exist for JCheckBox: public JCheckBox() JCheckBox aCheckBox = new JCheckBox(); public JCheckBox(Icon icon) JCheckBox aCheckBox = new JCheckBox(new DiamondIcon(Color.RED, false)); aCheckBox.setSelectedIcon(new DiamondIcon(Color.PINK, true)); public JCheckBox(Icon icon, boolean selected) JCheckBox aCheckBox = new JCheckBox(new DiamondIcon(Color.RED, false), true); aCheckBox.setSelectedIcon(new DiamondIcon(Color.PINK, true)); public JCheckBox(String text) JCheckBox aCheckBox = new JCheckBox("Spinach"); public JCheckBox(String text, boolean selected) JCheckBox aCheckBox = new JCheckBox("Onions", true); public JCheckBox(String text, Icon icon) JCheckBox aCheckBox = new JCheckBox("Garlic", new DiamondIcon(Color.RED, false)); aCheckBox.setSelectedIcon(new DiamondIcon(Color.PINK, true));
CHAPTER 5 ■ TOGGLE BUTTONS
public JCheckBox(String text, Icon icon, boolean selected) JCheckBox aCheckBox = new JCheckBox("Anchovies", new DiamondIcon(Color.RED, false), true); aCheckBox.setSelectedIcon(new DiamondIcon(Color.PINK, true)); public JCheckBox(Action action) Action action = ...; JCheckBox aCheckBox = new JCheckBox(action);
■Note Configuring a JCheckBox from an Action sets the label, state, and tooltip text, but not the icon.
Each allows you to customize either none or up to three properties for the label, icon, or initial selection state. Unless specified otherwise, there’s no text in the label and the default selected/unselected icon for the check box appears unselected. If you do initialize the icon in the constructor, it’s the icon for the unselected state of the check box, with the same icon displayed when the check box is selected. You must also either initialize the selected icon with the setSelectedIcon(Icon newValue) method, described later, or make sure the icon is state-aware and updates itself. If you don’t configure the selected icon and don’t use a state-aware icon, the same icon will appear for both the selected and unselected state. Normally, an icon that doesn’t change its visual appearance between selected and unselected states isn’t desirable for a JCheckBox.
■Note A state-aware icon is one that asks the associated component for the value of the selected property.
JCheckBox Properties After creating a JCheckBox, you can modify each of its many properties. Two properties specific to JCheckBox (shown in Table 5-3) override the behavior of its parent JToggleButton. The third borderPaintedFlat property was introduced in the 1.3 release of the JDK. All the remaining properties are inherited through parents of JToggleButton.
Table 5-3. JCheckBox Properties
Property Name
Data Type
Access
accessibleContext
AccessibleContext
Read-only
borderPaintedFlat
boolean
Read-write bound
UIClassID
String
Read-only
127
128
CHAPTER 5 ■ TOGGLE BUTTONS
The borderPaintedFlat property permits a look and feel to display the border around the check icon as two-dimensional (flat) instead of three-dimensional. By default, the borderPaintedFlat property is false, meaning the border will be three-dimensional. Figure 5-6 shows what a flat border looks like, where the first, third, and fifth borders are flat, and the second and fourth are not. A look and feel may choose to ignore this property. However, it is useful for renderers for components such tables and trees, where they show only state and are not selectable. The Windows and Motif look and feel types take advantage of the property; Metal (and Ocean) does not.
Figure 5-6. Alternating flat JCheckBox borders for the Windows look and feel: Anchovies, Onions, and Spinach are flat; Garlic and Pepperoni are not. As the constructor listing demonstrated, if you choose to set an icon with a constructor, the constructor sets only one icon for the unselected state. If you want the check box icon to show the correct state visually, you must use a state-aware icon or associate a different icon for the selected state with setSelectedIcon(). Having two different visual state representations is what most users expect from a JCheckBox, so unless you have a good reason to do otherwise, it’s best to follow the design convention for normal user interfaces. The fourth button at the bottom of the screen shown in Figure 5-7 demonstrates confusing icon usage within a JCheckBox. The check box always appears selected. The figure displays what the screen looks like with Pizza selected, Calzone unselected, Anchovies unselected, and Stuffed Crust unselected (although the last one appears selected).
Figure 5-7. Multiple JCheckBox components with various icons
CHAPTER 5 ■ TOGGLE BUTTONS
Listing 5-3 demonstrates three valid means of creating JCheckBox components with different icons, one using a state-aware icon. The last check box shows bad icon usage. Listing 5-3. Sampling JCheckBox import javax.swing.*; import java.awt.*; import java.awt.event.*; public class IconCheckBoxSample { private static class CheckBoxIcon implements Icon { private ImageIcon checkedIcon = new ImageIcon("Plus.gif"); private ImageIcon uncheckedIcon = new ImageIcon("Minus.gif"); public void paintIcon(Component component, Graphics g, int x, int y) { AbstractButton abstractButton = (AbstractButton)component; ButtonModel buttonModel = abstractButton.getModel(); g.translate(x,y); ImageIcon imageIcon = buttonModel.isSelected() ? checkedIcon : uncheckedIcon; Image image = imageIcon.getImage(); g.drawImage(image, 0, 0, component); g.translate(-x,-y); } public int getIconWidth() { return 20; } public int getIconHeight() { return 20; } } public static void main(String args[]) { Runnable runner = new Runnable() { public void run() { JFrame frame = new JFrame("Iconizing CheckBox"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); Icon checked = new DiamondIcon (Color.BLACK, true); Icon unchecked = new DiamondIcon (Color.BLACK, false); JCheckBox aCheckBox1 = new JCheckBox("Pizza", unchecked); aCheckBox1.setSelectedIcon(checked); JCheckBox aCheckBox2 = new JCheckBox("Calzone"); aCheckBox2.setIcon(unchecked); aCheckBox2.setSelectedIcon(checked);
Handling JCheckBox Selection Events As with the JToggleButton, you can handle JCheckBox events in any one of three ways: with an ActionListener, an ItemListener, or a ChangeListener. The constructor that accepts an Action just adds the parameter as an ActionListener.
Listening to JCheckBox Events with an ActionListener Subscribing to ActionEvent generation with an ActionListener allows you to find out when the user toggles the state of the JCheckBox. As with JToggleButton, the subscribed listener is told of the selection, but not the new state. To find out the selected state, you must get the model for the event source and ask, as the following sample ActionListener source shows. This listener modifies the check box label to reflect the selection state. ActionListener actionListener = new ActionListener() { public void actionPerformed(ActionEvent actionEvent) { AbstractButton abstractButton = (AbstractButton)actionEvent.getSource(); boolean selected = abstractButton.getModel().isSelected(); String newLabel = (selected ? SELECTED_LABEL : DESELECTED_LABEL); abstractButton.setText(newLabel); } };
Listening to JCheckBox Events with an ItemListener For JCheckBox, as with JToggleButton, the better listener to subscribe to is an ItemListener. The ItemEvent passed to the itemStateChanged() method of ItemListener includes the current state of the check box. This allows you to respond appropriately, without need to find out the current button state. To demonstrate, the following ItemListener swaps the foreground and background colors based on the state of a selected component. In this ItemListener, the foreground and background colors are swapped only when the state is selected.
CHAPTER 5 ■ TOGGLE BUTTONS
ItemListener itemListener = new ItemListener() { public void itemStateChanged(ItemEvent itemEvent) { AbstractButton abstractButton = (AbstractButton)itemEvent.getSource(); Color foreground = abstractButton.getForeground(); Color background = abstractButton.getBackground(); int state = itemEvent.getStateChange(); if (state == ItemEvent.SELECTED) { abstractButton.setForeground(background); abstractButton.setBackground(foreground); } } };
Listening to JCheckBox Events with a ChangeListener The ChangeListener responds to the JCheckBox just as with the JToggleButton. A subscribed ChangeListener would be notified when the button is armed, pressed, selected, or released. In addition, the ChangeListener is also notified of changes to the ButtonModel, such as for the keyboard mnemonic (KeyEvent.VK_S) of the check box. Because there are no ChangeListener differences to demonstrate between a JToggleButton and a JCheckBox, you could just attach the same listener from JToggleButton to the JCheckBox, and you’ll get the same selection responses. The sample program in Listing 5-4 demonstrates all the listeners subscribed to the events of a single JCheckBox. To demonstrate that the ChangeListener is notified of changes to other button model properties, a keyboard mnemonic is associated with the component. Given that the ChangeListener is registered before the mnemonic property is changed, the ChangeListener is notified of the property change. Because the foreground and background colors and text label aren’t button model properties, the ChangeListener isn’t told of these changes made by the other listeners.
■Note If you did want to listen for changes to the foreground or background color properties, you would need to attach a PropertyChangeListener to the JCheckBox.
Listing 5-4. Listening for JCheckBox Selection import import import import
public class SelectingCheckBox { private static String DESELECTED_LABEL = "Deselected"; private static String SELECTED_LABEL = "Selected";
131
132
CHAPTER 5 ■ TOGGLE BUTTONS
public static void main(String args[]) { Runnable runner = new Runnable() { public void run() { JFrame frame = new JFrame("Selecting CheckBox"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); JCheckBox checkBox = new JCheckBox(DESELECTED_LABEL); // Define ActionListener ActionListener actionListener = new ActionListener() { public void actionPerformed(ActionEvent actionEvent) { AbstractButton abstractButton = (AbstractButton)actionEvent.getSource(); boolean selected = abstractButton.getModel().isSelected(); String newLabel = (selected ? SELECTED_LABEL : DESELECTED_LABEL); abstractButton.setText(newLabel); } }; // Define ChangeListener ChangeListener changeListener = new ChangeListener() { public void stateChanged(ChangeEvent changeEvent) { AbstractButton abstractButton = (AbstractButton)changeEvent.getSource(); ButtonModel buttonModel = abstractButton.getModel(); boolean armed = buttonModel.isArmed(); boolean pressed = buttonModel.isPressed(); boolean selected = buttonModel.isSelected(); System.out.println("Changed: " + armed + "/" + pressed + "/" + selected); } }; // Define ItemListener ItemListener itemListener = new ItemListener() { public void itemStateChanged(ItemEvent itemEvent) { AbstractButton abstractButton = (AbstractButton)itemEvent.getSource(); Color foreground = abstractButton.getForeground(); Color background = abstractButton.getBackground(); int state = itemEvent.getStateChange(); if (state == ItemEvent.SELECTED) { abstractButton.setForeground(background); abstractButton.setBackground(foreground); } } }; // Attach Listeners checkBox.addActionListener(actionListener); checkBox.addChangeListener(changeListener); checkBox.addItemListener(itemListener);
CHAPTER 5 ■ TOGGLE BUTTONS
checkBox.setMnemonic(KeyEvent.VK_S); frame.add(checkBox, BorderLayout.NORTH); frame.setSize(300, 100); frame.setVisible(true); } }; EventQueue.invokeLater(runner); } } The SelectingCheckBox class produces the screen shown in Figure 5-8, after selecting and deselecting the JCheckBox.
Figure 5-8. SelectingCheckBox program screen
Customizing a JCheckBox Look and Feel Each installable Swing look and feel provides a different JCheckBox appearance and set of default UIResource values. Figure 5-9 shows the appearance of the JCheckBox component for the preinstalled set of look and feel types: Motif, Windows, and Ocean. The first, third, and fifth check boxes are selected; the third has the input focus.
-OTIF
7INDOWS
/CEAN
Figure 5-9. JCheckBox under different look and feel types
133
134
CHAPTER 5 ■ TOGGLE BUTTONS
Table 5-4 shows the set of available UIResource-related properties for a JCheckBox. The JCheckBox component has 20 different properties.
Table 5-4. JCheckBox UIResource Elements
Property String
Object Type
CheckBox.background
Color
CheckBox.border
Border
CheckBox.darkShadow
Color
CheckBox.disabledText
Color
CheckBox.focus
Color
CheckBox.focusInputMap
Object[ ]
CheckBox.font
Font
CheckBox.foreground
Color
CheckBox.gradient
List
CheckBox.highlight
Color
CheckBox.icon
Icon
CheckBox.interiorBackground
Color
CheckBox.light
Color
CheckBox.margin
Insets
CheckBox.rollover
Boolean
Checkbox.select*
Color
CheckBox.shadow
Color
CheckBox.textIconGap
Integer
CheckBox.textShiftOffset
Integer
CheckBoxUI
String
* Lowercase b is correct.
JRadioButton Class You use JRadioButton when you want to create a mutually exclusive group of toggleable components. Although, technically speaking, you could place a group of JCheckBox components into a ButtonGroup and only one would be selectable at a time, they wouldn’t look quite right. At least with the predefined look and feel types, JRadioButton and JCheckBox components look different, as Figure 5-10 shows. This difference in appearance tells the end user to expect specific behavior from the components.
CHAPTER 5 ■ TOGGLE BUTTONS
Figure 5-10. Comparing JRadioButton to JCheckBox appearance The JRadioButton is made up of several pieces. Like JToggleButton and JCheckBox, the JRadioButton uses a ToggleButtonModel to represent its data model. It uses a ButtonGroup through AbstractButton to provide the mutually exclusive grouping, and the user interface delegate is the RadioButtonUI. Let’s now explore how to use the different pieces of a JRadioButton.
Creating JRadioButton Components As with JCheckBox and JToggleButton, there are eight constructors for JRadioButton: public JRadioButton() JRadioButton aRadioButton = new JRadioButton(); public JRadioButton(Icon icon) JRadioButton aRadioButton = new JRadioButton(new DiamondIcon(Color.CYAN, false)); aRadioButton.setSelectedIcon(new DiamondIcon(Color.BLUE, true)); public JRadioButton(Icon icon, boolean selected) JRadioButton aRadioButton = new JRadioButton(new DiamondIcon(Color.CYAN, false), true); aRadioButton.setSelectedIcon(new DiamondIcon(Color.BLUE, true)); public JRadioButton(String text) JRadioButton aRadioButton = new JRadioButton("4 slices"); public JRadioButton(String text, boolean selected) JRadioButton aRadioButton = new JRadioButton("8 slices", true); public JRadioButton(String text, Icon icon) JRadioButton aRadioButton = new JRadioButton("12 slices", new DiamondIcon(Color.CYAN, false)); aRadioButton.setSelectedIcon(new DiamondIcon(Color.BLUE, true)); public JRadioButton(String text, Icon icon, boolean selected) JRadioButton aRadioButton = new JRadioButton("16 slices", new DiamondIcon(Color.CYAN, false), true); aRadioButton.setSelectedIcon(new DiamondIcon(Color.BLUE, true));
135
136
CHAPTER 5 ■ TOGGLE BUTTONS
public JRadioButton(Action action) Action action = ...; JRadioButton aRadioButton = new JRadioButton(action);
■Note As with a JCheckBox, configuring a JRadioButton from an Action sets the label, state, and tooltip text, but not the icon.
Each allows you to customize one or more of the label, icon, or initial selection state properties. Unless specified otherwise, there’s no text in the label, and the default selected/unselected icon for the check box appears unselected. After creating a group of radio button components, you need to place each into a single ButtonGroup so that they work as expected, with only one button in the group selectable at a time. If you do initialize the icon in the constructor, it’s the icon for the unselected state of the check box, with the same icon displayed when the check box is selected. You must also either initialize the selected icon with the setSelectedIcon(Icon newValue) method, described with JCheckBox, or make sure the icon is state-aware and updates itself.
JRadioButton Properties JRadioButton has two properties that override the behavior of its parent JToggleButton, as listed in Table 5-5.
Table 5-5. JRadioButton Properties
Property Name
Data Type
Access
accessibleContext
AccessibleContext
Read-only
UIClassID
String
Read-only
Grouping JRadioButton Components in a ButtonGroup The JRadioButton is the only JToggleButton subclass that should be placed in a ButtonGroup in order to work properly. Merely creating a bunch of radio buttons and placing them on the screen isn’t enough to make them behave appropriately. In addition to adding each radio button to a container, you need to create a ButtonGroup and add each radio button to the same ButtonGroup. Once all the JRadioButton items are in a group, whenever an unselected radio button is selected, the ButtonGroup causes the currently selected radio button to be deselected. Placing a set of JRadioButton components within a ButtonGroup on the screen is basically a four-step process: 1. Create a container for the group. JPanel aPanel = new JPanel(new GridLayout(0, 1));
CHAPTER 5 ■ TOGGLE BUTTONS
■Note The Box class described in Chapter 11 serves as a good container for a group of JRadioButton components.
2. Place a border around the container, to label the grouping. This is an optional step, but you’ll frequently want to add a border to label the group for the user. You can read more about borders in Chapter 7. Border border = BorderFactory.createTitledBorder("Slice Count"); aPanel.setBorder(border); 3. Create a ButtonGroup. ButtonGroup aGroup = new ButtonGroup(); 4. For each selectable option, create a JRadioButton, add it to a container, and then add it to the group. JRadioButton aRadioButton = new JRadioButton(...); aPanel.add(aRadioButton); aGroup.add(aRadioButton); You might find the whole process, especially the fourth step, a bit tedious after a while, especially when you add another step for handling selection events. The helper class shown in Listing 5-5, with its static createRadioButtonGrouping(String elements[], String title) method, could prove useful. It takes a String array for the radio button labels as well as the border title, and then it creates a set of JRadioButton objects with a common ButtonGroup in a JPanel with a titled border. Listing 5-5. Initial Support Class for Working with JRadioButton import javax.swing.*; import javax.swing.border.*; import java.awt.*; public class RadioButtonUtils { private RadioButtonUtils() { // Private constructor so you can't create instances } public static Container createRadioButtonGrouping (String elements[], String title) { JPanel panel = new JPanel(new GridLayout(0, 1)); // If title set, create titled border if (title != null) { Border border = BorderFactory.createTitledBorder(title); panel.setBorder(border); }
137
138
CHAPTER 5 ■ TOGGLE BUTTONS
// Create group ButtonGroup group = new ButtonGroup(); JRadioButton aRadioButton; // For each String passed in: // Create button, add to panel, and add to group for (int i=0, n=elements.length; i
CHAPTER 5 ■ TOGGLE BUTTONS
When you run this example, you’ll see the screen shown in Figure 5-11.
Figure 5-11. Grouping JRadioButton components with the RadioButtonUtils helper class
■Note If you’re familiar with the standard AWT library, the JRadioButton/ButtonGroup combination works exactly like the Checkbox/CheckboxGroup pair.
Handling JRadioButton Selection Events Like JToggleButton and JCheckBox, JRadioButton supports the registration of an ActionListener, an ItemListener, and a ChangeListener. And again, their usage with JRadioButton is somewhat different than with the other components.
Listening to JRadioButton Events with an ActionListener With a JRadioButton, it’s common to attach the same ActionListener to all the radio buttons in a ButtonGroup. That way, when one of the radio buttons is selected, the subscribed ActionListener will be notified. By overloading the earlier createRadioButtonGrouping() method, the method can accept an ActionListener argument and attach the listener object to each of the buttons as they’re created. public static Container createRadioButtonGrouping (String elements[], String title, ActionListener actionListener) { JPanel panel = new JPanel(new GridLayout(0, 1)); // If title set, create titled border if (title != null) { Border border = BorderFactory.createTitledBorder(title); panel.setBorder(border); } // Create group ButtonGroup group = new ButtonGroup(); JRadioButton aRadioButton; // For each String passed in: // Create button, add to panel, and add to group
139
140
CHAPTER 5 ■ TOGGLE BUTTONS
for (int i=0, n=elements.length; i
CHAPTER 5 ■ TOGGLE BUTTONS
method. As a result, there’s no direct route to find out which JRadioButton object (or objects) is selected within the ButtonGroup of the returned container. This may be necessary, for example, if there were an Order Pizza button on the screen and you wanted to find out which pizza-order options were selected after the user clicked that button. The following helper method, public Enumeration getSelectedElements(Container container), when added to the previously created RadioButtonUtils class (Listing 5-5), will provide the necessary answer. The helper method will work only if the container passed into the method is full of AbstractButton objects. This is true for those containers created with the previously described createRadioButtonGrouping() methods, although the getSelectedElements() method can be used separately. public static Enumeration<String> getSelectedElements(Container container) { Vector<String> selections = new Vector<String>(); Component components[] = container.getComponents(); for (int i=0, n=components.length; i " + selected.nextElement()); } } }; JButton button = new JButton ("Order Pizza"); button.addActionListener(buttonActionListener); It may be necessary for getSelectedElements() to return more than one value, because if the same ButtonModel is shared by multiple buttons in the container, multiple components of the ButtonGroup will be selected. Sharing a ButtonModel between components isn’t the norm. If you’re sure your button model won’t be shared, then you may want to provide a similar method that returns only a String.
141
142
CHAPTER 5 ■ TOGGLE BUTTONS
Listening to JRadioButton Events with an ItemListener Depending on what you’re trying to do, using an ItemListener with a JRadioButton is usually not the desired event-listening approach. When an ItemListener is registered, a new JRadioButton selection notifies the listener twice: once for deselecting the old value and once for selecting the new value. For reselections (selecting the same choice again), the listener is notified only once. To demonstrate, the following listener will detect reselections, as the ActionListener did earlier, and will report the selected (or deselected) element. ItemListener itemListener = new ItemListener() { String lastSelected; public void itemStateChanged(ItemEvent itemEvent) { AbstractButton aButton = (AbstractButton)itemEvent.getSource(); int state = itemEvent.getStateChange(); String label = aButton.getText(); String msgStart; if (state == ItemEvent.SELECTED) { if (label.equals(lastSelected)) { msgStart = "Reselected -> "; } else { msgStart = "Selected -> "; } lastSelected = label; } else { msgStart = "Deselected -> "; } System.out.println(msgStart + label); } }; To work properly, some new methods will be needed for RadioButtonUtils to enable you to attach the ItemListener to each JRadioButton in the ButtonGroup. They’re listed in the following section with the source for the complete example.
Listening to JRadioButton Events with a ChangeListener The ChangeListener responds to the JRadioButton just as it does with the JToggleButton and JCheckBox. A subscribed listener is notified when the selected radio button is armed, pressed, selected, or released and for various other properties of the button model. The only difference with JRadioButton is that the ChangeListener is also notified of the state changes of the radio button being deselected. The ChangeListener from the earlier examples could be attached to the JRadioButton as well. It will just be notified more frequently. The sample program shown in Listing 5-7 demonstrates all the listeners registered to the events of two different JRadioButton objects. In addition, a JButton reports on the selected elements of one of the radio buttons. Figure 5-12 shows the main window of the program.
CHAPTER 5 ■ TOGGLE BUTTONS
Listing 5-7. Radio Button Group Sample import import import import import
Figure 5-12. The GroupActionRadio program sample screen A few more changes were made to the RadioButtonUtils class to deal with registering ChangeListener objects to all the radio buttons in a ButtonGroup. The complete and final class definition is shown in Listing 5-8. Listing 5-8. Complete Support Class for Working with JRadioButton import import import import import import import
public class RadioButtonUtils { private RadioButtonUtils() { // Private constructor so you can't create instances }
145
146
CHAPTER 5 ■ TOGGLE BUTTONS
public static Enumeration<String> getSelectedElements(Container container) { Vector<String> selections = new Vector<String>(); Component components[] = container.getComponents(); for (int i=0, n=components.length; i
CHAPTER 5 ■ TOGGLE BUTTONS
if (title != null) { Border border = BorderFactory.createTitledBorder(title); panel.setBorder(border); } // Create group ButtonGroup group = new ButtonGroup(); JRadioButton aRadioButton; // For each String passed in: // Create button, add to panel, and add to group for (int i=0, n=elements.length; i
■Note One thing not shown here but explained in Chapter 4 in the discussion of ButtonModel and DefaultButtonModel is how to get the ButtonGroup when given a JRadioButton. If you want to find the ButtonGroup that a JRadioButton is in, you need to ask the DefaultButtonModel: ButtonGroup group = ((DefaultButtonModel)aJRadioButton.getModel()).getButtonGroup().
Customizing a JRadioButton Look and Feel Each installable Swing look and feel provides a different JRadioButton appearance and set of default UIResource values. Figure 5-13 shows the appearance of the JRadioButton component for the preinstalled set of look and feel types: Motif, Windows, and Ocean. All three screens show 4 slices of Thin Crust pizza as the order. In addition, the Thick Crust option has the input focus.
147
148
CHAPTER 5 ■ TOGGLE BUTTONS
-OTIF
7INDOWS
/CEAN
Figure 5-13. JRadioButton under different look and feel types Table 5-6 shows the set of available UIResource-related properties for a JRadioButton. The JRadioButton component has 20 different properties available.
Table 5-6. JRadioButton UIResource Elements
Property String
Object Type
RadioButton.background
Color
RadioButton.border
Border
RadioButton.darkShadow
Color
RadioButton.disabledText
Color
RadioButton.focus
Color
RadioButton.focusInputMap
Object[ ]
RadioButton.font
Font
RadioButton.foreground
Color
RadioButton.gradient
List
RadioButton.highlight
Color
RadioButton.icon
Icon
RadioButton.interiorBackground
Color
RadioButton.light
Color
RadioButton.margin
Insets
CHAPTER 5 ■ TOGGLE BUTTONS
Table 5-6. JRadioButton UIResource Elements (Continued)
Property String
Object Type
RadioButton.rollover
Boolean
RadioButton.select
Color
RadioButton.shadow
Color
RadioButton.textIconGap
Integer
RadioButton.textShiftOffset
Integer
RadioButtonUI
String
Summary This chapter described the components that can be toggled: JToggleButton, JCheckBox, and JRadioButton. You’ve seen how each component uses the JToggleButton.ToggleButtonModel class for its data model and how you can group the components into a ButtonGroup. In addition, you also saw how to handle selection events for each of the components. Chapter 6 explains how to work with the various menu-oriented Swing components.
149
CHAPTER 6 ■■■
Swing Menus and Toolbars M
any of the low-level Swing components were covered in the previous two chapters of this book. This chapter will delve into Swing’s menu-related components. Menus and toolbars help make your applications more user-friendly by providing visual command options. Users can avoid the somewhat archaic multiple-key command sequences that are holdovers from programs such as the early word processor WordStar and the more current emacs programmer’s editor. Although Swing menus do support multiple-key command sequences, the menus (and toolbars) are designed primarily for on-screen graphical selection with a mouse, rather than the keyboard. The menu components discussed in this chapter are used as follows: • For each cascading menu, you create a JMenu component and add it to the JMenuBar. • For the selections available from the JMenu, you create JMenuItem components and add them to the JMenu. • To create submenus, you add a new JMenu to a JMenu and place JMenuItem options on the new menu. • Then, when a JMenu is selected, the system displays its current set of components within a JPopupMenu.
In addition to the basic JMenuItem elements, this chapter covers other menu items, such as JCheckBoxMenuItem and JRadioButtonMenuItem, which you can place within a JMenu. You’ll also explore the JSeparator class, which serves to divide menu items into logical groups. You’ll find out how to use the JPopupMenu class for general support of pop-up menus that appear after a JMenu is selected, or in context for any component. As with abstract buttons (the AbstractButton class was introduced in Chapter 4), each menu element can have a mnemonic associated with it for keyboard selection. You’ll also learn about the support for keyboard accelerators, which allow users to avoid going through all the menuing levels for selection. Besides the individual menu-related components, in this chapter you’ll look at the JMenuBar selection model and event-related classes specific to menus. The selection model interface to examine is the SingleSelectionModel interface, as well as its default implementation DefaultSingleSelectionModel. You’ll explore the menu-specific listeners and events MenuListener/MenuEvent, MenuKeyListener/MenuKeyEvent, and MenuDragMouseListener/ MenuDragMouseEvent. In addition, you’ll examine creating other pop-up components with Popup and PopupFactory, as well as using toolbars with the JToolBar class.
151
152
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
Working with Menus Let’s begin with an example that demonstrates how all the menu components fit together. To start, create a frame with a menu bar, as shown in Figure 6-1. Accelerator JMenu Mnemonic
Figure 6-1. Menu component examples This simple menuing example has the following features: • On the menu bar are two ubiquitous menus: File and Edit. Under the File menu, the familiar options of New, Open, Close, and Exit will appear (although they aren’t shown in Figure 6-1). Under the Edit menu are options for Cut, Copy, Paste, and Find, and a submenu of Find options. The Options submenu will contain choices for search direction— forward or backward—and a toggle for case sensitivity. • In various places within the different menus, menu separators divide the options into logical sets. • Each of the menu options has a mnemonic associated with it to help with keyboard navigation and selection. The mnemonic allows users to make menu selections via the keyboard, for instance, by pressing Alt-F on a Windows platform to open the File menu. • In addition to the keyboard mnemonic, a keystroke associated with several options acts as a keyboard accelerator. Unlike the mnemonic, the accelerator can directly activate a menu option, even when the menu option isn’t visible. • The Options submenu has an icon associated with it. Although only one icon is shown in Figure 6-1, all menu components can have an icon, except for the JSeparator and JPopupMenu components.
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
Note that for this beginning example, none of the menu choices will do anything other than print which menu choice was selected. For example, selecting the Copy option from the Edit menu displays Selected: Copy. Listing 6-1 shows the complete source for the class that generated the example in Figure 6-1. Listing 6-1. The MenuSample Class Definition import java.awt.*; import java.awt.event.*; import javax.swing.*; public class MenuSample { static class MenuActionListener implements ActionListener { public void actionPerformed (ActionEvent actionEvent) { System.out.println ("Selected: " + actionEvent.getActionCommand()); } } public static void main(final String args[]) { Runnable runner = new Runnable() { public void run() { ActionListener menuListener = new MenuActionListener(); JFrame frame = new JFrame("MenuSample Example"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); JMenuBar menuBar = new JMenuBar(); // File Menu, F - Mnemonic JMenu fileMenu = new JMenu("File"); fileMenu.setMnemonic(KeyEvent.VK_F); menuBar.add(fileMenu); // File->New, N - Mnemonic JMenuItem newMenuItem = new JMenuItem("New", KeyEvent.VK_N); newMenuItem.addActionListener(menuListener); fileMenu.add(newMenuItem); // File->Open, O - Mnemonic JMenuItem openMenuItem = new JMenuItem("Open", KeyEvent.VK_O); openMenuItem.addActionListener(menuListener); fileMenu.add(openMenuItem); // File->Close, C - Mnemonic JMenuItem closeMenuItem = new JMenuItem("Close", KeyEvent.VK_C); closeMenuItem.addActionListener(menuListener); fileMenu.add(closeMenuItem);
153
154
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
// Separator fileMenu.addSeparator(); // File->Save, S - Mnemonic JMenuItem saveMenuItem = new JMenuItem("Save", KeyEvent.VK_S); saveMenuItem.addActionListener(menuListener); fileMenu.add(saveMenuItem); // Separator fileMenu.addSeparator(); // File->Exit, X - Mnemonic JMenuItem exitMenuItem = new JMenuItem("Exit", KeyEvent.VK_X); exitMenuItem.addActionListener(menuListener); fileMenu.add(exitMenuItem); // Edit Menu, E - Mnemonic JMenu editMenu = new JMenu("Edit"); editMenu.setMnemonic(KeyEvent.VK_E); menuBar.add(editMenu); // Edit->Cut, T - Mnemonic, CTRL-X - Accelerator JMenuItem cutMenuItem = new JMenuItem("Cut", KeyEvent.VK_T); cutMenuItem.addActionListener(menuListener); KeyStroke ctrlXKeyStroke = KeyStroke.getKeyStroke("control X"); cutMenuItem.setAccelerator(ctrlXKeyStroke); editMenu.add(cutMenuItem); // Edit->Copy, C - Mnemonic, CTRL-C - Accelerator JMenuItem copyMenuItem = new JMenuItem("Copy", KeyEvent.VK_C); copyMenuItem.addActionListener(menuListener); KeyStroke ctrlCKeyStroke = KeyStroke.getKeyStroke("control C"); copyMenuItem.setAccelerator(ctrlCKeyStroke); editMenu.add(copyMenuItem); // Edit->Paste, P - Mnemonic, CTRL-V - Accelerator, Disabled JMenuItem pasteMenuItem = new JMenuItem("Paste", KeyEvent.VK_P); pasteMenuItem.addActionListener(menuListener); KeyStroke ctrlVKeyStroke = KeyStroke.getKeyStroke("control V"); pasteMenuItem.setAccelerator(ctrlVKeyStroke); pasteMenuItem.setEnabled(false); editMenu.add(pasteMenuItem); // Separator editMenu.addSeparator();
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
// Edit->Find, F - Mnemonic, F3 - Accelerator JMenuItem findMenuItem = new JMenuItem("Find", KeyEvent.VK_F); findMenuItem.addActionListener(menuListener); KeyStroke f3KeyStroke = KeyStroke.getKeyStroke("F3"); findMenuItem.setAccelerator(f3KeyStroke); editMenu.add(findMenuItem); // Edit->Options Submenu, O - Mnemonic, at.gif - Icon Image File JMenu findOptionsMenu = new JMenu("Options"); Icon atIcon = new ImageIcon ("at.gif"); findOptionsMenu.setIcon(atIcon); findOptionsMenu.setMnemonic(KeyEvent.VK_O); // ButtonGroup for radio buttons ButtonGroup directionGroup = new ButtonGroup(); // Edit->Options->Forward, F - Mnemonic, in group JRadioButtonMenuItem forwardMenuItem = new JRadioButtonMenuItem("Forward", true); forwardMenuItem.addActionListener(menuListener); forwardMenuItem.setMnemonic(KeyEvent.VK_F); findOptionsMenu.add(forwardMenuItem); directionGroup.add(forwardMenuItem); // Edit->Options->Backward, B - Mnemonic, in group JRadioButtonMenuItem backwardMenuItem = new JRadioButtonMenuItem("Backward"); backwardMenuItem.addActionListener(menuListener); backwardMenuItem.setMnemonic(KeyEvent.VK_B); findOptionsMenu.add(backwardMenuItem); directionGroup.add(backwardMenuItem); // Separator findOptionsMenu.addSeparator(); // Edit->Options->Case Sensitive, C - Mnemonic JCheckBoxMenuItem caseMenuItem = new JCheckBoxMenuItem("Case Sensitive"); caseMenuItem.addActionListener(menuListener); caseMenuItem.setMnemonic(KeyEvent.VK_C); findOptionsMenu.add(caseMenuItem); editMenu.add(findOptionsMenu);
Menu Class Hierarchy Now that you’ve seen an example of how to create the cascading menus for an application, you should have an idea of what’s involved in using the Swing menu components. To help clarify, Figure 6-2 illustrates how all the Swing menu components are interrelated.
Figure 6-2. Swing menu class hierarchy The most important concept illustrated in Figure 6-2 is that all the Swing menu elements, as subclasses of JComponent, are AWT components in their own right. You can place JMenuItem, JMenu, and JMenuBar components anywhere that AWT components can go, not just on a frame. In addition, because JMenuItem inherits from AbstractButton, JMenuItem and its subclasses inherit support for various icons and for HTML text labels, as described in Chapter 5.
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
■Note Although technically possible, placing menus in locations where users wouldn’t expect them to be is poor user interface design.
In addition to being part of the basic class hierarchy, each of the selectable menu components implements the MenuElement interface. The interface describes the menu behavior necessary to support keyboard and mouse navigation. The predefined menu components already implement this behavior, so you don’t have to. But if you’re interested in how this interface works, see the “MenuElement Interface” section later in this chapter. Now let’s take a look at the different Swing menu components.
JMenuBar Class Swing’s menu bar component is the JMenuBar. Its operation requires you to fill the menu bar with JMenu elements that have JMenuItem elements. Then you add the menu bar to a JFrame or some other user interface component requiring a menu bar. The menu bar then relies on the assistance of a SingleSelectionModel to determine which JMenu to display or post after it’s selected.
Creating JMenuBar Components JMenuBar has a single constructor of the no-argument variety: public JMenuBar(). Once you create the menu bar, you can add it to a window with the setJMenuBar() method of JApplet, JDialog, JFrame, JInternalFrame, or JRootPane. (Yes, even applets can have menu bars.) JMenuBar menuBar = new JMenuBar(); // Add items to it ... JFrame frame = new JFrame("MenuSample Example"); frame.setJMenuBar(menuBar); With the system-provided look and feel types, the menu bar appears at the top of the window, below any window title (if present), with setJMenuBar(). Other look and feel types, like Aqua for the Macintosh, place the menu bar elsewhere. You can also use the add() method of a Container to add a JMenuBar to a window. When added with the add() method, a JMenuBar is arranged by the layout manager of the Container. After you have a JMenuBar, the remaining menu classes all work together to fill the menu bar.
Adding Menus to and Removing Menus from Menu Bars You need to add JMenu objects to a JMenuBar. Otherwise, the only thing displayed is the border with nothing in it. There’s a single method for adding menus to a JMenuBar: public JMenu add(JMenu menu)
157
158
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
By default, consecutively added menus are displayed from left to right. This makes the first menu added the leftmost menu and the last menu added the rightmost menu. Menus added in between are displayed in the order in which they’re added. For instance, in the sample program from Listing 6-1, the menus were added as follows: JMenu fileMenu = new JMenu("File"); menuBar.add(fileMenu); JMenu editMenu = new JMenu("Edit"); menuBar.add(editMenu);
■Note Placing a JMenuBar in the EAST or WEST area of a BorderLayout does not make the menus appear vertically, stacked one on top of another. You must customize the menu bar if you want menus to appear this way. See Figure 6-4, later in this chapter, for one implementation of a top-down menu bar.
In addition to the add() method from JMenuBar, several overloaded varieties of the add() method inherited from Container offer more control over menu positioning. Of particular interest is the add(Component component, int index) method, which allows you to specify the position in which the new JMenu is to appear. Using this second variety of add() allows you to place the File and Edit JMenu components in a JMenuBar in a different order, but with the same results: menuBar.add(editMenu); menuBar.add(fileMenu, 0); If you’ve added a JMenu component to a JMenuBar, you can remove it with either the remove(Component component) or remove(int index) method inherited from Container: bar.remove(edit); bar.remove(0);
■Tip Adding or removing menus from a menu bar is likely to confuse users. However, sometimes it’s necessary to do so—especially if you want to have an expert mode that enables a certain functionality that a nonexpert mode hides. A better approach is to disable/enable individual menu items or entire menus. If you do add or remove menus, you must then revalidate() the menu bar to display the changes.
JMenuBar Properties Table 6-1 shows the 11 properties of JMenuBar. Half the properties are read-only, allowing you only to query the current state of the menu bar. The remaining properties allow you to alter the appearance of the menu bar by deciding whether the border of the menu bar is painted and selecting the size of the margin between menu elements. The selected property and selection model control which menu on the menu bar, if any, is currently selected. When the selected
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
component is set to a menu on the menu bar, the menu components appear in a pop-up menu within a window.
■Caution The helpMenu property, although available with a set-and-get method, is unsupported in the Swing releases through 5.0. Calling either accessor method will throw an error. With some future release of Swing, the helpMenu property will likely make a specific JMenu the designated help menu. Exactly what happens when a menu is flagged as the help menu is specific to the installed look and feel. What tends to happen is that the menu becomes the last, or rightmost, menu.
Table 6-1. JMenuBar Properties
Property Name
Data Type
Access
accessibleContext
AccessibleContext
Read-only
borderPainted
boolean
Read-write
component
Component
Read-only
helpMenu
JMenu
Read-write
margin
Insets
Read-write
menuCount
int
Read-only
selected
boolean/Component
Read-write
selectionModel
SingleSelectionModel
Read-write
subElements
MenuElement[ ]
Read-only
UI
MenuBarUI
Read-write
UIClassID
String
Read-only
■Note The selected property of JMenuBar is nonstandard. The getter method returns a boolean to indicate if a menu component is selected on the menu bar. The setter method accepts a Component argument to select a component on the menu bar.
Customizing a JMenuBar Look and Feel Each predefined Swing look and feel provides a different appearance and set of default UIResource values for the JMenuBar and each of the menu components. Figure 6-3 shows the appearance of all these menu components for the preinstalled set of look and feel types: Motif, Windows, and Ocean.
159
160
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
Motif
Windows
Ocean
Figure 6-3. Menu components under different look and feel types In regard to the specific appearance of the JMenuBar, the available set of UIResource-related properties is shown in Table 6-2. There are 12 properties available for the JMenuBar component. Table 6-2. JMenuBar UIResource Elements
Property String
Object Type
MenuBar.actionMap
ActionMap
MenuBar.background
Color
MenuBar.border
Border
MenuBar.borderColor
Color
MenuBar.darkShadow
Color
MenuBar.font
Font
MenuBar.foreground
Color
MenuBar.gradient
List
MenuBar.highlight
Color
MenuBar.shadow
Color
MenuBar.windowBindings
Object[ ]
MenuBarUI
String
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
If you want a vertical menu bar, instead of a horizontal one, simply change the LayoutManager of the menu bar component. A setup such as a 0 row by 1 column GridLayout does the job, as shown in the following example, because the number of rows will grow infinitely for each JMenu added: import java.awt.*; import javax.swing.*; public class VerticalMenuBar extends JMenuBar { private static final LayoutManager grid = new GridLayout(0,1); public VerticalMenuBar() { setLayout(grid); } } Moving the menu bar shown in Figure 6-1 to the east side of a BorderLayout and making it a VerticalMenuBar instead of a JMenuBar produces the setup shown in Figure 6-4. Although the vertical menu bar may look a little unconventional here, it’s more desirable to have menu items appearing stacked vertically, rather than horizontally, on the right (or left) side of a window. You may, however, want to change the MenuBar.border property to a more appropriate border.
Figure 6-4. Using the VerticalMenuBar
■Note Changing the layout manager of the JMenuBar has one negative side effect: Because top-level menus are pull-down menus, open menus on a vertical bar will obscure the menu bar. If you want to correct this popup placement behavior, you must extend the JMenu class and override its protected getPopupMenuOrigin() method in order to make the pop-up menu span out, rather than drop down.
SingleSelectionModel Interface The SingleSelectionModel interface describes an index into an integer-indexed data structure where an element can be selected. The data structure behind the interface facade is most likely an array or vector in which repeatedly accessing the same position is guaranteed to return the same object. The SingleSelectionModel interface is the selection model for a JMenuBar as well as a JPopupMenu. In the case of a JMenuBar, the interface describes the currently selected JMenu that needs to be painted. In the case of a JPopupMenu, the interface describes the currently selected JMenuItem.
161
162
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
■Note SingleSelectionModel also serves as the selection model for JTabbedPane, a class described in Chapter 11.
The interface definition for SingleSelectionModel follows: public interface SingleSelectionModel { // Listeners public void addChangeListener(ChangeListener listener); public void removeChangeListener(ChangeListener listener); // Properties public int getSelectedIndex(); public void setSelectedIndex(int index); public boolean isSelected(); // Other Methods public void clearSelection(); } As you can see, in addition to the selection index, the interface requires maintenance of a ChangeListener list to be notified when the selection index changes. The default Swing-provided implementation of SingleSelectionModel is the DefaultSingleSelectionModel class. For both JMenuBar and JPopupMenu, it’s very unlikely that you will change their selection model from this default implementation. The DefaultSingleSelectionModel implementation manages the list of ChangeListener objects. In addition, the model uses a value of –1 to signify that nothing is currently selected. When the selected index is –1, isSelected() returns false; otherwise, the method returns true. When the selected index changes, any registered ChangeListener objects will be notified.
JMenuItem Class The JMenuItem component is the predefined component that a user selects on a menu bar. As a subclass of AbstractButton, JMenuItem acts as a specialized button component that behaves similarly to a JButton. Besides being a subclass of AbstractButton, the JMenuItem class shares the data model of JButton (ButtonModel interface and DefaultButtonModel implementation).
Creating JMenuItem Components Six constructors for JMenuItem follow. They allow you to initialize the menu item’s string or icon label and the mnemonic of the menu item. There’s no explicit constructor permitting you to set all three options at creation time, unless you make them part of an Action.
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
public JMenuItem() JMenuItem jMenuItem = new JMenuItem(); public JMenuItem(Icon icon) Icon atIcon = new ImageIcon("at.gif"); JMenuItem jMenuItem = new JMenuItem(atIcon); public JMenuItem(String text) JMenuItem jMenuItem = new JMenuItem("Cut"); public JMenuItem(String text, Icon icon) Icon atIcon = new ImageIcon("at.gif"); JMenuItem jMenuItem = new JMenuItem("Options", atIcon); public JMenuItem(String text, int mnemonic) JMenuItem jMenuItem = new JMenuItem("Cut", KeyEvent.VK_T); public JMenuItem(Action action) Action action = ...; JMenuItem jMenuItem = new JMenuItem(action); The mnemonic allows you to select the menu through keyboard navigation. For instance, you can simply press Alt-T on a Windows platform to select the Cut menu item if the item appears on an Edit menu that is already open. The mnemonic for a menu item usually appears underlined within the text label for the menu. However, if the letter doesn’t appear within the text label or if there is no text label, the user will have no visual clue as to its setting. Letters are specified by the different key constants within the java.awt.event.KeyEvent class. Other platforms might offer other meta-keys for selecting mnemonics. On UNIX, the meta-key is also an Alt key; on a Macintosh, it’s the Command key.
■Note Adding a JMenuItem with a label of “-” doesn’t create a menu separator as it did with AWT’s MenuItem.
JMenuItem Properties The JMenuItem class has many properties. Roughly 100 properties are inherited through its various superclasses. The 10 properties specific to JMenuItem are shown in Table 6-3.
163
164
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
Table 6-3. JMenuItem Properties
Property Name
Data Type
Access
accelerator
KeyStroke
Read-write bound
accessibleContext
AccessibleContext
Read-only
armed
boolean
Read-write
component
Component
Read-only
enabled
boolean
Write-only bound
menuDragMouseListeners
MenuDragMouseListener[ ]
Read-only
menuKeyListeners
MenuKeyListener[ ]
Read-only
subElements
MenuElement[ ]
Read-only
UI
MenuElementUI
Write-only bound
UIClassID
String
Read-only
One truly interesting property is accelerator. As explained in Chapter 2, KeyStroke is a factory class that lets you create instances based on key and modifier combinations. For instance, the following statements, from the example in Listing 6-1 earlier in this chapter, associate Ctrl-X as the accelerator for one particular menu item: KeyStroke ctrlXKeyStroke=KeyStroke.getKeyStroke("control X"); cutMenuItem.setAccelerator(ctrlXKeyStroke); The read-only component and subElements properties are part of the MenuElement interface, which JMenuItem implements. The component property is the menu item renderer (the JMenuItem itself). The subElements property is empty (that is, an empty array, not null), because a JMenuItem has no children.
■Note Swing menus don’t use AWT’s MenuShortcut class.
Handling JMenuItem Events You can handle events within a JMenuItem in at least five different ways. The component inherits the ability to allow you to listen for the firing of ChangeEvent and ActionEvent through the ChangeListener and ActionListener registration methods of AbstractButton. In addition, the JMenuItem component supports registering MenuKeyListener and MenuDragMouseListener objects when MenuKeyEvent and MenuDragMouseEvent events happen. These techniques are discussed in the following sections. A fifth way is to pass an Action to the JMenuItem constructor, which is like a specialized way of listening with an ActionListener. For more on using Action, see the discussion of using Action objects with menus, in the “JMenu Class” section a little later in this chapter.
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
Listening to JMenuItem Events with a ChangeListener Normally, you wouldn’t register a ChangeListener with a JMenuItem. However, demonstrating one hypothetical case helps to clarify the data model changes of the JMenuItem with respect to its ButtonModel. The changes with regard to arming, pressing, and selecting are the same as with a JButton. However, their naming might be a little confusing because the selected property of the model is never set. A JMenuItem is armed when the mouse passes over the menu choice and it becomes selected. A JMenuItem is pressed when the user releases the mouse button over it. Immediately after being pressed, the menu item becomes unpressed and unarmed. Between the menu item being pressed and unpressed, the AbstractButton is notified of the model changes, causing any registered ActionListener objects of the menu item to be notified. The button model for a plain JMenuItem never reports being selected. If you move the mouse to another menu item without selecting, the first menu item automatically becomes unarmed. To help you better visualize the different changes, Figure 6-5 shows a sequence diagram.
ActionListener List of JMenuItem
JMenuItem
ChangeListener List of JMenuItem
ActionListener List of ButtonModel
ButtonModel
Registers with Registers with Mouse Enters/Menu Selection Changed Armed Mouse Released Pressed Notifies Pressed Notifies
No Longer Pressed
No Longer Armed
Figure 6-5. JMenuItem selection sequence diagram
■Note Subclasses of JMenuItem can have their button model selected property set, like a radio button— but the predefined JMenuItem cannot.
165
166
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
Listening to JMenuItem Events with an ActionListener The better listener to attach to a JMenuItem is the ActionListener, or passing an Action to the constructor. It allows you to find out when a menu item is selected. Any registered ActionListener objects would be notified when a user releases the mouse button over a JMenuItem that is part of an open menu. Registered listeners are also notified if the user employs the keyboard (whether with arrow keys or mnemonics) or presses the menu item’s keyboard accelerator to make a selection. You must add an ActionListener to every JMenuItem for which you want an action to happen when selected. There’s no automatic shortcut allowing you to register an ActionListener with a JMenu or JMenuBar and have all their contained JMenuItem objects notify a single ActionListener. The sample program shown in Listing 6-1 associates the same ActionListener with every JMenuItem: class MenuActionListener implements ActionListener { public void actionPerformed(ActionEvent e) { System.out.println("Selected: " + e.getActionCommand()); } } However, more frequently, you would associate a different action with each item, so that each menu item can respond differently.
■Tip Instead of creating a custom ActionListener for the component, and adding it as a listener, you can also create a custom Action and call setAction() on the component.
Listening to JMenuItem Events with a MenuKeyListener The MenuKeyEvent is a special kind of KeyEvent used internally by the user interface classes for a JMenu and JMenuItem, allowing the components to listen for when their keyboard mnemonic is pressed. To listen for this keyboard input, each menu component registers a MenuKeyListener to pay attention to the appropriate input. If the keyboard mnemonic is pressed, the event is consumed and not passed to any registered listeners. If the keyboard mnemonic is not pressed, any registered key listeners (instead of menu key listeners) are notified. The MenuKeyListener interface definition follows: public interface MenuKeyListener extends EventListener { public void menuKeyPressed(MenuKeyEvent e); public void menuKeyReleased(MenuKeyEvent e); public void menuKeyTyped(MenuKeyEvent e); } Normally, you wouldn’t register objects as this type of listener yourself, although you could if you wanted to. If you do, and if a MenuKeyEvent happens (that is, a key is pressed/released), every JMenu on the JMenuBar will be notified, as will every JMenuItem (or subclass) on an open menu with a registered MenuKeyListener. That includes disabled menu items so that they can consume a pressed mnemonic. The definition of the MenuKeyEvent class follows:
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
public class MenuKeyEvent extends KeyEvent { public MenuKeyEvent(Component source, int id, long when, int modifiers, int keyCode, char keyChar, MenuElement path[], MenuSelectionManager mgr); public MenuSelectionManager getMenuSelectionManager(); public MenuElement[] getPath(); } It’s the job of the MenuSelectionManager to determine the current selection path. The selection path is the set of menu elements from the top-level JMenu on the JMenuBar to the selected components. For the most part, the manager works behind the scenes, and you never need to worry about it. Listening to JMenuItem Events with a MenuDragMouseListener Like MenuKeyEvent, the MenuDragMouseEvent is a special kind of event used internally by the user interface classes for JMenu and JMenuItem. As its name implies, the MenuDragMouseEvent is a special kind of MouseEvent. By monitoring when a mouse is moved within an open menu, the user interface classes use the listener to maintain the selection path, thus determining the currently selected menu item. Its definition follows: public interface MenuDragMouseListener extends EventListener { public void menuDragMouseDragged(MenuDragMouseEvent e); public void menuDragMouseEntered(MenuDragMouseEvent e); public void menuDragMouseExited(MenuDragMouseEvent e); public void menuDragMouseReleased(MenuDragMouseEvent e); } As with the MenuKeyListener, normally you don’t listen for this event yourself. If you’re interested in when a menu or submenu is about to be displayed, the better listener to register is the MenuListener, which can be registered with the JMenu, but not with an individual JMenuItem. You’ll look at this in the next section, which describes JMenu. The definition of the MenuDragMouseEvent class, the argument to each of the MenuDragMouseListener methods, is as follows: public class MenuDragMouseEvent extends MouseEvent { public MenuDragMouseEvent(Component source, int id, long when, int modifiers, int x, int y, int clickCount, boolean popupTrigger, MenuElement path[], MenuSelectionManager mgr); public MenuSelectionManager getMenuSelectionManager(); public MenuElement[] getPath(); }
Customizing a JMenuItem Look and Feel As with the JMenuBar, the predefined look and feel types each provide a different JMenuItem appearance and set of default UIResource values. Figure 6-3 showed the appearance of the JMenuItem component for the preinstalled set: Motif, Windows, and Ocean. The available set of UIResource-related properties for a JMenuItem are shown in Table 6-4. The JMenuItem component offers 20 different properties.
167
168
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
Table 6-4. JMenuItem UIResource Elements
Property String
Object Type
MenuItem.acceleratorDelimiter
String
MenuItem.acceleratorFont
Font
MenuItem.acceleratorForeground
Color
MenuItem.acceleratorSelectionForeground
Color
MenuItem.actionMap
ActionMap
MenuItem.arrowIcon
Icon
MenuItem.background
Color
MenuItem.border
Border
MenuItem.borderPainted
Boolean
MenuItem.checkIcon
Icon
MenuItem.commandSound
String
MenuItem.disabledForeground
Color
MenuItem.font
Font
MenuItem.foreground
Color
MenuItem.margin
Insets
MenuItem.opaque
Boolean
MenuItem.selectionBackground
Color
MenuItem.selectionForeground
Color
MenuItem.textIconGap
Integer
MenuItemUI
String
JMenu Class The JMenu component is the basic menu item container that is placed on a JMenuBar. When a JMenu is selected, the menu displays the contained menu items within a JPopupMenu. As with JMenuItem, the data model for the JMenu is an implementation of ButtonModel, or more specifically, DefaultButtonModel.
Creating JMenu Components Four constructors for JMenu allow you to initialize the string label of the menu if desired:
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
public JMenu() JMenu jMenu = new JMenu(); public JMenu(String label) JMenu jMenu = new JMenu("File"); public JMenu(String label, boolean useTearOffs) public JMenu(Action action) Action action = ...; JMenu jMenu = new JMenu(action); One constructor is for using a tear-off menu. However, tear-off menus aren’t currently supported; therefore, the argument is ignored. The fourth constructor pulls the properties of the menu from an Action.
■Note Tear-off menus are menus that appear in a window and remain open after selection, instead of automatically closing.
Adding Menu Items to a JMenu Once you have a JMenu, you need to add JMenuItem objects to it; otherwise, the menu will not display any choices. There are five methods for adding menu items defined within JMenu and one for adding a separator: public public public public public public
In Listing 6-1 earlier in this chapter, all the JMenuItem components were added to JMenu components with the first add() method. As a shortcut, you can pass the text label for a JMenuItem to the add() method of JMenu. This will create the menu item, set its label, and pass back the new menu item component. You can then bind a menu item event handler to this newly obtained menu item. The third add() method shows that you can place any Component on a JMenu, not solely a JMenuItem. The fourth add() lets you position the component. The last add() variety, with the Action argument, will be discussed in the next section of this chapter. You can add separator bars with the addSeparator() method of JMenu. For instance, in Listing 6-1, the File menu was created with code similar to the following:
169
170
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
JMenu fileMenu = new JMenu("File"); JMenuItem newMenuItem = new JMenuItem("New"); fileMenu.add(newMenuItem); JMenuItem openMenuItem = new JMenuItem("Open"); fileMenu.add(openMenuItem); JMenuItem closeMenuItem = new JMenuItem("Close"); fileMenu.add(closeMenuItem); fileMenu.addSeparator(); JMenuItem saveMenuItem = new JMenuItem("Save"); fileMenu.add(saveMenuItem); fileMenu.addSeparator(); JMenuItem exitMenuItem = new JMenuItem("Exit"); fileMenu.add(exitMenuItem); Notice the addSeparator() calls wrapped around the call to add the Save menu item. In addition to adding menu items at the end of a menu, you can insert them at specific positions or insert a separator at a specific position, as follows: public JMenuItem insert(JMenuItem menuItem, int pos); public JMenuItem insert(Action a, int pos); public void insertSeparator(int pos); When a menu item is added to a JMenu, it’s added to an internal JPopupMenu.
Using Action Objects with Menus The Action interface and its associated classes are described in Chapter 2. An Action is an extension of the ActionListener interface and contains some special properties for customizing components associated with its implementations. With the help of the AbstractAction implementation, you can easily define text labels, icons, mnemonics, tooltip text, enabled status, and an ActionListener apart from a component. Then you can create a component with an associated Action and not need to give the component a text label, icon, mnemonics, tooltip text, enabled status, or ActionListener, because those attributes would come from the Action. For a more complete description, refer to Chapter 2. To demonstrate, Listing 6-2 creates a specific implementation of AbstractAction and adds it to a JMenu multiple times. Once the Action is added to a JMenu, selecting the JMenuItem will display a pop-up dialog box with the help of the JOptionPane class, a topic covered in Chapter 9. Listing 6-2. About Action Definition import java.awt.*; import java.awt.event.*; import javax.swing.*; public class ShowAction extends AbstractAction { Component parentComponent; public ShowAction(Component parentComponent) { super("About"); putValue(Action.MNEMONIC_KEY, new Integer(KeyEvent.VK_A)); this.parentComponent = parentComponent; }
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
public void actionPerformed(ActionEvent actionEvent) { Runnable runnable = new Runnable() { public void run() { JOptionPane.showMessageDialog( parentComponent, "About Swing", "About Box V2.0", JOptionPane.INFORMATION_MESSAGE); } }; EventQueue.invokeLater(runnable); } } The next source creates a ShowAction and a JMenuItem for the File and Edit menus in the sample program (Listing 6-1). Without explicitly setting the menu item properties, it will then have an “About” text label and an A mnemonic, and will perform the defined actionPerformed() method as its ActionListener. In fact, you can create the Action once, and then associate it with as many places as necessary (or other components that support adding Action objects). Action showAction = new ShowAction(aComponent); JMenuItem fileAbout = new JMenuItem(showAction); fileMenu.add(fileAbout); JMenuItem editAbout = new JMenuItem(showAction); editMenu.add(editAbout); One complexity-busting side effect when using AbstractAction is that it lets you disable the Action with setEnabled(false), which, in turn, will disable all components created from it.
JMenu Properties Besides the 100-plus inherited properties of JMenu, 16 properties are available from JMenu-specific methods, as shown in Table 6-5. Several of the properties override the behavior of the inherited properties. For instance, the setter method for the accelerator property throws an error if you try to assign such a property. In other words, accelerators aren’t supported within JMenu objects. The remaining properties describe the current state of the JMenu object and its contained menu components.
Table 6-5. JMenu Properties
Property Name
Data Type
Access
accelerator
KeyStroke
Write-only
accessibleContext
AccessibleContext
Read-only
component
Component
Read-only
delay
int
Read-write
itemCount
int
Read-only
menuComponentCount
int
Read-only
menuComponents
Component[ ]
Read-only
171
172
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
Table 6-5. JMenu Properties (Continued)
Property Name
Data Type
Access
menuListeners
MenuListener[ ]
Read-only
model
ButtonModel
Write-only bound
popupMenu
JPopupMenu
Read-only
popupMenuVisible
boolean
Read-write
selected
boolean
Read-write
subElements
MenuElement[ ]
Read-only
tearOff
boolean
Read-only
topLevelMenu
boolean
Read-only
UIClassID
String
Read-only
■Tip Keep in mind that many property methods are inherited and that the parent class might offer a getter method where the current class defines only a new setter method, or vice versa.
The delay property represents the value for the time that elapses between selection of a JMenu and posting of the JPopupMenu. By default, this value is zero, meaning that the submenu will appear immediately. Trying to set the value to a negative setting will throw an IllegalArgumentException.
■Caution Since there is no support for tear-off menus, if you try to access the tearOff property, an error will be thrown.
Selecting Menu Components Normally, you don’t need to listen for the selection of JMenu components. You listen for only selection of individual JMenuItem components. Nevertheless, you may be interested in the different ways that ChangeEvent works with a JMenu as compared with a JMenuItem. In addition, a MenuEvent can notify you whenever a menu is posted or canceled.
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
Listening to JMenu Events with a ChangeListener As with a JMenuItem, you can register a ChangeListener with a JMenu if you’re interested in making changes to the underlying ButtonModel. Surprisingly, the only possible state change to the ButtonModel with a JMenu is with the selected property. When selected, the JMenu displays its menu items. When not selected, the pop-up goes away. Listening to JMenu Events with a MenuListener The better way to listen for when a pop-up is displayed or hidden is by registering MenuListener objects with your JMenu objects. Its definition follows: public interface MenuListener extends EventListener { public void menuCanceled(MenuEvent e); public void menuDeselected(MenuEvent e); public void menuSelected(MenuEvent e); } With a registered MenuListener, you’re notified when a JMenu is selected before the pop-up menu is opened with the menu’s choices. This allows you to customize its menu choices on the fly at runtime, with some potential interaction performance penalties. Besides being told when the associated pop-up menu is to be posted, you’re also notified when the menu has been deselected and when the menu has been canceled. As the following MenuEvent class definition shows, the only piece of information that comes with the event is the source (the menu): public class MenuEvent extends EventObject { public MenuEvent(Object source); }
■Tip If you choose to customize the items on a JMenu dynamically, be sure to call revalidate(), because the component waits until you are done before updating the display.
Customizing a JMenu Look and Feel As with the JMenuBar and JMenuItem, the predefined look and feel classes provide a different JMenu appearance and set of default UIResource values. Figure 6-3 shows the appearance of the JMenu object for the preinstalled set of look and feel types. The available set of UIResource-related properties for a JMenu is shown in Table 6-6. For the JMenu component, there are 30 different properties.
173
174
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
Table 6-6. JMenu UIResource Elements
Property String
Object Type
menu
Color
Menu.acceleratorDelimiter
String
Menu.acceleratorFont
Font
Menu.acceleratorForeground
Color
Menu.acceleratorSelectionForeground
Color
Menu.ActionMap
ActionMap
Menu.arrowIcon
Icon
Menu.background
Color
Menu.border
Border
Menu.borderPainted
Boolean
Menu.checkIcon
Icon
Menu.delay
Integer
Menu.disabledForeground
Color
Menu.font
Font
Menu.foreground
Color
Menu.margin
Insets
Menu.menuPopupOffsetX
Integer
Menu.menuPopupOffsetY
Integer
Menu.opaque
Boolean
Menu.selectionBackground
Color
Menu.selectionForeground
Color
Menu.shortcutKeys
int[ ]
Menu.submenuPopupOffsetX
Integer
Menu.submenuPopupOffsetY
Integer
Menu.textIconGap
Integer
Menu.useMenuBarBackgroundForTopLevel
Boolean
menuPressedItemB
Color
menuPressedItemF
Color
menuText
Color
MenuUI
String
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
JSeparator Class The JSeparator class is a special component that acts as a separator on a JMenu. The JPopupMenu and JToolBar classes also support separators, but each uses its own subclass of JSeparator. In addition to being placed on a menu, the JSeparator can be used anywhere you want to use a horizontal or vertical line to separate different areas of a screen. The JSeparator is strictly a visual component; therefore, it has no data model.
Creating JSeparator Components To create a separator for a JMenu, you don’t directly create a JSeparator, although you can. Instead, you call the addSeparator() method of JMenu, and the menu will create the separator and add the separator as its next item. The fact that it’s a JSeparator (which isn’t a JMenuItem subclass) is hidden. There’s also an insertSeparator(int index) method of JMenu that allows you to add a separator at a specific position on the menu, that isn’t necessarily the next slot. If you plan to use a JSeparator away from a menu (for example, to visually separate two panels in a layout), you should use one of the two constructors for JSeparator: public JSeparator() JSeparator jSeparator = new JSeparator(); public JSeparator(int orientation) JSeparator jSeparator = new JSeparator(JSeparator.VERTICAL); These constructors allow you to create a horizontal or vertical separator. If an orientation isn’t specified, the orientation is horizontal. If you want to explicitly specify an orientation, you use either of the JSeparator constants of HORIZONTAL and VERTICAL.
JSeparator Properties After you have a JSeparator, you add it to the screen like any other component. The initial dimensions of the component are empty (zero width and height), so if the layout manager of the screen asks the component what size it would like to be, the separator will reply that it needs no space. On the other hand, if the layout manager offers a certain amount of space, the separator will use the space if the orientation is appropriate. For instance, adding a horizontal JSeparator to the north side of a BorderLayout panel draws a separator line across the screen. However, adding a horizontal JSeparator to the east side of the same panel would result in nothing being drawn. For a vertical JSeparator, the behavior is reversed: The north side would be empty and a vertical line would appear on the east side. The four properties of JSeparator are listed in Table 6-7. Table 6-7. JSeparator Properties
Property Name
Data Type
Access
accessibleContext
AccessibleContext
Read-only
orientation
int
Read-write bound
UI
SeparatorUI
Read-write bound
UIClassID
String
Read-only
175
176
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
■Caution If the orientation property isn’t set to a value equivalent to either JSeparator.HORIZONTAL or JSeparator.VERTICAL, an IllegalArgumentException is thrown.
Customizing a JSeparator Look and Feel The appearance of the JSeparator under the preinstalled set of look and feel types is shown with the other menu components in Figure 6-3. The available set of UIResource-related properties for a JSeparator is shown in Table 6-8. For the JSeparator component, five different properties are available.
Table 6-8. JSeparator UIResource Elements
Property String
Object Type
Separator.background
Color
Separator.foreground
Color
Separator.insets
Insets
Separator.thickness
Integer
SeparatorUI
String
■Caution Two additional properties, highlight and shadow, are present but deprecated and should not be used.
JPopupMenu Class The JPopupMenu component is the container for pop-up menu components, displayable anywhere and used for support by JMenu. When a programmer-defined triggering event happens, you display the JPopupMenu, and the menu displays the contained menu components. Like JMenuBar, JPopupMenu uses the SingleSelectionModel to manage the currently selected element.
Creating JPopupMenu Components There are two constructors for JPopupMenu: public JPopupMenu() JPopupMenu jPopupMenu = new JPopupMenu(); public JPopupMenu(String title) JPopupMenu jPopupMenu = new JPopupMenu("Welcome");
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
Only one allows you to initialize the title for the menu, if desired. What happens with the title depends on the installed look and feel. The currently installed look and feel may ignore the title.
Adding Menu Items to a JPopupMenu As with a JMenu, once you have a JPopupMenu, you need to add menu item objects to it; otherwise, the menu will be empty. There are three JPopupMenu methods for adding menu items and one for adding a separator: public public public public
In addition, an add() method is inherited from Container for adding regular AWT components: public Component add(Component component);
■Note It generally isn’t wise to mix lightweight Swing components with heavyweight AWT components. However, because pop-up menus are more apt to be on top, it’s less of an issue in this case.
The natural way of adding menu items is with the first add() method. You create the menu item independently of the pop-up menu, including defining its behavior, and then you attach it to the menu. With the second variety of add(), you must attach an event handler to the menu item returned from the method; otherwise, the menu choice won’t respond when selected. The following source demonstrates the two approaches. Which you use depends entirely on your preference. A visual programming environment like JBuilder will use the first. Because the first approach is inherently less complex, most, if not all, programmers should also use the first approach. JPopupMenu popupenu = new JPopupMenu(); ActionListener anActionListener = ...; // The first way JMenuItem firstItem = new JMenuItem("Hello"); firstItem.addActionListener(anActionListener); popupMenu.add(firstItem); // The second way JMenuItem secondItem = popupMenu.add("World"); secondItem.addActionListener(anActionListener); Using an Action to create a menu item works the same with JPopupMenu as it does with JMenu. However, according to the Javadoc for the JPopupMenu class, using the Action variety of the add() method is discouraged. Instead, pass the Action to the constructor for JMenuItem, or
177
178
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
configure it with setAction(), and then add that to the JPopupMenu. Why the method isn’t just deprecated isn’t clear. Lastly, you can add a menu separator with the addSeparator() method. As well as adding menu items at the end of a menu, you can insert them at specific positions or insert a separator at a specific position: public JMenuItem insert(Component component, int position); public JMenuItem insert(Action action, int position); There’s no insertSeparator() method as there is with JMenu. But you can use the add(Component component, int position) method inherited from Container. If you want to remove components, use the remove(Component component) method specific to JPopupMenu.
■Note Accelerators on attached JMenuItem objects are ignored. Mnemonics might also be ignored depending on the currently installed look and feel.
Displaying the JPopupMenu Unlike the JMenu, simply populating the pop-up menu isn’t sufficient to use it. You need to associate the pop-up menu with an appropriate component. Prior to the 5.0 release of Swing, you needed to add event-handling code to trigger the display of the pop-up menu. Now, all you need to do is call the setComponentPopupMenu() method for the Swing component you wish to associate the pop-up menu with. When the platform-specific triggering event happens, the pop-up menu is automatically displayed.
■Note Why change the way pop-up menu display is triggered? The old code was very tightly tied to mouse events. It didn’t connect well with the accessibility framework. And the same code was being added everywhere to just show the pop-up menu at the x, y coordinates of the invoker.
You simply need to create an instance of JPopupMenu and attach it to any component you want to have display the pop-up menu, as follows: JPopupMenu popupMenu = ...; aComponent.setComponentPopupMenu(popupMenu); The methods of JComponent that are important to pop-up menus are getComponentPopupMenu(), setComponentPopupMenu(), getInheritsPopupMenu(), setInheritsPopupMenu(), and getPopupLocation(). The setInheritsPopupMenu() method accepts a boolean argument. When true, and no component pop-up menu has been directly set for the component, the parent container will be explored for a pop-up.
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
JPopupMenu Properties The 16 properties of JPopupMenu are listed in Table 6-9. Many more properties are also inherited through JComponent, Container, and Component.
Table 6-9. JPopupMenu Properties
Property Name
Data Type
Access
accessibleContext
AccessibleContext
Read-only
borderPainted
boolean
Read-write
component
Component
Read-only
invoker
Component
Read-only
label
String
Read-write bound
lightWeightPopupEnabled
boolean
Read-write
margin
Insets
Read-only
menuKeyListeners
MenuKeyListener[ ]
Read-only
popupMenuListeners
PopupMenuListener[ ]
Read-only
popupSize
Dimension
Write-only
selected
Component
Write-only
selectionModel
SingleSelectionModel
Read-write
subElements
MenuElement[ ]
Read-only
UI
PopupMenuUI
Read-write bound
UIClassID
String
Read-only
visible
boolean
Read-write
The most interesting property of JPopupMenu is lightWeightPopupEnabled. Normally, the JPopupMenu tries to avoid creating new heavyweight components for displaying its menu items. Instead, the pop-up menu uses a JPanel when the JPopupMenu can be displayed completely within the outermost window boundaries. Otherwise, if the menu items don’t fit, the JPopupMenu uses a JWindow. If, however, you’re mixing lightweight and heavyweight components on different window layers, displaying the pop-up within a JPanel might not work, because a heavyweight component displayed in the layer of the menu will appear in front of the JPanel. To correct this behavior, the pop-up menu can use a Panel for displaying the menu choices. By default, the JPopupMenu never uses a Panel.
179
180
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
■Note When the JPopupMenu is displayed in either a JPanel or a Panel, the outermost window relies on the layering effect of the JRootPane to ensure that the pop-up panel is displayed at the appropriate position in front of the other components. Chapter 8 describes the JRootPane class in more detail.
If you need to enable the display of a Panel, you can configure it at the individual JPopupMenu level or for your entire applet or application. At the individual pop-up level, just set the lightWeightPopupEnabled property to false. At the system level, this is done as follows: // From now on, all JPopupMenus will be heavyweight JPopupMenu.setDefaultLightWeightPopupEnabled(false); The method must be called before creating the pop-up menu. JPopupMenu objects created before the change will have the original value (the default is true).
Watching for Pop-Up Menu Visibility Like the JMenu, the JPopupMenu has a special event/listener combination to watch for when the pop-up menu is about to become visible, invisible, or canceled. The event is PopupMenuEvent, and the listener is PopupMenuListener. The event class simply references the source pop-up menu of the event. public class PopupMenuEvent extends EventObject { public PopupMenuEvent(Object source); } When a JPopupMenu fires the event, any registered PopupMenuListener objects are notified through one of its three interface methods. This lets you customize the current menu items based on the system state or who/what the pop-up menu invoker happens to be. The PopupMenuListener interface definition follows: public interface PopupMenuListener extends EventListener { public void popupMenuCanceled(PopupMenuEvent e); public void popupMenuWillBecomeInvisible(PopupMenuEvent e); public void popupMenuWillBecomeVisible(PopupMenuEvent e); }
Customizing a JPopupMenu Look and Feel Each installable Swing look and feel provides a different JPopupMenu appearance and set of default UIResource values. Figure 6-6 shows the appearance of the JPopupMenu component for the preinstalled set of look and feel types: Motif, Windows, and Ocean. Notice that of the predefined look and feel classes, only Motif uses the title property of the JPopupMenu.
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
Motif
Windows
Ocean
Figure 6-6. JPopupMenu under different look and feel types The available set of UIResource-related properties for a JPopupMenu is shown in Table 6-10. For the JPopupMenu component, there are five different properties.
JPopupMenu.Separator Class The JPopupMenu class maintains its own separator to permit a custom look and feel for the separator when it’s on a JPopupMenu. This custom separator is an inner class to the JPopupMenu. When you call the addSeparator() of JPopupMenu, an instance of this class is automatically created and added to the pop-up menu. In addition, you can create this separator by calling its no-argument constructor: JSeparator popupSeparator = new JPopupMenu.Separator(); Both methods create a horizontal separator.
■Note If you want to change the orientation of the separator, you must call the setOrientation() method inherited from JSeparator with an argument of JPopupMenu.Separator.VERTICAL. However, having a vertical separator on a pop-up menu is inappropriate.
A Complete Pop-Up Menu Usage Example The program in Listing 6-3 puts together all the pieces of using a JPopupMenu, including listening for selection of all the items on the menu, as well as listening for when it’s displayed. The output for the program is shown in Figure 6-7, with the pop-up visible.
Figure 6-7. JPopupMenu usage example output Listing 6-3. PopupSample Class Definition import import import import
JCheckBoxMenuItem Class Swing’s JCheckBoxMenuItem component behaves as if you have a JCheckBox on a menu as a JMenuItem. The data model for the menu item is the ToggleButtonModel, described in Chapter 5. It allows the menu item to have a selected or unselected state, while showing an appropriate icon for the state. Because the data model is the ToggleButtonModel, when JCheckBoxMenuItem is placed in a ButtonGroup, only one component in the group is ever selected. However, this isn’t the natural way to use a JCheckBoxMenuItem and is likely to confuse users. If you need this behavior, use JRadioButtonMenuItem, as described later in this chapter.
Creating JCheckBoxMenuItem Components There are seven constructors for JCheckBoxMenuItem. They allow you to initialize the text label, icon, and initial state. public JCheckBoxMenuItem() JCheckBoxMenuItem jCheckBoxMenuItem = new JCheckBoxMenuItem(); public JCheckBoxMenuItem(String text) JCheckBoxMenuItem jCheckBoxMenuItem = new JCheckBoxMenuItem("Boy");
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
public JCheckBoxMenuItem(Icon icon) Icon boyIcon = new ImageIcon("boy-r.jpg"); JCheckBoxMenuItem jCheckBoxMenuItem = new JCheckBoxMenuItem(boyIcon); public JCheckBoxMenuItem(String text, Icon icon) JCheckBoxMenuItem jCheckBoxMenuItem = new JCheckBoxMenuItem("Boy", boyIcon); public JCheckBoxMenuItem(String text, boolean state) JCheckBoxMenuItem jCheckBoxMenuItem = new JCheckBoxMenuItem("Girl", true); public JCheckBoxMenuItem(String text, Icon icon, boolean state) Icon girlIcon = new ImageIcon("girl-r.jpg"); JCheckBoxMenuItem jCheckBoxMenuItem = new JCheckBoxMenuItem("Girl", girlIcon, true); public JCheckBoxMenuItem(Action action) Action action = ...; JCheckBoxMenuItem jCheckBoxMenuItem = new JCheckBoxMenuItem(action); Unlike the JCheckBox, the icon is part of the label and not a separate device to indicate whether something is checked. If either the text label or the icon isn’t passed to the constructor, that part of the item label will be set to its default value of empty. By default, a JCheckBoxMenuItem is unselected.
■Note Creating a JCheckBoxMenuItem with an icon has no effect on the appearance of the check box next to the menu item. It’s strictly part of the label for the JCheckBoxMenuItem.
JCheckBoxMenuItem Properties Most of the JCheckBoxMenuItem properties are inherited from the many superclasses of JCheckBoxMenuItem. Table 6-11 lists the four properties defined by JCheckBoxMenuItem.
Table 6-11. JCheckBoxMenuItem Properties
Property Name
Data Type
Access
accessibleContext
AccessibleContext
Read-only
selectedObjects
Object[ ]
Read-only
state
boolean
Read-write
UIClassID
String
Read-only
185
186
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
Handling JCheckBoxMenuItem Selection Events With a JCheckBoxMenuItem, you can attach many different listeners for a great variety of events: • MenuDragMouseListener and MenuKeyListener from JMenuItem • ActionListener, ChangeListener, and ItemListener from AbstractButton • AncestorListener and VetoableChangeListener from JComponent • ContainerListener and PropertyChangeListener from Container • ComponentListener, FocusListener, HierarchyBoundsListener, HierarchyListener, InputMethodListener, KeyListener, MouseListener, MouseMotionListener, and MouseWheelListener from Component Although you can listen for 18 different types of events, the most interesting are ActionEvent and ItemEvent, described next. Listening to JCheckBoxMenuItem Events with an ActionListener Attaching an ActionListener to a JCheckBoxMenuItem allows you to find out when the menu item is selected. The listener is told of the selection, but not of the new state. To find out the selected state, you must get the model for the event source and query the selection state, as the following sample ActionListener source shows. This listener modifies both the check box text and the icon label, based on the current selection state. ActionListener aListener = new ActionListener() { public void actionPerformed(ActionEvent event) { Icon girlIcon = new ImageIcon("girl-r.jpg"); Icon boyIcon = new ImageIcon("boy-r.jpg"); AbstractButton aButton = (AbstractButton)event.getSource(); boolean selected = aButton.getModel().isSelected(); String newLabel; Icon newIcon; if (selected) { newLabel = "Girl"; newIcon = girlIcon; } else { newLabel = "Boy"; newIcon = boyIcon; } aButton.setText(newLabel); aButton.setIcon(newIcon); } };
■Note Keep in mind that you can also associate an Action from the constructor that can do the same thing.
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
Listening to JCheckBoxMenuItem with an ItemListener If you listen for JCheckBoxMenuitem selection with an ItemListener, you don’t need to query the event source for the selection state—the event already carries that information. Based on this state, you respond accordingly. Re-creating the ActionListener behavior with an ItemListener requires just a few minor changes to the previously listed source, as follows: ItemListener iListener = new ItemListener() { public void itemStateChanged(ItemEvent event) { Icon girlIcon = new ImageIcon("girl-r.jpg"); Icon boyIcon = new ImageIcon("boy-r.jpg"); AbstractButton aButton = (AbstractButton)event.getSource(); int state = event.getStateChange(); String newLabel; Icon newIcon; if (state == ItemEvent.SELECTED) { newLabel = "Girl"; newIcon = girlIcon; } else { newLabel = "Boy"; newIcon = boyIcon; } aButton.setText(newLabel); aButton.setIcon(newIcon); } };
Customizing a JCheckBoxMenuItem Look and Feel The appearance of the JCheckBoxMenuItem under the preinstalled set of look and feel types is shown with the other menu components in Figure 6-3. The available set of UIResource-related properties for a JCheckBoxMenuItem is shown in Table 6-12. The JCheckBoxMenuItem component has 19 different properties.
Table 6-12. JCheckBoxMenuItem UIResource Elements
Property String
Object Type
CheckBoxMenuItem.acceleratorFont
Font
CheckBoxMenuItem.acceleratorForeground
Color
CheckBoxMenuItem.acceleratorSelectionForeground
Color
CheckBoxMenuItem.actionMap
ActionMap
CheckBoxMenuItem.arrowIcon
Icon
CheckBoxMenuItem.background
Color
CheckBoxMenuItem.border
Border
CheckBoxMenuItem.borderPainted
Boolean
187
188
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
Table 6-12. JCheckBoxMenuItem UIResource Elements (Continued)
Property String
Object Type
CheckBoxMenuItem.checkIcon
Icon
CheckBoxMenuItem.commandSound
String
CheckBoxMenuItem.disabledForeground
Color
CheckBoxMenuItem.font
Font
CheckBoxMenuItem.foreground
Color
CheckBoxMenuItem.gradient
List
CheckBoxMenuItem.margin
Insets
CheckBoxMenuItem.opaque
Boolean
CheckBoxMenuItem.selectionBackground
Color
CheckBoxMenuItem.selectionForeground
Color
CheckBoxMenuItemUI
String
The Icon associated with the CheckBoxMenuItem.checkIcon property key is the one displayed on the JCheckBoxMenuItem. If you don’t like the default icon, you can change it with the following line of source, assuming the new icon has already been defined and created: UIManager.put("CheckBoxMenuItem.checkIcon", someIcon); For this new icon to display an appropriate selected image, the Icon implementation must check the state of the associated menu component within its paintIcon() method. The DiamondIcon created in Chapter 4 wouldn’t work for this icon because it doesn’t ask the component for its state. Instead, the state is fixed at constructor time. Listing 6-4 shows a class that represents one icon that could be used. Listing 6-4. State-Aware Icon Definition import java.awt.*; import javax.swing.*; public class DiamondAbstractButtonStateIcon implements Icon { private final int width = 10; private final int height = 10; private Color color; private Polygon polygon; public DiamondAbstractButtonStateIcon(Color color) { this.color = color; initPolygon(); }
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
private void initPolygon() { polygon = new Polygon(); int halfWidth = width/2; int halfHeight = height/2; polygon.addPoint (0, halfHeight); polygon.addPoint (halfWidth, 0); polygon.addPoint (width, halfHeight); polygon.addPoint (halfWidth, height); } public int getIconHeight() { return width; } public int getIconWidth() { return height; } public void paintIcon(Component component, Graphics g, int x, int y) { boolean selected = false; g.setColor (color); g.translate (x, y); if (component instanceof AbstractButton) { AbstractButton abstractButton = (AbstractButton)component; selected = abstractButton.isSelected(); } if (selected) { g.fillPolygon (polygon); } else { g.drawPolygon (polygon); } g.translate (-x, -y); } }
■Note If the DiamondAbstractButtonStateIcon icon were used with a component that isn’t an AbstractButton type, the icon would always be deselected, because the selection state is a property of AbstractButton.
JRadioButtonMenuItem Class The JRadioButtonMenuItem component has the longest name of all the Swing components. It works like a JRadioButton, but resides on a menu. When placed with other JRadioButtonMenuItem components within a ButtonGroup, only one component will be selected at a time. As with the JRadioButton, the button model for the JRadioButtonMenuItem is the JToggleButton. ToggleButtonModel.
189
190
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
Creating JRadioButtonMenuItem Components The JRadioButtonMenuItem has seven constructors. They allow you to initialize the text label, icon, and initial state. public JCheckBoxMenuItem() JCheckBoxMenuItem jCheckBoxMenuItem = new JCheckBoxMenuItem(); public JCheckBoxMenuItem(String text) JCheckBoxMenuItem jCheckBoxMenuItem = new JCheckBoxMenuItem("Boy"); public JCheckBoxMenuItem(Icon icon) Icon boyIcon = new ImageIcon("boy-r.jpg"); JCheckBoxMenuItem jCheckBoxMenuItem = new JCheckBoxMenuItem(boyIcon); public JCheckBoxMenuItem(String text, Icon icon) JCheckBoxMenuItem jCheckBoxMenuItem = new JCheckBoxMenuItem("Boy", boyIcon); public JCheckBoxMenuItem(String text, boolean state) JCheckBoxMenuItem jCheckBoxMenuItem = new JCheckBoxMenuItem("Girl", true); public JCheckBoxMenuItem(String text, Icon icon, boolean state) Icon girlIcon = new ImageIcon("girl-r.jpg"); JCheckBoxMenuItem jCheckBoxMenuItem = new JCheckBoxMenuItem("Girl", girlIcon, true); public JCheckBoxMenuItem(Action action) Action action = ...; JCheckBoxMenuItem jCheckBoxMenuItem = new JCheckBoxMenuItem(action); Similar to the JCheckBoxMenuItem component, the icon for the JRadioButtonMenuItem is part of the label. This is unlike the JRadioButton, in which the icon indicates whether the radio button is selected. If either the text label or icon isn’t part of the constructor, that part of the item label will be empty. By default, a JRadioButtonMenuItem is unselected. If you create a JRadioButtonMenuItem that is selected and then add it to a ButtonGroup, the button group will deselect the menu item if the group already has a selected item in the group.
■Note After creating JRadioButtonMenuItem instances, remember to add them to a ButtonGroup, so they will work as a mutually exclusive group.
Handling JRadioButtonMenuItem Selection Events The JRadioButtonMenuItem shares the same 18 different event/listener pairs with JCheckBoxMenuItem. To listen for selection, attaching an ActionListener is the normal approach. In addition, you might want to attach the same listener to all the JRadioButtonMenuItem objects in a ButtonGroup—after all, they’re in a group for a reason. If you use the same listener, that listener can employ the current selection to perform some common operation. In other cases, such as that in Figure 6-1, selection of any JRadioButtonMenuItem option does nothing.
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
Only when someone selects the Find menu element would the current selection of the ButtonGroup for the set of JRadioButtonMenuItem components have any meaning.
Configuring JRadioButtonMenuItem Properties As with JCheckBoxMenuItem, most of the JRadioButtonMenuItem properties are inherited. The two shown in Table 6-13 merely override the behavior from the superclass.
Table 6-13. JRadioButtonMenuItem Properties
Property Name
Data Type
Access
accessibleContext
AccessibleContext
Read-only
UIClassID
String
Read-only
Customizing a JRadioButtonMenuItem Look and Feel The appearance of the JRadioButtonMenuItem under the preinstalled set of look and feel types is shown with the other menu components in Figure 6-3. The available set of UIResource-related properties for a JRadioButtonMenuItem is shown in Table 6-14. For the JRadioButtonMenuItem component, there are 19 different properties.
Table 6-14. JRadioButtonMenuItem UIResource Elements
Table 6-14. JRadioButtonMenuItem UIResource Elements (Continued)
Property String
Object Type
RadioButtonMenuItem.selectionBackground
Color
RadioButtonMenuItem.selectionForeground
Color
RadioButtonMenuItemUI
String
A Complete JRadioButtonMenuItem Usage Example To help you understand the JRadioButtonMenuItem usage, the program shown in Listing 6-5 demonstrates how to put everything together, including listening for selection of all the items on the menu, from either an ActionListener or an ItemListener. The output for the program is shown in Figure 6-8.
Figure 6-8. JRadioButtonMenuItem usage example output Listing 6-5. The RadioButtonSample Class Definition import javax.swing.*; import java.awt.*; import java.awt.event.*; public class RadioButtonSample { static Icon threeIcon = new ImageIcon("3.gif"); static Icon fourIcon = new ImageIcon("4.gif"); static Icon fiveIcon = new ImageIcon("5.gif"); static Icon sixIcon = new ImageIcon("6.gif"); public static class ButtonActionListener implements ActionListener { public void actionPerformed (ActionEvent actionEvent) { AbstractButton aButton = (AbstractButton)actionEvent.getSource(); boolean selected = aButton.getModel().isSelected(); System.out.println (actionEvent.getActionCommand() + " - selected? " + selected); } }
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
public static class ButtonItemListener implements ItemListener { public void itemStateChanged(ItemEvent itemEvent) { AbstractButton aButton = (AbstractButton)itemEvent.getSource(); int state = itemEvent.getStateChange(); String selected = ((state == ItemEvent.SELECTED) ? "selected" : "not selected"); System.out.println (aButton.getText() + " - selected? " + selected); } } public static void main(String args[]) { Runnable runner = new Runnable() { public void run() { final ActionListener actionListener = new ButtionActionListener(); final ItemListener itemListener = new ButtonItemListener(); JFrame frame = new JFrame("Radio Menu Example"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); JMenuBar menuBar = new JMenuBar(); JMenu menu = new JMenu("Menu"); ButtonGroup buttonGroup = new ButtonGroup(); menu.setMnemonic(KeyEvent.VK_M); JRadioButtonMenuItem emptyMenuItem = new JRadioButtonMenuItem(); emptyMenuItem.setActionCommand("Empty"); emptyMenuItem.addActionListener(actionListener); buttonGroup.add(emptyMenuItem); menu.add(emptyMenuItem); JRadioButtonMenuItem oneMenuItem = new JRadioButtonMenuItem("Partridge"); oneMenuItem.addActionListener(actionListener); buttonGroup.add(oneMenuItem); menu.add(oneMenuItem); JRadioButtonMenuItem twoMenuItem = new JRadioButtonMenuItem("Turtle Doves", true); twoMenuItem.addActionListener(actionListener); buttonGroup.add(twoMenuItem); menu.add(twoMenuItem);
■Note Notice that the actionCommand property is set for those menu items lacking text labels. This allows registered ActionListener objects to determine the selected object. This is only necessary when listeners are shared across components.
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
Creating Custom MenuElement Components: The MenuElement Interface One thing all the selectable menu components have in common is that they implement the MenuElement interface. The JSeparator doesn’t implement the interface, but that’s okay because it isn’t selectable. The purpose of the MenuElement interface is to allow the MenuSelectionManager to notify the different menu elements as a user moves around a program’s menu structure. As the following interface definition shows, the MenuElement interface is made up of five methods: public interface MenuElement { public Component getComponent(); public MenuElement[] getSubElements(); public void menuSelectionChanged(boolean isInclude); public void processKeyEvent(KeyEvent event, MenuElement path[], MenuSelectionManager mgr); public void processMouseEvent(MouseEvent event, MenuElement path[], MenuSelectionManager mgr); } The getComponent() method returns the menu’s rendering component. This is usually the menu component itself, although that isn’t a requirement. The getSubElements() method returns an array of any menu elements contained within this element. If this menu element isn’t the top of a submenu, the method should return a zero-length array of MenuElement objects, not null. The menuSelectionChanged() method is called whenever the menu item is placed in or taken out of the selection path for the menu selection manager. The two processKeyEvent() and processMouseEvent() methods are for processing a key event or mouse event that’s generated over a menu. How your menu item processes events depends on what the component supports. For instance, unless you support accelerators, you probably want to respond to key events only when your menu item is in the current selection path.
■Note If, for example, your new menu element was something like a JComboBoxMenuItem, where the MenuElement acted like a JComboBox, the processKeyEvent() might pass along the key character to the KeySelectionManager. See Chapter 13 for more on the KeySelectionManager.
To demonstrate the MenuElement interface, Listing 6-6 creates a new menu component called a JToggleButtonMenuItem. This component will look and act like a JToggleButton, although it can be on a menu. It’s important to ensure that the menu goes away once the item is selected and that the component is displayed differently when in the current selection path.
195
196
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
■Note Although you can add any component to a menu, if the component doesn’t implement the MenuElement interface, it won’t act properly when a mouse moves over the component or when the component is selected.
Listing 6-6. Toggle Button As Menu Item Class Definition import import import import
public class JToggleButtonMenuItem extends JToggleButton implements MenuElement { Color savedForeground = null; private static MenuElement NO_SUB_ELEMENTS[] = new MenuElement[0]; public JToggleButtonMenuItem() { init(); } public JToggleButtonMenuItem(String label) { super(label); init(); } public JToggleButtonMenuItem(String label, Icon icon) { super(label, icon); init(); } public JToggleButtonMenuItem(Action action) { super(action); init(); } private void init() { updateUI(); setRequestFocusEnabled(false); // Borrows heavily from BasicMenuUI MouseInputListener mouseInputListener = new MouseInputListener() { // If mouse released over this menu item, activate it public void mouseReleased(MouseEvent mouseEvent) { MenuSelectionManager menuSelectionManager = MenuSelectionManager.defaultManager(); Point point = mouseEvent.getPoint(); if ((point.x >= 0) && (point.x < getWidth()) && (point.y >= 0) && (point.y < getHeight())) { menuSelectionManager.clearSelectedPath(); // Component automatically handles "selection" at this point // doClick(0); // not necessary
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
} else { menuSelectionManager.processMouseEvent(mouseEvent); } } // If mouse moves over menu item, add to selection path, so it becomes armed public void mouseEntered(MouseEvent mouseEvent) { MenuSelectionManager menuSelectionManager = MenuSelectionManager.defaultManager(); menuSelectionManager.setSelectedPath(getPath()); } // When mouse moves away from menu item, disarm it and select something else public void mouseExited(MouseEvent mouseEvent) { MenuSelectionManager menuSelectionManager = MenuSelectionManager.defaultManager(); MenuElement path[] = menuSelectionManager.getSelectedPath(); if (path.length > 1) { MenuElement newPath[] = new MenuElement[path.length-1]; for(int i=0, c=path.length-1; i
197
198
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
public void menuSelectionChanged(boolean isIncluded) { ButtonModel model = getModel(); // Only change armed state if different if(model.isArmed() != isIncluded) { model.setArmed(isIncluded); } if (isIncluded) { savedForeground = getForeground(); if (!savedForeground.equals(Color.BLUE)) { setForeground(Color.BLUE); } else { // In case foreground blue, use something different setForeground(Color.RED); } } else { setForeground(savedForeground); // If null, get foreground from installed look and feel if (savedForeground == null) { updateUI(); } } } public void processKeyEvent(KeyEvent keyEvent, MenuElement path[], MenuSelectionManager manager) { // If user presses space while menu item armed, select it if (getModel().isArmed()) { int keyChar = keyEvent.getKeyChar(); if (keyChar == KeyEvent.VK_SPACE) { manager.clearSelectedPath(); System.out.println("Selected: JToggleButtonMenuItem, by KeyEvent"); doClick(0); // inherited from AbstractButton } } } public void processMouseEvent(MouseEvent mouseEvent, MenuElement path[], MenuSelectionManager manager) { // For when mouse dragged over menu and button released if (mouseEvent.getID() == MouseEvent.MOUSE_RELEASED) { manager.clearSelectedPath(); System.out.println("Selected: JToggleButtonMenuItem, by MouseEvent"); doClick(0); // inherited from AbstractButton } }
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
// Borrows heavily from BasicMenuItemUI.getPath() private MenuElement[] getPath() { MenuSelectionManager menuSelectionManager = MenuSelectionManager.defaultManager(); MenuElement oldPath[] = menuSelectionManager.getSelectedPath(); MenuElement newPath[]; int oldPathLength = oldPath.length; if (oldPathLength == 0) return new MenuElement[0]; Component parent = getParent(); if (oldPath[oldPathLength-1].getComponent() == parent) { // Going deeper under the parent menu newPath = new MenuElement[oldPathLength+1]; System.arraycopy(oldPath, 0, newPath, 0, oldPathLength); newPath[oldPathLength] = this; } else { // Sibling/child menu item currently selected int newPathPosition; for (newPathPosition = oldPath.length-1; newPathPosition >= 0; newPathPosition--) { if (oldPath[newPathPosition].getComponent() == parent) { break; } } newPath = new MenuElement[newPathPosition+2]; System.arraycopy(oldPath, 0, newPath, 0, newPathPosition+1); newPath[newPathPosition+1] = this; } return newPath; } }
■Note The MouseInputListener defined in the init() method and the getPath() method borrow heavily from the system BasicMenuUI class. Normally, the user interface delegate deals with what happens when the mouse moves over a menu component. Because the JToggleButton isn’t a predefined menu component, its UI class doesn’t deal with it. For better modularity, these two methods should be moved into an extended ToggleButtonUI.
Once you’ve created this JToggleButtonMenuItem class, you can use it like any other menu item: JToggleButtonMenuItem toggleItem = new JToggleButtonMenuItem("Balloon Help"); editMenu.add(toggleItem);
199
200
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
Working with Pop-Ups: The Popup Class Not everything you want to pop up needs to be a menu. Through the Popup and PopupFactory classes, you can pop up any component over another. This is different from tooltips, which are in a read-only, unselectable label. You can pop up selectable buttons, trees, or tables.
Creating Pop-Up Components Popup is a simple class with two methods—hide() and show()—with two protected constructors. Instead of creating Popup objects directly, you acquire them from the PopupFactory class. PopupFactory factory = PopupFactory.getSharedInstance(); Popup popup = factory.getPopup(owner, contents, x, y); The Popup with the contents component created by PopupFactory will thus be “above” other components within the owner component.
A Complete Popup/PopupFactory Usage Example Listing 6-7 demonstrates the usage of Popup and PopupFactory to show a JButton above another JButton. Selecting the initial JButton will cause the second one to be created above the first, at some random location. When the second button is visible, each is selectable. Selecting the initially visible button multiple times will cause even more pop-up buttons to appear, as shown in Figure 6-9. Each pop-up button will disappear after three seconds. In this example, selecting the pop-up button just displays a message to the console.
Figure 6-9. Popup/PopupFactory example Listing 6-7. The ButtonPopupSample Class Definition import import import import
Working with Toolbars: The JToolBar Class Toolbars are an integral part of the main application windows in a modern user interface. Toolbars provide users with easy access to the more commonly used commands, which are usually buried within a hierarchical menuing structure. The Swing component that supports this capability is the JToolBar. The JToolBar is a specialized Swing container for holding components. This container can then be used as a toolbar within your Java applet or application, with the potential for it to be floating or draggable, outside the main window of the program. JToolBar is a very simple component to use and understand.
Creating JToolBar Components There are four constructors for creating JToolBar components: public JToolBar() JToolBar jToolBar = new JToolBar(); public JToolBar(int orientation) JToolBar jToolBar = new JToolBar(JToolBar.VERTICAL); public JToolBar(String name) JToolBar jToolBar = new JToolBar("Window Title"); public JToolBar(String name,int orientation) JToolBar jToolBar = new JToolBar("Window Title", ToolBar.VERTICAL); By default, a toolbar is created in a horizontal direction. However, you can explicitly set the orientation by using either of the JToolBar constants of HORIZONTAL and VERTICAL. Also by default, toolbars are floatable. Therefore, if you create the toolbar with one orientation, the user could change its orientation while dragging the toolbar around outside the window. When floating, the title will be visible on the toolbar’s frame.
Adding Components to a JToolBar Once you have a JToolBar, you need to add components to it. Any Component can be added to the toolbar. When dealing with horizontal toolbars, for aesthetic reasons, it’s best if the toolbar components are all roughly the same height. For a vertical toolbar, it’s best if they’re roughly the same width. There’s only one method defined by the JToolBar class for adding toolbar
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
items; the remaining methods, such as add(Component), are inherited from Container. In addition, you can add a separator to a toolbar. public JButton add(Action action); public void addSeparator(); public void addSeparator(Dimension size); When using the add(Action) method of JToolBar, the added Action is encapsulated within a JButton object. This is different from adding actions to JMenu or JPopupMenu components, in which JMenuItem objects are added instead. As with JMenu and JPopupMenu, adding an Action in this fashion is discouraged in the Javadoc for the class. For separators, if you don’t specify the size, the installed look and feel forces a default size setting.
■Note For more information about dealing with the Action interface, see Chapter 2 or the section “Using Action Objects with Menus” earlier in this chapter.
To remove components from a toolbar, use the following method: public void remove(Component component)
JToolBar Properties The JToolBar class defines nine properties, which are listed in Table 6-15.
Table 6-15. JToolBar Properties
Property Name
Data Type
Access
accessibleContext
AccessibleContext
Read-only
borderPainted
boolean
Read-write bound
floatable
boolean
Read-write bound
layout
LayoutManager
Write-only
margin
Insets
Read-write bound
orientation
int
Read-write bound
rollover
boolean
Read-write bound
UI
ToolBarUI
Read-write
UIClassID
String
Read-only
By default, the border of a JToolBar is painted. If you don’t want the border painted, you can set the borderPainted property to false. Without using the borderPainted property, you would need to change the setting of the border property (inherited from the superclass JComponent).
203
204
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
The orientation property can be set to only one of the HORIZONTAL or VERTICAL constants of JToolBar. If another nonequivalent value is used, an IllegalArgumentException is thrown. Changing the orientation changes the layout manager of the toolbar. If you directly change the layout manager with setLayout(), changing the orientation will undo your layout change. Consequently, it’s best not to manually change the layout manager of a JToolBar. As previously mentioned, a toolbar is floatable by default. This means that a user can drag the toolbar from where you place it and move it elsewhere. To drag a toolbar, the user selects an empty part of it. The toolbar can than be left outside the original program window, floating above the main window in its own window, or dropped onto another area of the original program window. If the layout manager of the original window is BorderLayout, the droppable areas are the edges of the layout manager without any components. (You can’t drop the toolbar in the center of the window.) Otherwise, the toolbar would be dropped into the last spot of the container. Figure 6-10 shows the different phases of the dragging and docking process. Dragging Hot Spot
Docked in a Different Area
Figure 6-10. JToolBar phases
Floating
Undocked Floating Toolbar
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
The rollover property defines a behavior specific to the look and feel for when the user moves the mouse over the different components within the toolbar. This behavior could involve coloration or border differences.
Handling JToolBar Events There are no events specific to the JToolBar. You need to attach listeners to each item on the JToolBar that you want to respond to user interaction. Of course, JToolBar is a Container, so you could listen to its events.
Customizing a JToolBar Look and Feel Each installable Swing look and feel provides its own JToolBar appearance and set of default UIResource values. Most of this appearance is controlled by the components actually within the toolbar. Figure 6-11 shows the appearance of the JToolBar component for the preinstalled set of look and feel types: Motif, Windows, and Ocean. Each toolbar has five JButton components, with a separator between the fourth and fifth.
Motif
Windows
Ocean
Figure 6-11. JToolBar under different look and feel types The available set of UIResource-related properties for a JToolBar is shown in Table 6-16. For the JToolBar component, there are 22 different properties.
Table 6-16. JToolBar UIResource Elements
Property String
Object Type
ToolBar.actionMap
ActionMap
ToolBar.ancestorInputMap
InputMap
ToolBar.background
Color
205
206
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
Table 6-16. JToolBar UIResource Elements (Continued)
Property String
Object Type
ToolBar.border
Border
ToolBar.borderColor
Color
ToolBar.darkShadow
Color
ToolBar.dockingBackground
Color
ToolBar.dockingForeground
Color
ToolBar.floatingBackground
Color
ToolBar.floatingForeground
Color
ToolBar.font
Font
ToolBar.foreground
Color
ToolBar.handleIcon
Icon
ToolBar.highlight
Color
ToolBar.isRollover
Boolean
ToolBar.light
Color
ToolBar.nonrolloverBorder
Border
ToolBar.rolloverBorder
Border
ToolBar.separatorSize
Dimension
ToolBar.shadow
Color
ToolBarSeparatorUI
String
ToolBarUI
String
A Complete JToolBar Usage Example The program in Listing 6-8 demonstrates a complete JToolBar example that results in a toolbar with a series of diamonds on the buttons. The program also reuses the ShowAction defined for the menuing example, presented in Listing 6-2 earlier in this chapter. The rollover property is enabled to demonstrate the difference for the current look and feel. See Figure 6-12 for the output as you move your mouse over the different buttons.
Figure 6-12. JToolBar example with isRollover enabled
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
Listing 6-8. The ToolBarSample Class Definition import java.awt.*; import java.awt.event.*; import javax.swing.*; public class ToolBarSample { private static final int COLOR_POSITION = 0; private static final int STRING_POSITION = 1; static Object buttonColors[][] = { {Color.RED, "RED"}, {Color.BLUE, "BLUE"}, {Color.GREEN, "GREEN"}, {Color.BLACK, "BLACK"}, null, // separator {Color.CYAN, "CYAN"} }; public static class TheActionListener implements ActionListener { public void actionPerformed (ActionEvent actionEvent) { System.out.println(actionEvent.getActionCommand()); } }; public static void main(final String args[]) { Runnable runner = new Runnable() { public void run() { JFrame frame = new JFrame("JToolBar Example"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); ActionListener actionListener = new TheActionListener(); JToolBar toolbar = new JToolBar(); toolbar.setRollover(true); for (Object[] color: buttonColors) { if (color == null) { toolbar.addSeparator(); } else { Icon icon = new DiamondIcon((Color)color[COLOR_POSITION], true, 20, 20); JButton button = new JButton(icon); button.setActionCommand((String)color[STRING_POSITION]); button.addActionListener(actionListener); toolbar.add(button); } }
207
208
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
Action action = new ShowAction(frame); JButton button = new JButton(action); toolbar.add(button); Container contentPane = frame.getContentPane(); contentPane.add(toolbar, BorderLayout.NORTH); JTextArea textArea = new JTextArea(); JScrollPane pane = new JScrollPane(textArea); contentPane.add(pane, BorderLayout.CENTER); frame.setSize(350, 150); frame.setVisible(true); } }; EventQueue.invokeLater(runner); } }
JToolBar.Separator Class The JToolBar class maintains its own separator to permit a custom look and feel for the separator when on a JToolBar. This separator is automatically created when you call the addSeparator() method of JToolBar. In addition, there are two constructors for creating a JToolBar.Separator if you want to manually create the component. public JToolBar.Separator() JSeparator toolBarSeparator = new JToolBar.Separator(); public JToolBar.Separator(Dimension size) Dimension dimension = new Dimension(10, 10); JSeparator toolBarSeparator = new JToolBar.Separator(dimension); Both constructors create a horizontal separator. You can configure the size. If you don’t specify this, the look and feel decides what size to make the separator. As with JPopupMenu.Separator, if you want to change the orientation of the separator, you must call the setOrientation() method inherited from JSeparator, this time with an argument of JToolBar.Separator.VERTICAL.
Summary This chapter introduced the many Swing menu-related classes and their interrelationships, and Swing’s toolbar class. First, you learned about the JMenuBar and its selection model, and learned how menu bars can be used within applets as well as applications. Next, you explored the JMenuItem, which is the menu element the user selects, along with two new event/listener pairs the system uses for dealing with events, MenuKeyEvent/MenuKeyListener and MenuDragMouseEvent/MenuDragMouseListener. Then, you moved on to the JMenu component, upon which JMenuItem instances are placed, along with its new event/listener pair, MenuEvent/MenuListener, which is used to determine when a menu is about to be posted.
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
Next, you learned about the JSeparator component and how you can use it as a menu separator or as a visual display separator outside of menus. You then explored the JPopupMenu, which JMenu uses to display its set of JMenuItem components. For the JPopupMenu, you learned about the pop-up menu’s own event/listener pair, PopupMenuEvent/PopupMenuListener. Then the selectable menu elements in JCheckBoxMenuItem and JRadioButtonMenuItem were explored with their MenuElement interface, and you saw how to create a custom menu component. Menus aren’t the only things that might pop up, so you explored Popup and PopupFactory. Finally, the chapter covered the JToolBar class, a close cousin of Swing’s menu classes. In Chapter 7, you’ll look at the different classes Swing provides for customizing the border around a Swing component.
209
CHAPTER 7 ■■■
Borders S
wing components offer the option of customizing the border area surrounding that component. With great ease, you can use any one of the eight predefined borders (including one compound border that is a combination of any of the other seven), or you can create your own individualized borders. In this chapter, you’ll learn how to best use each of the existing borders and how to fashion your own.
Some Basics on Working with Borders A border is a JComponent property with the standard setBorder() and getBorder() property methods. Therefore, every Swing component that is a subclass of JComponent can have a border. By default, a component doesn’t have a custom border associated with it. (The getBorder() method of JComponent returns null.) Instead, the default border displayed for a component is the border appropriate for its state, based on the current look and feel. For instance, with a JButton, the border could appear pressed, unpressed, or disabled, with specific different borders for each look and feel (Metal, Windows, and so on). Although the initial border property setting for every component is null, you can change the border of a component by calling the setBorder(Border newValue) method of JComponent. Once set, the changed value overrides the border for the current look and feel, and it draws the new border in the area of the component’s insets. If at a later time, you want to reset the border back to a border that’s appropriate for the state as well as the look and feel, change the border property to null, using setBorder(null), and call updateUI() for the component. The updateUI() call notifies the look and feel to reset the border. If you don’t call updateUI(), the component will have no border.
■Note Those Swing components that aren’t subclasses of JComponent, such as JApplet and JFrame, lack a setBorder() method to change their border. If you want them to have a border, you must add a JPanel or other Swing component to the container, and then change the border of that component.
211
212
CHAPTER 7 ■ BORDERS
Examine Figure 7-1 to see a sampling of the various border configurations around a JLabel, with a text label designating the border type. How to create the different borders will be discussed in later sections of this chapter.
Figure 7-1. Border examples, using a 4-by-2 GridLayout with 5-pixel horizontal and vertical gaps
Exploring the Border Interface The Border interface can be found in the javax.swing.border package. This interface forms the basis of all the border classes. The interface is directly implemented by the AbstractBorder class, which is the parent class of all the predefined Swing border classes: BevelBorder, CompoundBorder, EmptyBorder, EtchedBorder, LineBorder, MatteBorder, SoftBevelBorder, and TitledBorder. Of additional interest is the BorderFactory class, found in the javax.swing package. This class uses the Factory design pattern to create borders, hiding the details of the concrete implementations and caching various operations to optimize shared usages. The Border interface shown here consists of three methods: paintBorder(), getBorderInsets(), and isBorderOpaque(). These methods are described in the following sections.
paintBorder() The paintBorder() method is the key method of the interface. It has the following definition: public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) The actual drawing of the border is done in this method. Frequently, the Border implementation will ask for the Insets dimensions first, and then draw the border in the four rectangular outer regions, as shown in Figure 7-2. If a border is opaque, the paintBorder() implementation must fill the entire insets area. If a border is opaque and doesn’t fill the area, then it’s a bug and needs to be corrected.
CHAPTER 7 ■ BORDERS
Figure 7-2. Areas of border insets Listing 7-1 shows a simple paintBorder() implementation that fills in the left and right sides with a brighter color than the top and bottom. Listing 7-1. Filled-in Border Inset Areas public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) { Insets insets = getBorderInsets(c); Color color = c.getForeground(); Color brighterColor = color.brighter(); // Translate coordinate space g.translate(x, y); // Top g.setColor(color); g.fillRect(0, 0, width, insets.top); // Left g.setColor(brighterColor); g.fillRect(0, insets.top, insets.left, height-insets.top-insets.bottom); // Bottom g.setColor(color); g.fillRect(0, height-insets.bottom, width, insets.bottom);
213
214
CHAPTER 7 ■ BORDERS
// Right g.setColor(brighterColor); g.fillRect(width-insets.right, insets.top, insets.right, height-insets.top-insets.bottom); // Translate coordinate space back g.translate(-x, -y); } When creating your own borders, you’ll frequently find yourself filling in the same nonoverlapping rectangular regions. The use of the translate() method of Graphics simplifies the specification of the drawing coordinates. Without translating the coordinates, you would need to offset the drawing by the origin (x, y).
■Caution You cannot take a shortcut by inserting g.fillRect(x, y, width, height), because this would fill in the entire component area, not just the border area.
getBorderInsets() The getBorderInsets() method returns the space necessary to draw a border around the given component c as an Insets object. It has the following definition: public Insets getBorderInsets(Component c) These inset areas, shown in Figure 7-2, define the only legal area in which a border can be drawn. The Component argument allows you to use some of its properties to determine the size of the insets area.
■Caution You can ask the component argument for font-sizing information to determine the insets’ size, but if you ask about the size of the component, a StackOverflowError occurs because the size of the component is dependent on the size of the border insets.
isBorderOpaque() Borders can be opaque or transparent. The isBorderOpaque() method returns true or false, to indicate which form the border is. It has the following definition: public boolean isBorderOpaque()
CHAPTER 7 ■ BORDERS
When this method returns true, the border needs to be opaque, filling its entire insets area. When it returns false, any area not drawn will retain the background of the component in which the border is installed.
Introducing BorderFactory Now that you have a basic understanding of how the Border interface works, let’s take a quick look at the BorderFactory class as a means to create borders swiftly and easily. Found in the javax.swing package, the BorderFactory class offers a series of static methods for creating predefined borders. Instead of laboriously calling the specific constructors for different borders, you can create almost all the borders through this factory class. The factory class also caches the creation of some borders to avoid re-creating commonly used borders multiple times. The class definition follows. public class BorderFactory { public static Border createBevelBorder(int public static Border createBevelBorder(int Color shadow); public static Border createBevelBorder(int Color highlightInner, Color shadowOuter,
type); type, Color highlight, type, Color highlightOuter, Color shadowInner);
public static CompoundBorder createCompoundBorder(); public static CompoundBorder createCompoundBorder(Border outside, Border inside); public static Border createEmptyBorder(); public static Border createEmptyBorder(int top, int left, int bottom, int right); public static Border public static Border public static Border public static Border Color shadow);
createEtchedBorder(); createEtchedBorder(Color highlight, Color shadow); createEtchedBorder(int type); createEtchedBorder(int type, Color highlight,
public static Border createLineBorder(Color color); public static Border createLineBorder(Color color, int thickness); public static Border createLoweredBevelBorder(); public static MatteBorder createMatteBorder(int top, int left, int bottom, int right, Color color); public static MatteBorder createMatteBorder(int top, int left, int bottom, int right, Icon icon);
215
216
CHAPTER 7 ■ BORDERS
public static Border createRaisedBevelBorder(); public static TitledBorder createTitledBorder(Border border); public static TitledBorder createTitledBorder(Border border, String public static TitledBorder createTitledBorder(Border border, String int justification, int position); public static TitledBorder createTitledBorder(Border border, String int justification, int position, Font font); public static TitledBorder createTitledBorder(Border border, String int justification, int position, Font font, Color color); public static TitledBorder createTitledBorder(String title);
title); title, title, title,
} I’ll describe the different methods of this class during the process of describing the specific border types they create. For instance, to create a border with a red line, you can use the following statement, and then attach the border to a component. Border lineBorder = BorderFactory.createLineBorder(Color.RED);
■Note Interestingly enough, no factory method exists for creating a SoftBevelBorder.
Starting with AbstractBorder Before looking at the individual borders available within the javax.swing.border package, one system border deserves special attention: AbstractBorder. As previously mentioned, the AbstractBorder class is the parent border of all the other predefined borders.
■Tip When creating your own borders, you should create a subclass of AbstractBorder and just override the necessary methods, instead of implementing the Border interface directly yourself. There are some internal optimizations in place for subclasses.
Creating Abstract Borders There is one constructor for AbstractBorder: public AbstractBorder() Because AbstractBorder is the parent class of all the other standard borders, this constructor is eventually called automatically for all of them.
CHAPTER 7 ■ BORDERS
■Note Borders are not meant to be used as JavaBean components. Some border classes even lack a no-argument (“no-arg” for short) constructor. Nevertheless, those border classes still call this constructor.
Examining AbstractBorder Methods The AbstractBorder class provides implementations for the three methods of the Border interface. public Insets getBorderInsets(Component c) The insets of an AbstractBorder are zero all around. Each of the predefined subclasses overrides the getBorderInsets() method. public boolean isBorderOpaque() The default opaque property setting of an abstract border is false. This means that if you were to draw something like dashed lines, the component background would show through. Many predefined subclasses override the isBorderOpaque() method. public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) The painted border for an AbstractBorder is empty. All subclasses should override this behavior to actually draw a border, except perhaps EmptyBorder. In addition to providing default implementations of the Border methods, AbstractBorder adds two other capabilities that you can take advantage of, or just let the system use. First, there’s an additional version of getBorderInsets() available that takes two arguments: Component and Insets: public Insets getBorderInsets(Component c, Insets insets) In this version of the method, instead of creating and returning a new Insets object, the Insets object passed in is first modified and then returned. Use of this method avoids the creation and later destruction of an additional Insets object each time the border insets is queried. The second new method available is getInteriorRectangle(), which has both a static and a nonstatic version. Given the Component, Border, and four integer parameters (for x, y, width, and height), the method will return the inner Rectangle such that a component can paint itself only in the area within the border insets. (See the piece labeled “Component” in Figure 7-2, shown earlier in the chapter.)
■Note Currently, getBorderInsets() is used only once in Sun’s Swing source. That place is the MotifButtonUI class found in the com.sun.java.swing.plaf.motif package.
217
218
CHAPTER 7 ■ BORDERS
Examining the Predefined Borders Now that the basics have been described, let’s look at the specifics of each of the predefined border classes, somewhat in order of complexity.
EmptyBorder Class The empty border, logically enough, is a border with nothing drawn in it. You can use EmptyBorder where you might have otherwise overridden insets() or getInsets() with a regular AWT container. It allows you to reserve extra space around a component to spread your screen components out a little or to alter centering or justification somewhat. Figure 7-3 shows both an empty border and one that is not empty.
Figure 7-3. EmptyBorder sample, with insets of 20 for top and left, 0 for right and bottom EmptyBorder has two constructors and two factory methods of BorderFactory: public static Border createEmptyBorder() Border emptyBorder = BorderFactory.createEmptyBorder();
public static Border createEmptyBorder(int top, int left, int bottom, int right) Border emptyBorder = BorderFactory.createEmptyBorder(5, 10, 5, 10);
public EmptyBorder(Insets insets) Insets insets = new Insets(5, 10, 5, 10); Border EmptyBorder = new EmptyBorder(insets);
public EmptyBorder(int top, int left, int bottom, int right) Border EmptyBorder = new EmptyBorder(5, 10, 5, 10); Each allows you to customize the border insets in its own manner. The no-argument version creates a truly empty border with zero insets all around; otherwise, you can specify the insets as either an AWT Insets instance or as the inset pieces. The EmptyBorder is transparent by default.
CHAPTER 7 ■ BORDERS
■Note When creating an empty border, with zeros all around, you should use the factory method to create the border, avoiding the direct constructors. This allows the factory to create one truly empty border to be shared by all. If all you want to do is hide the border, and the component is an AbstractButton subclass, just call setBorderPainted(false).
LineBorder Class The line border is a single-color line of a user-defined thickness that surrounds a component. It can have squared-off or rounded corners. If you want to alter the thickness on different sides, you’ll need to use MatteBorder, which is described in the section “Matte Border Class” later in this chapter. Figure 7-4 shows a sampling of using LineBorder, with 1- and 12-pixel line thicknesses, with and without rounded corners.
Figure 7-4. LineBorder sample
Creating Line Borders The LineBorder class has three constructors, two factory methods within it, and two factory methods of BorderFactory: public LineBorder(Color color) Border lineBorder = new LineBorder (Color.RED);
public LineBorder(Color color, int thickness) Border lineBorder = new LineBorder (Color.RED, 5);
public LineBorder (Color color, int thickness, boolean roundedCorners) Border lineBorder = new LineBorder (Color.RED, 5, true);
public static Border createBlackLineBorder() Border blackLine = LineBorder.createBlackLineBorder();
219
220
CHAPTER 7 ■ BORDERS
public static Border createGrayLineBorder() Border grayLine = LineBorder.createGrayLineBorder();
public static Border createLineBorder(Color color) Border lineBorder = BorderFactory.createLineBorder(Color.RED);
public static Border createLineBorder(Color color, int thickness) Border lineBorder = BorderFactory.createLineBorder(Color.RED, 5);
■Note The LineBorder factory methods work as follows: If you create the same border twice, the same LineBorder object will be returned. However, as with all object comparisons, you should always use the equals() method for checking object equality.
Each allows you to customize the border color and line thickness. If a thickness isn’t specified, a default value of 1 is used. The two factory methods of LineBorder are for the commonly used colors of black and gray. Because the border fills in the entire insets area, the LineBorder is opaque, unless there are rounded corners. So, the opacity of the border is the opposite of the rounded-corner setting.
Configuring Line Border Properties Table 7-1 lists the inherited borderOpaque property from AbstractBorder and the immutable properties of LineBorder.
Table 7-1. LineBorder Properties
Property Name
Data Type
Access
borderOpaque
boolean
Read-only
lineColor
Color
Read-only
roundedCorners
boolean
Read-only
thickness
int
Read-only
BevelBorder Class A bevel border draws a border with a three-dimensional appearance, which can appear to be raised or lowered. When the border is raised, a shadow effect appears along the bottom and right side of the border. When lowered, the position of the shading is reversed. Figure 7-5 shows raised and lowered bevel borders with default and custom colors.
CHAPTER 7 ■ BORDERS
Figure 7-5. Raised and lowered BevelBorder sample Drawing two different pairs of 1-pixel-wide lines around the component produces a simulated three-dimensional appearance. The border sides that aren’t shaded are drawn with what is called a highlight color, and the other two sides are drawn with a shadow color. The highlight color and shadow color are each drawn in two different shades for the outer and inner edges of the bevel. As such, a drawn bevel border uses four different colors in all. Figure 7-6 shows how these four colors fit together.
Figure 7-6. Bevel color analysis There are three constructors and one factory method of BevelBorder, as well as five factory methods by which BorderFactory creates BevelBorder objects: public BevelBorder(int bevelType) Border bevelBorder = new BevelBorder(BevelBorder.RAISED);
public static Border createBevelBorder(int bevelType) Border bevelBorder = BorderFactory.createBevelBorder(BevelBorder.RAISED);
public static Border createLoweredBevelBorder() Border bevelBorder = BorderFactory.createLoweredBevelBorder();
221
222
CHAPTER 7 ■ BORDERS
public static Border createRaisedBevelBorder() Border bevelBorder = BorderFactory.createRaisedBevelBorder();
public BevelBorder(int bevelType, Color highlight, Color shadow) Border bevelBorder = new BevelBorder(BevelBorder.RAISED, Color.PINK, Color.RED);
public static Border createBevelBorder(int bevelType, Color highlight, Color shadow) Border bevelBorder = BorderFactory.createBevelBorder(BevelBorder.RAISED, Color.PINK, Color.RED);
public BevelBorder(int bevelType, Color highlightOuter, Color highlightInner, Color shadowOuter, Color shadowInner) Border bevelBorder = new BevelBorder(BevelBorder.RAISED, Color.PINK, Color.PINK.brighter(), Color.RED, Color.RED.darker());
public static Border createBevelBorder(int bevelType, Color highlightOuter, Color highlightInner, Color shadowOuter, Color shadowInner) Border bevelBorder = BorderFactory.createBevelBorder(BevelBorder.RAISED, Color.PINK, Color.PINK.brighter(), Color.RED, Color.RED.darker()); Each allows you to customize both the bevel type and the coloration of the highlighting and shadowing within the border. The bevel type is specified by one of two values: BevelBorder.RAISED or BevelBorder.LOWERED. If highlight and shadow colors aren’t specified, the appropriate colors are generated by examining the background of the component for the border. If you do specify them, remember that the highlight color should be brighter, possibly done by calling theColor.brighter(). A BevelBorder is opaque, by default.
SoftBevelBorder Class The soft bevel border is a close cousin of the bevel border. It rounds out the corners so that their edges aren’t as sharp, and it draws only one line, using the appropriate outer color for the bottom and right sides. As Figure 7-7 shows, the basic appearance of the raised and lowered SoftBevelBorder is roughly the same as that of the BevelBorder.
Figure 7-7. Raised and lowered SoftBevelBorder sample
CHAPTER 7 ■ BORDERS
SoftBevelBorder has three constructors: public SoftBevelBorder(int bevelType) Border softBevelBorder = new SoftBevelBorder(SoftBevelBorder.RAISED);
public SoftBevelBorder(int bevelType, Color highlight, Color shadow) Border softBevelBorder = new SoftBevelBorder(SoftBevelBorder.RAISED, Color.RED, Color.PINK);
public SoftBevelBorder(int bevelType, Color highlightOuter, Color highlightInner, Color shadowOuter, Color shadowInner) Border softBevelBorder = new SoftBevelBorder(SoftBevelBorder.RAISED, Color.RED, Color.RED.darker(), Color.PINK, Color.PINK.brighter()); Each allows you to customize both the bevel type and the coloration of the highlighting and shadowing within the border. The bevel type is specified by one of two values: SoftBevelBorder.RAISED or SoftBevelBorder.LOWERED. As with BevelBorder, the default coloration is derived from the background color. A soft bevel border doesn’t completely fill in the given insets area, so a SoftBevelBorder is created to be transparent (not opaque). There are no static BorderFactory methods to create these borders.
EtchedBorder Class An EtchedBorder is a special case of a BevelBorder, but it’s not a subclass. When the outer highlight color of a BevelBorder is the same color as the inner shadow color and the outer shadow color is the same color as the inner highlight color, you have an EtchedBorder. (See Figure 7-6 earlier in this chapter for a depiction of bevel colors.) Figure 7-8 shows what a raised and lowered etched border might look like.
Figure 7-8. EtchedBorder samples There are four constructors for EtchedBorder, as well as four factory methods of BorderFactory for creating EtchedBorder objects:
223
224
CHAPTER 7 ■ BORDERS
public EtchedBorder() Border etchedBorder = new EtchedBorder();
public EtchedBorder(int etchType) Border etchedBorder = new EtchedBorder(EtchedBorder.RAISED);
public EtchedBorder(Color highlight, Color shadow) Border etchedBorder = new EtchedBorder(Color.RED, Color.PINK);
public EtchedBorder(int etchType, Color highlight, Color shadow) Border etchedBorder = new EtchedBorder(EtchedBorder.RAISED, Color.RED, Color.PINK);
public static Border createEtchedBorder() Border etchedBorder = BorderFactory.createEtchedBorder();
public static Border createEtchedBorder(Color highlight, Color shadow) Border etchedBorder = BorderFactory.createEtchedBorder(Color.RED, Color.PINK);
public static Border createEtchedBorder(EtchedBorder.RAISED) Border etchedBorder = BorderFactory.createEtchedBorder(Color.RED, Color.PINK);
public static Border createEtchedBorder(int type, Color highlight, Color shadow) Border etchedBorder = BorderFactory.createEtchedBorder(EtchedBorder.RAISED, Color.RED, Color.PINK); Each allows you to customize both the etching type and the coloration of the highlighting and shadowing within the border. If no etching type is specified, the border is lowered. As with BevelBorder and SoftBevelBorder, you can specify the etching type through one of two constants: EtchedBorder.RAISED or EtchedBorder.LOWERED. Again, if no colors are specified, they’re derived from the background color of the component passed into paintBorder(). By default, all EtchedBorder objects are created to be opaque.
MatteBorder Class MatteBorder is one of the more versatile borders available. It comes in two varieties. The first is demonstrated in Figure 7-9 and shows a MatteBorder used like a LineBorder to fill the border with a specific color, but with a different thickness on each side (something a plain LineBorder cannot handle).
CHAPTER 7 ■ BORDERS
Figure 7-9. MatteBorder color sample The second variety uses an Icon tiled throughout the border area. This Icon could be an ImageIcon, if created from an Image object, or it could be one you create yourself by implementing the Icon interface. Figure 7-10 demonstrates both implementations.
Figure 7-10. MatteBorder icon samples
■Tip When tiling an icon, the right and bottom areas may not look very attractive if the border size, component size, and icon size fail to mesh well.
There are seven constructors and two factory methods of BorderFactory for creating MatteBorder objects: public MatteBorder(int top, int left, int bottom, int right, Color color) Border matteBorder = new MatteBorder(5, 10, 5, 10, Color.GREEN);
public MatteBorder(int top, int left, int bottom, int right, Icon icon) Icon diamondIcon = new DiamondIcon(Color.RED); Border matteBorder = new MatteBorder(5, 10, 5, 10, diamondIcon);
public MatteBorder(Icon icon) Icon diamondIcon = new DiamondIcon(Color.RED); Border matteBorder = new MatteBorder(diamondIcon);
225
226
CHAPTER 7 ■ BORDERS
public MatteBorder(Insets insets, Color color) Insets insets = new Insets(5, 10, 5, 10); Border matteBorder = new MatteBorder(insets, Color.RED);
public MatteBorder(Insets insets, Icon icon) Insets insets = new Insets(5, 10, 5, 10); Icon diamondIcon = new DiamondIcon(Color.RED); Border matteBorder = new MatteBorder(insets, diamondIcon);
public static MatteBorder createMatteBorder(int top, int left, int bottom, int right, Color color) Border matteBorder = BorderFactory.createMatteBorder(5, 10, 5, 10, Color.GREEN);
public static MatteBorder createMatteBorder(int top, int left, int bottom, int right, Icon icon) Icon diamondIcon = new DiamondIcon(Color.RED); Border matteBorder = BorderFactory.createMatteBorder(5, 10, 5, 10, diamondIcon); Each allows you to customize what will be matted within the border area. When tiling an Icon, if you don’t specify the border insets size, the actual icon dimensions will be used.
CompoundBorder Class After EmptyBorder, the compound border is probably one of the simplest predefined borders to use. It takes two existing borders and combines them, using the Composite design pattern, into a single border. A Swing component can have only one border associated with it, therefore, the CompoundBorder allows you to combine borders before associating them with a component. Figure 7-11 shows two examples of CompoundBorder in action. The border on the left is a beveled, line border. The one on the right is a six-line border, with several borders combined together.
Figure 7-11. CompoundBorder samples
Creating Compound Borders There are two constructors for CompoundBorder and two factory methods that BorderFactory offers for creating CompoundBorder objects (the no-argument constructor and factory methods are completely useless here, because there are no setter methods to later change the compounded borders, so no source examples are shown for them):
CHAPTER 7 ■ BORDERS
public CompoundBorder()
public static CompoundBorder createCompoundBorder()
public CompoundBorder(Border outside, Border inside) Border compoundBorder = new CompoundBorder(lineBorder, matteBorder);
■Tip Keep in mind that CompoundBorder is itself a Border, so you can combine multiple borders into one border many levels deep.
The opacity of a compound border depends on the opacity of the contained borders. If both contained borders are opaque, so is the compound border. Otherwise, a compound border is considered transparent.
Configuring Properties In addition to the borderOpaque property inherited from AbstractBorder, Table 7-2 lists the two read-only properties CompoundBorder adds.
Table 7-2. CompoundBorder Properties
Property Name
Data Type
Access
borderOpaque
boolean
Read-only
insideBorder
Border
Read-only
outsideBorder
Border
Read-only
TitledBorder Class Probably the most interesting border, TitledBorder can also be the most complicated to use. The titled border allows you to place a text string around a component. In addition to surrounding a single component, you can place a titled border around a group of components, like JRadioButton objects, as long as they’re placed within a container such as a JPanel. The TitledBorder can be difficult to use, but there are several ways to simplify its usage. Figure 7-12 shows both a simple titled border and one that’s a little more complex.
227
228
CHAPTER 7 ■ BORDERS
Figure 7-12. TitledBorder samples
Creating Titled Borders Six constructors and six BorderFactory factory methods exist for creating TitledBorder objects. Each allows you to customize the text, position, and appearance of a title within a specified border. When unspecified, the current look and feel controls the border, title color, and title font. The default location for the title is the upper-left corner, while the default title is the empty string. A titled border is always at least partially transparent because the area beneath the title text shows through. Therefore, isBorderOpaque() reports false. If you look at each of the following methods, shown in pairs, this will be easier to understand. First shown is the constructor method; next shown is the equivalent BorderFactory method. public TitledBorder(Border border) Border titledBorder = new TitledBorder(lineBorder); public static TitledBorder createTitledBorder(Border border) Border titledBorder = BorderFactory.createTitledBorder(lineBorder);
public TitledBorder(String title) Border titledBorder = new TitledBorder("Hello"); public static TitledBorder createTitledBorder(String title) Border titledBorder = BorderFactory.createTitledBorder("Hello");
public TitledBorder(Border border, String title) Border titledBorder = new TitledBorder(lineBorder, "Hello"); public static TitledBorder createTitledBorder(Border border, String title) Border titledBorder = BorderFactory.createTitledBorder(lineBorder, "Hello");
public TitledBorder(Border border, String title, int justification, int position) Border titledBorder = new TitledBorder(lineBorder, "Hello", TitledBorder.LEFT, TitledBorder.BELOW_BOTTOM);
CHAPTER 7 ■ BORDERS
public static TitledBorder createTitledBorder(Border border, String title, int justification, int position) Border titledBorder = BorderFactory.createTitledBorder(lineBorder, "Hello", TitledBorder.LEFT, TitledBorder.BELOW_BOTTOM);
public TitledBorder(Border border, String title, int justification, int position, Font font) Font font = new Font("Serif", Font.ITALIC, 12); Border titledBorder = new TitledBorder(lineBorder, "Hello", TitledBorder.LEFT, TitledBorder.BELOW_BOTTOM, font); public static TitledBorder createTitledBorder(Border border, String title, int justification, int position, Font font) Font font = new Font("Serif", Font.ITALIC, 12); Border titledBorder = BorderFactory.createTitledBorder(lineBorder, "Hello", TitledBorder.LEFT, TitledBorder.BELOW_BOTTOM, font);
public TitledBorder(Border border, String title, int justification, int position, Font font, Color color) Font font = new Font("Serif", Font.ITALIC, 12); Border titledBorder = new TitledBorder(lineBorder, "Hello", TitledBorder.LEFT, TitledBorder.BELOW_BOTTOM, font, Color.RED); public static TitledBorder createTitledBorder(Border border, String title, int justification, int position, Font font, Color color) Font font = new Font("Serif", Font.ITALIC, 12); Border titledBorder = BorderFactory.createTitledBorder(lineBorder, "Hello", TitledBorder.LEFT, TitledBorder.BELOW_BOTTOM, font, Color.RED);
Configuring Properties Unlike all the other predefined borders, titled borders have six setter methods to modify their attributes after border creation. As shown in Table 7-3, you can modify a titled border’s underlying border, title, drawing color, font, text justification, and text position. Table 7-3. TitledBorder Properties
Property Name
Data Type
Access
border
Border
Read-write
borderOpaque
boolean
Read-only
title
String
Read-write
titleColor
Color
Read-write
titleFont
Font
Read-write
titleJustification
int
Read-write
titlePosition
int
Read-write
229
230
CHAPTER 7 ■ BORDERS
■Tip To reduce screen redrawing, it’s better to modify the properties of a titled border prior to placing the border around a component.
Text justification of the title string within a TitledBorder is specified by one of four class constants: • CENTER: Place the title in the center. • DEFAULT_JUSTIFICATION: Use the default setting to position the text. The value is equivalent to LEFT. • LEFT: Place the title on the left edge. • RIGHT: Place the title on the right edge. Figure 7-13 shows the same TitledBorder with three different justifications.
Figure 7-13. Title justifications You can position title strings in any one of six different locations, as specified by one of seven class constants: • ABOVE_BOTTOM: Place the title above the bottom line. • ABOVE_TOP: Place the title above the top line. • BELOW_BOTTOM: Place the title below the bottom line. • BELOW_TOP: Place the title below the top line. • BOTTOM: Place the title on the bottom line. • DEFAULT_POSITION: Use the default setting to place the text. This value is equivalent to TOP. • TOP: Place the title on the top line. Figure 7-14 shows the six different positions available for the title on a TitledBorder.
CHAPTER 7 ■ BORDERS
Figure 7-14. Title positioning Because a TitledBorder contains another Border, you can combine more than one border to place multiple titles along a single border. For example, Figure 7-15 shows a title along the top and bottom of the border.
Figure 7-15. Showing multiple titles on a TitledBorder The program used to generate Figure 7-15 is shown in Listing 7-2. Listing 7-2. Multiple Titles on a TitledBorder import javax.swing.*; import javax.swing.border.*; import java.awt.*; public class DoubleTitle { public static void main(String args[]) { Runnable runner = new Runnable() { public void run() { JFrame frame = new JFrame("Double Title"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); TitledBorder topBorder = BorderFactory.createTitledBorder("Top"); topBorder.setTitlePosition(TitledBorder.TOP); TitledBorder doubleBorder = new TitledBorder(topBorder, "Bottom", TitledBorder.RIGHT, TitledBorder.BOTTOM); JButton doubleButton = new JButton(); doubleButton.setBorder(doubleBorder); frame.add(doubleButton, BorderLayout.CENTER);
Customizing TitledBorder Look and Feel The available set of UIResource-related properties for a TitledBorder is shown in Table 7-4. It has three different properties.
Table 7-4. TitledBorder UIResource Elements
Property String
Object Type
TitledBorder.font
Font
TitledBorder.titleColor
Color
TitledBorder.border
Border
Creating Your Own Borders When you want to create your own distinctive border, you can either create a new class that implements the Border interface directly or you can extend the AbstractBorder class. As previously mentioned, extending the AbstractBorder class is the better way to go, because optimizations are built in to certain Swing classes to take advantage of some of the AbstractBorder-specific methods. For instance, if a border is an AbstractBorder, JComponent will reuse an Insets object when getting the Insets of a border. Thus, one fewer object will need to be created and destroyed each time the insets are fetched. In addition to thinking about subclassing AbstractBorder versus implementing the Border interface yourself, you need to consider whether or not you want a static border. If you attach a border to a button, you want that button to be able to signal selection. You must examine the component passed into the paintBorder() method and react accordingly. In addition, you should also draw a disabled border to indicate when the component isn’t selectable. Although setEnabled(false) disables the selection of the component, if the component has a border associated with it, the border still must be drawn, even when disabled. Figure 7-16 shows one border in action that looks at all these options for the component passed into the border’s paintBorder() method.
Figure 7-16. Active custom border examples
CHAPTER 7 ■ BORDERS
The source for the custom border and the sample program is shown in Listing 7-3. Listing 7-3. Custom Colorized Border import javax.swing.*; import javax.swing.border.*; import java.awt.*; public class RedGreenBorder extends AbstractBorder { public boolean isBorderOpaque() { return true; } public Insets getBorderInsets(Component c) { return new Insets(3, 3, 3, 3); } public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) { Insets insets = getBorderInsets(c); Color horizontalColor; Color verticalColor; if (c.isEnabled()) { boolean pressed = false; if (c instanceof AbstractButton) { ButtonModel model = ((AbstractButton)c).getModel(); pressed = model.isPressed(); } if (pressed) { horizontalColor = Color.RED; verticalColor = Color.GREEN; } else { horizontalColor = Color.GREEN; verticalColor = Color.RED; } } else { horizontalColor = Color.LIGHT_GRAY; verticalColor = Color.LIGHT_GRAY; } g.setColor(horizontalColor); g.translate(x, y); // Top g.fillRect(0, 0, width, insets.top); // Bottom g.fillRect(0, height-insets.bottom, width, insets.bottom);
233
234
CHAPTER 7 ■ BORDERS
g.setColor(verticalColor); // Left g.fillRect(0, insets.top, insets.left, height-insets.top-insets.bottom); // Right g.fillRect(width-insets.right, insets.top, insets.right, height-insets.top-insets.bottom); g.translate(-x, -y); } public static void main(String args[]) { Runnable runner = new Runnable() { public void run() { JFrame frame = new JFrame("My Border"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); Border border = new RedGreenBorder(); JButton helloButton = new JButton("Hello"); helloButton.setBorder(border); JButton braveButton = new JButton("Brave New"); braveButton.setBorder(border); braveButton.setEnabled(false); JButton worldButton = new JButton("World"); worldButton.setBorder(border); frame.add(helloButton, BorderLayout.NORTH); frame.add(braveButton, BorderLayout.CENTER); frame.add(worldButton, BorderLayout.SOUTH); frame.setSize(300, 100); frame.setVisible(true); } }; EventQueue.invokeLater(runner); } }
■Note Another interesting custom border is one that displays an active component instead of a text title in a TitledBorder. Imagine a border that has a JCheckBox or JRadioButton instead of a text string for the title. You can also use a JLabel and pass in HTML for the text.
Summary In this chapter, you learned about the use of the Border interface and its many predefined implementations. You also learned how to create predefined borders using the Factory design pattern provided by the BorderFactory class. Lastly, you saw how to define your own borders and why subclassing AbstractBorder is beneficial. In Chapter 8, you’ll move beyond low-level components and examine the window-like container objects available in Swing.
CHAPTER 8 ■■■
Root Pane Containers I
n Chapter 7, you looked at working with borders around Swing components. In this chapter, you’ll explore the high-level Swing containers and discover how they differ from their AWT counterparts. Working with top-level containers in Swing is a bit different from working with top-level AWT containers. With the AWT containers of Frame, Window, Dialog, and Applet, you added components directly to the container, and there was only one place you could add them. In the Swing world, the top-level containers of JFrame, JWindow, JDialog, and JApplet, plus the JInternalFrame container, rely on something called a JRootPane. Instead of adding components directly to the container, you add them to a part of the root pane. The root pane then manages them all internally. Why was this indirect layer added? Believe it or not, it was done to simplify things. The root pane manages its components in layers so that elements such as tooltip text will always appear above components, and you don’t need to worry about dragging some components around behind others. The one container without an AWT counterpart, JInternalFrame, also provides some additional capabilities when placed within a desktop (within a JDesktopPane to be specific). The JInternalFrame class can be used as the basis for creating a Multiple Document Interface (MDI) application architecture within a Swing program. You can manage a series of internal frames within your program, and they’ll never go beyond the bounds of your main program window. Let’s begin by exploring the new JRootPane class, which manages the internals of all the top-level containers.
JRootPane Class The JRootPane class acts as a container delegate for the top-level Swing containers. Because the container holds only a JRootPane when you add or remove components from a top-level container, instead of directly altering the components in the container, you indirectly add or remove components from its JRootPane instance. In effect, the top-level containers are acting as proxies, with the JRootPane doing all the work. The JRootPane container relies on its inner class RootLayout for layout management and takes up all the space of the top-level container that holds it. There are only two components within a JRootPane: a JLayeredPane and a glass pane (Component). The glass pane is in front, can be any component, and tends to be invisible. The glass pane ensures that elements such as
235
CHAPTER 8 ■ ROOT PANE CONTAINERS
tooltip text appear in front of any other Swing components. In the back is the JLayeredPane, which contains an optional JMenuBar on top and a content pane (Container) below it in another layer. It is within the content pane that you would normally place components in the JRootPane. Figure 8-1 should help you visualize how the RootLayout lays out the components.
■Note A JLayeredPane is just another Swing container (it’s described later in this chapter). It can contain any components and has a special layering characteristic. The default JLayeredPane used within the JRootPane pane contains only a JMenuBar and a Container as its content pane. The content pane has its own layout manager, which is BorderLayout by default.
Creating a JRootPane Although the JRootPane has a public no-argument constructor, a JRootPane isn’t something you would normally create yourself. Instead, a class that implements the RootPaneContainer interface creates the JRootPane. Then, you can get the root pane from that component, through the RootPaneContainer interface, described shortly.
JRootPane Properties As Table 8-1 shows, there are 11 properties of JRootPane. In most cases, when you get or set one of these properties for a top-level container, like JFrame, the container simply passes along the request to its JRootPane. The glass pane for a JRootPane must not be opaque. Because the glass pane takes up the entire area in front of the JLayeredPane, an opaque glass pane would render the menu bar and content pane invisible. And, because the glass pane and content pane share the same bounds, the optimizedDrawingEnabled property returns the visibility of the glass pane as its setting.
CHAPTER 8 ■ ROOT PANE CONTAINERS
Table 8-1. JRootPane Properties
Property Name
Data Type
Access
accessibleContext
AccessibleContext
Read-only
contentPane
Container
Read-write
defaultButton
JButton
Read-write bound
glassPane
Component
Read-write
jMenuBar
JMenuBar
Read-write
layeredPane
JLayeredPane
Read-write
optimizedDrawingEnabled
boolean
Read-only
UI
RootPaneUI
Read-write
UIClassID
String
Read-only
validateRoot
boolean
Read-only
windowDecorationStyle
int
Read-write bound
The windowDecorationStyle property is meant to describe the window adornments (border, title, buttons for closing window) for the window containing the JRootPane. It can be set to one of the following JRootPane class constants: • COLOR_CHOOSER_DIALOG • ERROR_DIALOG • FILE_CHOOSER_DIALOG • FRAME • INFORMATION_DIALOG • NONE • PLAIN_DIALOG • QUESTION_DIALOG • WARNING_DIALOG What exactly happens with the windowDecorationStyle setting depends on the current look and feel. It is just a hint. By default, this setting is NONE. If this setting is not NONE, the setUndecorated() method of JDialog or JFrame has been called with a value of true, and the getSupportsWindowDecorations() method of the current look and feel reports true, then the look and feel, rather than the window manager, will provide the window adornments. This allows you to have programs with top-level windows that look like they do not come from the platform the user is working on but from your own environment, though still providing iconify, maximize, minimize, and close buttons.
237
238
CHAPTER 8 ■ ROOT PANE CONTAINERS
For the Metal look and feel (and Ocean theme), getSupportsWindowDecorations() reports true. The other system-provided look and feel types report false. Figure 8-2 demonstrates what a frame looks like with the window adornments provided by the Metal look and feel.
Figure 8-2. Metal window adornments for a JFrame The source to produce Figure 8-2 is shown in Listing 8-1. Listing 8-1. Setting the Window Decoration Style import java.awt.*; import javax.swing.*; public class AdornSample { public static void main(final String args[]) { Runnable runner = new Runnable() { public void run() { JFrame frame = new JFrame("Adornment Example"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setUndecorated(true); frame.getRootPane().setWindowDecorationStyle(JRootPane.FRAME); frame.setSize(300, 100); frame.setVisible(true); } }; EventQueue.invokeLater(runner); } }
Customizing a JRootPane Look and Feel Table 8-2 shows the 12 UIResource-related properties for a JRootPane. Most of these settings have to do with the default border to use when configuring the window decoration style. Table 8-2. JRootPane UIResource Elements
Property String
Object Type
RootPane.actionMap
ActionMap
RootPane.ancestorInputMap
InputMap
RootPane.colorChooserDialogBorder
Border
CHAPTER 8 ■ ROOT PANE CONTAINERS
Table 8-2. JRootPane UIResource Elements (Continued)
Property String
Object Type
RootPane.defaultButtonWindowKeyBindings
Object[ ]
RootPane.errorDialogBorder
Border
RootPane.fileChooserDialogBorder
Border
RootPane.frameBorder
Border
RootPane.informationDialogBorder
Border
RootPane.plainDialogBorder
Border
RootPane.questionDialogBorder
Border
RootPane.warningDialogBorder
Border
RootPaneUI
String
RootPaneContainer Interface The RootPaneContainer interface defines the setter/getter methods for accessing the different panes within the JRootPane, as well as accessing the JRootPane itself. public interface RootPaneContainer { // Properties public Container getContentPane(); public void setContentPane(Container contentPane); public Component getGlassPane(); public void setGlassPane(Component glassPane); public JLayeredPane getLayeredPane(); public void setLayeredPane(JLayeredPane layeredPane); public JRootPane getRootPane(); } Among the predefined Swing components, the JFrame, JWindow, JDialog, JApplet, and JInternalFrame classes implement the RootPaneContainer interface. For the most part, these implementations simply pass along the request to a JRootPane implementation for the highlevel container. The following source code is one such implementation for the glass pane of a RootPaneContainer implementer: public Component getGlassPane() { return getRootPane().getGlassPane(); } public void setGlassPane(Component glassPane) { getRootPane().setGlassPane(glassPane); }
JLayeredPane Class The JLayeredPane serves as the main component container of a JRootPane. The JLayeredPane manages the z-order, or layering, of components within itself. This ensures that the correct
239
240
CHAPTER 8 ■ ROOT PANE CONTAINERS
component is drawn on top of other components for tasks such as creating tooltip text, pop-up menus, and dragging for drag-and-drop. You can use the system-defined layers, or you can create your own layers. Although initially a JLayeredPane container has no layout manager, there’s nothing to stop you from setting the layout property of the container, defeating the layering aspect of the container.
Creating a JLayeredPane As with the JRootPane, you’ll almost never create an instance of the JLayeredPane class yourself. When the default JRootPane is created for one of the predefined classes that implement RootPaneContainer, the JRootPane creates a JLayeredPane for its main component area, adding an initial content pane.
Adding Components in Layers A layer setting for each added component manages the z-order of components within a JLayeredPane. The higher the layer setting, the closer to the top the component will be drawn. You can set the layer with the layout manager constraints when you add a component to a JLayeredPane: Integer layer = new Integer(20); aLayeredPane.add(aComponent, layer); You can also call the public void setLayer(Component comp, int layer) or public void setLayer(Component comp, int layer, int position) method before adding the component to the JLayeredPane. aLayeredPane.setLayer(aComponent, 10); aLayeredPane.add(aComponent); The JLayeredPane class predefines six constants for special values. In addition, you can find out the topmost current layer with public int c and the bottom layer with public int lowestLayer(). Table 8-3 lists the six predefined layer constants.
Table 8-3. JLayeredPane Layer Constants
Constant
Description
FRAME_CONTENT_LAYER
Level –30,000 for holding the menu bar and content pane; not normally used by developers
DEFAULT_LAYER
Level 0 for the normal component level
PALETTE_LAYER
Level 100 for holding floating toolbars and the like
MODAL_LAYER
Level 200 for holding pop-up dialog boxes that appear on top of components on the default layer, on top of palettes, and below pop-ups
POPUP_LAYER
Level 300 for holding pop-up menus and tooltips
DRAG_LAYER
Level 400 for ensuring that dragged objects remain on top
CHAPTER 8 ■ ROOT PANE CONTAINERS
Although you can use your own constants for layers, use them with care—because the system will use the predefined constants for its needs. If your constants don’t fit in properly, the components may not work as you intended. To visualize how the different layers fit in, see Figure 8-3.
Figure 8-3. JLayeredPane layers
Working with Component Layers and Positions Components in a JLayeredPane have both a layer and a position. When a single component is on a layer, it’s at position 0. When multiple components are on the same layer, components added later have higher position numbers. The lower the position setting, the closer to the top the component will appear. (This is the reverse of the layering behavior.) Figure 8-4 shows the positions for four components on the same layer. To rearrange components on a single layer, you can use either the public void moveToBack(Component component) or public void moveToFront(Component component) method. When you move a component to the front, it goes to position 0 for the layer. When you move a component to the back, it goes to the highest position number for the layer. You can also manually set the position with public void setPosition(Component component, int position). A position of –1 is automatically the bottom layer with the highest position (see Figure 8-4).
241
242
CHAPTER 8 ■ ROOT PANE CONTAINERS
Figure 8-4. JLayeredPane positions
JLayeredPane Properties Table 8-4 shows the two properties of JLayeredPane. The optimizedDrawingEnabled property determines whether components within the JLayeredPane can overlap. By default, this setting is true because in the standard usage with JRootPane the JMenuBar and content pane can’t overlap. However, the JLayeredPane automatically validates the property setting to reflect the current state of the contents of the pane.
Table 8-4. JLayeredPane Properties
Property Name
Data Type
Access
accessibleContext
AccessibleContext
Read-only
optimizedDrawingEnabled
boolean
Read-only
JFrame Class The JFrame class is the Swing high-level container that uses a JRootPane and implements the RootPaneContainer interface. In addition, it uses the WindowConstants interface to help manage closing operations.
CHAPTER 8 ■ ROOT PANE CONTAINERS
Creating a JFrame The JFrame class provides two primary constructors: one for creating a frame without a title and one for creating a frame with a title. There are two additional constructors for creating frames with a specialized GraphicsConfiguration. public JFrame() JFrame frame = new JFrame(); public JFrame(String title) JFrame frame = new JFrame("Title Bar"); public JFrame(GraphicsConfiguration config) GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); GraphicsDevice gsd[] = ge.getScreenDevices(); GraphicsConfiguration gc[] = gsd[0].getConfigurations(); JFrame frame = new JFrame(gc[0]); public JFrame(String title, GraphicsConfiguration config) GraphicsConfiguration gc = ...; JFrame frame = new JFrame("Title Bar", gc);
JFrame Properties Table 8-5 shows the nine properties of the JFrame.
Table 8-5. JFrame Properties
Property Name
Data Type
Access
accessibleContext
AccessibleContext
Read-only
contentPane
Container
Read-write
defaultCloseOperation
int
Read-write
glassPane
Component
Read-write
iconImage
Image
Write-only
jMenuBar
JMenuBar
Read-write
layeredPane
JLayeredPane
Read-write
layout
LayoutManager
Write-only
rootPane
JRootPane
Read-only
243
244
CHAPTER 8 ■ ROOT PANE CONTAINERS
Although most properties are the result of implementing the RootPaneContainer interface, two properties are special: defaultCloseOperation and layout. (You first looked at the defaultCloseOperation property in Chapter 2.) By default, a JFrame hides itself when the user closes the window. To change the setting, you can use one of the constants listed in Table 8-6 as arguments when setting the default close operation. The first comes from JFrame directly; the others are part of the WindowConstants interface. aFrame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
Table 8-6. Close Operation Constants
Constant
Description
EXIT_ON_CLOSE
Call System.exit(0).
DISPOSE_ON_CLOSE
Call dispose() on the frame.
DO_NOTHING_ON_CLOSE
Ignore the request.
HIDE_ON_CLOSE
Call setVisible(false) on the frame; this is the default.
The layout property is odd. By default, setting the layout manager of the JFrame passes the call along to the content pane. You can’t change the default layout manager of the JFrame.
■Tip You can use the state property (inherited from Frame) to say whether the JFrame is currently iconified. When using the property, be sure to use one of the additional Frame constants of NORMAL or ICONIFIED to set its state.
There is an additional static property of JFrame: defaultLookAndFeelDecorated. This works with the windowDecorationStyle property of JRootPane. When set to true, newly created frames will be adorned with decorations from the look and feel instead of the window manager. Of course, this happens only if the current look and feel supports window decorations. Listing 8-2 shows an alternate way to generate the same screen (with the window adornments provided by the Metal look and feel) as the one shown earlier in Figure 8-2. Listing 8-2. Alternative Way of Setting the Window Decoration Style import java.awt.*; import javax.swing.*; public class AdornSample2 {
CHAPTER 8 ■ ROOT PANE CONTAINERS
public static void main(final String args[]) { Runnable runner = new Runnable() { public void run() { JFrame.setDefaultLookAndFeelDecorated(true); JFrame frame = new JFrame("Adornment Example"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setSize(300, 100); frame.setVisible(true); } }; EventQueue.invokeLater(runner); } }
Adding Components to a JFrame Because JFrame implements the RootPaneContainer interface and uses a JRootPane, you don’t add components directly to the JFrame. Instead, you add them to the JRootPane contained within the JFrame. Prior to J2SE 5.0, you needed to add components like this: JRootPane rootPane = aJFrame.getRootPane(); Container contentPane = rootPane.getContentPane(); contentPane.add(...); This can be shortened to the following form: aJFrame.getContentPane().add(...); If you tried to add components directly to the JFrame, it resulted in a runtime error being thrown. Due to many suggestions (complaints?), Sun finally decided to change the add() method into a proxy: // J2SE 5.0 aJFrame.add(...); With J2SE 5.0, when you add components to the JFrame, they actually are added to the content pane of the RootPaneContainer.
Handling JFrame Events The JFrame class supports the registration of eleven different listeners: • ComponentListener: To find out when the frame moves or is resized. • ContainerListener: Normally not added to a JFrame because you add components to the content pane of its JRootPane. • FocusListener: To find out when the frame gets or loses input focus.
245
246
CHAPTER 8 ■ ROOT PANE CONTAINERS
• HierarchyBoundsListener: To find out when the frame moves or is resized. This works similarly to ComponentListener, since the frame is the top-level container of component. • HierarchyListener: To find out when the frame is shown or hidden. • InputMethodListener: To work with input methods for internationalization. • KeyListener: Normally not added to a JFrame. Instead, you register a keyboard action for its content pane, like this: JPanel content = (JPanel)frame.getContentPane(); KeyStroke stroke = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0); content.registerKeyboardAction(actionListener, stroke, JComponent.WHEN_IN_FOCUSED_WINDOW); • MouseListener and MouseMotionListener: To listen for mouse and mouse motion events. • PropertyChangeListener: To listen for changes to bound properties. • WindowListener: To find out when a window is iconified or deiconified or a user is trying to open or close the window. With the help of the defaultCloseOperation property, you typically don’t need to add a WindowListener to help with closing the frame or stopping the application.
Extending JFrame If you need to extend JFrame, this class has two important protected methods: protected void frameInit() protected JRootPane createRootPane() By overriding either of these methods in a subclass, you can customize the initial appearance and behavior of the frame or that of its JRootPane. For example, in the ExitableJFrame class shown in Listing 8-3, the default close operation is initialized to the EXIT_ON_CLOSE state. Instead of calling setDefaultCloseOperation() for every frame created, you can use this class instead. Because JFrame was subclassed, you don’t need to add a call to the frameInit() method in either of the constructors. The parent class automatically calls the method. Listing 8-3. Closing Frames by Default import javax.swing.JFrame; public class ExitableJFrame extends JFrame { public ExitableJFrame () { } public ExitableJFrame (String title) { super (title); }
■Caution If you do override the frameInit() method of JFrame, remember to call super.frameInit() first, to initialize the default behaviors. If you forget and don’t reimplement all the default behaviors yourself, your new frame will look and act differently.
JWindow Class The JWindow class is similar to the JFrame class. It uses a JRootPane for component management and implements the RootPaneContainer interface. Basically, it is a top-level window with no adornments.
Creating a JWindow The JWindow class has five constructors: public JWindow() JWindow window = new JWindow(); public JWindow(Frame owner) JWindow window = new JWindow(aFrame); public JWindow(GraphicsConfiguration config) GraphicsConfiguration gc = ...; JWindow window = new JWindow(gc); public JWindow(Window owner) JWindow window = new JWindow(anotherWindow); public JWindow(Window owner, GraphicsConfiguration config) GraphicsConfiguration gc = ...; JWindow window = new JWindow(anotherWindow, gc); You can create a window without specifying a parent or by specifying the parent as a Frame or Window. If no parent is specified, an invisible one is used.
247
248
CHAPTER 8 ■ ROOT PANE CONTAINERS
JWindow Properties Table 8-7 lists the six properties of JWindow. These are similar in nature to the JFrame properties, except that JWindow has no property for a default close operation or a menu bar.
Table 8-7. JWindow Properties
Property Name
Data Type
Access
accessibleContext
AccessibleContext
Read-only
contentPane
Container
Read-write
glassPane
Component
Read-write
layeredPane
JLayeredPane
Read-write
layout
LayoutManager
Write-only
rootPane
JRootPane
Read-only
Handling JWindow Events The JWindow class adds no additional event-handling capabilities beyond those of the JFrame and Window classes. See the “Handling JFrame Events” section earlier in this chapter for a list of listeners you can attach to a JWindow.
Extending JWindow If you need to extend JWindow, the class has two protected methods of importance: protected void windowInit() protected JRootPane createRootPane()
JDialog Class The JDialog class represents the standard pop-up window for displaying information related to a Frame. It acts like a JFrame, whereby its JRootPane contains a content pane and an optional JMenuBar, and it implements the RootPaneContainer and WindowConstants interfaces.
Creating a JDialog There are 11 constructors for creating JDialog windows: public JDialog() JDialog dialog = new JDialog(); public JDialog(Dialog owner) JDialog dialog = new JDialog(anotherDialog);
CHAPTER 8 ■ ROOT PANE CONTAINERS
public JDialog(Dialog owner, boolean modal) JDialog dialog = new JDialog(anotherDialog, true); public JDialog(Dialog owner, String title) JDialog dialog = new JDialog(anotherDialog, "Hello"); public JDialog(Dialog owner, String title, boolean modal) JDialog dialog = new JDialog(anotherDialog, "Hello", true); public JDialog(Dialog owner, String title, boolean modal, GraphicsConfiguration gc) GraphicsConfiguration gc = ...; JDialog dialog = new JDialog(anotherDialog, "Hello", true, gc); public JDialog(Frame owner) JDialog dialog = new JDialog(aFrame); public JDialog(Frame owner, String windowTitle) JDialog dialog = new JDialog(aFrame, "Hello"); public JDialog(Frame owner, boolean modal) JDialog dialog = new JDialog(aFrame, false); public JDialog(Frame owner, String title, boolean modal) JDialog dialog = new JDialog(aFrame, "Hello", true); public JDialog(Frame owner, String title, boolean modal, GraphicsConfiguration gc) GraphicsConfiguration gc = ...; JDialog dialog = new JDialog(aFrame, "Hello", true, gc);
■Note Instead of manually creating a JDialog and populating it, you may find yourself having JOptionPane automatically create and fill the JDialog for you. You’ll explore the JOptionPane component in Chapter 9.
Each constructor allows you to customize the dialog owner, the window title, and the modality of the pop-up. When a JDialog is modal, it blocks input to the owner and the rest of the application. When a JDialog is nonmodal, it allows a user to interact with the JDialog as well as the rest of your application.
■Caution For modality to work properly among the different Java versions, avoid mixing heavyweight AWT components with lightweight Swing components in a JDialog.
249
250
CHAPTER 8 ■ ROOT PANE CONTAINERS
JDialog Properties Other than the settable icon image, the JDialog class has the same properties as JFrame. These eight properties are listed in Table 8-8.
Table 8-8. JDialog Properties
Property Name
Data Type
Access
accessibleContext
AccessibleContext
Read-only
contentPane
Container
Read-write
defaultCloseOperation
int
Read-write
glassPane
Component
Read-write
jMenuBar
JMenuBar
Read-write
layeredPane
JLayeredPane
Read-write
layout
LayoutManager
Write-only
rootPane
JRootPane
Read-only
The constants to use for specifying the default close operation are the WindowConstants shown earlier in Table 8-6 (basically all but EXIT_ON_CLOSE). By default, the defaultCloseOperation property is set to HIDE_ON_CLOSE, which is the desirable default behavior for a dialog pop-up. Like JFrame, JDialog also has a static defaultLookAndFeelDecorated property. This controls whether or not dialogs are decorated by the look and feel, by default.
Handling JDialog Events There are no special JDialog events for you to deal with; it has the same events as those for the JFrame class. One thing that you may want to do with a JDialog is specify that pressing the Escape key cancels the dialog. The easiest way to do this is to register an Escape keystroke to a keyboard action within the JRootPane of the dialog, causing the JDialog to become hidden when Escape is pressed. Listing 8-4 demonstrates this behavior. Most of the source duplicates the constructors of JDialog. The createRootPane() method maps the Escape key to the custom Action. Listing 8-4. A JDialog That Closes When Escape Is Pressed import javax.swing.*; import java.awt.*; import java.awt.event.*; public class EscapeDialog extends JDialog { public EscapeDialog() { this((Frame)null, false); }
■Note If you use the static creation methods of JOptionPane, the JDialog windows it creates automatically have the Escape key registered to close the dialog.
Extending JDialog If you need to extend JDialog, the class has two protected methods of importance: protected void dialogInit() protected JRootPane createRootPane() The latter method is demonstrated in the previous example in Listing 8-4.
JApplet Class The JApplet class is an extension to the AWT Applet class. For event handling to work properly within applets that use Swing components, your applets must subclass JApplet instead of Applet. The JApplet works the same as the other high-level containers by implementing the RootPaneContainer interface. One important difference between JApplet and Applet is the default layout manager. Because you add components to the content pane of a JApplet, its default layout manager is BorderLayout. This is unlike the default layout manager of Applet, which is FlowLayout. In addition, Swing applets can also have a menu bar, or more specifically a JMenuBar, which is just another attribute of the JRootPane of the applet. If you plan to deploy an applet that uses the Swing components, it is best to use the Java Plug-in from Sun Microsystems, because that will install the Swing libraries with the runtime.
■Tip To make sure you are running the Java Plug-in under Internet Explorer, select Internet Options from the Tools menu, and then choose the Advanced tab. Scroll down to the Java section immediately above Microsoft VM and make sure Use JRE [VERSION] for