Start of Tutorial > Start of Trail > Start of Lesson |
Search
Feedback Form |
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 these capabilities. Although the demo application contains a customized instance of
- A model, known as a document, to manage the component's content.
- A view, which is in charge of displaying the component on screen.
- A controller, known as an editor kit, that can read and write text and that implements editing capabilities with actions.
- Support for infinite undo and redo.
- Pluggable caret and support for caret change listeners and navigation filters.
JTextPane
, the capabilities discussed in this section are inherited by all ofJTextComponent
's subclasses.The upper text component is the customized text pane. The lower text component is an instance of 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:
- Run TextComponentDemo using JavaTM Web Start. Or, to compile and run the example yourself, consult the example index.
- 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.
All Swing text components supports standard editing commands such as cut, copy, paste, and inserting characters. Each editing command is represented and implemented by anAction
object. (You can learn about actions by reading How to Use Actions.) 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.You can invoke 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 aHashMap
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 aHashMap
:Here's a convenient method for retrieving an action by its name from the hash map:private void createActionTable(JTextComponent textComponent) { actions = new HashMap(); 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 bygetActionByName(DefaultEditorKit.cutAction)
is shared by the uneditableJTextArea
at the bottom of the window. This has two important ramifications:Here's the code that creates the Style 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 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, theJTextArea
shares actions with theJTextPane
. (Select some text in the text area, then choose thecut-to-clipboard
menu item. You'll hear a beep because the text area is uneditable.) 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 key stroke, using a text component's input map. Input maps are described in How to Use Key Bindings.The text pane in the
TextComponentDemo
supports four key bindings not provided by default.The following code adds the
CTRL-B
for moving the caret backward one characterCTRL-F
for moving the caret forward one characterCTRL-N
for moving the caret down one lineCTRL-P
for moving the caret up one lineCTRL-B
key binding to the text pane. The code for adding the other three is similar.InputMap inputMap = textPane.getInputMap(); KeyStroke key = KeyStroke.getKeyStroke(KeyEvent.VK_B, Event.CTRL_MASK); inputMap.put(key, DefaultEditorKit.backwardAction);The code starts off by getting the text component's input map. Next, it gets a
KeyStroke
object representing theCTRL-B
key sequence. Finally, the code binds the key stroke to theAction
that moves the cursor backward.
Version note: Before 1.3, you needed to use aJTextComponent
feature called a keymap to associate key strokes with actions. The keymap API still exists and works, but it is now implemented in terms of the key binding infrastructure added in 1.3. Here's the pre-1.3 equivalent of the preceding code://PRE-1.3 CODE Keymap 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); ... textPane.setKeymap(keymap);
Implementing undo and redo has two parts:
- Remembering undoable edits.
- Implementing the undo and redo commands and providing a user interface for them.
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 theUndoManager
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
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.
Note: By default, undoable edits can be as fine-grained as single key presses. It is possible, with some effort, to group edits so that (for example) a series of key presses is combined into one undoable edit. The implementation would require defining a class that intercepts undoable edit events from the document, combining them if appropriate and forwarding the results to your 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
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(); } 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 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(); } updateRedoState(); undoAction.updateUndoState(); }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.
Note: The implementation of undo and redo inTextComponentDemo
was taken from theNotePad
demo that comes with the Java 2 SDK. Many programmers will also be able to copy this implementation of undo/redo without modification.
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 Using 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:The Swing text package contains a subinterface of
- Contains the text. A document stores the textual content in
Element
objects, which can represent any logical text structure, such as paragraphs, text runs that share styles, and so on. We do not coverElement
s. However, The Swing Connection has at least one article on the subject.- Provides support for editing the text through the
remove
andinsertString
methods.- Notifies document listeners and undoable edit listeners of changes to the text.
- Manages
Position
objects, which track a particular location within the text even as the text is modified.- Allows you to get information about the text, such as its length, and segments of the text as a string.
Document
,StyledDocument
, that adds support for marking up the text with styles. OneJTextComponent
subclass,JTextPane
, requires that its document be aStyledDocument
rather than merely aDocument
.The
javax.swing.text
package provides the following hierarchy of document classes, which implement specialized documents for the variousJTextComponent
subclasses:A 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 ofPlainDocument
by default. The default document for a standardJTextPane
in an instance ofDefaultStyledDocument
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 usesetPage
to load text into an editor pane or text pane, the document instance used by the pane might change. Refer to How to Use Editor Panes and Text Panes for details.Although you can set the document of a text component, it's usually easier to let it be set automatically, and use a document filter if necessary to change how the text component's data is set. You can implement certain customizations either by installing a document filter or by replacing a text component's document with one of your own, For example, the text pane in
TextComponentDemo
has a document filter that limits the number of characters the text pane can contain.
To implement a document filter, you create a subclass ofDocumentFilter
and then attach it to a document using thesetFilter
method defined inAbstractDocument
. Although it's possible to have documents that don't descend fromAbstractDocument
, by default Swing text components useAbstractDocument
subclasses for their documents.The
TextComponentDemo
application has a document filter,DocumentSizeFilter
, that limits the number of characters that the text pane can contain. Here's the code that creates the filter and attaches it to the text pane's document:To limit the characters allowed in the document,...//Where member variables are declared: JTextPane textPane; AbstractDocument doc; static final int MAX_CHARACTERS = 300; ... textPane = new JTextPane(); ... StyledDocument styledDoc = textPane.getStyledDocument(); if (styledDoc instanceof AbstractDocument) { doc = (AbstractDocument)styledDoc; doc.setDocumentFilter(new DocumentSizeFilter(MAX_CHARACTERS)); }DocumentSizeFilter
overridesDocumentFilter
'sinsertString
method, which is called each time text is inserted into the document. It also overrides thereplace
method, which is most likely to be called when the user changes text by pasting it in. In general, text insertion can be the result of the user typing or pasting text in, or because of a call tosetText
. Here isDocumentSizeFilter
's implementation ofinsertString
:public void insertString(FilterBypass fb, int offs, String str, AttributeSet a) throws BadLocationException { if ((fb.getDocument().getLength() + str.length()) <= maxCharacters) super.insertString(fb, offs, str, a); else Toolkit.getDefaultToolkit().beep(); }The code for
replace
is similar. TheFilterBypass
parameter to the methods defined byDocumentFilter
is simply an object that enables updating the document in a thread-safe way.Because the preceding document filter is only concerned about additions to the document's data, it only overrides the
insertString
andreplace
methods. Most document filters would overrideDocumentFilter
'sremove
method, as well.
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 ofMyDocumentListener
as a listener on the text pane's document:Here's the implementation ofdoc.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 document = (Document)e.getDocument(); int changeLength = e.getLength(); changeLog.append(e.getType().toString() + ": " + changeLength + " character" + ((changeLength == 1) ? ". " : "s. ") + " Text length = " + document.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 filter for this text pane limits the number of characters allowed in the document. If you try to add more text than the document filter allows, the document filter 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. However, you should never modify the contents of a text component from within a document listener. In fact, if you do, your program will likely deadlock! Instead, you can use a formatted text field or provide a document filter.
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 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 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 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 navigation filter or a custom caret instead.
Implementing a navigation filter is similar to implementing a document filter. First, you write a subclass of
NavigationFilter
. You then attach an instance of it to a text component with thesetNavigationFilter
method.You might create a custom caret to customize the appearance of a caret. To create a caret, write a class that implements the
Caret
interface perhaps by extending theDefaultCaret
class. Then provide an instance of your class as an argument tosetCaret
on a text component.
Under the hood, text components use an
EditorKit
to tie the various pieces of the text component together. It provides the view factory, document, caret, and actions. An editor kit also reads and writes 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 or text area. Editor panes and text panes provide thegetEditorKit
method to get the current editor kit and thesetEditorKit
method to change it.For all components,
JTextComponent
provides API for you to indirectly invoke or customize some 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.The Swing text package provides these editor kits:
Each of the editor kits above has been registered with the
DefaultEditorKit
- Reads and writes plain text, and provides a basic set of editing commands. Details of how the text system treats newlines are in the
DefaultEditorKit
API documentation. (To summarize: The '\n' character is used internally, but the document or platform line separators are used when writing files.) All the other editor kits are descendants ofDefaultEditorKit
.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
.RTFEditorKit
- Reads, writes, and edits RTF. This is a subclass of
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 extendJEditorPane
to support your own text format by creating an editor kit for it, and then usingJEditorPane
'sregisterEditorKitForContentType
to associate your kit with your text format.
Start of Tutorial > Start of Trail > Start of Lesson |
Search
Feedback Form |
Copyright 1995-2004 Sun Microsystems, Inc. All rights reserved.