The Definitive Guide To Java Swing: Creating Based Graphical User Interfaces Using J2SE 5 Swing Component Set, Third Ed Gui

User Manual:

Open the PDF directly: View PDF PDF.
Page Count: 913

DownloadThe Definitive Guide To Java Swing: Creating Java-based Graphical User Interfaces Using J2SE 5 Swing Component Set, Third Ed Based Gui
Open PDF In BrowserView PDF
The Definitive Guide to
Java Swing
Third Edition
JOHN ZUKOWSKI

The Definitive Guide to Java Swing, Third Edition
Copyright © 2005 by John Zukowski
All rights reserved. No part of this work may be reproduced or transmitted in any form or by any means,
electronic or mechanical, including photocopying, recording, or by any information storage or retrieval
system, without the prior written permission of the copyright owner and the publisher.
ISBN (pbk): 1-59059-447-9
Printed and bound in the United States of America 9 8 7 6 5 4 3 2 1
Trademarked names may appear in this book. Rather than use a trademark symbol with every occurrence
of a trademarked name, we use the names only in an editorial fashion and to the benefit of the trademark
owner, with no intention of infringement of the trademark.
Lead Editor: Steve Anglin
Technical Reviewer: Robert Castaneda
Editorial Board: Steve Anglin, Dan Appleman, Ewan Buckingham, Gary Cornell, Tony Davis, Jason Gilmore,
Jonathan Hassell, Chris Mills, Dominic Shakeshaft, Jim Sumser
Assistant Publisher: Grace Wong
Project Manager: Beth Christmas
Copy Edit Manager: Nicole LeClerc
Copy Editor: Marilyn Smith
Production Manager: Kari Brooks-Copony
Production Editor: Ellie Fountain
Compositor: Susan Glinert
Proofreaders: Linda Seifert, Liz Welch
Indexer: Michael Brinkman
Artist: Kinetic Publishing Services, LLC
Cover Designer: Kurt Krames
Manufacturing Manager: Tom Debolski
Distributed to the book trade in the United States by Springer-Verlag New York, Inc., 233 Spring Street,
6th Floor, New York, NY 10013, and outside the United States by Springer-Verlag GmbH & Co. KG,
Tiergartenstr. 17, 69112 Heidelberg, Germany.
In the United States: phone 1-800-SPRINGER, fax 201-348-4505, e-mail orders@springer-ny.com, or visit
http://www.springer-ny.com. Outside the United States: fax +49 6221 345229, e-mail orders@springer.de,
or visit http://www.springer.de.
For information on translations, please contact Apress directly at 2560 Ninth Street, Suite 219, Berkeley, CA
94710. Phone 510-549-5930, fax 510-549-5939, e-mail info@apress.com, or visit http://www.apress.com.
The information in this book is distributed on an “as is” basis, without warranty. Although every precaution
has been taken in the preparation of this work, neither the author(s) nor Apress shall have any liability to
any person or entity with respect to any loss or damage caused or alleged to be caused directly or indirectly
by the information contained in this work.
The source code for this book is available to readers at http://www.apress.com in the Downloads section.

Contents at a Glance
About the Author . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xix
About the Technical Reviewers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xxi
Acknowledgments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xxiii
Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xxv

CHAPTER 1

Swing Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1

CHAPTER 2

Event Handling with the Swing Component Set . . . . . . . . . . . . . . . . 17

CHAPTER 3

The Model-View-Controller Architecture . . . . . . . . . . . . . . . . . . . . . . 59

CHAPTER 4

Core Swing Components . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67

CHAPTER 5

Toggle Buttons . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115

CHAPTER 6

Swing Menus and Toolbars . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151

CHAPTER 7

Borders . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 211

CHAPTER 8

Root Pane Containers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 235

CHAPTER 9

Pop-Ups and Choosers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 267

CHAPTER 10

Layout Managers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 343

CHAPTER 11

Advanced Swing Containers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 377

CHAPTER 12

Bounded Range Components . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 419

CHAPTER 13

List Model Controls . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 451

CHAPTER 14

Spinner Model Controls . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 509

CHAPTER 15

Basic Text Components . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 521

CHAPTER 16

Advanced Text Capabilities . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 585

CHAPTER 17

Trees . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 623

CHAPTER 18

Tables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 675

CHAPTER 19

Drag-and-Drop Support . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 729

CHAPTER 20

The Pluggable Look and Feel Architecture . . . . . . . . . . . . . . . . . . . 741

CHAPTER 21

The Undo Framework . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 783

iii

iv

■C O N T E N T S A T A G L A N C E

CHAPTER 22

Accessibility . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 805

APPENDIX

UI Manager Properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 813

INDEX . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 847

Contents
About the Author . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xix
About the Technical Reviewers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xxi
Acknowledgments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xxiii
Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xxv

■CHAPTER 1

Swing Overview

............................................1

Getting to Know the Swing Components . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
AWT Component Replacements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
Non-AWT Upgraded Components . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
Event Handling and Layout Management . . . . . . . . . . . . . . . . . . . . . . . . . 10
Undo Framework . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
SwingSet Demonstration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
Swing Component to Chapter Mapping . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15

■CHAPTER 2

Event Handling with the Swing Component Set

. . . . . . . . . 17

Delegation-Based Event Handling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
Event Delegation Model . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
Event Listeners As Observers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
Multithreaded Swing Event Handling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
Using SwingUtilities for Mouse Button Identification . . . . . . . . . . . . 23
Using Property Change Listeners As Observers . . . . . . . . . . . . . . . . 26
Managing Listener Lists . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
Timer Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
Swing-Specific Event Handling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
Action Interface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
AbstractAction Class. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
KeyStroke Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
Using Mnemonics and Accelerators . . . . . . . . . . . . . . . . . . . . . . . . . . 46

v

vi

■C O N T E N T S

Swing Focus Management . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
Moving the Focus . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
Examining Focus Cycles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
FocusTraversalPolicy Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
KeyboardFocusManager Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
Verifying Input During Focus Traversal . . . . . . . . . . . . . . . . . . . . . . . 56
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57

■CHAPTER 3

The Model-View-Controller Architecture . . . . . . . . . . . . . . . . . 59
Understanding the Flow of MVC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
MVC Communication . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
UI Delegates for Swing Components . . . . . . . . . . . . . . . . . . . . . . . . . 60
Sharing Data Models . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
Understanding the Predefined Data Models . . . . . . . . . . . . . . . . . . . . . . . 63
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65

■CHAPTER 4

Core Swing Components

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67

JComponent Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
Component Pieces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
JComponent Properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
Handling JComponent Events . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80
JToolTip Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
Creating a JToolTip . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
Creating Customized JToolTip Objects . . . . . . . . . . . . . . . . . . . . . . . 84
Displaying Positional Tooltip Text . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
Customizing a JToolTip Look and Feel . . . . . . . . . . . . . . . . . . . . . . . . 86
ToolTipManager Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
ToolTipManager Properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
JLabel Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88
Creating a JLabel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
JLabel Properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
JLabel Event Handling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
Customizing a JLabel Look and Feel . . . . . . . . . . . . . . . . . . . . . . . . . 92
Interface Icon . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92
Creating an Icon . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
Using an Icon . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94
ImageIcon Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94
GrayFilter Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97

■C O N T E N T S

AbstractButton Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98
AbstractButton Properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98
Handling AbstractButton Events . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102
JButton Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104
Creating a JButton . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104
JButton Properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105
Handling JButton Events . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106
Customizing a JButton Look and Feel . . . . . . . . . . . . . . . . . . . . . . . 108
JPanel Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110
Creating a JPanel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110
Using a JPanel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110
Customizing a JPanel Look and Feel . . . . . . . . . . . . . . . . . . . . . . . . 112
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112

■CHAPTER 5

Toggle Buttons

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115

ToggleButtonModel Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115
ButtonGroup Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116
JToggleButton Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119
Creating JToggleButton Components . . . . . . . . . . . . . . . . . . . . . . . . 119
JToggleButton Properties. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120
Handling JToggleButton Selection Events . . . . . . . . . . . . . . . . . . . . 121
Customizing a JToggleButton Look and Feel. . . . . . . . . . . . . . . . . . 124
JCheckBox Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125
Creating JCheckBox Components . . . . . . . . . . . . . . . . . . . . . . . . . . . 126
JCheckBox Properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127
Handling JCheckBox Selection Events . . . . . . . . . . . . . . . . . . . . . . . 130
Customizing a JCheckBox Look and Feel . . . . . . . . . . . . . . . . . . . . 133
JRadioButton Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134
Creating JRadioButton Components . . . . . . . . . . . . . . . . . . . . . . . . . 135
JRadioButton Properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 136
Grouping JRadioButton Components in a ButtonGroup . . . . . . . . . 136
Handling JRadioButton Selection Events . . . . . . . . . . . . . . . . . . . . . 139
Customizing a JRadioButton Look and Feel . . . . . . . . . . . . . . . . . . 147
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149

■CHAPTER 6

Swing Menus and Toolbars . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151
Working with Menus . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 152
Menu Class Hierarchy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156
JMenuBar Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157

vii

viii

■C O N T E N T S

SingleSelectionModel Interface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161
JMenuItem Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 162
JMenu Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 168
JSeparator Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 175
JPopupMenu Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 176
JCheckBoxMenuItem Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 184
JRadioButtonMenuItem Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 189
Creating Custom MenuElement Components:
The MenuElement Interface. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 195
Working with Pop-Ups: The Popup Class . . . . . . . . . . . . . . . . . . . . . . . . . 200
Creating Pop-Up Components . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 200
A Complete Popup/PopupFactory Usage Example . . . . . . . . . . . . . 200
Working with Toolbars: The JToolBar Class . . . . . . . . . . . . . . . . . . . . . . 202
Creating JToolBar Components. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 202
Adding Components to a JToolBar . . . . . . . . . . . . . . . . . . . . . . . . . . 202
JToolBar Properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 203
Handling JToolBar Events . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 205
Customizing a JToolBar Look and Feel . . . . . . . . . . . . . . . . . . . . . . 205
A Complete JToolBar Usage Example . . . . . . . . . . . . . . . . . . . . . . . 206
JToolBar.Separator Class. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 208
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 208

■CHAPTER 7

Borders . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 211
Some Basics on Working with Borders . . . . . . . . . . . . . . . . . . . . . . . . . . 211
Exploring the Border Interface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 212
Introducing BorderFactory . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 215
Starting with AbstractBorder . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 216
Examining the Predefined Borders . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 218
EmptyBorder Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 218
LineBorder Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 219
BevelBorder Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 220
SoftBevelBorder Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 222
EtchedBorder Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 223
MatteBorder Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 224
CompoundBorder Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 226
TitledBorder Class. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 227
Creating Your Own Borders . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 232
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 234

■C O N T E N T S

■CHAPTER 8

Root Pane Containers

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 235

JRootPane Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 235
Creating a JRootPane . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 236
JRootPane Properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 236
Customizing a JRootPane Look and Feel . . . . . . . . . . . . . . . . . . . . . 238
RootPaneContainer Interface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 239
JLayeredPane Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 239
JFrame Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 242
Creating a JFrame . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 243
JFrame Properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 243
Adding Components to a JFrame . . . . . . . . . . . . . . . . . . . . . . . . . . . 245
Handling JFrame Events . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 245
Extending JFrame . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 246
JWindow Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 247
Creating a JWindow . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 247
JWindow Properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 248
Handling JWindow Events . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 248
Extending JWindow . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 248
JDialog Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 248
Creating a JDialog . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 248
JDialog Properties. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 250
Handling JDialog Events . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 250
Extending JDialog . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 252
JApplet Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 252
Working with a Desktop . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 252
JInternalFrame Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 253
JDesktopPane Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 262
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 266

■CHAPTER 9

Pop-Ups and Choosers

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 267

JOptionPane Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 267
Creating a JOptionPane . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 268
Displaying a JOptionPane . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 271
Automatically Creating a JOptionPane in a Pop-Up Window . . . . . 274
JOptionPane Properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 280
Customizing a JOptionPane Look and Feel . . . . . . . . . . . . . . . . . . . 287

ix

x

■C O N T E N T S

ProgressMonitor Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 291
Creating a ProgressMonitor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 292
Using a ProgressMonitor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 293
ProgressMonitor Properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 296
Customizing a ProgressMonitor Look and Feel . . . . . . . . . . . . . . . . 297
ProgressMonitorInputStream Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 297
Creating a ProgressMonitorInputStream . . . . . . . . . . . . . . . . . . . . . 297
Using a ProgressMonitorInputStream . . . . . . . . . . . . . . . . . . . . . . . . 298
ProgressMonitorInputStream Properties . . . . . . . . . . . . . . . . . . . . . 299
JColorChooser Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 300
Creating a JColorChooser . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 301
Using JColorChooser . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 302
JColorChooser Properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 307
Customizing a JColorChooser Look and Feel . . . . . . . . . . . . . . . . . 320
JFileChooser Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 322
Creating a JFileChooser . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 323
Using JFileChooser . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 323
JFileChooser Properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 326
Working with File Filters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 328
Customizing a JFileChooser Look and Feel . . . . . . . . . . . . . . . . . . . 336
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 341

■CHAPTER 10 Layout Managers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 343
Layout Manager Responsibilities . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 343
LayoutManager Interface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 344
Exploring the LayoutManager Interface . . . . . . . . . . . . . . . . . . . . . . 344
Exploring the LayoutManager2 Interface . . . . . . . . . . . . . . . . . . . . . 345
FlowLayout Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 345
BorderLayout Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 347
GridLayout Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 349
GridBagLayout Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 350
GridBagLayout Rows and Columns . . . . . . . . . . . . . . . . . . . . . . . . . . 353
GridBagConstraints Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 353
CardLayout Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 357
BoxLayout Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 357
Creating a BoxLayout . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 358
Laying Out Components . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 359
OverlayLayout Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 365
SizeRequirements Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 370

■C O N T E N T S

ScrollPaneLayout Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 370
ViewportLayout Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 371
SpringLayout Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 371
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 375

■CHAPTER 11 Advanced Swing Containers . . . . . . . . . . . . . . . . . . . . . . . . . . . . 377
Box Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 377
Creating a Box . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 378
Box Properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 379
Working with Box.Filler . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 380
Creating Areas That Grow . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 380
Creating Rigid Areas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 382
JSplitPane Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 383
Creating a JSplitPane . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 384
JSplitPane Properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 385
Listening for JSplitPane Property Changes . . . . . . . . . . . . . . . . . . . 390
Customizing a JSplitPane Look and Feel . . . . . . . . . . . . . . . . . . . . . 393
JTabbedPane Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 394
Creating a JTabbedPane . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 395
Adding and Removing Tabs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 397
JTabbedPane Properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 398
Listening for Changing Tab Selection . . . . . . . . . . . . . . . . . . . . . . . . 399
Customizing a JTabbedPane Look and Feel . . . . . . . . . . . . . . . . . . 401
JScrollPane Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 403
Creating a JScrollPane . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 404
Changing the Viewport View . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 406
Scrollable Interface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 406
JScrollPane Properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 407
Customizing a JScrollPane Look and Feel . . . . . . . . . . . . . . . . . . . . 410
JViewport Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 412
Creating a JViewport . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 412
JViewport Properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 412
Customizing a JViewport Look and Feel . . . . . . . . . . . . . . . . . . . . . 417
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 417

■CHAPTER 12 Bounded Range Components . . . . . . . . . . . . . . . . . . . . . . . . . . . . 419
BoundedRangeModel Interface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 419
DefaultBoundedRangeModel Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 420

xi

xii

■C O N T E N T S

JScrollBar Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 421
Creating JScrollBar Components . . . . . . . . . . . . . . . . . . . . . . . . . . . 422
Handling Scrolling Events . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 423
JScrollBar Properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 426
Customizing a JScrollBar Look and Feel . . . . . . . . . . . . . . . . . . . . . 427
JSlider Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 428
Creating JSlider Components . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 428
Handling JSlider Events . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 430
JSlider Properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 431
Customizing a JSlider Look and Feel . . . . . . . . . . . . . . . . . . . . . . . . 435
JSlider Client Properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 438
JProgressBar Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 439
Creating JProgressBar Components . . . . . . . . . . . . . . . . . . . . . . . . . 439
JProgressBar Properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 440
Handling JProgressBar Events . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 445
Customizing a JProgressBar Look and Feel . . . . . . . . . . . . . . . . . . 446
JTextField Class and BoundedRangeModel Interface . . . . . . . . . . . . . . . 447
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 449

■CHAPTER 13 List Model Controls . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 451
ListModel Interface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 451
AbstractListModel Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 452
DefaultListModel Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 453
Listening for ListModel Events with a ListDataListener . . . . . . . . . 454
ComboBoxModel Interface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 460
MutableComboBoxModel Interface . . . . . . . . . . . . . . . . . . . . . . . . . . 460
DefaultComboBoxModel Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 460
JList Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 463
Creating JList Components . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 463
JList Properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 464
Scrolling JList Components . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 466
Rendering JList Elements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 468
Selecting JList Elements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 473
Displaying Multiple Columns . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 479
Customizing a JList Look and Feel . . . . . . . . . . . . . . . . . . . . . . . . . . 480
Creating a Dual List Box . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 481
Adding Element-Level Tooltips to List Items . . . . . . . . . . . . . . . . . . 488

■C O N T E N T S

JComboBox Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 490
Creating JComboBox Components . . . . . . . . . . . . . . . . . . . . . . . . . . 491
JComboBox Properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 491
Rendering JComboBox Elements . . . . . . . . . . . . . . . . . . . . . . . . . . . 493
Selecting JComboBox Elements . . . . . . . . . . . . . . . . . . . . . . . . . . . . 493
Editing JComboBox Elements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 497
Customizing a JComboBox Look and Feel . . . . . . . . . . . . . . . . . . . . 503
Sharing the Data Model for a JComboBox and JList . . . . . . . . . . . . . . . 506
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 508

