JTextComponent
is the
foundation for Swing's text components, and provides these customizable
features for all of its descendants:
JTextPane
, the capabilities discussed in this section are
inherited by all of JTextComponent
's subclasses.
JTextArea
, 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.
Using this example application as a reference point, this section covers these topics:
Try this:
- Compile and run the application. The source is in
TextComponentDemo.java
andLimitedStyledDocument.java
.
See Getting Started with Swing if you need help compiling or running this application.- Use the mouse to select text and place the cursor in the text pane. Information about the selection and cursor is displayed at the bottom of the window.
- Enter text by typing at the keyboard. You can move the caret around using four emacs key bindings:
CTRL-B
(backward one character),CTRL-F
(forward one character),CTRL-N
(down one line), andCTRL-P
(up one line).- Bring up the Edit menu, and use its various menu items to perform editing on the text in the text pane. Make a selection in the text area at the bottom of the window. Because the text area is uneditable, only some of the Edit menu's commands, like copy-to-clipboard, work. It's important to note, though, that the menu operates on both text components.
- Use the items in the Style menu to apply different styles to the text in the text pane.
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 and is an instance
of a class that implements the Document
interface. A document provides these services for a text component:
Element
objects, which can represent any logical text
structure, such as paragraphs, text runs that share styles, and so on. We do
not cover Element
s. However, The Swing
Connection has at least one article on the subject.
remove
and insertString
methods.
Position
objects, which track a particular location
within the text even as the text is modified.
Document
, StyledDocument
, that
adds support for marking up the text with styles. One
JTextComponent
subclass, JTextPane
, requires that
its document be a StyledDocument
rather than merely a
Document
.
The javax.swing.text
package provides the following hierarchy
of document classes, which implement specialized documents for the various
JTextComponent
subclasses:
PlainDocument
is the default document for text fields,
password fields, and text areas. PlainDocument
provides a basic
container for text where all the text is displayed in the same font. Even
though an editor pane is a styled text component, it uses an instance of
PlainDocument
by default. The default document for a standard
JTextPane
in an instance of DefaultStyledDocument
--a
container for styled text in no particular format. However, the document
instance used by any particular editor pane or text pane depends on the type
of content bound to it. If you use setPage
to load text into an
editor pane or text pane, the document instance used by the pane might change.
Refer to Concepts:
Editor Panes and Text Panes for details.
Text components inherit the setDocument
method, which you can
use to dynamically change a component's document. Also most
JTextComponent
subclasses provide constructors that set the
document when creating the component. By replacing a text component's document
with one of your own, you can implement certain customizations. For example,
the text pane in TextComponentDemo
has a custom document that
limits the number of characters it can contain.
The TextComponentDemo
application has a custom
document, LimitedStyledDocument
, that limits the number
of characters that the text pane can contain.
LimitedStyledDocument
is a subclass of
DefaultStyledDocument
, the default document for
JTextPane
. The example needs to use a subclass of
DefaultStyledDocument
because JTextPane
requires its
document to be of that type. If you changed the superclass to
PlainDocument
, the document would work for a text field or text
area -- any text component except a text pane. No other code changes would be
required, although you would probably remove Styled
from the
class name, for clarity.
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 lsd = new LimitedStyledDocument(MAX_CHARACTERS); ... //Create the text pane and configure it textPane = new JTextPane(lsd); ...
LimitedStyledDocument
overrides its superclass's
insertString
method, which is called each time text is inserted
into the document. Text insertion can be the result of the user typing or
pasting text in, or because of a call to setText
. Here is
LimitedStyledDocument
's implementation of
insertString
:
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 the remove
method , which is called each time
text is removed from the document.
One common use of a custom document is to create a change-validated text field (a field whose value is checked each time its text changes). For two examples of validated text fields, refer to Creating a Validated Text Field.
You can register two different types of listeners on a document: document listeners and undoable edit listeners. This subsection covers document listeners. For information about undoable edit listeners, refer to Implementing Undo and Redo.
A document notifies registered document 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. The
following line of code registers an instance of
MyDocumentListener
as a listener on the
LimitedStyledDocument
used in the example:
Here's the implementation oflsd.addDocumentListener(new MyDocumentListener());
MyDocumentListener
:
The listener implements three methods for handling three different types of document events: insertion, removal, and style changes.protected class MyDocumentListener implements DocumentListener { public void insertUpdate(DocumentEvent e) { displayEditInfo(e); } public void removeUpdate(DocumentEvent e) { displayEditInfo(e); } public void changedUpdate(DocumentEvent e) { displayEditInfo(e); } private void displayEditInfo(DocumentEvent e) { Document doc = (Document)e.getDocument(); int changeLength = e.getLength(); changeLog.append(e.getType().toString() + ": " + changeLength + " character" + ((changeLength == 1) ? ". " : "s. ") + " Text length = " + doc.getLength() + "." + newline); } }
StyledDocument
s can fire all three types of events.
PlainDocument
s fire events only for insertion and removal. For
general information about document listeners and document events, see How
to Write a Document Listener.
Remember that the document for this text pane limits
the number of characters allowed in the document. If you try to add more text
than the document allows, 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 do, your program will likely deadlock! Instead, provide a custom
document and override the insertString
and remove
methods as needed.
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. Actions makes it easy for you to associate a command with a GUI
component, such as a menu item or button, and therefore build a GUI around a
text component.
Under the hood, text components use an EditorKit
to create and
manage 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.
Although all text components use editor kits, some components hide theirs. You
can't set or get the editor kit used by a text field, password field, or text
area. Editor panes and text panes provide the getEditorKit
method
to get the current editor kit and the setEditorKit
to change it.
For all components, JTextComponent
provides API for you to
indirectly invoke or customize some editor kit capabilities. For example,
JTextComponent
provides read
and write
methods, which invoke the editor kit's read
and
write
methods. JTextComponent
also provides a
method, getActions
, which returns all of the actions supported by
a component.
The Swing text package provides these editor kits:
DefaultEditorKit
StyledEditorKit
DefaultEditorKit
and
is the editor kit used by JTextPane
by default.
HTMLEditorKit
StyledEditorKit
.
RTFEditorKit
StyledEditorKit
. JEditorPane
class and associated with the
text format that the kit reads, writes, and edits. When a file is loaded into
an editor pane, the pane checks the format of the file against its registered
kits. If a registered kit is found that supports that file format, the pane
uses the kit to read the file, display, and edit it. Thus, the editor pane
effectively transforms itself into an editor for that text format. You can
extend JEditorPane
to support your own text format by creating an
editor kit for it, and then using JEditorPane
's
registerEditorKitForContentType
to associate your kit with your
text format.
As we mentioned before, you can call the getActions
method on any text component to get an array containing all of the actions
supported by it. Often it's convenient to load the array of actions into a
Hashtable
so your program can retrieve an action by name. Here's
the code from TextComponentDemo
that gets the actions from the
text pane and loads them into a Hashtable
:
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 verbatim in your programs.private Action getActionByName(String name) { return (Action)(actions.get(name)); }
Now let's look at how the Cut menu item is created and associated with the action of removing text from the text component:
This code gets the action by name using the handy method shown previously. It then 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 efficiency, text components share actions. The Action
object returned by getActionByName(DefaultEditorKit.cutAction)
is
shared by the uneditable JTextArea
at the bottom of the window.
This has two important ramifications:
Action
objects you
get from editor kits. If you do, the changes affect 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 it's
uneditable, the JTextArea
shares actions with the
JTextPane
. (Select some text in the text area, then choose the
cut-to-clipboard
menu item. You'll hear a beep because the text
area is uneditable.) If you don't want to share, consider instantiating the
Action
object yourself. DefaultEditorKit
defines a
number of useful Action
subclasses. Theprotected JMenu createStyleMenu() { JMenu menu = new JMenu("Style"); Action action = new StyledEditorKit.BoldAction(); action.putValue(Action.NAME, "Bold"); menu.add(action); ...
StyledEditorKit
provides
Action
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 the BoldAction
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 Text Actions with Keystrokes shows you how.
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 Text Actions with Menus and Buttons.
Every text component has one or more Keymap
objects.
A keymap contains a collection of name-value pairs where the name is a
KeyStroke
and the value is an Action
. 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:
JTextComponent
's addKeymap
method.
Keymap
's
addActionForKeyStroke
method. The default keymap is shared
among text components, so use this with caution.
Keymap
's
removeKeyStrokeBinding
method. The default keymap is shared
among text components, so again, use this with caution. The text pane in the TextComponentDemo
supports four
key bindings not provided by the default keymap.
CTRL-B
for moving the caret backward one character
CTRL-F
for moving the caret forward one character
CTRL-N
for moving the caret down one line
CTRL-P
for moving the caret up one line CTRL-B
key binding to it. The code for adding the other three is
similar.
The code first adds a keymap to the component's hierarchy. TheKeymap keymap = textPane.addKeymap("MyEmacsBindings", textPane.getKeymap()); Action action = getActionByName(DefaultEditorKit.backwardAction); KeyStroke key = KeyStroke.getKeyStroke(KeyEvent.VK_B, Event.CTRL_MASK); keymap.addActionForKeyStroke(key, action);
addKeymap
method 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 a KeyStroke
object
representing the CTRL-B
key sequence. Finally, the code adds the
action and keystroke pair to the keymap, thereby binding the key to the
action.
Note: The implementation of undo and redo inTextComponentDemo
was taken from theNotePad
demo that comes with the JFC 1.1 and JDK 1.2 releases. Many programmers will also be able to copy this implementation of undo/redo without modification.
Implementing undo and redo has two parts:
Part 1: Remembering Undoable Edits
To support undo and redo, a text component must remember each edit
that occurs, the order of edits, and what it takes to undo each edit. The
example program uses an instance of the UndoManager
class 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 to register
an undoable edit listener on the document of the text component. The following
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:lsd.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.updateUndoState(); redoAction.updateRedoState(); } }
undoAction
and redoAction
. 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 and 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
and
RedoAction
, respectively. These classes are inner classes of the
example's primary class.
When the user invokes the Undo command,
UndoAction
's actionPerformed
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(); } updateUndoState(); redoAction.updateRedoState(); }
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 in RedoAction
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(); } updateRedoState(); undoAction.updateUndoState(); }
redo
method.
Much of the code in the UndoAction
and RedoAction
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.
The TextComponentDemo
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 class in this example is a JLabel
subclass.
Here's the code that creates the caret listener label and makes it a caret
listener of the text pane:
A caret listener must implement one method,//Create the status area CaretListenerLabel caretListenerLabel = new CaretListenerLabel( "Caret Status"); ... textPane.addCaretListener(caretListenerLabel);
caretUpdate
, which is called each time the caret moves or the
selection changes. Here's the CaretListenerLabel
implementation
of caretUpdate
:
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 to display 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. If you want to change the caret or selection, then you should use a
custom caret instead. To create a custom caret, write a class that implements
the Caret
interface, then provide an instance of your class as an argument to
setCaret
on a text component.