The Java TM Tutorial

| Start of Tutorial > Start of Trail > Start of Lesson | Search

Trail: Creating a GUI with JFC/Swing
Lesson: Using Swing Components

## General Rules for Using Text Components

> `JTextComponent` is the foundation for Swing's text components, and provides these customizable features for all of its descendants:
> - 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.
> - A separate controller, known as an editor kit, that can read and write text and that implements editing capabilities with actions.
> - Customizable keymaps and key bindings.
> - Support for infinite undo and redo.
> - Pluggable caret and support for caret change listeners.
> This section uses the application shown below to explore each of these capabilities. Although the demo application contains a customized instance of `JTextPane`, the capabilities discussed in this section are inherited by all of `JTextComponent`'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.

> > Try this:
> > 1. Compile and run the application. The source is in `TextComponentDemo.java` and `LimitedStyledDocument.java`.
> >    See Getting Started with Swing if you need help compiling or running this application.
> > 2. 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.
> > 3. 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), and `CTRL-P` (up one line).
> > 4. 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.
> > 5. Use the items in the Style menu to apply different styles to the text in the text pane.

> Using this example application as a reference point, this section covers these topics:
> - Concepts: About Documents
> - Customizing a Document
> - Listening for Changes on a Document
> - Concepts: About Editor Kits
> - Associating Text Actions with Menus and Buttons
> - Concepts: About Keymaps
> - Associating Text 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 and is an instance of a class that implements the `Document` interface. A document provides these services for a text component:

> - 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 cover `Element`s. However, The Swing Connection has at least one article on the subject.
> - Provides support for editing the text through the `remove` and `insertString` 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.
> The Swing text package contains a subinterface of `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:

> 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 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.

### Customizing a Document

> 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:

> >     ...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);
> >         ...

> To limit the characters allowed in the document, `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`:

> >     public void insertString(int offs, String str, AttributeSet a)
> >         throws BadLocationException
> >     {
> >         if ((getLength() + str.length()) <= maxCharacters)
> >             super.insertString(offs, str, a);
> >         else
> >             Toolkit.getDefaultToolkit().beep();
> >     }

> In addition to `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.

### Listening for Changes on a Document

> 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:

> >     lsd.addDocumentListener(new MyDocumentListener());

> Here's the implementation of `MyDocumentListener`:

> >     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);
> >         }
> >     }

> The listener implements three methods for handling three different types of document events: insertion, removal, and style changes. `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.

### 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. 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`
>   Reads and writes plain 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 by `JTextPane` 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`.
> Each of the editor kits above has been registered with the `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.

### Associating Text Actions with Menus and Buttons

> 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`:

> >     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);
> >         }
> >     }

> And here's a convenient method for retrieving an action by its name from the hashtable:

> >     private Action getActionByName(String name) {
> >         return (Action)(actions.get(name));
> >     }

> You can use both methods verbatim in your programs.

> Now let's look at how the Cut menu item is created and associated with the action of removing text from the text component:

> >     protected JMenu createEditMenu() {
> >         JMenu menu = new JMenu("Edit");
> >         ...
> >         menu.add(getActionByName(DefaultEditorKit.cutAction));
> >         ...

> 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 from `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:

> - 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, 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.
> Here's the code that creates the Style menu and puts the Bold menu item in it:

> >     protected JMenu createStyleMenu() {
> >         JMenu menu = new JMenu("Style");

> >         Action action = new StyledEditorKit.BoldAction();
> >         action.putValue(Action.NAME, "Bold");
> >         menu.add(action);
> >         ...

> The `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.

### 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 Text 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 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:

> - Add a custom keymap to the text component with `JTextComponent`'s `addKeymap` method.
> - Add key bindings to the default keymap with `Keymap`'s `addActionForKeyStroke` method. The default keymap is shared among text components, so use this with caution.
> - Remove key bindings from the default keymap with `Keymap`'s `removeKeyStrokeBinding` method. The default keymap is shared among text components, so again, use this with caution.
> 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.

### Associating Text Actions with Keystrokes

> 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
> The following code adds a new keymap to the text pane and adds the `CTRL-B` key binding to it. The code for adding the other three is similar.

> >     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);

> The code first adds a keymap to the component's hierarchy. The `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.

### Implementing Undo and Redo

> > Note: The implementation of undo and redo in `TextComponentDemo` was taken from the `NotePad` 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:

> - 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 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:

> >     protected UndoManager undo = new UndoManager();

> Now, let's look at how the program finds out about undoable edits and adds them to the undo manager.

> 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:

> >     lsd.addUndoableEditListener(new MyUndoableEditListener());

> The undoable edit listener used in our example adds the edit to the undo manager's list:

> >     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();
> >         }
> >     }

> Note that this method updates two objects: `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/Redo Commands
> The first step in this part of implementing undo and redo is to create the actions to put in the Edit menu.

> >     JMenu 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);
> >     ...

> The undo and redo actions are implemented by custom `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:

> >     public void actionPerformed(ActionEvent e) {
> >         try {
> >             undo.undo();
> >         } catch (CannotUndoException ex) {
> >             System.out.println("Unable to undo: " + ex);
> >             ex.printStackTrace();
> >         }
> >         updateUndoState();
> >         redoAction.updateRedoState();
> >     }

> This method calls the undo manager's `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:

> >     public void actionPerformed(ActionEvent e) {
> >         try {
> >             undo.redo();
> >         } catch (CannotRedoException ex) {
> >             System.out.println("Unable to redo: " + ex);
> >             ex.printStackTrace();
> >         }
> >         updateRedoState();
> >         undoAction.updateUndoState();
> >     }

> This method is similar except that it calls the undo manager's `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.

### Listening for Caret and Selection Changes

> 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:

> >     //Create the status area
> >     CaretListenerLabel caretListenerLabel = new CaretListenerLabel(
> >     						"Caret Status");
> >     ...
> >     textPane.addCaretListener(caretListenerLabel);

> A caret listener must implement one method, `caretUpdate`, which is called each time the caret moves or the selection changes. Here's the `CaretListenerLabel` implementation of `caretUpdate`:

> >     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 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.

> 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.

| Start of Tutorial > Start of Trail > Start of Lesson | Search