Using Swing Components |
JTextComponent
is the foundation for Swing's text components, and provides these customizable features for all of its descendants:This section uses the application shown below to explore each of these capabilities. The demo application contains a
- A separate model, known as a document, to manage the component's content.
- A separate view, which is in charge of displaying the component on screen. This tutorial does not discuss views.
- A separate controller, known as an editor kit, that can read and write text and implements editing capabilities with action commands.
- Customizable keymaps and key bindings.
- Support for infinite undo/redo.
- Pluggable caret and support for caret change listeners.
JTextPane
-- one ofJTextComponent
's subclasses that supports styled text, embedded icons, and embedded components-- to illustrate the capabilities inherited by all ofJTextComponent
's subclasses. For information specific toJTextPane
refer to How to Use Editor Panes and Text Panes. The upper text component is the customizedJTextPane
. The lower text component is aJTextArea
, which serves as a log that reports all changes made to the contents of the text pane. The status line at the bottom of the window reports either the location of the selection or the position of the caret, depending on whether text is selected.Through this example application, you will learn how to use a text component's capabilities and how to customize them. This section covers these topics, which can be applied to all
Note: The main source file for this application isTextComponentDemo.java
. You also needLimitedStyledDocument.java
.
JTextComponent
subclasses: :
- Concepts: About Documents
- Customizing a Document
- Listening for Changes on a Document
- Concepts: About Editor Kits
- Associating Actions with Menus and Buttons
- Concepts: About Keymaps
- Associating Actions with Keystrokes
- Implementing Undo and Redo
- Listening for Caret and Selection Changes
Concepts: About Documents
Like other Swing components, a text component separates its data (known as the model) from its view of the data. If you are not yet familiar with the model-view split used by Swing components, refer to Separate Data and State Models.A text component's model is known as a document. It contains text, supports editing of text, and notifies listeners of changes to the text. A document is an instance of a class that implements the
Document
interface or itsStyledDocument
subinterface.The
javax.swing.text
package provides this hierarchy of document classes:By default, a text field, a password field, and a text area each use an instance ofAbstractDocument (implements Document) | +-------+-------+ | | PlainDocument DefaultStyledDocument (implements StyledDocument)PlainDocument
as its document.PlainDocument
provides a basic container for text where all the text is displayed in the same font. An editor pane and a text pane each use an instance ofDefaultStyledDocument
for its document.DefaultStyledDocument
provides additional support for styled text.You can customize any Swing text component by setting its document to an instance of any class, including those that you write, that implements the
Document
orStyledDocument
interfaces.Customizing a Document
TheTextComponentDemo
application has a custom document,LimitedStyledDocument
, that limits the number of characters that it can contain.LimitedStyledDocument
is a subclass ofDefaultStyledDocument
, the default document forJTextPane
.Here's the code from the example program that creates a
LimitedStyledDocument
and makes it the document for the text pane:To limit the characters allowed in the document,...where the member variables are declared... JTextPane textPane; static final int MAX_CHARACTERS = 300; ...in the constructor for the frame... //Create the document for the text area LimitedStyledDocument lpd = new LimitedStyledDocument(MAX_CHARACTERS); ... //Create the text pane and configure it textPane = new JTextPane(); textPane.setDocument(lpd); ...LimitedStyledDocument
overrides its superclass'sinsertString
method, which is called each time text is inserted into the document.In addition topublic void insertString(int offs, String str, AttributeSet a) throws BadLocationException { if ((getLength() + str.length()) <= maxCharacters) super.insertString(offs, str, a); else Toolkit.getDefaultToolkit().beep(); }insertString
, custom documents commonly override theremove
method , which is called each time text is removed from the document.One common use of a custom document is to create a keystroke-validated text field (a field whose value is checked each time the field is edited). For two examples of validated text fields, refer to Creating a Validated Text Field.
For more information, see these API tables: Classes and Interfaces that Represent Documents and Useful Methods for Working with Documents.
Listening for Changes on a Document
A document notifies interested listeners of changes to the document. Use a document listener to react when text is inserted or removed from a document, or when the style of some of the text changes.The
TextComponentDemo
program uses a document listener to update the change log whenever a change is made to the text pane. This line of code registers an instance ofMyDocumentListener
as a listener on theLimitedStyledDocument
used in the example:Here's the implementation ofLimitedStyledDocument lpd = new LimitedStyledDocument(MAX_CHARACTERS); lpd.addDocumentListener(new MyDocumentListener());MyDocumentListener
:The listener in our example displays the type of change that occurred and, if affected by the change, the length of the text. For general information about document listeners and document events, see How to Write a Document Listener.protected class MyDocumentListener implements DocumentListener { public void insertUpdate(DocumentEvent e) { update(e); } public void removeUpdate(DocumentEvent e) { update(e); } public void changedUpdate(DocumentEvent e) { //Display the type of edit that occurred changeLog.append(e.getType().toString() + ": from " + e.getOffset() + " to " + (e.getOffset() + e.getLength() - 1) + newline); changeLog.setCaretPosition(changeLog.getDocument().getLength() - 1); } private void update(DocumentEvent e) { //Display the type of edit that occurred and //the resulting text length changeLog.append(e.getType().toString() + ": text length = " + e.getDocument().getLength() + newline); changeLog.setCaretPosition(changeLog.getDocument().getLength() - 1); } }Remember that the document for this text pane limits the number of characters allowed in the document. If you try to add text so that the maximum would be exceeded, the document blocks the change and the listener's
insertUpdate
method is not called. Document listeners are notified of changes only if the change has already occurred.Sometimes, you might be tempted to change the document's text from within a document listener. For example, if you have a text field that should contain only integers and the user enters some other type of data, you might want to change the text to
0
. However, you should never modify the contents of text component from within a document listener. In fact, if you attempt to modify the text in a text component from within a document listener, your program will likely deadlock! Instead, provide a custom document and override theinsert
andremove
methods. Creating a Validated Text Field shows you how.Concepts: About Editor Kits
All Swing text components supports standard editing commands such as cut, copy, paste, and inserting characters. Each editing command is represented and implemented by an action object. This makes it easy for you to associate a command with a GUI component, such as a menu item or button, and build a GUI around a text component.Under the hood, a text component uses an
EditorKit
object to create and manage its actions. Besides managing a set of actions for a text component, an editor kit also knows how to read and write documents of a particular format.The Swing text package provides these editor kits:
Most programmers don't need to write code that interacts directly with editor kits because
DefaultEditorKit
- Reads and writes unstyled text. Provides a basic set of editing commands. All the other editor kits are descendants of this one.
StyledEditorKit
- Reads and writes styled text and provides a minimal set of actions for styled text. This class is a subclass of
DefaultEditorKit
and is the editor kit used byJTextPane
by default.HTMLEditorKit
- Reads, writes, and edits HTML. This is a subclass of
StyledEditorKit
.JTextComponent
provides the API you need to indirectly invoke editor kit capabilities. For example,JTextComponent
providesread
andwrite
methods, which invoke the editor kit'sread
andwrite
methods.JTextComponent
also provides a method,getActions
, which returns all of the actions supported by a component. This method gets the list of actions from the component's editor kit. However, the editor kit classes provide useful inner classes and class variables that are useful when creating a GUI around a text component. Associating Actions with Menu Items shows you how to hook an action up to a menu item and Associating Actions with Keystrokes shows you how to hook an action up to a specific keystroke. Both sections make use of handy classes or variables defined in Swing's standard editor kits.Associating Actions with Menus and Buttons
As mentioned previously, you can call thegetActions
method on any text component to get an array containing all of the actions supported by it. It's often convenient to load the array of actions into aHashtable
so your program can retrieve an action by name. Here's the code fromTextComponentDemo
that gets the actions from the text pane and loads them into aHashtable
:And here's a convenient method for retrieving an action by its name from the hashtable:private void createActionTable(JTextComponent textComponent) { actions = new Hashtable(); Action[] actionsArray = textComponent.getActions(); for (int i = 0; i < actionsArray.length; i++) { Action a = actionsArray[i]; actions.put(a.getValue(Action.NAME), a); } }You can use both methods almost verbatim in your programs. Just changeprivate Action getActionByName(String name) { return (Action)(actions.get(name)); }actions
to the name of your hashtable.Now, let's look at how the Cut menu item is created and associated to the action of removing text from the text component:
This code gets the action by name using the handy method described previously and adds the action to the menu. That's all you need to do. The menu and the action take care of everything else. You'll note that the name of the action comes fromprotected JMenu createEditMenu() { JMenu menu = new JMenu("Edit"); ... menu.add(getActionByName(DefaultEditorKit.cutAction)); ...DefaultEditorKit
. This kit provides actions for basic text editing and is the superclass for all the editor kits provided by Swing. So its capabilities are available to all text components unless overridden by a customization.For performance and efficiency reasons, text components share actions. The
Action
object returned bygetActionByName(DefaultEditorKit.cutAction)
is shared by the (uneditable)JTextArea
at the bottom of the window. This has two important ramifications:Setting up the Style menu is similar. Here's the code that creates the menu and puts the Bold menu item in it:
- Generally speaking, you shouldn't modify
Action
objects you get from editor kits. If you do, the changes affects all text components in your program.Action
objects can operate on other text components in the program, perhaps more than you intended. In this example, even though its uneditable, theJTextArea
shares actions with theJTextPane
. If you don't want to share, consider instantiating theAction
object yourself.DefaultEditorKit
defines a number of usefulAction
subclasses.Theprotected JMenu createStyleMenu() { JMenu menu = new JMenu("Style"); Action action = new StyledEditorKit.BoldAction(); action.putValue(Action.NAME, "Bold"); menu.add(action); ...StyledEditorKit
providesAction
subclasses to implement editing commands for styled text. You'll note that instead of getting the action from the editor kit, this code creates an instance of theBoldAction
class. Thus, this action is not shared with any other text component, and changing its name won't affect any other text component.In addition to associating an action with a GUI component, you can also associate an action with a keystroke. Associating Actions with Keystrokes shows you how.
See the Text Editing Commands API table for related API.
Concepts: About Keymaps
This section assumes that you understand actions and how to get them from the editor kit. If you don't, read Concepts: About Editor Kits and Associating Actions with Menus and Buttons.Every text component has one or more keymaps-- each of which is an instance of the
Keymap
class. A keymap contains a collection of name-value pairs where the name is aKeyStroke
and the value is anAction
. Each pair binds the keystroke to the action such that when the user types the keystroke, the action occurs.By default, a text component has one keymap named
JTextComponent.DEFAULT_KEYMAP
. This keymap contains standard, basic key bindings. For example, the arrow keys are mapped to caret movement, and so on. You can enhance or modify the default keymap in the following ways:When resolving a keystroke to its action, the text component checks the keymaps in the order they are added to the text component. Thus, the binding for a specific keystroke in a keymap that you add to a text component overrides any binding for the same keystroke in the default keymap.
- Add a custom keymap to the text component with
JTextComponent
'saddKeymap
method.- Add key bindings to the default keymap with
Keymap
'saddActionForKeyStroke
method. The default keymap is shared among text components, so use this with caution.- Remove key bindings from the default keymap with
Keymap
'sremoveKeyStrokeBinding
method. The default Keymap is shared among text components, so use this with caution.Associating Actions with Keystrokes
The text pane in theTextComponentDemo
adds four key bindings to the default keymap.The following code adds the
CTRL-B
for moving the caret backward one characterCTRL-F
for moving the caret forward one characterCTRL-P
for moving the caret up one lineCTRL-N
for moving the caret down one lineCTRL-B
key binding to the default keymap. The code for adding the other three is similar.The code first adds a keymap to the components hierarchy. The//Get the current, default map Keymap keymap = textPane.addKeymap("MyEmacsBindings", textPane.getKeymap()); //Ctrl-b to go backward one character Action action = getActionByName(StyledEditorKit.backwardAction); KeyStroke key = KeyStroke.getKeyStroke(KeyEvent.VK_B, Event.CTRL_MASK); keymap.addActionForKeyStroke(key, action);addKeymap
methods creates the keymap for you with the name and parent provided in the method call. In the example, the parent is the text pane's default keymap. Next, the code gets the backward action from the editor kit and gets aKeyStroke
object representing theCTRL-B
key sequence. Finally, the code adds the action and keystroke pair to the keymap, thereby binding the key to the action.For related API, see the Binding KeyStrokes to Actions API table.
Implementing Undo and Redo
Note: The implementation of undo and redo inTextComponentDemo
was copied directly from the NotePad demo that comes with Swing. Many programmers will also be able to copy this implementation of undo/redo without modification.
Implementing undo/redo has two parts:
- Remembering the undoable edits that occur.
- Implementing the undo and redo commands and providing a user interface for them.
Part 1: Remembering Undoable Edits
To support undo/redo, a text component must remember each edit that occurs on it, the order edits occur in relation to one another, and what it takes to undo it. The example program uses an undo manager, an instance of theUndoManager
class in Swing'sundo
package, to manage its list of undoable edits. The undo manager is created where the member variables are declared:Now, let's look at how the program finds out about undoable edits and adds them to the undo manager.protected UndoManager undo = new UndoManager();A document notifies interested listeners whenever an undoable edit occurs on its content. An important step in implementing undo and redo is register an undoable edit listener on the document of the text component. This code adds an instance of
MyUndoableEditListener
to the text pane's document:The undoable edit listener used in our example adds the edit to the undo manager's list:lpd.addUndoableEditListener(new MyUndoableEditListener());Note that this method updates two objects:protected class MyUndoableEditListener implements UndoableEditListener { public void undoableEditHappened(UndoableEditEvent e) { //Remember the edit and update the menus undo.addEdit(e.getEdit()); undoAction.update(); redoAction.update(); } }undoAction
andredoAction
. These are the action objects attached to the Undo and Redo menu items, respectively. The next step shows you how the menu items are created and the implementation of the two actions.For general information about undoable edit listeners and undoable edit events, see How to Write an Undoable Edit Listener.
Part 2: Implementing the Undo/Redo Commands
The first step in this part of implementing undo and redo is to create the actions to put in the Edit menu.The undo and redo actions are implemented by customJMenu menu = new JMenu("Edit"); //Undo and redo are actions of our own creation undoAction = new UndoAction(); menu.add(undoAction); redoAction = new RedoAction(); menu.add(redoAction); ...AbstractAction
subclasses:UndoAction
andRedoAction
respectively. These classes are inner classes of the example's primary class.When the user invokes the Undo command,
UndoAction
'sactionPerformed
method, shown here, gets called:This method calls the undo manager'spublic void actionPerformed(ActionEvent e) { try { undo.undo(); } catch (CannotUndoException ex) { System.out.println("Unable to undo: " + ex); ex.printStackTrace(); } update(); redoAction.update(); }undo
method and updates the menu items to reflect the new undo/redo state.Similarly, when the user invokes the Redo command, the
actionPerformed
method inRedoAction
gets called:This method is similar except that it calls the undo manager'spublic void actionPerformed(ActionEvent e) { try { undo.redo(); } catch (CannotRedoException ex) { System.out.println("Unable to redo: " + ex); ex.printStackTrace(); } update(); undoAction.update(); }redo
method.Much of the code in the
UndoAction
andRedoAction
classes is dedicated to enabling and disabling the actions as appropriate for the current state, and changing the names of the menu items to reflect the edit to be undone or redone.[PENDING: Can more be said here? Should talk more about what's in the undo package and about sharing undo managers between objects (or not).]
Listening for Caret and Selection Changes
TheTextComponentDemo
program uses a caret listener to display the current position of the caret or, if text is selected, the extent of the selection.The caret listener in this example is also a label. Here's the code that creates the caret listener label, adds it to the window, and makes it a caret listener of the text pane:
A caret listener must implement one method,//Create the status area JPanel statusPane = new JPanel(new GridLayout(1, 1)); CaretListenerLabel caretListenerLabel = new CaretListenerLabel( "Caret Status"); statusPane.add(caretListenerLabel); ... textPane.addCaretListener(caretListenerLabel);caretUpdate
, which is called each time the caret moves or the selection changes. Here's theCaretListenerLabel
implementation ofcaretUpdate
:As you can see, this listener updates its text label to reflect the current state of the caret or selection. The listener gets the information displayed from the caret event object. For general information about caret listeners and caret events, see How to Write a Caret Listener.public void caretUpdate(CaretEvent e) { //Get the location in the text int dot = e.getDot(); int mark = e.getMark(); if (dot == mark) { // no selection try { Rectangle caretCoords = textPane.modelToView(dot); //Convert it to view coordinates setText("caret: text position: " + dot + ", view location = [" + caretCoords.x + ", " + caretCoords.y + "]" + newline); } catch (BadLocationException ble) { setText("caret: text position: " + dot + newline); } } else if (dot < mark) { setText("selection from: " + dot + " to " + mark + newline); } else { setText("selection from: " + mark + " to " + dot + newline); } }As with document listeners, a caret listener is passive. It reacts to changes in the caret or in the selection but does not change the caret or the selection. Instead of modifying the caret or selection from a caret listener, you should use a custom caret. To create a custom caret, write a class that implements the
Caret
interface, then provide an instance of your class as an argument tosetCaret
on a text component.For related API, see the JTextComponent Methods for Manipulating the Current Selection and Manipulating Carets and Selection Highlighters API tables.
Using Swing Components |