■CHAPTER 14 Spinner Model Controls . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 509
JSpinner Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 509
Creating JSpinner Components . . . . . . . . . . . . . . . . . . . . . . . . . . . . 510
JSpinner Properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 510
Listening for JSpinner Events with a ChangeListener . . . . . . . . . . 511
Customizing a JSpinner Look and Feel . . . . . . . . . . . . . . . . . . . . . . 512
SpinnerModel Interface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 513
AbstractSpinnerModel Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 513
SpinnerDateModel Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 514
SpinnerListModel Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 515
SpinnerNumberModel Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 516
Custom Models . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 517
JSpinner Editors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 518
JSpinner.DefaultEditor Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 518
JSpinner.DateEditor Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 519
JSpinner.ListEditor Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 519
JSpinner.NumberEditor Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 520
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 520

■CHAPTER 15 Basic Text Components . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 521
Overview of the Swing Text Components . . . . . . . . . . . . . . . . . . . . . . . . 521
JTextComponent Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 523
JTextComponent Properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 523
JTextComponent Operations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 526
JTextField Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 526
Creating a JTextField . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 527
Using JLabel Mnemonics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 527
JTextField Properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 529

xiii

xiv

■C O N T E N T S

JTextComponent Operations with a JTextField . . . . . . . . . . . . . . . . 530
Document Interface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 537
DocumentListener and DocumentEvent Interfaces . . . . . . . . . . . . . 546
Caret and Highlighter Interfaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . 547
CaretListener Interface and CaretEvent Class . . . . . . . . . . . . . . . . . 550
NavigationFilter Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 552
Keymap Interface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 554
JTextComponent.KeyBinding Class . . . . . . . . . . . . . . . . . . . . . . . . . 556
Handling JTextField Events . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 556
Customizing a JTextField Look and Feel . . . . . . . . . . . . . . . . . . . . . 562
JPasswordField Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 563
Creating a JPasswordField . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 563
JPasswordField Properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 564
Customizing a JPasswordField Look and Feel . . . . . . . . . . . . . . . . 565
JFormattedTextField Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 566
Creating a JFormattedTextField . . . . . . . . . . . . . . . . . . . . . . . . . . . . 566
JFormattedTextField Properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . 567
Customizing a JFormattedTextField Look and Feel . . . . . . . . . . . . 569
JTextArea Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 570
Creating a JTextArea . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 570
JTextArea Properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 571
Handling JTextArea Events . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 572
Customizing a JTextArea Look and Feel . . . . . . . . . . . . . . . . . . . . . 572
JEditorPane Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 574
Creating a JEditorPane . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 575
JEditorPane Properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 575
Handling JEditorPane Events . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 576
Customizing a JEditorPane Look and Feel . . . . . . . . . . . . . . . . . . . . 579
JTextPane Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 580
Creating a JTextPane . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 580
JTextPane Properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 580
Customizing a JTextPane Look and Feel . . . . . . . . . . . . . . . . . . . . . 581
Loading a JTextPane with Content . . . . . . . . . . . . . . . . . . . . . . . . . . 582
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 584

■CHAPTER 16 Advanced Text Capabilities . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 585
Using Actions with Text Components . . . . . . . . . . . . . . . . . . . . . . . . . . . . 585
Listing Actions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 586
Using Actions. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 589
Finding Actions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 591

■C O N T E N T S

Creating Styled Text . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 595
StyledDocument Interface and DefaultStyledDocument Class . . . 595
AttributeSet Interface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 597
MutableAttributeSet Interface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 597
SimpleAttributeSet Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 597
StyleConstants Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 601
TabStop and TabSet Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 603
Style Interface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 606
StyleContext Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 606
The Editor Kits . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 607
Loading HTML Documents. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 607
Iterating Through HTML Documents . . . . . . . . . . . . . . . . . . . . . . . . . 608
JFormattedTextField Formats . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 612
Dates and Numbers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 612
Input Masks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 618
DefaultFormatterFactory Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 620
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 621

■CHAPTER 17 Trees . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 623
Introducing Trees . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 623
JTree Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 624
Creating a JTree . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 624
Scrolling Trees . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 627
JTree Properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 628
Customizing a JTree Look and Feel . . . . . . . . . . . . . . . . . . . . . . . . . 630
TreeCellRenderer Interface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 634
DefaultTreeCellRenderer Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 635
DefaultTreeCellRenderer Properties . . . . . . . . . . . . . . . . . . . . . . . . . 635
Creating a Custom Renderer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 637
Working with Tree Tooltips . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 641
Editing Tree Nodes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 643
CellEditor Interface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 644
TreeCellEditor Interface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 644
DefaultCellEditor Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 645
DefaultTreeCellEditor Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 647
Creating a Proper ComboBox Editor for a Tree . . . . . . . . . . . . . . . . 648
Creating an Editor Just for Leaf Nodes. . . . . . . . . . . . . . . . . . . . . . . 648
CellEditorListener Interface and ChangeEvent Class . . . . . . . . . . . 650
Creating a Better Check Box Node Editor . . . . . . . . . . . . . . . . . . . . 650

xv

xvi

■C O N T E N T S

Working with the Nodes of the Tree . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 659
TreeNode Interface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 659
MutableTreeNode Interface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 660
DefaultMutableTreeNode Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 661
Traversing Trees . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 664
JTree.DynamicUtilTreeNode Class . . . . . . . . . . . . . . . . . . . . . . . . . . 666
TreeModel Interface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 667
DefaultTreeModel Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 667
TreeModelListener Interface and TreeModelEvent Class . . . . . . . . 668
TreeSelectionModel Interface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 668
DefaultTreeSelectionModel Class . . . . . . . . . . . . . . . . . . . . . . . . . . . 670
TreeSelectionListener Interface and TreeSelectionEvent Class. . . 671
TreePath Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 671
Additional Expansion Events . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 672
TreeExpansionListener Interface and
TreeExpansionEvent Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 672
TreeWillExpandListener Interface and
ExpandVetoException Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 673
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 674

■CHAPTER 18 Tables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 675
Introducing Tables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 675
JTable Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 677
Creating a JTable . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 677
Scrolling JTable Components . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 678
Manually Positioning the JTable View . . . . . . . . . . . . . . . . . . . . . . . 679
Removing Column Headers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 680
JTable Properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 680
Rendering Table Cells . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 686
Handling JTable Events . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 689
Customizing a JTable Look and Feel . . . . . . . . . . . . . . . . . . . . . . . . 689
TableModel Interface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 690
AbstractTableModel Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 691
DefaultTableModel Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 696
Sorting JTable Elements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 700
TableColumnModel Interface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 707
DefaultTableColumnModel Class . . . . . . . . . . . . . . . . . . . . . . . . . . . 708
Listening to JTable Events with a TableColumnModelListener . . . 709
TableColumn Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 712

■C O N T E N T S

JTableHeader Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 715
Creating a JTableHeader . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 716
JTableHeader Properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 716
Using Tooltips in Table Headers . . . . . . . . . . . . . . . . . . . . . . . . . . . . 716
Customizing a JTableHeader Look and Feel . . . . . . . . . . . . . . . . . . 717
Editing Table Cells . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 718
TableCellEditor Interface and DefaultCellEditor Class . . . . . . . . . . 718
Creating a Simple Cell Editor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 718
Creating a Complex Cell Editor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 722
Printing Tables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 724
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 728

■CHAPTER 19 Drag-and-Drop Support . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 729
Built-in Drag-and-Drop Support . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 729
TransferHandler Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 731
Drag-and-Drop Support for Images . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 733
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 740

■CHAPTER 20 The Pluggable Look and Feel Architecture . . . . . . . . . . . . . . 741
LookAndFeel Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 741
Listing the Installed Look and Feel Classes . . . . . . . . . . . . . . . . . . . 742
Changing the Current Look and Feel . . . . . . . . . . . . . . . . . . . . . . . . 743
Customizing the Current Look and Feel . . . . . . . . . . . . . . . . . . . . . . 747
Creating a New Look and Feel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 767
Using the WindowsLookAndFeel on a Non-Windows Machine . . . 767
Adding UI Delegates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 771
Working with Metal Themes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 772
MetalTheme Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 772
DefaultMetalTheme and OceanTheme Classes . . . . . . . . . . . . . . . . 774
Using an Auxiliary Look and Feel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 776
SynthLookAndFeel Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 777
Configuring Synth . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 777
Default Synth Properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 780
Working with Synth Images . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 780
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 781

xvii

xviii

■C O N T E N T S

■CHAPTER 21 The Undo Framework . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 783
Working with the Undo Framework . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 783
Using the Undo Framework with Swing Text Components . . . . . . . . . . 784
The Command Design Pattern . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 788
Undo Framework Components . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 789
UndoableEdit Interface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 789
AbstractUndoableEdit Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 791
CompoundEdit Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 791
UndoManager Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 792
UndoableEditListener Interface and UndoableEditEvent Class . . . 794
UndoableEditSupport Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 794
A Complete Undoable Program Example . . . . . . . . . . . . . . . . . . . . . 795
Using an Outside Object to Manage Undo States . . . . . . . . . . . . . . . . . . 800
StateEditable Interface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 800
StateEdit Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 801
A Complete StateEditable/StateEdit Example . . . . . . . . . . . . . . . . . 801
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 804

■CHAPTER 22 Accessibility . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 805
Accessibility Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 805
Accessible Interface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 806
AccessibleContext Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 806
Creating Accessible Components . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 807
Working with the Java Access Bridge . . . . . . . . . . . . . . . . . . . . . . . . . . . 808
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 811

■APPENDIX

UI Manager Properties

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 813

■INDEX . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 847

About the Author

■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

javax.swing.*;
java.awt.*;
java.awt.event.*;
java.beans.*;
java.util.Random;

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

keyTextComponent.addActionListener(actionListener);
frame.add(keyTextComponent, BorderLayout.CENTER);
frame.add(textField, BorderLayout.SOUTH);
frame.setSize(300, 200);
frame.setVisible(true);
}
};
EventQueue.invokeLater(runner);
}
}

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
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("
WINDOW
' '"); // Define ActionListener Action actionListener = new AbstractAction() { public void actionPerformed(ActionEvent actionEvent) { JButton source = (JButton)actionEvent.getSource(); System.out.println("Activated: " + source.getText()); } }; KeyStroke controlAlt7 = KeyStroke.getKeyStroke("control alt 7"); InputMap inputMap = buttonA.getInputMap(); inputMap.put(controlAlt7, ACTION_KEY); ActionMap actionMap = buttonA.getActionMap(); actionMap.put(ACTION_KEY, actionListener); KeyStroke enter = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0, true); inputMap = buttonB.getInputMap(); inputMap.put(enter, ACTION_KEY); buttonB.setActionMap(actionMap); CHAPTER 2 ■ EVENT HANDLING WITH THE SWING COMPONENT SET KeyStroke shiftF4 = KeyStroke.getKeyStroke(KeyEvent.VK_F4, InputEvent.SHIFT_MASK); inputMap = buttonC.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); inputMap.put(shiftF4, ACTION_KEY); buttonC.setActionMap(actionMap); KeyStroke space = KeyStroke.getKeyStroke(' '); inputMap = buttonD.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); inputMap.put(space, ACTION_KEY); buttonD.setActionMap(actionMap); frame.setLayout(new GridLayout(2,2)); frame.add(buttonA); frame.add(buttonB); frame.add(buttonC); frame.add(buttonD); frame.setSize(400, 200); frame.setVisible(true); } }; EventQueue.invokeLater(runner); } } ■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 Listing 2-13. Reversing Focus Traversal import import import import import import javax.swing.*; java.awt.*; java.awt.event.*; java.util.Comparator; java.util.Arrays; java.util.List; 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. Table 3-1. Swing Component Models Component Data Model Interface Implementations AbstractButton ButtonModel DefaultButtonModel JColorChooser ColorSelectionModel DefaultColorSelectionModel JComboBox ComboBoxModel N/A MutableComboBoxModel DefaultComboBoxModel JFileChooser ListModel BasicDirectoryModel JList ListModel AbstractListModel DefaultListModel ListSelectionModel DefaultListSelectionModel JMenuBar SingleSelectionModel DefaultSingleSelectionModel JPopupMenu SingleSelectionModel DefaultSingleSelectionModel JProgressBar BoundedRangeModel DefaultBoundedRangeModel JScrollBar BoundedRangeModel DefaultBoundedRangeModel JSlider BoundedRangeModel DefaultBoundedRangeModel JSpinner SpinnerModel AbstractSpinnerModel SpinnerDateModel SpinnerListModel SpinnerNumberModel JTabbedPane SingleSelectionModel DefaultSingleSelectionModel JTable TableModel AbstractTableModel DefaultTableModel JTextComponent TableColumnModel DefaultTableColumnModel ListSelectionModel DefaultListSelectionModel Document AbstractDocument PlainDocument StyledDocument DefaultStyleDocument HTMLDocument CHAPTER 3 ■ THE MODEL-VIEW-CONTROLLER ARCHITECTURE Table 3-1. Swing Component Models (Continued) Component Data Model Interface Implementations JToggleButton ButtonModel JToggleButton ToggleButtonModel JTree TreeModel DefaultTreeModel TreeSelectionModel DefaultTreeSelectionModel JTree.EmptySelectionModel 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 static static static static static static static static static static 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(); CHAPTER 4 ■ CORE SWING COMPONENTS } else if (e.getPropertyName().equals(Action.SMALL_ICON)) { Icon icon = (Icon)e.getNewValue(); button.setIcon(icon); button.invalidate(); 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. Table 4-4. JComponent Inherited Event Listeners Class Event Listener Event Object Component ComponentListener componentHidden(ComponentEvent) componentMoved(ComponentEvent) componentResized(ComponentEvent) componentShown(ComponentEvent) Component FocusListener focusGained(FocusEvent) focusLost(FocusEvent) Component HierarchyBoundsListener ancestorMoved(HierarchyEvent) ancestorResized(HierarchyEvent) Component HierarchyListener hierarchyChanged(HierarchyEvent) Component InputMethodListener caretPositionChanged (InputMethodEvent) inputMethodTextChanged (InputMethodEvent) Component KeyListener keyPressed(KeyEvent) keyReleased(KeyEvent) keyTyped(KeyEvent) Component MouseListener mouseClicked(MouseEvent) mouseEntered(MouseEvent) mouseExited(MouseEvent) mousePressed(MouseEvent) mouseReleased(MouseEvent) Component MouseMotionListener mouseDragged(MouseEvent) mouseMoved(MouseEvent) Component MouseWheelListener mouseWheelMoved(MouseWheelEvent) Container ContainerListener componentAdded(ContainerEvent) componentRemoved(ContainerEvent) 83 84 CHAPTER 4 ■ CORE SWING COMPONENTS 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 = "HTML 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); 107 108 CHAPTER 4 ■ CORE SWING COMPONENTS Icon warnIcon = new ImageIcon("Warn.gif"); JButton button2 = new JButton(warnIcon); button2.setActionCommand("Second"); button2.addActionListener(actionListener); frame.add(button2); JButton button3 = new JButton("Warning", warnIcon); button3.setActionCommand("Third"); button3.addActionListener(actionListener); frame.add(button3); String htmlButton = "HTML Button
" + "Multi-line"; JButton button4 = new JButton(htmlButton); button4.setActionCommand("Fourth"); button4.addActionListener(actionListener); frame.add(button4); JRootPane rootPane = frame.getRootPane(); rootPane.setDefaultButton(button2); frame.setSize(300, 200); frame.setVisible(true); } }; EventQueue.invokeLater(runner); } } 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: true/false/false true/true/false true/true/true true/false/true false/false/true 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 javax.swing.*; javax.swing.event.*; java.awt.*; java.awt.event.*; public class SelectingToggle { public static void main(String args[]) { Runnable runner = new Runnable() { public void run() { JFrame frame = new JFrame("Selecting Toggle"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); JToggleButton toggleButton = new JToggleButton("Toggle Button"); // Define ActionListener 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"); } }; // 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); } }; 123 124 CHAPTER 5 ■ TOGGLE BUTTONS // 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); 129 130 CHAPTER 5 ■ TOGGLE BUTTONS Icon checkBoxIcon = new CheckBoxIcon(); JCheckBox aCheckBox3 = new JCheckBox("Anchovies", checkBoxIcon); JCheckBox aCheckBox4 = new JCheckBox("Stuffed Crust", checked); frame.setLayout(new GridLayout(0,1)); frame.add(aCheckBox1); frame.add(aCheckBox2); frame.add(aCheckBox3); frame.add(aCheckBox4); frame.setSize(300, 200); frame.setVisible(true); } }; EventQueue.invokeLater(runner); } } 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 javax.swing.*; javax.swing.event.*; java.awt.*; java.awt.event.*; 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 getSelectedElements(Container container) { Vector selections = new Vector(); 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 javax.swing.*; javax.swing.event.*; java.awt.*; java.awt.event.*; java.util.Enumeration; public class GroupActionRadio { private static final String sliceOptions[] = {"4 slices", "8 slices", "12 slices", "16 slices"}; private static final String crustOptions[] = {"Sicilian", "Thin Crust", "Thick Crust", "Stuffed Crust"}; public static void main(String args[]) { Runnable runner = new Runnable() { public void run() { JFrame frame = new JFrame("Grouping Example"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // Slice Parts ActionListener sliceActionListener = new ActionListener() { public void actionPerformed(ActionEvent actionEvent) { AbstractButton aButton = (AbstractButton)actionEvent.getSource(); System.out.println("Selected: " + aButton.getText()); } }; Container sliceContainer = RadioButtonUtils.createRadioButtonGrouping(sliceOptions, "Slice Count", sliceActionListener); // Crust Parts ActionListener crustActionListener = new ActionListener() { String lastSelected; public void actionPerformed(ActionEvent actionEvent) { AbstractButton aButton = (AbstractButton)actionEvent.getSource(); String label = aButton.getText(); String msgStart; if (label.equals(lastSelected)) { msgStart = "Reselected: "; } else { msgStart = "Selected: "; } lastSelected = label; System.out.println(msgStart + label); } }; 143 144 CHAPTER 5 ■ TOGGLE BUTTONS 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); } }; ChangeListener changeListener = new ChangeListener() { public void stateChanged(ChangeEvent changEvent) { AbstractButton aButton = (AbstractButton)changEvent.getSource(); ButtonModel aModel = aButton.getModel(); boolean armed = aModel.isArmed(); boolean pressed = aModel.isPressed(); boolean selected = aModel.isSelected(); System.out.println("Changed: " + armed + "/" + pressed + "/" + selected); } }; final Container crustContainer = RadioButtonUtils.createRadioButtonGrouping(crustOptions, "Crust Type", crustActionListener, itemListener, changeListener); // Button Parts ActionListener buttonActionListener = new ActionListener() { public void actionPerformed(ActionEvent actionEvent) { Enumeration selected = RadioButtonUtils.getSelectedElements(crustContainer); while (selected.hasMoreElements()) { System.out.println ("Selected -> " + selected.nextElement()); } } }; CHAPTER 5 ■ TOGGLE BUTTONS JButton button = new JButton ("Order Pizza"); button.addActionListener(buttonActionListener); frame.add(sliceContainer, BorderLayout.WEST); frame.add(crustContainer, BorderLayout.EAST); frame.add(button, BorderLayout.SOUTH); frame.setSize(300, 200); frame.setVisible(true); } }; EventQueue.invokeLater(runner); } } 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 javax.swing.*; javax.swing.event.*; javax.swing.border.*; java.awt.*; java.awt.event.*; java.util.Enumeration; java.util.Vector; public class RadioButtonUtils { private RadioButtonUtils() { // Private constructor so you can't create instances } 145 146 CHAPTER 5 ■ TOGGLE BUTTONS public static Enumeration getSelectedElements(Container container) { Vector selections = new Vector(); Component components[] = container.getComponents(); for (int i=0, n=components.length; iNew, 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); 155 156 CHAPTER 6 ■ SWING MENUS AND TOOLBARS frame.setJMenuBar(menuBar); frame.setSize(350, 250); frame.setVisible(true); } }; EventQueue.invokeLater(runner); } } 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 JMenuItem add(JMenuItem menuItem); JMenuItem add(String label); Component add(Component component); Component add(Component component, int index); JMenuItem add(Action action); void addSeparator(); 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 JMenuItem add(JMenuItem menuItem); JMenuItem add(String label); JMenuItem add(Action action); void addSeparator(); 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. Table 6-10. JPopupMenu UIResource Elements Property String Object Type PopupMenu.actionMap ActionMap PopupMenu.background Color PopupMenu.border Border PopupMenu.consumeEventOnClose Boolean PopupMenu.font Font PopupMenu.foreground Color PopupMenu.popupSound String PopupMenu.selectedWindowInputMapBindings Object[ ] PopupMenu.selectedWindowInputMapBindings.RightToLeft Object[ ] PopupMenuSeparatorUI String PopupMenuUI String 181 182 CHAPTER 6 ■ SWING MENUS AND TOOLBARS 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 java.awt.*; java.awt.event.*; javax.swing.*; javax.swing.event.*; public class PopupSample { CHAPTER 6 ■ SWING MENUS AND TOOLBARS // Define ActionListener static class PopupActionListener implements ActionListener { public void actionPerformed(ActionEvent actionEvent) { System.out.println("Selected: " + actionEvent.getActionCommand()); } } // Define PopupMenuListener static class MyPopupMenuListener implements PopupMenuListener { public void popupMenuCanceled(PopupMenuEvent popupMenuEvent) { System.out.println("Canceled"); } public void popupMenuWillBecomeInvisible(PopupMenuEvent popupMenuEvent) { System.out.println("Becoming Invisible"); } public void popupMenuWillBecomeVisible(PopupMenuEvent popupMenuEvent) { System.out.println("Becoming Visible"); } } public static void main(final String args[]) { Runnable runner = new Runnable() { public void run() { // Create frame JFrame frame = new JFrame("PopupSample Example"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); ActionListener actionListener = new PopupActionListener(); PopupMenuListener popupMenuListener = new MyPopupMenuListener(); // Create popup menu, attach popup menu listener JPopupMenu popupMenu = new JPopupMenu("Title"); popupMenu.addPopupMenuListener(popupMenuListener); // Cut JMenuItem cutMenuItem = new JMenuItem("Cut"); cutMenuItem.addActionListener(actionListener); popupMenu.add(cutMenuItem); // Copy JMenuItem copyMenuItem = new JMenuItem("Copy"); copyMenuItem.addActionListener(actionListener); popupMenu.add(copyMenuItem); 183 184 CHAPTER 6 ■ SWING MENUS AND TOOLBARS // Paste JMenuItem pasteMenuItem = new JMenuItem("Paste"); pasteMenuItem.addActionListener(actionListener); pasteMenuItem.setEnabled(false); popupMenu.add(pasteMenuItem); // Separator popupMenu.addSeparator(); // Find JMenuItem findMenuItem = new JMenuItem("Find"); findMenuItem.addActionListener(actionListener); popupMenu.add(findMenuItem); JButton label = new JButton(); frame.add(label); label.setComponentPopupMenu(popupMenu); frame.setSize(350, 250); frame.setVisible(true); } }; EventQueue.invokeLater(runner); } } 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 Property String Object Type RadioButtonMenuItem.acceleratorFont Font RadioButtonMenuItem.acceleratorForeground Color RadioButtonMenuItem.acceleratorSelectionForeground Color RadioButtonMenuItem.actionMap ActionMap RadioButtonMenuItem.arrowIcon Icon RadioButtonMenuItem.background Color RadioButtonMenuItem.border Border RadioButtonMenuItem.borderPainted Boolean RadioButtonMenuItem.checkIcon Icon RadioButtonMenuItem.commandSound String RadioButtonMenuItem.disabledForeground Color RadioButtonMenuItem.font Font RadioButtonMenuItem.foreground Color RadioButtonMenuItem.gradient List RadioButtonMenuItem.margin Insets RadioButtonMenuItem.opaque Boolean 191 192 CHAPTER 6 ■ SWING MENUS AND TOOLBARS 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); 193 194 CHAPTER 6 ■ SWING MENUS AND TOOLBARS JRadioButtonMenuItem threeMenuItem = new JRadioButtonMenuItem("French Hens", threeIcon); threeMenuItem.addItemListener(itemListener); buttonGroup.add(threeMenuItem); menu.add(threeMenuItem); JRadioButtonMenuItem fourMenuItem = new JRadioButtonMenuItem("Calling Birds", fourIcon, true); fourMenuItem.addActionListener(actionListener); buttonGroup.add(fourMenuItem); menu.add(fourMenuItem); JRadioButtonMenuItem fiveMenuItem = new JRadioButtonMenuItem(fiveIcon); fiveMenuItem.addActionListener(actionListener); fiveMenuItem.setActionCommand("Rings"); buttonGroup.add(fiveMenuItem); menu.add(fiveMenuItem); JRadioButtonMenuItem sixMenuItem = new JRadioButtonMenuItem(sixIcon, true); sixMenuItem.addActionListener(actionListener); sixMenuItem.setActionCommand("Geese"); buttonGroup.add(sixMenuItem); menu.add(sixMenuItem); menuBar.add(menu); frame.setJMenuBar(menuBar); frame.setSize(350, 250); frame.setVisible(true); } }; EventQueue.invokeLater(runner); } } ■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 java.awt.*; java.awt.event.*; javax.swing.*; javax.swing.event.*; 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= 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 java.awt.*; java.awt.event.*; javax.swing.*; java.util.Random; public class ButtonPopupSample { CHAPTER 6 ■ SWING MENUS AND TOOLBARS static final Random random = new Random(); // Define ActionListener static class ButtonActionListener implements ActionListener { public void actionPerformed(ActionEvent actionEvent) { System.out.println("Selected: " + actionEvent.getActionCommand()); } } // Define Show Popup ActionListener static class ShowPopupActionListener implements ActionListener { private Component component; ShowPopupActionListener(Component component) { this.component = component; } public synchronized void actionPerformed(ActionEvent actionEvent) { JButton button = new JButton("Hello, World"); ActionListener listener = new ButtonActionListener(); button.addActionListener(listener); PopupFactory factory = PopupFactory.getSharedInstance(); int x = random.nextInt(200); int y = random.nextInt(200); final Popup popup = factory.getPopup(component, button, x, y); popup.show(); ActionListener hider = new ActionListener() { public void actionPerformed(ActionEvent e) { popup.hide(); } }; // Hide popup in 3 seconds Timer timer = new Timer(3000, hider); timer.start(); } } public static void main(final String args[]) { Runnable runner = new Runnable() { public void run() { // Create frame JFrame frame = new JFrame("Button Popup Sample"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); ActionListener actionListener = new ShowPopupActionListener(frame); JButton start = new JButton("Pick Me for Popup"); start.addActionListener(actionListener); frame.add(start); 201 202 CHAPTER 6 ■ SWING MENUS AND TOOLBARS frame.setSize(350, 250); frame.setVisible(true); } }; EventQueue.invokeLater(runner); } } 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); public static CompoundBorder createCompoundBorder(Border outside, Border inside) Border compoundBorder = BorderFactory.createCompoundBorder(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); 231 232 CHAPTER 7 ■ BORDERS frame.setSize(300, 100); frame.setVisible(true); } }; EventQueue.invokeLater(runner); } } 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. *-ENU"AR #ONTENT 0ANE #ONTAINER 'LASS 0ANE #OMPONENT *,AYERED0ANE 236 Figure 8-1. JRootPane containment diagram ■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); } CHAPTER 8 ■ ROOT PANE CONTAINERS protected void frameInit() { super.frameInit(); setDefaultCloseOperation(EXIT_ON_CLOSE); } } ■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); } CHAPTER 8 ■ ROOT PANE CONTAINERS public EscapeDialog(Frame owner) { this(owner, false); } public EscapeDialog(Frame owner, boolean modal) { this(owner, null, modal); } public EscapeDialog(Frame owner, String title) { this(owner, title, false); } public EscapeDialog(Frame owner, String title, boolean modal) { super(owner, title, modal); } public EscapeDialog(Frame owner, String title, boolean modal, GraphicsConfiguration gc) { super(owner, title, modal, gc); } public EscapeDialog(Dialog owner) { this(owner, false); } public EscapeDialog(Dialog owner, boolean modal) { this(owner, null, modal); } public EscapeDialog(Dialog owner, String title) { this(owner, title, false); } public EscapeDialog(Dialog owner, String title, boolean modal) { super(owner, title, modal); } public EscapeDialog(Dialog owner, String title, boolean modal, GraphicsConfiguration gc) { super(owner, title, modal, gc); } protected JRootPane createRootPane() { JRootPane rootPane = new JRootPane(); KeyStroke stroke = KeyStroke.getKeyStroke("ESCAPE"); Action actionListener = new AbstractAction() { public void actionPerformed(ActionEvent actionEvent) { setVisible(false); } } ; InputMap inputMap = rootPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); inputMap.put(stroke, "ESCAPE"); rootPane.getActionMap().put("ESCAPE", actionListener); return rootPane; } } 251 252 CHAPTER 8 ■ ROOT PANE CONTAINERS ■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 (requires restart) is selected. If [VERSION] isn’t recent enough, you’ll need to get a newer version from Sun at http://www.java.com. If you need to extend the JApplet class, it has only one protected method of importance: protected JRootPane createRootPane() Working with a Desktop Swing provides for the management of a set of frames within a common window or desktop. As discussed in Chapter 1, this management is commonly called MDI. The frames can be layered on top of one another or dragged around, and their appearance is specific to the current look and feel. The frames are instances of the JInternalFrame class, whereas the desktop is a specialized JLayeredPane called JDesktopPane. The management of the frames within a desktop is the responsibility of a DesktopManager, in which the default implementation that’s provided is DefaultDesktopManager. The iconified form of a JInternalFrame on the desktop is represented CHAPTER 8 ■ ROOT PANE CONTAINERS by the JDesktopIcon inner class of JInternalFrame. There are also an InternalFrameListener, InternalFrameAdapter, and InternalFrameEvent for event handling. First, let’s look at the parts that make up the desktop, and then you’ll see a complete example that uses all the parts. ■Note The Swing libraries provide only those tools necessary to build an application using MDI. You use these tools in whatever manner you see fit. JInternalFrame Class The JInternalFrame class is similar to the JFrame class. It acts as a high-level container, using the RootPaneContainer interface, but it isn’t a top-level window. You must place internal frames within another top-level window. When dragged around, internal frames stay within the bounds of their container, which is usually a JDesktopPane. In addition, internal frames are lightweight and therefore offer a UI-delegate to make internal frames appear as the currently configured look and feel. ■Note As with the creation of a JFrame, the JInternalFrame is hidden when first created. Creating a JInternalFrame There are six constructors for JInternalFrame: public JInternalFrame() JInternalFrame frame = new JInternalFrame(); public JInternalFrame(String title) JInternalFrame frame = new JInternalFrame("The Title"); public JInternalFrame(String title, boolean resizable) JInternalFrame frame = new JInternalFrame("The Title", true); public JInternalFrame(String title, boolean resizable, boolean closable) JInternalFrame frame = new JInternalFrame("The Title", false, true); public JInternalFrame(String title, boolean resizable, boolean closable, boolean maximizable) JInternalFrame frame = new JInternalFrame("The Title", true, false, true); public JInternalFrame(String title, boolean resizable, boolean closable, boolean maximizable, boolean iconifiable) JInternalFrame frame = new JInternalFrame("The Title", false, true, false, true); 253 254 CHAPTER 8 ■ ROOT PANE CONTAINERS These constructors cascade in such a way that each adds a parameter to another constructor. With no arguments, the created JInternalFrame has no title and can’t be resized, closed, maximized, or iconified. Internal frames can always be dragged, however. ■Note In addition to your creating a JInternalFrame directly, you can rely on the JOptionPane to create an internal frame for common pop-up dialog boxes hosted by a JInternalFrame instead of being hosted by the standard JDialog. JInternalFrame Properties The 30 different properties for the JInternalFrame class are listed in Table 8-9. The layer property is listed twice as it has two setter methods, one for an int and another for an Integer. Table 8-9. JInternalFrame Properties Property Name Data Type Access accessibleContext AccessibleContext Read-only closable boolean Read-write bound closed boolean Read-write bound constrained contentPane Container Read-write bound defaultCloseOperation int Read-write desktopIcon JInternalFrame.JDesktopIcon Read-write bound desktopPane JDesktopPane Read-only focusCycleRoot boolean Read-write focusCycleRootAncester Container Read-only focusOwner Component Read-only frameIcon Icon Read-write bound glassPane Component Read-write bound icon boolean Read-write bound constrained iconifiable boolean Read-write internalFrameListeners InternalFrameListener[ ] Read-only jMenuBar JMenuBar Read-write bound layer int Read-write layer Integer Write-only layeredPane JLayeredPane Read-write bound CHAPTER 8 ■ ROOT PANE CONTAINERS Table 8-9. JInternalFrame Properties (Continued) Property Name Data Type Access layout LayoutManager Write-only maximizable boolean Read-write bound maximum boolean Read-write bound constrained mostRecentFocusOwner Component Read-only normalBounds Rectangle Read-write resizable boolean Read-write bound rootPane JRootPane Read-only bound selected boolean Read-write bound constrained title String Read-write bound UI InternalFrameUI Read-write UIClassID String Read-only warningString String Read-only The initial defaultCloseOperation property setting for a JInternalFrame is DISPOSE_ON_CLOSE for Java 1.3 releases and later. Earlier releases had a default setting of HIDE_ON_CLOSE. You can set this property to any of the WindowConstants settings shown earlier in Table 8-6. The normalBounds property describes where an iconified internal frame would appear when deiconified. The focusOwner property provides the actual Component with the input focus when the specific JInternalFrame is active. The JInternalFrame contains the only four constrained properties within the Swing classes: closed, icon, maximum, and selected. They’re directly related to the four boolean constructor parameters. Each allows you to check on the current state of the property as well as change its setting. However, because the properties are constrained, whenever you try to set one, the attempt must be in a try-catch block, catching PropertyVetoException: try { // Try to iconify internal frame internalFrame.setIcon(false); } catch (PropertyVetoException propertyVetoException) { System.out.println("Rejected"); } To help you work with some of the bound properties, the JInternalFrame class defines 11 constants, as listed in Table 8-10. They represent the string that should be returned by getPropertyName() for a PropertyChangeEvent within a PropertyChangeListener. 255 256 CHAPTER 8 ■ ROOT PANE CONTAINERS Table 8-10. JInternalFrame Property Constants Property Name Constant Associated Property CONTENT_PANE_PROPERTY contentPane FRAME_ICON_PROPERTY frameIcon GLASS_PANE_PROPERTY glassPane IS_CLOSED_PROPERTY closed IS_ICON_PROPERTY icon IS_MAXIMUM_PROPERTY maximum IS_SELECTED_PROPERTY selected LAYERED_PANE_PROPERTY layeredPane MENU_BAR_PROPERTY jMenuBar ROOT_PANE_PROPERTY rootPane TITLE_PROPERTY title The following class example demonstrates the use of the constants within a PropertyChangeListener. import java.beans.*; import javax.swing.*; public class InternalFramePropertyChangeHandler implements PropertyChangeListener { public void propertyChange(PropertyChangeEvent propertyChangeEvent) { String propertyName = propertyChangeEvent.getPropertyName(); if (propertyName.equals(JInternalFrame.IS_ICON_PROPERTY)) { System.out.println("Icon property changed. React."); } } } Handling JInternalFrame Events To help you use a JInternalFrame as you would use a JFrame, there’s an additional event listener for responding to internal frame opening- and closing-related events. The interface is called InternalFrameListener, and its definition follows. It works similarly to the AWT WindowListener interface, but with a JInternalFrame instead of an AWT Window class. public public public public public public public public } interface InternalFrameListener extends EventListener { void internalFrameActivated(InternalFrameEvent internalFrameEvent); void internalFrameClosed(InternalFrameEvent internalFrameEvent); void internalFrameClosing(InternalFrameEvent internalFrameEvent); void internalFrameDeactivated(InternalFrameEvent internalFrameEvent); void internalFrameDeiconified(InternalFrameEvent internalFrameEvent); void internalFrameIconified(InternalFrameEvent internalFrameEvent); void internalFrameOpened(InternalFrameEvent internalFrameEvent); CHAPTER 8 ■ ROOT PANE CONTAINERS In addition, like the WindowAdapter class that has all the WindowListener methods stubbed out, there is an InternalFrameAdapter class with all the InternalFrameListener methods stubbed out. If you’re not interested in all the event happenings of a JInternalFrame, you can subclass InternalFrameAdapter and override only those methods you’re interested in. For instance, the listener shown in Listing 8-5 is interested in only the iconification methods. Instead of providing stubs for the other five methods of InternalFrameListener, you would need to subclass only InternalFrameAdapter and override the two relevant methods. Listing 8-5. Custom InternalFrameListener import javax.swing.*; import javax.swing.event.*; public class InternalFrameIconifyListener extends InternalFrameAdapter { public void internalFrameIconified(InternalFrameEvent internalFrameEvent) { JInternalFrame source = (JInternalFrame)internalFrameEvent.getSource(); System.out.println ("Iconified: " + source.getTitle()); } public void internalFrameDeiconified(InternalFrameEvent internalFrameEvent) { JInternalFrame source = (JInternalFrame)internalFrameEvent.getSource(); System.out.println ("Deiconified: " + source.getTitle()); } } The InternalFrameEvent class is a subclass of AWTEvent. To define the values returned by the public int getID() method of AWTEvent, the InternalFrameEvent class defines a constant for each of the specific event subtypes that can be used. In addition, two other constants designate the range of valid values. Table 8-11 lists the nine constants. You can also get the actual JInternalFrame from the event with getInternalFrame(). Table 8-11. InternalFrameEvent Event Subtypes Event Subtype ID Associated Interface Method INTERNAL_FRAME_ACTIVATED internalFrameActivated INTERNAL_FRAME_CLOSED internalFrameClosed INTERNAL_FRAME_CLOSING internalFrameClosing INTERNAL_FRAME_DEACTIVATED internalFrameDeactivated INTERNAL_FRAME_DEICONIFIED internalFrameDeiconified INTERNAL_FRAME_FIRST N/A INTERNAL_FRAME_ICONIFIED internalFrameIconified INTERNAL_FRAME_LAST N/A INTERNAL_FRAME_OPENED internalFrameOpened Customizing a JInternalFrame Look and Feel Because the JInternalFrame is a lightweight component, it has an installable look and feel. Each installable Swing look and feel provides a different JInternalFrame appearance and set of 257 258 CHAPTER 8 ■ ROOT PANE CONTAINERS default UIResource values. Figure 8-5 shows the appearance of the JWindow container for the preinstalled set of look and feel types. -OTIF 7INDOWS /CEAN Figure 8-5. JInternalFrame under different look and feel types CHAPTER 8 ■ ROOT PANE CONTAINERS The available set of UIResource-related properties for a JInternalFrame is shown in Table 8-12. For the JInternalFrame component, there are 60 different properties, including those for the internal frame’s title pane. Table 8-12. JInternalFrame UIResource Elements Property String Object Type InternalFrame.actionMap ActionMap InternalFrame.activeBorderColor Color InternalFrame.activeTitleBackground Color InternalFrame.activeTitleForeground Color InternalFrame.activeTitleGradient List InternalFrame.border Border InternalFrame.borderColor Color InternalFrame.borderDarkShadow Color InternalFrame.borderHighlight Color InternalFrame.borderLight Color InternalFrame.borderShadow Color InternalFrame.borderWidth Integer InternalFrame.closeButtonToolTip String InternalFrame.closeIcon Icon InternalFrame.closeSound String InternalFrame.icon Icon InternalFrame.iconButtonToolTip String InternalFrame.iconifyIcon Icon InternalFrame.inactiveBorderColor Color InternalFrame.inactiveTitleBackground Color InternalFrame.inactiveTitleForeground Color InternalFrame.inactiveTitleGradient List InternalFrame.layoutTitlePaneAtOrigin Boolean InternalFrame.maxButtonToolTip String InternalFrame.maximizeIcon Icon InternalFrame.maximizeSound String InternalFrame.minimizeIcon Icon InternalFrame.minimizeIconBackground Color InternalFrame.minimizeSound String 259 260 CHAPTER 8 ■ ROOT PANE CONTAINERS Table 8-12. JInternalFrame UIResource Elements (Continued) Property String Object Type InternalFrame.optionDialogBorder Border InternalFrame.paletteBorder Border InternalFrame.paletteCloseIcon Icon InternalFrame.paletteTitleHeight Integer InternalFrame.resizeIconHighlight Color InternalFrame.resizeIconShadow Color InternalFrame.restoreButtonToolTip String InternalFrame.restoreDownSound String InternalFrame.restoreUpSound String InternalFrame.titleButtonHeight Integer InternalFrame.titleButtonWidth Integer InternalFrame.titleFont Font InternalFrame.titlePaneHeight Integer InternalFrame.useTaskBar Boolean InternalFrame.windowBindings Object[ ] InternalFrameTitlePane.closeButtonAccessibleName String InternalFrameTitlePane.closeButtonText String InternalFrameTitlePane.closeIcon Icon InternalFrameTitlePane.iconifyButtonAccessibleName String InternalFrameTitlePane.iconifyIcon Icon InternalFrameTitlePane.maximizeButtonAccessibleName String InternalFrameTitlePane.maximizeButtonText String InternalFrameTitlePane.maximizeIcon Icon InternalFrameTitlePane.minimizeButtonText String InternalFrameTitlePane.minimizeIcon Icon InternalFrameTitlePane.moveButtonText String InternalFrameTitlePane.restoreButtonText String InternalFrameTitlePane.sizeButtonText String InternalFrameTitlePane.titlePaneLayout LayoutManager InternalFrameTitlePaneUI String InternalFrameUI String CHAPTER 8 ■ ROOT PANE CONTAINERS In addition to the many configurable properties in Table 8-12, with the Metal look and feel, you can designate an internal frame to be a “palette” by using a special client property, JInternalFrame.isPalette. When set to Boolean.TRUE, this internal frame will have a slightly different appearance from the others and a shorter title bar, as shown in Figure 8-6. 0ALETTE Figure 8-6. A JInternalFrame palette with other frames If you also add an internal frame to the PALETTE_LAYER of the desktop, the frame will always appear on top of all the other frames (as noted in Figure 8-6): JInternalFrame palette = new JInternalFrame("Palette", true, false, true, false); palette.setBounds(150, 0, 100, 100); palette.putClientProperty("JInternalFrame.isPalette", Boolean.TRUE); desktop.add(palette, JDesktopPane.PALETTE_LAYER); The complete source for creating the program in Figure 8-6 appears in Listing 8-6 later in this chapter. ■Note If the current look and feel is something other than Metal, the palette layer will still be honored, but its appearance won’t be quite as distinctive. Changing the JDesktopIcon The JInternalFrame relies on an inner class, JDesktopIcon, to provide a UI delegate for the iconified view of the JInternalFrame. The class is merely a specialized JComponent for providing this capability, not a specialized Icon implementation, as the name might imply. In fact, the JDesktopIcon class comments say that the class is temporary, so you shouldn’t try to customize it directly. (Of course, the class has been around for some time now.) If you do want to customize the JDesktopIcon, you can change some of the UIResourcerelated properties. Table 8-13 lists the eight UIResource-related properties for the JDesktopIcon component. 261 262 CHAPTER 8 ■ ROOT PANE CONTAINERS Table 8-13. JInternalFrame.DesktopIcon UIResource Elements Property String Object Type DesktopIcon.background Color DesktopIcon.border Border DesktopIcon.font Font DesktopIcon.foreground Color DesktopIcon.icon Icon DesktopIcon.width Integer DesktopIcon.windowBindings Object[ ] DesktopIconUI String JDesktopPane Class Another class for working with groups of internal frames is the JDesktopPane class. The sole purpose of the desktop pane is to contain a set of internal frames. When internal frames are contained within a desktop pane, they delegate most of their behavior to the desktop manager of the desktop pane. You’ll also learn about the DesktopManager interface in greater detail later in this chapter. Creating a JDesktopPane The JDesktopPane has a single no-argument constructor. Once it’s created, you’d typically place the desktop in the center of a container managed by a BorderLayout. This ensures that the desktop takes up all the room in the container. Adding Internal Frames to a JDesktopPane The JDesktopPane doesn’t implement RootPaneContainer. Instead of adding components to the different panes within a JRootPane, you add them directly to the JDesktopPane: desktop.add(anInternalFrame); JDesktopPane Properties As Table 8-14 shows, there are eight properties of JDesktopPane. The JInternalFrame at index 0 of the allFrames property array is the internal frame in front of the desktop (JInternalFrame f = desktop.getAllFrames()[0]). Besides getting all the frames within the JDesktopPane, you can get only those within a specific layer: public JInternalFrame[] getAllFramesInLayer(int layer). (Remember JLayeredPane, covered earlier in this chapter in the “Working with Component Layers and Positions” section, the parent class of JDesktopPane?) Valid dragMode property settings are the LIVE_DRAG_MODE and OUTLINE_DRAG_MODE constants of the class. CHAPTER 8 ■ ROOT PANE CONTAINERS Table 8-14. JDesktopPane Properties Property Name Data Type Access accessibleContext AccessibleContext Read-only allFrames JInternalFrame[ ] Read-only desktopManager DesktopManager Read-write dragMode int Read-write bound opaque boolean Read-only selectedFrame JInternalFrame Read-write UI DesktopPaneUI Read-write UIClassID String Read-only ■Note There is also a special client property (JDesktopPane.dragMode) for configuring the drawing mode when dragging an internal frame around. The client property has been replaced by the speedier versions available with the standard property. Customizing a JDesktopPane Look and Feel Back in Figure 8-5 you can see JInternalFrame objects within a JDesktopPane. The basic appearance of JDesktopPane is the same in each look and feel. As Table 8-15 shows, there aren’t many UIResource-related properties for a JDesktopPane to configure. Table 8-15. JDesktopPane UIResource Elements Property String Object Type desktop Color Desktop.ancestorInputMap InputMap Desktop.background Color Desktop.windowBindings Object[ ] DesktopPane.actionMap ActionMap DesktopPaneUI String Complete Desktop Example Now that you have the major desktop-related classes under your belt, let’s look at a complete desktop example. The basic process involves creating a group of JInternalFrame objects and putting them in a single JDesktopPane. Event handling can be done for individual components on each of the internal frames, if desired, or for individual frames. In this example, simply use 263 264 CHAPTER 8 ■ ROOT PANE CONTAINERS the InternalFrameIconifyListener class, presented earlier in Listing 8-5, to listen for internal frames being iconified and deiconified. Figure 8-6 shows how the program looks when it first starts. One particular internal frame has been designated a palette, and the outline drag mode is enabled. The complete source for the example is shown in Listing 8-6. Listing 8-6. Mixing JInternalFrames and the JDesktopPane import import import import javax.swing.*; javax.swing.event.*; java.awt.*; java.awt.event.*; public class DesktopSample { public static void main(final String[] args) { Runnable runner = new Runnable() { public void run() { String title = (args.length==0 ? "Desktop Sample" : args[0]); JFrame frame = new JFrame(title); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); JDesktopPane desktop = new JDesktopPane(); JInternalFrame internalFrames[] = { new JInternalFrame("Can Do All", true, true, true, true), new JInternalFrame("Not Resizable", false, true, true, true), new JInternalFrame("Not Closable", true, false, true, true), new JInternalFrame("Not Maximizable", true, true, false, true), new JInternalFrame("Not Iconifiable", true, true, true, false) }; InternalFrameListener internalFrameListener = new InternalFrameIconifyListener(); int pos = 0; for(JInternalFrame internalFrame: internalFrames) { // Add to desktop desktop.add(internalFrame); // Position and size internalFrame.setBounds(pos*25, pos*25, 200, 100); pos++; // Add listener for iconification events internalFrame.addInternalFrameListener(internalFrameListener); JLabel label = new JLabel(internalFrame.getTitle(), JLabel.CENTER); internalFrame.add(label, BorderLayout.CENTER); CHAPTER 8 ■ ROOT PANE CONTAINERS // Make visible internalFrame.setVisible(true); } JInternalFrame palette = new JInternalFrame("Palette", true, false, true, false); palette.setBounds(350, 150, 100, 100); palette.putClientProperty("JInternalFrame.isPalette", Boolean.TRUE); desktop.add(palette, JDesktopPane.PALETTE_LAYER); palette.setVisible(true); desktop.setDragMode(JDesktopPane.OUTLINE_DRAG_MODE); frame.add(desktop, BorderLayout.CENTER); frame.setSize(500, 300); frame.setVisible(true); } }; EventQueue.invokeLater(runner); } } DesktopManager Interface One remaining piece of the puzzle for working on a desktop is the desktop manager, which is an implementation of the DesktopManager interface, shown here: public interface DesktopManager { public void activateFrame(JInternalFrame frame); public void beginDraggingFrame(JComponent frame); public void beginResizingFrame(JComponent frame, int direction); public void closeFrame(JInternalFrame frame); public void deactivateFrame(JInternalFrame frame); public void deiconifyFrame(JInternalFrame frame); public void dragFrame(JComponent frame, int newX, int newY); public void endDraggingFrame(JComponent frame); public void endResizingFrame(JComponent frame); public void iconifyFrame(JInternalFrame frame); public void maximizeFrame(JInternalFrame frame); public void minimizeFrame(JInternalFrame frame); public void openFrame(JInternalFrame frame); public void resizeFrame(JComponent frame, int newX, int newY, int newWidth, int newHeight); public void setBoundsForFrame(JComponent frame, int newX, int newY, int newWidth, int newHeight); } 265 266 CHAPTER 8 ■ ROOT PANE CONTAINERS ■Note For the DesktopManager methods that accept a JComponent argument, the arguments are usually a JInternalFrame or another lightweight Swing component. When JInternalFrame objects are in a JDesktopPane, they shouldn’t attempt operations such as iconifying or maximizing themselves. Instead, they should ask the desktop manager of the desktop pane in which they’re installed to perform the operation: getDesktopPane().getDesktopManager().iconifyFrame(anInternalFrame); The DefaultDesktopManager class provides one such implementation of a DesktopManager. If the default isn’t sufficient, a look and feel might provide its own DesktopManager implementation class, as the Windows look and feel does with the WindowsDesktopManager. You can also define your own manager, but this usually isn’t necessary. Summary In this chapter, you explored the JRootPane class and how implementers of the RootPaneContainer interface rely on a JRootPane for internal component management. You also learned how in Swing you work with the JRootPane of a JFrame, JDialog, JWindow, JApplet, or JInternalFrame class. The root pane can then layer components with the help of a JLayeredPane in such a way that tooltip text and pop-up menus will always appear above their associated components. The JInternalFrame can also reside within a desktop environment, in which a JDesktopPane and DesktopManager manage how and where the internal frames act and appear. You can also respond to internal frame events by associating InternalFrameListener implementations with a JInternalFrame. In Chapter 9, you’ll examine the specialized pop-up components within the Swing libraries: JColorChooser, JFileChooser, JOptionPane, and ProgressMonitor. CHAPTER 9 ■■■ Pop-Ups and Choosers I n Chapter 8, you looked at the top-level containers such as JFrame and JApplet. In addition, you explored the JDialog class used to create pop-up windows to display messages or get user input. Although the JDialog class works perfectly well, the Swing component set also offers several simpler approaches to get user input from pop-up windows, which you will explore in this chapter. The JOptionPane class is useful for displaying messages, obtaining textual user input, or getting the answer to a question. The ProgressMonitor and ProgressMonitorInputStream classes enable you to monitor the progress of lengthy tasks. In addition, the JColorChooser and JFileChooser classes come equipped with feature-filled pop-up windows for getting a color choice from a user or getting a file or directory name. By using these additional classes, your user interface development tasks can be accomplished much more quickly and easily. JOptionPane Class JOptionPane is a special class for creating a panel to be placed in a pop-up window. The purpose of the panel is to display a message to a user and get a response from that user. To accomplish its task, the panel presents content in four areas (see Figure 9-1): • Icon: The icon area is for the display of an Icon to indicate the type of message being displayed to the user. It’s the responsibility of the installed look and feel to provide default icons for certain types of messages, but you can provide your own if you need to display another icon type. • Message: The primary purpose of this area is to display a text message. In addition, the area can contain any optional set of objects to make the message more informational. • Input: The input area allows a user to provide a response to a message. The response can be free form, in a text field, or from a pick list in a combo box or list control. For yes or no type questions, the button area should be used instead. • Button: The button area is also for getting user input. Selection of a button in this area signals the end of the usage of the JOptionPane. Default sets of button labels are available, or you can display any number of buttons, including none, with any labels you desire. 267 268 CHAPTER 9 ■ POP-UPS AND CHOOSERS Figure 9-1. JOptionPane parts All the areas are optional (although having a panel without at least a message and a button makes the option pane virtually useless). Besides being a panel with four sections within a pop-up window, the JOptionPane is capable of automatically placing itself in a pop-up window and managing the acquisition of the user’s response. It can place itself in either a JDialog or a JInternalFrame, depending on the type of GUI you’re providing to the user. With the help of an Icon and set of JButton components, the JOptionPane can easily be configured to show a variety of messages and input dialogs. ■Note Because the JOptionPane can automatically place itself in a JDialog, you might never need to create a JDialog directly. Creating a JOptionPane You can either manually create a JOptionPane through one of its 7 constructors or go through one of the 25 factory methods discussed later in the chapter, in the “Automatically Creating a JOptionPane in a Pop-Up Window” section. You have the most control when manually creating the JOptionPane. However, you then must place it in a pop-up window, show the window, and finally manage getting the response. Because of the ease of use provided by the methods that do everything automatically, you might think you would only use the factory methods when working with JOptionPane. However, throughout this chapter, you’ll discover several other reasons why you might want to do things manually. In addition, when you use a visual-programming environment, the environment treats the JOptionPane as a JavaBean component and will ignore the factory methods. For the seven constructors, you can have different permutations of six different arguments. The arguments allow you to configure something in one of the four different areas shown in Figure 9-1. The six arguments are the message, the message type, an option type, an icon, an array of options, and an initial option setting. The use of these arguments is shared with the factory methods. Let’s first look at the seven constructors, and then explore the different arguments. Notice that the constructor arguments are cascading and only add additional arguments to the previous constructor. CHAPTER 9 ■ POP-UPS AND CHOOSERS public JOptionPane() JOptionPane optionPane = new JOptionPane(); public JOptionPane(Object message) JOptionPane optionPane = new JOptionPane("Printing complete"); public JOptionPane(Object message, int messageType) JOptionPane optionPane = new JOptionPane("Printer out of paper", JOptionPane.WARNING_MESSAGE); public JOptionPane(Object message, int messageType, int optionType) JOptionPane optionPane = new JOptionPane("Continue printing?", JOptionPane.QUESTION_MESSAGE, JOptionPane.YES_NO_OPTION); public JOptionPane(Object message, int messageType, int optionType, Icon icon) Icon printerIcon = new ImageIcon("printer.jpg"); JOptionPane optionPane = new JOptionPane("Continue printing?", JOptionPane.QUESTION_MESSAGE, JOptionPane.YES_NO_OPTION, printerIcon); public JOptionPane(Object message, int messageType, int optionType, Icon icon, Object options[ ]) Icon greenIcon = new DiamondIcon(Color.GREEN); Icon redIcon = new DiamondIcon(Color.RED); Object optionArray[] = new Object[] { greenIcon, redIcon} ; JOptionPane optionPane = new JOptionPane("Continue printing?", JOptionPane.QUESTION_MESSAGE, JOptionPane.YES_NO_OPTION, printerIcon, optionArray); public JOptionPane(Object message, int messageType, int optionType, Icon icon, Object options[], Object initialValue) JOptionPane optionPane = new JOptionPane("Continue printing?", JOptionPane.QUESTION_MESSAGE, JOptionPane.YES_NO_OPTION, printerIcon, optionArray, redIcon); The JOptionPane Message Argument The message argument is an Object, not a String. While you normally pass only a quoted string as this argument, with an Object argument, you can basically display anything you want in the message area. In the “Understanding the Message Property,” section later in this chapter, you’ll look at the more advanced uses of this argument. Briefly, though, there are four basic rules to interpret the meaning of an Object-typed message argument. For elements within the Object, recursively follow these rules: 269 270 CHAPTER 9 ■ POP-UPS AND CHOOSERS • If the message is an array of objects (Object[ ]), make the JOptionPane place each entry onto a separate row. • If the message is a Component, place the component in the message area. • If the message is an Icon, place the Icon within a JLabel and display the label in the message area. • If the message is an Object, convert it to a String with toString(), place the String in a JLabel, and display the label in the message area. The JOptionPane Message Type and Icon Arguments The messageType constructor argument is used to represent the type of message being displayed within the JOptionPane. If you don’t provide a custom icon for the JOptionPane, the installed look and feel will use the messageType argument setting to determine which icon to display within the icon area. Five different message types are available as JOptionPane constants: • ERROR_MESSAGE for displaying an error message • INFORMATION_MESSAGE for displaying an informational message • QUESTION_MESSAGE for displaying a query message • WARNING_MESSAGE for displaying a warning message • PLAIN_MESSAGE for displaying any other type of message If you’re using a constructor with both messageType and icon arguments and want the JOptionPane to use the default icon for the messageType, just specify null as the value for the icon argument. If the icon argument is non-null, the specified icon will be used, no matter what the message type is. If the messageType constructor argument isn’t specified, the default message type is PLAIN_MESSAGE. The JOptionPane Option Type Argument The optionType constructor argument is used to determine the configuration for the set of buttons in the button area. If one of the options argument described next is provided, then the optionType argument is ignored and configuration for the set of buttons is acquired from the options argument. Four different option types are available as JOptionPane constants: • DEFAULT_OPTION for a single OK button • OK_CANCEL_OPTION for OK and Cancel buttons • YES_NO_CANCEL_OPTION for Yes, No, and Cancel buttons • YES_NO_OPTION for Yes and No buttons If the optionType constructor argument isn’t specified, the default option type is DEFAULT_OPTION. CHAPTER 9 ■ POP-UPS AND CHOOSERS The JOptionPane Options and Initial Value Arguments The options argument is an Object array used to construct a set of JButton objects for the button area of the JOptionPane. If this argument is null (or a constructor without this argument is used), the button labels will be determined by the optionType argument. Otherwise, the array works similarly to the message argument, but without supporting recursive arrays: • If an options array element is a Component, place the component in the button area. • If an options array element is an Icon, place the Icon within a JButton and place the button in the button area. • If an options array element is an Object, convert it to a String with toString(), place the String in a JButton, and place the button in the button area. Normally, the options argument will be an array of String objects. You may want to have an Icon on the JButton, although the resulting button won’t have a label. If you want to have both an icon and a text label on the button, you can manually create a JButton and place it in the array. Alternatively, you can directly include any other Component within the array. There’s one minor problem with these latter two approaches, however. It’s your responsibility to handle responding to component selection and tell the JOptionPane when the user selects this component. The “Adding Components to the Button Area” section later in this chapter shows how to properly handle this behavior. When the options argument is non-null, the initialValue argument specifies which of the buttons will be the default button when the pane is initially displayed. If it’s null, the first component in the button area will be the default button. In either case, the first button will have the input focus, unless there is an input component in the message area, in which case, the input component will have the initial input focus. ■Tip To have no buttons on the option pane, pass an empty array as the options setting: new Object[] { }. Displaying a JOptionPane After you’ve created the JOptionPane with one of the constructors, what you have is a panel filled with components. In other words, the obtained JOptionPane is not yet in a pop-up window. You need to create a JDialog, a JInternalFrame, or another pop-up window, and then place the JOptionPane within that. In addition, if you pick this manual style of JOptionPane construction, you need to handle the closing of the pop-up window. You must listen for selection of a component in the button area, and then hide the pop-up window after selection. Because there is so much to do here, the JOptionPane includes two helper methods to place a JOptionPane within either a modal JDialog or a JInternalFrame and take care of all the previously described behavior: 271 272 CHAPTER 9 ■ POP-UPS AND CHOOSERS public JDialog createDialog(Component parentComponent, String title) public JInternalFrame createInternalFrame(Component parentComponent, String title) ■Note When using the createDialog() and createInternalFrame() methods to create a pop-up window, selection of an automatically created button results in the closing of the created pop-up. You would then need to ask the JOptionPane which option the user selected with getValue() and, if appropriate, get the input value with getInputValue(). The first argument to the methods is a component over which the pop-up window will be centered. The second argument is the title for the pop-up window. Once you create the pop-up window, whether it’s a JDialog or JInternalFrame, you show it. The pop-up is then closed after one of the components in the button area is selected, at which point, your program continues. The following lines of source code show the creation of one such JOptionPane shown within a JDialog. The resulting pop-up window is shown in Figure 9-2. JOptionPane optionPane = new JOptionPane("Continue printing?", JOptionPane.QUESTION_MESSAGE, JOptionPane.YES_NO_OPTION); JDialog dialog = optionPane.createDialog(source, "Manual Creation"); dialog.setVisible(true); Figure 9-2. Sample JOptionPane in a JDialog After you create the JOptionPane, place it in a pop-up window, and show it, and the user has responded, you need to find out what the user selected. The selection is provided via the public Object getValue() method of JOptionPane. The value returned by getValue() is determined by whether an options array was provided to the JOptionPane constructor. If you provide the array, the argument selected will be returned. If you don’t provide the array, an Integer object is returned, and its value represents the position of the button selected within the button area. In another case, getValue() could return null if nothing was selected, such as when the JDialog is closed by selecting the appropriate window decoration from the title bar of the pop-up window. CHAPTER 9 ■ POP-UPS AND CHOOSERS To make this multifaceted response easier to grasp, Listing 9-1 shows an OptionPaneUtils class that defines the method public static int getSelection(JOptionPane optionPane). Given an option pane, this method returns the position of the selected value as an int, whether or not an options array was provided. To indicate that nothing was selected, JOptionPane.CLOSED_OPTION (-1) is returned. Listing 9-1. JOptionPane Utility Class import javax.swing.*; public final class OptionPaneUtils { private OptionPaneUtils() { } public static int getSelection(JOptionPane optionPane) { // Default return value, signals nothing selected int returnValue = JOptionPane.CLOSED_OPTION; // Get selected value Object selectedValue = optionPane.getValue(); // If none, then nothing selected if (selectedValue != null) { Object options[] = optionPane.getOptions(); if (options == null) { // Default buttons, no array specified if(selectedValue instanceof Integer) { returnValue = ((Integer)selectedValue).intValue(); } } else { // Array of option buttons specified for (int i=0, n = options.length; i < n; i++) { if(options[i].equals(selectedValue)) { returnValue = i; break; // out of for loop } } } } return returnValue; } } 273 274 CHAPTER 9 ■ POP-UPS AND CHOOSERS With the help of this new OptionPaneUtils.getSelection(JOptionPane) helper method, you can now find out the option pane selection with one line of code, and then act accordingly based on the response. int selection = OptionPaneUtils.getSelection(optionPane); switch (selection) { case ...: ... break; case ...: ... break; default: ... } If you create a JOptionPane with a null options array, you can use the constants within the JOptionPane class to indicate the position of the default button labels and their return values from the OptionPaneUtils.getSelection(JOptionPane) method. These constants are listed in Table 9-1. Using these constants enables you to avoid hard-coding constants such as 0, 1, 2, or –1. Table 9-1. JOptionPane Option Position Constants Position Description CANCEL_OPTION Used when the Cancel button is pressed CLOSED_OPTION Used when the pop-up window closed without the user pressing a button NO_OPTION Used when the No button is pressed OK_OPTION Used when the OK button is pressed YES_OPTION Used when the Yes button is pressed Automatically Creating a JOptionPane in a Pop-Up Window You can manually create a JOptionPane, place it in a JDialog or JInternalFrame (or any other container), and fetch the response. Alternatively, you could use the JOptionPane factory methods for creating JOptionPane components directly within either a JDialog or a JInternalFrame. Using the many factory methods, you can create the option pane, place it in a pop-up window, and get the response with a single line of source code. There are 25 methods, which are first broken down into two sets: those that create the JOptionPane and show it within a JDialog and those that show the pane within a JInternalFrame. Methods that show the JOptionPane within a JInternalFrame are named showInternalXXXDialog(), and methods that create the pane within a JDialog are named showXXXDialog(). The second grouping of factory methods for JOptionPane is what fills in the XXX part of the method names. This represents the various message types of option panes that you can create and display. In addition, the message type defines what is returned after the user selects something in the option pane. The four different message types are as follows: CHAPTER 9 ■ POP-UPS AND CHOOSERS • Message: With a message pop-up, there’s no return value. Therefore, the method is defined void show[Internal]MessageDialog(...). • Input: With an input pop-up, the return value is either what the user typed in a text field (a String) or what the user picked from a list of options (an Object). Therefore, the show[Internal]InputDialog(...) methods return either a String or Object, depending on which version you use. • Confirm: With the confirm pop-up, the return value signifies which, if any, button the user picked within the option pane. After a button is picked, the pop-up window is dismissed, and the returned value is one of the integer constants shown in Table 9-1. Therefore, the method here is defined as int show[Internal]ConfirmDialog(...). • Option: With the option pop-up, the return value is an int, the same type as the confirm pop-up, so the methods are defined int show[Internal]OptionDialog(...). If the button labels are manually specified with a non-null argument, the integer represents the selected button position. The information in Table 9-2 should help you understand the 25 methods and their arguments. The method names (and return types) are found on the left side of the table, and their argument lists (and data types) are on the right. The numbers that repeat across the columns for each method name indicate a specific set of arguments for that method. For instance, the showInputDialog row shows a 3 in the Parent Component column, Message column, Title column, and Message Type column. Therefore, the showInputDialog method has one version defined like this: public static String showInputDialog(Component parentComponent, Object message, String title, int messageType) ■Note With the exception of two of the showInputDialog() methods, the parent component argument is required for all method varieties. The message argument is the only one required for all without exception. What good is a pop-up dialog without a message? With the way the different showXXXDialog() methods are defined, you don’t need to bother with discovering the selected button yourself, or even the user input. The return value for the various methods is one of the following: nothing (void return type), an int from Table 9-1, a String, or an Object, depending on the type of dialog box shown. ■Caution There is a significant difference between the JOptionPane constructors and the factory methods: The option type and message type arguments are reversed. 275 276 CHAPTER 9 ■ POP-UPS AND CHOOSERS Table 9-2. JOptionPane Static create and show Methods Method Name/Return Type Parent Component Component Message Object Title String Option Type int Message Type int showMessageDialog Return type: void[123] 123 123 23 23 showInternalMessageDialog Return type: void[123] 123 123 23 23 showConfirmDialog Return type: int[1234] 1234 1234 234 234 34 showInternalConfirmDialog Return type: int 1234 1234 234 234 34 showInputDialog Return type: String[12356]/Object[4] 2345 123456 34 34 showInternalInputDialog Return type: String[12]/Object[3] 123 123 23 23 showOptionDialog Return type: int[1] 1 1 1 1 1 showInternalOptionDialog Return type: int[1] 1 1 1 1 1 JOptionPane Arguments for Factory Methods Almost all the arguments for the factory methods match the JOptionPane constructor arguments. Two lists in the “Creating a JOptionPane” section earlier in this chapter describe the acceptable values for the message type and option type arguments. In addition, the usage of the message, options, and initial value arguments are also described. The parent component and title argument are passed along to one of the createDialog() or createInternalFrame() methods, depending on the type of pop-up in which the JOptionPane is embedded. You next need to consider the selection values argument and the initial selection value argument of the showInputDialog() method. With an input dialog box, you can ask the user for text input and allow the user to type in anything, or you can present the user with a list of predefined choices. The selection values argument to showInputDialog() determines how you provide that set of choices. The initial selection value represents the specific option to be chosen when the JOptionPane first appears. The look and feel will determine the appropriate Swing component to be used based on the number of choices presented. For small lists, a JComboBox is used. For larger lists, starting at 20 with the Motif, Metal/Ocean, and Windows look and feel types, a JList is used. CHAPTER 9 ■ POP-UPS AND CHOOSERS Table 9-2. JOptionPane Static create and show Methods (Continued) Icon Icon Options Object[ ] Initial Value Object Selection Values Object[ ] Initial Selection Object 4 456 3 3 4 4 4 3 3 3 1 1 1 1 1 1 ■Note When the parent component argument is null, a hidden frame is used and the pop-up is centered on the screen. See the getSharedOwnerFrame() method of SwingUtilities for more details on the hidden frame. There are other focus-related usability issues that you might run into when specifying null as a parent component, if the hidden frame and dialog box are swapped to the background. Message Pop-Ups The showMessageDialog() and showInternalMessageDialog() methods create an INFORMATION_MESSAGE pop-up with the pop-up title “Message,” unless different argument settings are specified for the message type and window title. Because the sole purpose of the message dialog box is to display a message, these dialog boxes provide only an OK button and return no value. Figure 9-3 shows sample message pop-ups created from the following lines of source: JOptionPane.showMessageDialog(parent, "Printing complete"); JOptionPane.showInternalMessageDialog(desktop, "Printing complete"); 277 278 CHAPTER 9 ■ POP-UPS AND CHOOSERS Figure 9-3. Sample JOptionPane message pop-ups Confirm Pop-Ups The showConfirmDialog() and showInternalConfirmDialog() methods, by default, create a pop-up with a QUESTION_MESSAGE type and the pop-up title “Select an Option.” Because confirm dialog boxes ask a question, their default option type is YES_NO_CANCEL_OPTION, giving them Yes, No, and Cancel buttons. The return value from a call to any of these methods is one of the JOptionPane class constants YES_OPTION, NO_OPTION, or CANCEL_OPTION. No prizes for guessing which constant maps to which option pane button! Figure 9-4 shows sample confirm pop-ups created from the following lines of source: JOptionPane.showConfirmDialog(parent, "Continue printing?"); JOptionPane.showInternalConfirmDialog(desktop, "Continue printing?"); Figure 9-4. Sample JOptionPane confirm pop-ups Input Pop-Ups By default, the showInputDialog() and showInternalInputDialog() methods create a QUESTION_MESSAGE pop-up with an “Input” pop-up title. The option type for input dialogs is OK_CANCEL_OPTION, giving them an OK and a Cancel button, and the option type isn’t changeable. The return data type for these methods is either a String or an Object. If you don’t specify selection values, the pop-up prompts the user with a text field and returns the input as a String. If you do specify selection values, you get back an Object from the selection values array. Figure 9-5 shows some input pop-ups created from the following lines of source: JOptionPane.showInputDialog(parent, "Enter printer name:"); // Moons of Neptune String smallList[] = { "Naiad", "Thalassa", "Despina", "Galatea", "Larissa", "Proteus", "Triton", "Nereid"} ; CHAPTER 9 ■ POP-UPS AND CHOOSERS JOptionPane.showInternalInputDialog(desktop, "Pick a printer", "Input", JOptionPane.QUESTION_MESSAGE, null, smallList, "Triton"); // Twenty of the moons of Saturn String bigList[] = {"Pan", "Atlas", "Prometheus", "Pandora", "Epimetheus", "Janus", "Mimas", "Enceladus", "Telesto", "Tethys", "Calypso", "Dione", "Helene", "Rhea", "Titan", "Hyperion", "Iapetus", "Phoebe", "Skadi", "Mundilfari"}; JOptionPane.showInputDialog(parent, "Pick a printer", "Input", JOptionPane.QUESTION_MESSAGE, null, bigList, "Titan"); Figure 9-5. Sample JOptionPane input pop-ups ■Note It is the responsibility of the look and feel to determine the type of input component. A look and feel can use something other than a JTextField, JComboBox, or JList. It’s just that all the system-provided look and feel types (from Sun) use these three components. 279 280 CHAPTER 9 ■ POP-UPS AND CHOOSERS Option Pop-Ups The showOptionDialog() and showInternalOptionDialog() methods provide the most flexibility because they allow you to configure all the arguments. There are no default arguments, and the return value is an int. If an options argument is not specified, the return value will be one of the constants listed in Table 9-1. Otherwise, the value returned represents the component position of the selected option from the options argument. Figure 9-6 shows a couple of input pop-ups created from the following lines of source, in which icons (instead of text) are provided on the buttons: Icon greenIcon = new DiamondIcon(Color.GREEN); Icon redIcon = new DiamondIcon(Color.RED); Object iconArray[] = { greenIcon, redIcon} ; JOptionPane.showOptionDialog(source, "Continue printing?", "Select an Option", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, null, iconArray, iconArray[1]); Icon blueIcon = new DiamondIcon(Color.BLUE); Object stringArray[] = { "Do It", "No Way"} ; JOptionPane.showInternalOptionDialog(desktop, "Continue printing?", "Select an Option", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, blueIcon, stringArray, stringArray[0]); Figure 9-6. Sample JOptionPane option pop-ups ■Caution When using a factory method to show a JOptionPane within a JDialog, the dialog box is automatically modal, preventing another window from getting the input focus. When showing the JOptionPane within a JInternalFrame, the internal frame might be modal, but other windows might not be. Therefore, a user could do something within one of the other windows of the application, including an action on the JDesktopPane. JOptionPane Properties Table 9-3 shows the 15 properties of JOptionPane. These properties are accessible only if you don’t use one of the factory methods of JOptionPane. For most of the arguments, their meaning maps directly to one of the constructor arguments. CHAPTER 9 ■ POP-UPS AND CHOOSERS Table 9-3. JOptionPane Properties Property Name Data Type Access accessibleContext AccessibleContext Read-only icon Icon Read-write bound initialSelectionValue Object Read-write bound initialValue Object Read-write bound inputValue Object Read-write bound maxCharactersPerLineCount int Read-only message Object Read-write bound messageType int Read-write bound options Object[] Read-write bound optionType int Read-write bound selectionValues Object[] Read-write bound UI OptionPaneUI Read-write bound UIClassID String Read-only value Object Read-write bound wantsInput boolean Read-write bound The wantsInput property is automatically set to true for the input dialog boxes or when the selectionValues property is non-null. The inputValue property is the item picked from an input dialog box. The value property indicates the option selected from the button area. Displaying Multiline Messages The maxCharactersPerLineCount property is set to an extremely large value, Integer.MAX_VALUE, by default. For some strange reason, the Swing developers chose not to provide a setter method for this property. If you want to change the setting, you must subclass JOptionPane and override the public int getMaxCharactersPerLineCount() method. This causes a long text message to be broken up into multiple lines within an option pane. In addition, you cannot use any of the factory methods because they don’t know about your subclass. To help you create narrow JOptionPane components, you can add the source shown in Listing 9-2 to the OptionPaneUtils class definition shown earlier in Listing 9-1. The new method provides a way of specifying the desired option pane character width. 281 282 CHAPTER 9 ■ POP-UPS AND CHOOSERS Listing 9-2. Helper Method to Create a Narrow JOptionPane public static JOptionPane getNarrowOptionPane(int maxCharactersPerLineCount) { // Our inner class definition class NarrowOptionPane extends JOptionPane { int maxCharactersPerLineCount; NarrowOptionPane(int maxCharactersPerLineCount) { this.maxCharactersPerLineCount = maxCharactersPerLineCount; } public int getMaxCharactersPerLineCount() { return maxCharactersPerLineCount; } } return new NarrowOptionPane(maxCharactersPerLineCount); } Once the method and new class are defined, you can create an option pane of a specified character width, manually configure all the properties, place it in a pop-up window, show it, and then determine the user’s response. The following source demonstrates using these new capabilities, with the long message trimmed a bit. String msg = "this is a really long message ... this is a really long message"; JOptionPane optionPane = OptionPaneUtils.getNarrowOptionPane(72); optionPane.setMessage(msg); optionPane.setMessageType(JOptionPane.INFORMATION_MESSAGE); JDialog dialog = optionPane.createDialog(source, "Width 72"); dialog.setVisible(true); Figure 9-7 demonstrates what would happen if you didn’t change the maxCharactersPerLineCount property. Figure 9-7 also shows the new narrow JOptionPane. Figure 9-7. Default JOptionPane and a narrow JOptionPane Although this seems like a lot of work, it’s the best way to create multiline option panes, unless you want to manually parse the message into separate lines. CHAPTER 9 ■ POP-UPS AND CHOOSERS ■Note Including the characters \ n in the message text will force the message to be displayed on multiple lines. Then it’s your responsibility to count the number of characters in each message line. The message text in a JOptionPane can be formatted with HTML tags, as it can in other Swing components. Understanding the Message Property In all the previous examples in this chapter of using the message argument to the JOptionPane constructors and using the factory methods, the message was a single string. As described earlier in the “The JOptionPane Message Argument” section, this argument doesn’t need to be a single string. For instance, if the argument were an array of strings, each string would be on a separate line. This eliminates the need to use the narrow JOptionPane, but requires you to count the characters yourself. However, because you’re splitting apart the message, you can use one of the 25 factory methods. For instance, the following source creates the pop-up window shown in Figure 9-8. String multiLineMsg[] = { "Hello,", "World"} ; JOptionPane.showMessageDialog(source, multiLineMsg); Figure 9-8. Using JOptionPane with a string array ■Caution If you manually count the characters within a long message to split it into a multiline message, the output may not be the best. For instance, when using a proportional font in which character widths vary, a line of 20 w characters would be much wider than a line of 20 i or l characters. The message argument not only supports displaying an array of strings, but it also can support an array of any type of object. If an element in the array is a Component, it’s placed directly into the message area. If the element is an Icon, the icon is placed within a JLabel, and the JLabel is placed into the message area. All other objects are converted to a String, placed into a JLabel, and displayed in the message area, unless the object is itself an array; in that case, these rules are applied recursively. To demonstrate the possibilities, Figure 9-9 shows off the true capabilities of the JOptionPane. The actual content isn’t meant to show anything in particular—just that you can display a lot of different stuff. The message argument is made up of the following array: Object complexMsg[] = { "Above Message", new DiamondIcon(Color.RED), new JButton("Hello"), new JSlider(), new DiamondIcon(Color.BLUE), "Below Message"} ; 283 284 CHAPTER 9 ■ POP-UPS AND CHOOSERS Figure 9-9. Using JOptionPane with a complex message property Adding Components to the Message Area If you were to display the pop-up in Figure 9-9, you would notice a slight problem. The option pane doesn’t know about the embedded JSlider setting, unlike the way it automatically knows about input to the automatic JTextField, JComboBox, or JList components. If you want the JOptionPane (or for that matter, any input component) to get the JSlider value, you need to have your input component change the inputValue property of the JOptionPane. When this value is changed, the option pane tells the pop-up window to close because the JOptionPane has acquired its input value. Attaching a ChangeListener to the JSlider component enables you to find out when its value has changed. Adding yet another method to the OptionPaneUtils class shown earlier in Listing 9-1 allows you to reuse this specialized JSlider with multiple JOptionPane objects more easily. The important method call is shown in boldface in Listing 9-3. A similar line would need to be added for any input component that you wanted to place within a JOptionPane. The line notifies the option pane when the user has changed the value of the input component. Listing 9-3. Helper Method for Creating a JSlider for Use in a JOptionPane public static JSlider getSlider(final JOptionPane optionPane) { JSlider slider = new JSlider(); slider.setMajorTickSpacing (10); slider.setPaintTicks(true); slider.setPaintLabels(true); ChangeListener changeListener = new ChangeListener() { public void stateChanged(ChangeEvent changeEvent) { JSlider theSlider = (JSlider)changeEvent.getSource(); if (!theSlider.getValueIsAdjusting()) { optionPane.setInputValue(new Integer(theSlider.getValue())); } } }; slider.addChangeListener(changeListener); return slider; } Now that the specialized JSlider is created, you need to place it on a JOptionPane. This requires the manual creation of a JOptionPane component and, surprisingly, doesn’t require CHAPTER 9 ■ POP-UPS AND CHOOSERS the setting of the wantsInput property. The wantsInput property is set to true only when you want the JOptionPane to provide its own input component. Because you’re providing one, this isn’t necessary. The resulting pop-up window is shown in Figure 9-10. (The JSlider component will be more fully described in Chapter 12.) JOptionPane optionPane = new JOptionPane(); JSlider slider = OptionPaneUtils.getSlider(optionPane); optionPane.setMessage(new Object[] { "Select a value: " , slider} ); optionPane.setMessageType(JOptionPane.QUESTION_MESSAGE); optionPane.setOptionType(JOptionPane.OK_CANCEL_OPTION); JDialog dialog = optionPane.createDialog(source, "My Slider"); dialog.setVisible(true); System.out.println ("Input: " + optionPane.getInputValue()); Figure 9-10. Using JOptionPane with a JSlider ■Note If the user doesn’t move the slider, JOptionPane.getInputValue() correctly returns JOptionPane.UNINITIALIZED_VALUE. Adding Components to the Button Area In “The JOptionPane Options and Initial Value Arguments” section earlier in this chapter, you saw that if you have a Component in the array of options for the JOptionPane, you must configure the component yourself to handle selection. The same holds true for any components you add via the options property. When a component is configured to handle selection, the pop-up window that a JOptionPane is embedded in will disappear when the component is selected. The default set of buttons works this way. When installing your own components, you must notify the option pane when one of the components has been selected by setting the value property of the option pane. To demonstrate this mechanism, create a JButton with both an icon and a text label that can be placed in an option pane. Without defining this component yourself, the option pane supports only the display of a label or an icon on the button. When the button is selected, the button tells the option pane it was selected by setting the option pane’s value property to the current text label of the button. Adding yet another method to OptionPaneUtils shown earlier in Listing 9-1 allows you to create such a button. The boldfaced line in the source shown in Listing 9-4 is the important method call to add to any other such component that you want to combine with the component array for the options property of a JOptionPane. The line would be called after selection of such a component. 285 286 CHAPTER 9 ■ POP-UPS AND CHOOSERS Listing 9-4. A JButton for Use on a JOptionPane public static JButton getButton( final JOptionPane optionPane, String text, Icon icon) { final JButton button = new JButton (text, icon); ActionListener actionListener = new ActionListener() { public void actionPerformed(ActionEvent actionEvent) { // Return current text label, instead of argument to method optionPane.setValue(button.getText()); } }; button.addActionListener(actionListener); return button; } After the specialized JButton is created, you need to place it in a JOptionPane. Unfortunately, this, too, requires the long form of the JOptionPane usage. The resulting pop-up window is shown in Figure 9-11. JOptionPane optionPane = new JOptionPane(); optionPane.setMessage("I got an icon and a text label"); optionPane.setMessageType(JOptionPane.INFORMATION_MESSAGE); Icon icon = new DiamondIcon (Color.BLUE); JButton jButton = OptionPaneUtils.getButton(optionPane, "OK", icon); optionPane.setOptions(new Object[] {jButton} ); JDialog dialog = optionPane.createDialog(source, "Icon/Text Button"); dialog.setVisible(true); Figure 9-11. Using JOptionPane with a JButton containing a text label and an icon ■Tip Setting the value of the JOptionPane with setValue() will hide the option pane when a user selects the button. If you want to prevent users from closing the window without selecting a button, you can set the default close operation of the dialog containing the JOptionPane to JDialog.DO_NOTHING_ON_CLOSE. Then users won’t be able to select the close icon from the window adornments. Well, they can select it; it just won’t do anything. Listening for Property Changes The JOptionPane class defines the following 11 constants to assist with listening for bound property changes: CHAPTER 9 ■ POP-UPS AND CHOOSERS • ICON_PROPERTY • INITIAL_SELECTION_VALUE_PROPERTY • INITIAL_VALUE_PROPERTY • INPUT_VALUE_PROPERTY • MESSAGE_PROPERTY • MESSAGE_TYPE_PROPERTY • OPTION_TYPE_PROPERTY • OPTIONS_PROPERTY • SELECTION_VALUES_PROPERTY • VALUE_PROPERTY • WANTS_INPUT_PROPERTY If you don’t use the factory methods of JOptionPane, you can instead use a PropertyChangeListener to listen for changes to the bound properties. This would allow you to passively listen for changes to bound properties, instead of actively getting them after the change. Customizing a JOptionPane Look and Feel Each installable Swing look and feel provides a different JOptionPane appearance and set of default UIResource values. Figure 9-12 shows the appearance of the JOptionPane container for the preinstalled set of look and feel types: Motif, Windows, and Ocean. Figure 9-12. JOptionPane under different look and feel types 287 288 CHAPTER 9 ■ POP-UPS AND CHOOSERS The message type of the JOptionPane helps determine the default icon to display in the icon area of the option pane. For plain messages, there are no icons. The remaining four default icons—for informational, question, warning, and error messages—are shown in Table 9-4 for the different look and feel types. Table 9-4. JOptionPane Icons for the Different Look and Feel Types Look and Feel Informational Question Warning Error Motif Windows Metal Ocean The available set of UIResource-related properties for a JOptionPane is shown in Table 9-5. For the JOptionPane component, there are 56 different properties. Table 9-5. JOptionPane UIResource Elements Property String Object Type OptionPane.actionMap ActionMap OptionPane.background Color OptionPane.border Border OptionPane.buttonAreaBorder Border OptionPane.buttonClickThreshhold Integer OptionPane.buttonFont Font OptionPane.buttonOrientation Integer OptionPane.buttonPadding Integer OptionPane.cancelButtonMnemonic String OptionPane.cancelButtonText String OptionPane.cancelIcon Icon OptionPane.errorDialog.border.background Color OptionPane.errorDialog.titlePane.background Color OptionPane.errorDialog.titlePane.foreground Color OptionPane.errorDialog.titlePane.shadow Color CHAPTER 9 ■ POP-UPS AND CHOOSERS Table 9-5. JOptionPane UIResource Elements (Continued) Property String Object Type OptionPane.errorIcon Icon OptionPane.errorSound String OptionPane.font Font OptionPane.foreground Color OptionPane.informationIcon Icon OptionPane.informationSound String OptionPane.inputDialogTitle String OptionPane.isYesLast Boolean OptionPane.messageAnchor Integer OptionPane.messageAreaBorder Border OptionPane.messageFont Font OptionPane.messageForeground Color OptionPane.messageDialogTitle String OptionPane.minimumSize Dimension OptionPane.noButtonMnemonic String OptionPane.noButtonText String OptionPane.noIcon Icon OptionPane.okButtonMnemonic String OptionPane.okButtonText String OptionPane.okIcon Icon OptionPane.questionDialog.border.background Color OptionPane.questionDialog.titlePane.background Color OptionPane.questionDialog.titlePane.foreground Color OptionPane.questionDialog.titlePane.shadow Color OptionPane.questionIcon Icon OptionPane.questionSound String OptionPane.sameSizeButtons Boolean OptionPane.separatorPadding Integer OptionPane.setButtonMargin Boolean OptionPane.titleText String OptionPane.warningDialog.border.background Color OptionPane.warningDialog.titlePane.background Color 289 290 CHAPTER 9 ■ POP-UPS AND CHOOSERS Table 9-5. JOptionPane UIResource Elements (Continued) Property String Object Type OptionPane.warningDialog.titlePane.foreground Color OptionPane.warningDialog.titlePane.shadow Color OptionPane.warningIcon Icon OptionPane.warningSound String OptionPane.windowBindings Object[ ] OptionPane.yesButtonMnemonic String OptionPane.yesButtonText String OptionPane.yesIcon Icon OptionPaneUI String One good use of the resources in Table 9-5 is for customizing default button labels to match the locale or language of the user. For instance, to change the four labels for the Cancel, No, OK, and Yes buttons into French, add the following code to your program. (You may be able to get the translated text from a java.util.ResourceBundle.) // Set JOptionPane button labels to French UIManager.put("OptionPane.cancelButtonText", "Annuler"); UIManager.put("OptionPane.noButtonText", "Non"); UIManager.put("OptionPane.okButtonText", "D'accord"); UIManager.put("OptionPane.yesButtonText", "Oui"); Now when you display the option pane, the buttons will have localized button labels. Of course, this would require translating the messages for the option pane, too. Figure 9-13 shows how a pop-up would look for the following line of source that asks if the user is 18 or older. Because the pop-up window title isn’t a property, you must pass the title to every created dialog box. int result = JOptionPane.showConfirmDialog( aFrame, "Est-ce que vous avez 18 ans ou plus?", "Choisisez une option", JOptionPane.YES_NO_CANCEL_OPTION); Figure 9-13. A JOptionPane in French CHAPTER 9 ■ POP-UPS AND CHOOSERS The JOptionPane component supports localized JOptionPane button labels. Out of the box, the JOptionPane displays Chinese or Japanese button labels for the standard Yes, No, Cancel, and OK buttons for the appropriate locale. For instance, the left side of Figure 9-14 shows buttons with Japanese labels for Yes, No, and Cancel, and the right side of Figure 9-14 shows buttons with Japanese labels for OK and Cancel. Obviously, you would need to change the message in the option pane, but the buttons are set for you (assuming you have the fonts to support it). Figure 9-14. A JOptionPane with Japanese-language buttons Thankfully, the 5.0 release of the JDK includes translations for the standard JOptionPane (as well as the JFileChooser and JColorChooser) labels. These are available for German (de), Spanish (es), French (fr), Italian (it), Japanese (ja), Korean (ko), English, Swedish (sv), and Chinese (Simplified/zh_CN and Traditional/zh_TW). ■Tip To start the Java runtime with a different language, just set the user.language property, as in java -Duser.language=FR ClassName. Then, whenever you create a JOptionPane, you would get the French labels for Yes, No, OK, and Cancel. The button labels would be like those shown in Figure 9-14, but without you needing to manually do the UIManager.put() calls. (Instead of D'accord, Sun chose to leave OK as OK.) ProgressMonitor Class The ProgressMonitor class is used to report on the status of a time-consuming task. The class is a special Swing class that’s not a GUI component, an option pane, or a JavaBean component. Instead, you tell the ProgressMonitor when each part of the task is done. If the task is taking an extended length of time to complete, the ProgressMonitor displays a pop-up window like the one shown in Figure 9-15. Figure 9-15. ProgressMonitor sample 291 292 CHAPTER 9 ■ POP-UPS AND CHOOSERS After the ProgressMonitor displays the pop-up window, the user can do one of two things. The user can watch the ProgressMonitor display to see how much of the task has been completed; when the task is done, the ProgressMonitor’s display automatically disappears. Or, if the user selects the Cancel button, this tells the ProgressMonitor that the task needs to be canceled. To detect the cancellation, the task needs to check the ProgressMonitor periodically to see if the user canceled the task’s operation. Otherwise, the task will continue. The pop-up window that the ProgressMonitor class displays is a JOptionPane with a maxCharactersPerLineCount property setting of 60, allowing the option pane to automatically word wrap any displayed messages. The option pane is embedded within a nonmodal JDialog whose title is “Progress . . .”. Because the JDialog isn’t modal, a user can still interact with the main program. The JOptionPane for a ProgressMonitor will always get an informational icon within its icon area. In addition, the message area of the option pane consists of three objects: • At the top of the message area is a fixed message that stays the same throughout the life of the JOptionPane. The message can be a text string or an array of objects just like the message property of JOptionPane. • In the middle of the message area is a note or variable message that can change as the task progresses. • At the bottom of the message area is a progress bar (JProgressBar component) that fills as an increasing percentage of the task is completed. The button area of the option pane shows a Cancel button. Creating a ProgressMonitor When you create a ProgressMonitor, there are five arguments to the single constructor: public ProgressMonitor(Component parentComponent, Object message, String note, int minimum, int maximum) The first argument represents the parent component for the JOptionPane for when the ProgressMonitor needs to appear. The parent component is the component over which the pop-up window appears, and acts like the parentComponent argument for the createDialog() method of JOptionPane. You then provide the static and variable message parts for the message area of the JOptionPane. Either of these message parts could be null, although null means that this part of the message area will never appear. Lastly, you provide minimum and maximum values as the range for the progress bar. The difference between these two values represents the expected number of operations to be performed, such as the number of files to load or the size of a file to read. Normally, the minimum setting is zero, but that isn’t required. The number of completed operations determines how far the progress bar moves. Initially, the pop-up window isn’t displayed. By default, the progress monitor checks every half second (500 milliseconds) to see if the task at hand will complete in two seconds. If the task has shown some progress and it still won’t complete in two seconds, then the pop-up window appears. The time to completion is configurable by changing the millisToDecideToPopup and millisToPopup properties of the ProgressMonitor. The following line of source demonstrates the creation of a ProgressMonitor with 200 steps in the operation. A reference to the ProgressMonitor would need to be saved so that it can be notified as the task progresses. CHAPTER 9 ■ POP-UPS AND CHOOSERS ProgressMonitor monitor = new ProgressMonitor( parent, "Loading Progress", "Getting Started...", 0, 200); Using a ProgressMonitor Once you’ve created the ProgressMonitor, you need to begin the task whose progress is being monitored. As the task completes one or many steps, the ProgressMonitor needs to be notified of the task’s progress. Notification is done with a call to the public void setProgress(int newValue) method, where the argument represents the progress completed thus far and the newValue needs to be in the minimum...maximum range initially specified. This progress value needs to be maintained outside the ProgressMonitor, because you can’t ask the monitor how much progress has been made (no public int getProgress() method of ProgressMonitor exists). If the progress value were maintained in a variable named progress, the following two lines would update the progress value and notify the ProgressMonitor. progress += 5; monitor.setProgress(progress); ■Note It’s possible that multiple calls to setProgress() may not advance the progress bar in the option pane. The changes to the progress setting must be enough to make the progress bar advance at least one pixel in length. For instance, if the minimum and maximum settings were zero and 2 billion, increasing the progress setting 1,000 times by 5 would have no visible effect on the progress bar, because the fractional amount would be negligible. The progress setting could represent the number of files loaded thus far, or the number of bytes read in from a file. In addition to updating the count, you should update the note to reflect the progress. If the difference between the minimum and maximum arguments used in the ProgressMonitor constructor were 100, then the current progress could be viewed as a percentage of the task. Otherwise, the progress property merely represents the progress completed so far. monitor.setNote("Loaded " + progress + " files"); It’s the responsibility of the executing task to check whether the user pressed the Cancel button in the ProgressMonitor dialog box. If the task is canceled, the ProgressMonitor automatically closes the dialog box, but the task must actively check for the change by adding a simple check at the appropriate place or places in the source: if (monitor.isCanceled()) { // Task canceled - cleanup ... } else { // Continue doing task ... } 293 294 CHAPTER 9 ■ POP-UPS AND CHOOSERS Most tasks requiring a ProgressMonitor will be implemented using separate threads to avoid blocking the responsiveness of the main program. Listing 9-5 shows a program that creates a ProgressMonitor and allows you to either manually or automatically increase its progress property (see the following section for a description of this property). These tasks are handled by on-screen buttons (see Figure 9-16). Selecting the Start button creates the ProgressMonitor. Selecting the Manual Increase button causes the progress to increase by 5. Selecting the Automatic Increase button causes the progress to increase by 3 every 250 milliseconds (1/4 second). Pressing the Cancel button in the pop-up window during the automatic increase demonstrates what should happen when the operation is canceled; the timer stops sending updates. Figure 9-16. Main ProgressMonitor sample frame ■Note The pop-up window won’t appear until some progress is shown. The ProgressMonitorHandler inner class at the start of Listing 9-5 is necessary to ensure that the ProgressMonitor is accessed only from the event thread. Otherwise, the access wouldn’t be thread-safe in some random thread. Listing 9-5. Sample ProgressMonitor Usage import javax.swing.*; import java.awt.*; import java.awt.event.*; public class SampleProgress { static ProgressMonitor monitor; static int progress; static Timer timer; static class ProgressMonitorHandler implements ActionListener { // Called by Timer public void actionPerformed(ActionEvent actionEvent) { if (monitor == null) return; CHAPTER 9 ■ POP-UPS AND CHOOSERS if (monitor.isCanceled()) { System.out.println("Monitor canceled"); timer.stop(); } else { progress += 3; monitor.setProgress(progress); monitor.setNote("Loaded " + progress + " files"); } } } public static void main(String args[]) { Runnable runner = new Runnable() { public void run() { JFrame frame = new JFrame("ProgressMonitor Sample"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setLayout(new GridLayout (0, 1)); // Define Start Button JButton startButton = new JButton ("Start"); ActionListener startActionListener = new ActionListener() { public void actionPerformed(ActionEvent actionEvent) { Component parent = (Component)actionEvent.getSource(); monitor = new ProgressMonitor(parent, "Loading Progress", "Getting Started...", 0, 200); progress = 0; } }; startButton.addActionListener(startActionListener); frame.add(startButton); // Define Manual Increase Button // Pressing this button increases progress by 5 JButton increaseButton = new JButton ("Manual Increase"); ActionListener increaseActionListener = new ActionListener() { public void actionPerformed(ActionEvent actionEvent) { if (monitor == null) return; if (monitor.isCanceled()) { System.out.println("Monitor canceled"); } else { progress += 5; monitor.setProgress(progress); monitor.setNote("Loaded " + progress + " files"); } } }; 295 296 CHAPTER 9 ■ POP-UPS AND CHOOSERS increaseButton.addActionListener(increaseActionListener); frame.add(increaseButton); // Define Automatic Increase Button // Start Timer to increase progress by 3 every 250 ms JButton autoIncreaseButton = new JButton ("Automatic Increase"); ActionListener autoIncreaseActionListener = new ActionListener() { public void actionPerformed(ActionEvent actionEvent) { if (monitor != null) { if (timer == null) { timer = new Timer(250, new ProgressMonitorHandler()); } timer.start(); } } }; autoIncreaseButton.addActionListener(autoIncreaseActionListener); frame.add(autoIncreaseButton); frame.setSize(300, 200); frame.setVisible(true); } }; EventQueue.invokeLater(runner); } } ProgressMonitor Properties Table 9-6 shows the eight properties of ProgressMonitor. Table 9-6. ProgressMonitor Properties Property Name Data Type Access accessibleContext AccessibleContext Read-only canceled boolean Read-only maximum int Read-write millisToDecideToPopup int Read-write millisToPopup int Read-write minimum int Read-write note String Read-write progress int Write-only CHAPTER 9 ■ POP-UPS AND CHOOSERS The millisToDecideToPopup property represents the number of milliseconds that the monitor waits before deciding if it needs to display the pop-up window. If the progress property hasn’t changed yet, the monitor waits for another increment of this time period before checking again. When the ProgressMonitor checks and the progress property has changed, it estimates whether the task will be completed in the number of milliseconds in the millisToPopup property. If the ProgressMonitor thinks the monitored task will complete on time, the pop-up window is never displayed. Otherwise, the pop-up will display after millisToPopup milliseconds have passed from the time the task started. ■Caution Although technically possible, it isn’t a good practice to move the minimum and maximum properties after the pop-up has appeared. This could result in the progress bar increasing and decreasing in an erratic manner. The same behavior happens if you move the progress setting in a nonlinear fashion. Customizing a ProgressMonitor Look and Feel Changing the look and feel of ProgressMonitor requires changing the appearance of both the JProgressBar and the JLabel, as well as the JOptionPane the ProgressMonitor uses. The ProgressMonitor has one UIResource-related property: • ProgressMonitor.progressText of type String ProgressMonitorInputStream Class The ProgressMonitorInputStream class represents an input stream filter that uses a ProgressMonitor to check the progress of the reading of an input stream. If the reading is taking too long to complete, a ProgressMonitor appears, and the user can select the Cancel button in the pop-up window, causing the reading to be interrupted and the input stream to throw an InterruptedIOException. Creating a ProgressMonitorInputStream Like other filtering streams, the ProgressMonitorInputStream is created with a reference to the stream it needs to filter. Besides a reference to this filter, the single constructor for ProgressMonitorInputStream requires two arguments for its ProgressMonitor: a parent component and a message. As seen here, the constructor takes the ProgressMonitor arguments first: public ProgressMonitorInputStream( Component parentComponent, Object message, InputStream inputStream) As with the JOptionPane and ProgressMonitor, the message argument is an Object, not a String, so you can display an array of components or strings on multiple lines. The following code creates one ProgressMonitorInputStream. 297 298 CHAPTER 9 ■ POP-UPS AND CHOOSERS FileInputStream fis = new FileInputStream(filename); ProgressMonitorInputStream pmis = new ProgressMonitorInputStream(parent, "Reading " + filename, fis); ■Note The minimum...maximum range for the ProgressMonitorInputStream ProgressMonitor is [0...size of stream]. Using a ProgressMonitorInputStream As with all input streams, once you’ve created a ProgressMonitorInputStream, you need to read from it. If the input stream isn’t read quickly enough, the underlying ProgressMonitor causes the progress pop-up window to appear. Once that window appears, a user can monitor the progress or cancel the reading by selecting the Cancel button. If the Cancel button is selected, an InterruptedIOException is thrown, and the bytesTransferred field of the exception is set to the number of bytes successfully read. Figure 9-17 shows what one ProgressMonitorInputStream pop-up might look like. For a little variety, the pop-up uses two JLabel components in the message, instead of just one. Figure 9-17. ProgressMonitorInputStream pop-up Listing 9-6 shows a complete source example. The boldfaced lines are the keys to using the ProgressMonitorInputStream. They set up the dialog box’s message and create the input stream. The program uses a file name specified from the command line, reads the file, and copies the file to standard output (the console). If the file is large enough, the progress monitor will appear. If you press the Cancel button, the reading stops and Canceled is printed to standard error. Listing 9-6. ProgressMonitorInputStream Demonstration import java.io.*; import java.awt.*; import javax.swing.*; public class ProgressInputSample { public static final int NORMAL = 0; public static final int BAD_FILE = 1; public static final int CANCELED = NORMAL; public static final int PROBLEM = 2; CHAPTER 9 ■ POP-UPS AND CHOOSERS public static void main(String args[]) { int returnValue = NORMAL; if (args.length != 1) { System.err.println("Usage:"); System.err.println("java ProgressInputSample filename"); } else { try { FileInputStream fis = new FileInputStream(args[0]); JLabel filenameLabel = new JLabel(args[0], JLabel.RIGHT); Object message[] = { "Reading:", filenameLabel} ; ProgressMonitorInputStream pmis = new ProgressMonitorInputStream(null, message, fis); InputStreamReader isr = new InputStreamReader(pmis); BufferedReader br = new BufferedReader(isr); String line; while ((line = br.readLine()) != null) { System.out.println(line); } br.close(); } catch (FileNotFoundException exception) { System.err.println("Bad File " + exception); returnValue = BAD_FILE; } catch (InterruptedIOException exception) { System.err.println("Canceled"); returnValue = CANCELED; } catch (IOException exception) { System.err.println("I/O Exception " + exception); returnValue = PROBLEM; } } // AWT Thread created - must exit System.exit(returnValue); } } ■Note Having a null argument for the parent component to the ProgressMonitorInputStream constructor causes the pop-up window to appear centered on the screen. ProgressMonitorInputStream Properties Table 9-7 shows the single property of ProgressMonitorInputStream. The ProgressMonitor is created when the input stream is created. You shouldn’t need to modify the ProgressMonitor. 299 300 CHAPTER 9 ■ POP-UPS AND CHOOSERS However, you might want to provide a longer or shorter delay (the millisToDecideToPopup property of ProgressMonitor) before the pop-up window is displayed. Table 9-7. ProgressMonitorInputStream Property Property Name Data Type Access progressMonitor ProgressMonitor Read-only JColorChooser Class You can think of a JColorChooser as an input-only JOptionPane whose input field asks you to choose a color. Like a JOptionPane, the JColorChooser is just a bunch of components in a container, not a ready-to-use pop-up window. Figure 9-18 shows how a JColorChooser might appear in your own application window. At the top are three selectable color chooser panels; at the bottom is a preview panel. The “I Love Swing” bit is not part of the chooser, but of the application that contains the chooser. Figure 9-18. JColorChooser sample In addition to appearing within your application windows, the JColorChooser class also provides support methods for automatically placing the group of components in a JDialog. Figure 9-19 shows one such automatically created pop-up. CHAPTER 9 ■ POP-UPS AND CHOOSERS Figure 9-19. JColorChooser pop-up sample In support of this behavior, the JColorChooser class requires the help of several support classes found in the javax.swing.colorchooser package. The data model for the JColorChooser is an implementation of the ColorSelectionModel interface. The javax.swing.colorchooser package provides the DefaultColorSelectionModel class as an implementation of the ColorSelectionModel interface. For the user interface, the JColorChooser class relies on the ColorChooserComponentFactory to create the default panels from which to choose a color. These panels are specific subclasses of the AbstractColorChooserPanel class, and if you don’t like the default set, you can create your own. By default, when multiple chooser panels are in a JColorChooser, each panel is shown on a tab of a JTabbedPane. However, the ColorChooserUI can deal with multiple panels in any way it desires. Creating a JColorChooser If you want to create a JColorChooser and place it in your own window, you use one of the following three constructors for the JColorChooser class: public JColorChooser() JColorChooser colorChooser = new JColorChooser(); public JColorChooser(Color initialColor) JColorChooser colorChooser = new JColorChooser(aComponent.getBackground()); public JColorChooser(ColorSelectionModel model) JColorChooser colorChooser = new JColorChooser(aColorSelectionModel); By default, the initial color for the chooser is white. If you don’t want white as the default, you can provide the initial color as a Color object or ColorSelectionModel. 301 302 CHAPTER 9 ■ POP-UPS AND CHOOSERS Using JColorChooser Once you’ve created a JColorChooser from a constructor, you can place it in any Container, just like any other Component. For instance, the source shown in Listing 9-7 created the GUI shown earlier in Figure 9-18. Listing 9-7. Using a JColorChooser in Your JFrame import import import import java.awt.*; javax.swing.*; javax.swing.event.*; javax.swing.colorchooser.*; public class ColorSample { public static void main(String args[]) { Runnable runner = new Runnable() { public void run() { JFrame frame = new JFrame("JColorChooser Popup"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); final JLabel label = new JLabel("I Love Swing", JLabel.CENTER); label.setFont(new Font("Serif", Font.BOLD | Font.ITALIC, 48)); frame.add(label, BorderLayout.SOUTH); final JColorChooser colorChooser = new JColorChooser(label.getBackground()); colorChooser.setBorder( BorderFactory.createTitledBorder("Pick Foreground Color")); // More source to come frame.add(colorChooser, BorderLayout.CENTER); frame.pack(); frame.setVisible(true); } }; EventQueue.invokeLater(runner); } } Although this source code creates the GUI, selecting a different color within the JColorChooser doesn’t do anything yet. Let’s now look at the code that causes color changes. CHAPTER 9 ■ POP-UPS AND CHOOSERS Listening for Color Selection Changes The JColorChooser uses a ColorSelectionModel as its data model. As the following interface definition shows, the data model includes a single property, selectedColor, for managing the state of the color chooser. public interface ColorSelectionModel { // Listeners public void addChangeListener(ChangeListener listener); public void removeChangeListener(ChangeListener listener); // Properties public Color getSelectedColor(); public void setSelectedColor(Color newValue); } When a user changes the color within the JColorChooser, the selectedColor property changes, and the JColorChooser generates a ChangeEvent to notify any registered ChangeListener objects. Therefore, to complete the earlier ColorSample example in the previous section, and have the foreground color of the label change when the user changes the color selection within the JColorChooser, you need to register a ChangeListener with the color chooser. This involves creating a ChangeListener and adding it to the ColorSelectionModel. Placing the source code shown in Listing 9-8 where the //More source to come comment appears in the Listing 9-7 is necessary for this example to work properly. Listing 9-8. Activating the JColorChooser Example ColorSelectionModel model = colorChooser.getSelectionModel(); ChangeListener changeListener = new ChangeListener() { public void stateChanged(ChangeEvent changeEvent) { Color newForegroundColor = colorChooser.getColor(); label.setForeground(newForegroundColor); } }; model.addChangeListener(changeListener); Once this source is added, the example is complete. Running the program brings up Figure 9-18, and selecting a new color alters the foreground of the label. Creating and Showing a JColorChooser Pop-Up Window Although the previous example is sufficient if you want to include a JColorChooser within your own window, more often than not, you want the JColorChooser to appear in a separate pop-up window. This window might appear as the result of selecting a button on the screen, or possibly even selecting a menu item. To support this behavior, the JColorChooser includes the following factory method: public static Color showDialog(Component parentComponent, String title, Color initialColor) 303 304 CHAPTER 9 ■ POP-UPS AND CHOOSERS When called, showDialog() creates a modal dialog box with the given parent component and title. Within the dialog box is a JColorChooser whose initial color is the one provided. As you can see in Figure 9-18 (shown earlier in the chapter), along the bottom are three buttons: OK, Cancel, and Reset. When OK is pressed, the pop-up window disappears and the showDialog() method returns the currently selected color. When Cancel is pressed, null is returned instead of the selected color or the initial color. Selection of the Reset button causes the JColorChooser to change its selected color to the initial color provided at startup. What normally happens with the showDialog() method is that the initial color argument is some color property of an object. The returned value of the method call then becomes the new setting for the same color property. This usage pattern is shown in the following lines of code, where the changing color property is the background for a button. As with JOptionPane, the null parent-component argument causes the pop-up window to be centered on the screen instead of over any particular component. Color initialBackground = button.getBackground(); Color background = JColorChooser.showDialog( null, "Change Button Background", initialBackground); if (background != null) { button.setBackground(background); } To place this code in the context of a complete example, Listing 9-9 shows source code that offers a button that, when selected, displays a JColorChooser. The color selected within the chooser becomes the background color of the button after the OK button is selected. Listing 9-9. Using showDialog with the JColorChooser import java.awt.*; import java.awt.event.*; import javax.swing.*; public class ColorSamplePopup { public static void main(String args[]) { Runnable runner = new Runnable() { public void run() { JFrame frame = new JFrame("JColorChooser Sample Popup"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); final JButton button = new JButton("Pick to Change Background"); ActionListener actionListener = new ActionListener() { public void actionPerformed(ActionEvent actionEvent) { Color initialBackground = button.getBackground(); Color background = JColorChooser.showDialog( null, "Change Button Background", initialBackground); CHAPTER 9 ■ POP-UPS AND CHOOSERS if (background != null) { button.setBackground(background); } } }; button.addActionListener(actionListener); frame.add(button, BorderLayout.CENTER); frame.setSize(300, 100); frame.setVisible(true); } }; EventQueue.invokeLater(runner); } } Providing Your Own OK/Cancel Event Listeners If the showDialog() method provides too much automatic behavior, you may prefer another JColorChooser method that allows you to customize the chooser before displaying it and define what happens when the OK and Cancel buttons are selected: public static JDialog createDialog(Component parentComponent, String title, boolean modal, JColorChooser chooserPane, ActionListener okListener, ActionListener cancelListener) In createDialog(), the parent component and title arguments are the same as showDialog(). The modal argument allows the pop-up window to be nonmodal, unlike showDialog() in which the pop-up is always modal. When the pop-up is not modal, the user can still interact with the rest of the application. The OK and Cancel buttons in the pop-up window automatically have one associated ActionListener that hides the pop-up window after selection. It’s your responsibility to add your own listeners if you need any additional response from selection. To demonstrate proper usage of createDialog(), the program shown in Listing 9-10 duplicates the functionality of the program shown in Listing 9-9. However, instead of automatically accepting the new color, the color change is rejected if the new background is the same color as the foreground. In addition, if the user selects the Cancel button, the button background color is set to red. Listing 9-10. Custom Action Listeners on JColorChooser Buttons import import import import import java.awt.*; java.awt.event.*; javax.swing.*; javax.swing.event.*; javax.swing.colorchooser.*; public class CreateColorSamplePopup { 305 306 CHAPTER 9 ■ POP-UPS AND CHOOSERS public static void main(String args[]) { Runnable runner = new Runnable() { public void run() { JFrame frame = new JFrame("JColorChooser Create Popup Sample"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); final JButton button = new JButton("Pick to Change Background"); ActionListener actionListener = new ActionListener() { public void actionPerformed(ActionEvent actionEvent) { Color initialBackground = button.getBackground(); final JColorChooser colorChooser = new JColorChooser(initialBackground); // For okay selection, change button background to selected color ActionListener okActionListener = new ActionListener() { public void actionPerformed(ActionEvent actionEvent) { Color newColor = colorChooser.getColor(); if (newColor.equals(button.getForeground())) { System.out.println("Color change rejected"); } else { button.setBackground(colorChooser.getColor()); } } }; // For cancel selection, change button background to red ActionListener cancelActionListener = new ActionListener() { public void actionPerformed(ActionEvent actionEvent) { button.setBackground(Color.RED); } }; final JDialog dialog = JColorChooser.createDialog(null, "Change Button Background", true, colorChooser, okActionListener, cancelActionListener); // Wait for current event dispatching to complete before showing Runnable showDialog = new Runnable() { public void run() { dialog.setVisible(true); } }; CHAPTER 9 ■ POP-UPS AND CHOOSERS EventQueue.invokeLater(showDialog); } }; button.addActionListener(actionListener); frame.add(button, BorderLayout.CENTER); frame.setSize(300, 100); frame.setVisible(true); } }; EventQueue.invokeLater(runner); } } ■Note Notice that the actionPerformed() method that shows the color chooser uses the EventQueue.invokeLater() method to show the chooser. The current event handler needs to finish before showing the chooser. Otherwise, the previous action event processing won’t complete before the chooser is shown. JColorChooser Properties Table 9-8 lists information on the eight properties of the JColorChooser, including the three data types of the single property color. Table 9-8. JColorChooser Properties Property Name Data Type Access accessibleContext AccessibleContext Read-only chooserPanels AbstractColorChooserPanel[] Read-write bound color Color Read-write color int rgb Write-only color int red, int green, int blue Write-only dragEnabled boolean Read-write previewPanel JComponent Read-write bound selectionModel ColorSelectionModel Read-write bound UI ColorChooserUI Read-write bound UIClassID String Read-only 307 308 CHAPTER 9 ■ POP-UPS AND CHOOSERS The color property is special in that it has three ways of setting itself: • Directly from a Color • From one integer representing its red-green-blue values combined into one int variable using the nibble allocation 0xAARRGGBB, where A is for alpha value (and is ignored, using 255 instead) • From three integers, separating the red, green, and blue color components into three separate int variables If you don’t use showDialog(), you can customize the JColorChooser before displaying it. Besides customizing the color property, which is settable in the JColorChooser constructor, you can customize the component to be displayed in the preview area and the color chooser panels. Changing the Preview Panel It’s the responsibility of the ColorChooserComponentFactory class to provide the default component for the preview area of the JColorChooser. For the standard look and feel types, the preview panel is in the bottom portion of the color chooser. If you don’t want a preview panel in the color chooser, you must change the previewPanel property to a component value that isn’t null. When the property is set to null, the default preview panel for the look and feel is shown. Setting the property to an empty JPanel serves the purpose of not showing the preview panel. colorChooser.setPreviewPanel(new JPanel()); Figure 9-20 shows what one such color chooser might look like without the preview panel. Because the JPanel has no size when nothing is in it, this effectively removes the panel. Figure 9-20. JColorChooser without a preview panel If you want the preview panel present, but just don’t like the default appearance, you can add your own JComponent to the area. Configuration entails placing your new preview panel in CHAPTER 9 ■ POP-UPS AND CHOOSERS a title-bordered container, and having the foreground of the preview panel change when the user selects a new color. ■Caution A bug in the ColorChooserUI implementation class (BasicColorChooserUI) requires an extra step to properly install the preview panel. Besides calling setPreviewPanel(newPanel), you must set the panel’s size and border to enable the user interface to properly configure the new preview panel. The exact steps seem to vary with which JDK release you are using. See http://bugs.sun.com/ bugdatabase/view_bug.do?bug_id=5029286 for more details. There are some other related bugs (search the Bug Parade for setPreviewPanel). The following source demonstrates the use of a JLabel as the custom preview panel with the necessary work-around. Figure 9-21 demonstrates what the JColorChooser that uses this preview panel would look like. final JLabel previewLabel = new JLabel("I Love Swing", JLabel.CENTER); previewLabel.setFont(new Font("Serif", Font.BOLD | Font.ITALIC, 48)); previewLabel.setSize(previewLabel.getPreferredSize()); previewLabel.setBorder(BorderFactory.createEmptyBorder(0,0,1,0)); colorChooser.setPreviewPanel(previewLabel); Figure 9-21. JColorChooser with custom preview panel ■Note Because the initial setting for the foreground of the preview panel is its background color, the panel will appear to be empty. This is one reason why the default preview panel shows text with contrasting background colors. 309 310 CHAPTER 9 ■ POP-UPS AND CHOOSERS Changing the Color Chooser Panels The various tabs in the upper part of the JColorChooser represent the AbstractColorChooserPanel implementations. Each allows the user to pick a color in a different manner. By default, the ColorChooserComponentFactory provides the JColorChooser with three panels (see Figure 9-22): • The Swatches panel lets a user pick a color from a set of predefined color swatches, as if at a paint store. • The HSB panel allows a user to pick a color using the Hue-Saturation-Brightness color model. • The RGB panel is for picking colors using the Red-Green-Blue color model. Figure 9-22. The default JColorChooser panels CHAPTER 9 ■ POP-UPS AND CHOOSERS If you don’t like the default chooser panels, or you just want to add other color chooser panels that work differently, you can create your own by subclassing the AbstractColorChooserPanel class. To add a new panel to the existing set, call the following method: public void addChooserPanel(AbstractColorChooserPanel panel) If you later decide that you no longer want the new panel, you can remove it with this method: public AbstractColorChooserPanel removeChooserPanel(AbstractColorChooserPanel panel) To replace the existing set of panels, call this method: setChooserPanels(AbstractColorChooserPanel panels[ ]) Creating a new panel entails subclassing AbstractColorChooserPanel and filling in the details of choosing a color for the new panel. The class definition, shown in the following code lines, includes five abstract methods. These five methods are what must be overridden. public abstract class AbstractColorChooserPanel extends JPanel { public AbstractColorChooserPanel(); protected abstract void buildChooser(); protected Color getColorFromModel(); public ColorSelectionModel getColorSelectionModel(); public int getDisplayMnemonicIndex(); public abstract String getDisplayName(); public abstract Icon getLargeDisplayIcon(); public int getMnemonic(); public abstract Icon getSmallDisplayIcon(); public void installChooserPanel(JColorChooser); public void paint(Graphics); public void uninstallChooserPanel(JColorChooser); public abstract void updateChooser(); } To demonstrate how to work with color chooser panels, let’s look at how to create a new one that displays a list of colors from the Color and SystemColor class. From this list, the user must pick one. The panel will use a JComboBox to represent the list of colors. (The details of using a JComboBox are explained in Chapter 13.) Figure 9-23 shows the finished panel. The panel is created and added with the following source: SystemColorChooserPanel newChooser = new SystemColorChooserPanel(); AbstractColorChooserPanel chooserPanels[] = {newChooser}; colorChooser.setChooserPanels(chooserPanels); 311 312 CHAPTER 9 ■ POP-UPS AND CHOOSERS Figure 9-23. Replacing all panels with the new SystemColor chooser panel The first method to define is public String getDisplayName(). This method returns a text label to display on the tab when multiple chooser panels are available. If there’s only one chooser panel, this name isn’t shown. public String getDisplayName() { return "SystemColor"; } The return values for the two Icon methods do nothing with the system look and feel types. You can return null from them or return an Icon to check that nothing has been done with them. A custom ColorChooserUI could use the two Icon methods somewhere, possibly for the icon on a chooser panel tab. public Icon getSmallDisplayIcon() { return new DiamondIcon(Color.BLUE); } public Icon getLargeDisplayIcon() { return new DiamondIcon(Color.GREEN); } The protected void buildChooser() method is called by the installChooserPanel() method of AbstractColorChooserPanel when the panel is added to the chooser. You use this method to add the necessary components to the container. In the sample SystemColorChooserPanel chooser, this involves creating the JComboBox and adding it to the panel. Because AbstractColorChooserPanel is a JPanel subclass, you can just add() the combo box. The combo box must be filled with options and an event handler installed for when the user selects the component. The specifics of the event handling are described after the following block of source code. protected void buildChooser() { comboBox = new JComboBox(labels); comboBox.addItemListener(this); add(comboBox); } CHAPTER 9 ■ POP-UPS AND CHOOSERS ■Note In addition, if you choose to override uninstallChooserPanel (JColorChooser enclosingChooser), you need to call super.uninstallChooserPanel (JColorChooser enclosingChooser) last, instead of first. When a user changes the color value in an AbstractColorChooserPanel, the panel must notify the ColorSelectionModel of the change in color. In the SystemColorChooserPanel panel, this equates to the user selecting a new choice in the JComboBox. Therefore, when the combo box value changes, find the Color that equates to the choice and tell the model about the change. public void itemStateChanged(ItemEvent itemEvent) { int state = itemEvent.getStateChange(); if (state == ItemEvent.SELECTED) { int position = findColorLabel(itemEvent.getItem()); // Last position is bad (not selectable) if ((position != NOT_FOUND) && (position != labels.length-1)) { ColorSelectionModel selectionModel = getColorSelectionModel(); selectionModel.setSelectedColor(colors[position]); } } } The final AbstractColorChooserPanel method to implement is public void updateChooser(). It, too, is called by installChooserPanel() at setup time. In addition, it’s also called whenever the ColorSelectionModel of the JColorChooser changes. When updateChooser() is called, the chooser panel should update its display to show that the current color of the model is selected. Not all panels show which color is currently selected, so a call may do nothing. (The systemprovided Swatches panel is one that doesn’t display the current color.) In addition, it’s possible that the current color isn’t displayable on the panel. For instance, on the SystemColorChooserPanel, if the current selection isn’t a SystemColor or Color constant, you can either do nothing or display something to signify a custom color. Therefore, in the updateChooser() implementation, you need to get the current color from the ColorSelectionModel and change the color for the panel. The actual setting is done in a helper method called setColor(Color newValue). public void updateChooser() { Color color = getColorFromModel(); setColor(color); } The setColor(Color newColor) method simply looks up the color in a lookup table using the position returned from findColorPosition(Color newColor). // Change combo box to match color, if possible private void setColor(Color newColor) { int position = findColorPosition(newColor); comboBox.setSelectedIndex(position); } 313 314 CHAPTER 9 ■ POP-UPS AND CHOOSERS The specifics of the findColorLabel(Object label) and findColorPosition(Color newColor) methods are shown in the complete source in Listing 9-11, coming up shortly. If you don’t use the showDialog() means of showing the chooser pop-up window, once the chooser panel has been defined, and you’ve created a chooser panel, it can be placed within a JColorChooser with addChooserPanel(). AbstractColorChooserPanel newChooser = new SystemColorChooserPanel(); colorChooser.addChooserPanel(newChooser); After showing the JColorChooser and picking the appropriate tab, your new chooser will be available for use, as shown in Figure 9-24. Figure 9-24. After adding the new SystemColor chooser panel The complete source for the SystemColorChooserPanel is shown in Listing 9-11. The program should use the ComboBoxModel to store the labels and colors arrays of the example in one data model. However, the complexities of using the MVC capabilities of the JComboBox will be saved for Chapter 13. Feel free to change the example in order to use the appropriate data model for the JComboBox or some of the other Collections API classes available. Listing 9-11. Custom AbstractColorChooserPanel import import import import javax.swing.*; javax.swing.colorchooser.*; java.awt.*; java.awt.event.*; public class SystemColorChooserPanel extends AbstractColorChooserPanel implements ItemListener { CHAPTER 9 ■ POP-UPS AND CHOOSERS private static int NOT_FOUND = -1; JComboBox comboBox; String labels[] = { "BLACK", "BLUE", "CYAN", "DARK_GRAY", "GRAY", "GREEN", "LIGHT_GRAY", "MAGENTA", "ORANGE", "PINK", "RED", "WHITE", "YELLOW", "activeCaption", "activeCaptionBorder", "activeCaptionText", "control", "controlDkShadow", "controlHighlight", "controlLtHighlight", "controlShadow", "controlText", "desktop", "inactiveCaption", "inactiveCaptionBorder", "inactiveCaptionText", "info", "infoText", "menu", "menuText", "scrollbar", "text", "textHighlight", "textHighlightText", "textInactiveText", "textText", "window", "windowBorder", "windowText", ""}; 315 316 CHAPTER 9 ■ POP-UPS AND CHOOSERS Color colors[] = { Color.BLACK, Color.BLUE, Color.CYAN, Color.DARK_GRAY, Color.GRAY, Color.GREEN, Color.LIGHT_GRAY, Color.MAGENTA, Color.ORANGE, Color.PINK, Color.RED, Color.WHITE, Color.YELLOW, SystemColor.activeCaption, SystemColor.activeCaptionBorder, SystemColor.activeCaptionText, SystemColor.control, SystemColor.controlDkShadow, SystemColor.controlHighlight, SystemColor.controlLtHighlight, SystemColor.controlShadow, SystemColor.controlText, SystemColor.desktop, SystemColor.inactiveCaption, SystemColor.inactiveCaptionBorder, SystemColor.inactiveCaptionText, SystemColor.info, SystemColor.infoText, SystemColor.menu, SystemColor.menuText, SystemColor.scrollbar, SystemColor.text, SystemColor.textHighlight, SystemColor.textHighlightText, SystemColor.textInactiveText, SystemColor.textText, SystemColor.window, SystemColor.windowBorder, SystemColor.windowText, null}; // Change combo box to match color, if possible private void setColor(Color newColor) { int position = findColorPosition(newColor); comboBox.setSelectedIndex(position); } CHAPTER 9 ■ POP-UPS AND CHOOSERS // Given a label, find the position of the label in the list private int findColorLabel(Object label) { String stringLabel = label.toString(); int position = NOT_FOUND; for (int i=0,n=labels.length; i