It to Model Text in Swing Applications
Swing has a interesting interface, called the Element,
which has the power to describe various structural parts of a
document, such as paragraphs, lines of text, or even (in HTML
documents) items in lists. Conceptually, the Element interface
captures some of the spirit of an SGML document. So if you know
SGML, you may already have some understanding of Swing's Element
This article explains how the Element interface works and
shows how you can make use of it in your Swing programs. The article
may help you get a better understanding of how Swing's Document
interface models its content, as well as how operations on the
Document interface affect the structure of its associated document.
(The Document interface is the model of the JTextComponent
class, from which the JTextField
subclasses are derived).
By Scott Violet
The interface Element is defined in javax.swing.text.Element
file. This article introduces the Element interface by examining
these major topics:
In Swing, the interface Element defines a structural
piece of a Document, like a paragraph, a line of text, or a list
item in an HTML document. These are the methods that define the
int getElement(int index);
Every Element is either a branch or a leaf.
If an element is a branch, the
isLeaf() method returns false.
If an element is a a leaf,isLeaf()
Branches can have any number of children. Leaves do
not have children. To determine how many children a branch has,
you can call getElementCount().
To determine the parent of an Element, you can call getParentElement().
Root elements don't have parents, so calling getParentElement()
on a root returns null.
An Element represents a specific region in a Document
that begins with startOffset
and ends just before endOffset.
The start offset of a branch Element is usually the start offset
of its first child. Similarly, the end offset of a branch Element
is usually the end offset of its last child.
Every Element is associated with an AttributeSet
that you can access by calling getAttributes().
In an Element, and AttributeSet is essentially a set of key/value
pairs. These pairs are generally used for markup -- such as determining
the Element's foreground color, font size, and so on. But it is
up to the model, and the developer, to determine what is stored
in the AttributeSet.
You can obtain the root Element (or Elements) of
a Document by calling the methods getDefaultRootElement()
which are defined in the Document interface.
The Document interrface is responsible for translating
a linear view of the characters into Element operations. It is up
to each Document implementation to define what the Element structure
class defines an Element structure in which the root node has a
child node for each line of text in the model. Figure
1 shows how two lines of text would be modeled by a PlainDocument:
How a PlainDocument models two lines of text
Figure 2 shows how how those
same two lines of text might map to actual content:
Mapping text lines to actual content
As just mentioned, a PlainDocument contains a root
Element, which in turn contains an Element for each line of text.
When text is inserted into a PlainDocument, it creates the Elements
that are needed for an Element to exist for each newline. To illustrate,
let's say you wanted to insert a newline at offset 2 in Figure
2, above. To accomplish this objective, you could use the Document
using this syntax:
document.insertString(2, "\n", null);
After invoking the insertString()method,
the Element structure would look like the one shown in Figure
Inserting a newline
As another example, let's say you wanted to insert
the pattern "new\ntext\n" at offset 2 as shown previously
in Figure 2. This operation would
have the result shown in Figure 4.
Inserting a pattern
In the preceding illustrations, I have changed the
name of the line Elements after the insertion to match the line
numbers. But notice that when I do this, the AttributeSets remain
the same. For example, in Figure 2 , the AttributeSet
of Line 2 matches that of the AttributeSet of Line 4 in Figure
text from a PlainDocument
Removal of text results in a structure change if
the deletion spans more than one line. Consider a deletion of seven
characters starting at Offset 1 shown previously in Figure
3. In this case, the Element representing Line 2 is completely
removed, as the region it represents is contained in the deleted
region. The Elements representing Lines 1 and 3 are joined, as they
are partially contained in the deleted region. Thus, we have the
class, used for styled text, contains another level of Elements.
This extra level is needed so that each paragraph can contain different
styles of text. In the two paragraphs shown in Figure
6, the first paragraph contains two styles and the second paragraph
contains three styles.
Figure 7 shows how those same
Elements might map to content.
Mapping to content
As previously mentioned,
DefaultStyledDocument maintains an Element structure such that the
root Element contains a child Element for each paragraph. In turn,
each of these paragraph Elements contains an Element for each style
of text in the paragraph. As an example, let's say you had a document
containing one paragraph, and that this paragraph contained two
styles, as shown in Figure 8.
Inserting styled lines into a paragraph
If you then wanted to insert a newline at offset
2, you would again use the method insertString(),
This operation would have the result shown in Figure
Inserting three styled lines into two paragraphs
It's important to note that the AttributeSet passed
matches that of the attributes of Style 1. If the AttributeSet passed
did not match, the result would be the situation shown in Figure
The result of mismatched attributes
Removing text from a DefaultStyledDocument is similar
to removing text from a PlainDocument. The only difference is the
extra level of Elements. Consider what would happen if you deleted
two characters at Offset 1 from Figure 10, above.
Since the the second Element of Paragraph 1 is completely contained
in the deleted region, it would be removed. Assuming the attributes
of Paragraph 1's first child matched those of Paragraph2's first
child, the results would be those shown in Figure
Removing styled text
If the attributes did not match, we would get the
results shown in Figure 12.
The result of more mismatched attributes
class provides a method named setCharacterAttributes(),
which allows you to set the attributes on the character Elements
in a given range:
public void setCharacterAttributes
(int offset, int length, AttributeSet s, boolean replace);
Recall that in the diagrams shown in the previous
section, all leaf Elements shown in the drawings were also character
Elements. That means that the setCharacterAttributes()method
could be used to set their attributes.
method takes four arguments . The first and second arguments identify
a region in the Document that is to be changed. The third argument
specifies the new attributes (as an AttributeSet), and the fourth
argument determines if the new attributes should be added to the
existing attributes (a value of false)
or if the character Element should replace its existing attributes
with the new attributes (a value of true).
As an example, let's say you wanted to change the
attributes of the first three characters in Figure
9, shown previously. The first two arguments passed to setCharacterAttributes()
would be 0 and 3. The third argument would be the AttributeSet containing
the new attributes. In the example we are considering, it doesn't
matter what the fourth argument is.
As the start and end offsets of the changed region
(0 and 3) fall on character Element boundaries, no structure change
is needed. That is, only the attributes of the character Element
style 1 will change.
Now let's look at an example that requires a structure
change. Instead of changing the first three characters shown in
Figure 9, let's change the first two characters.
Because the end change offset (2) does not fall on a character Element
boundary, the Element at offset 2 must be split in such a way that
offset 2 is the boundary of two Elements. Invoking setCharacterAttributes()
with a start offset of 0 and length of 2 has the result shown earlier
in Figure 10.
Paragraph Attributes in a StyledDocument
The StyledDocument class provides a method named
which can be used to change the attributes of a paragraph Element:
public void setParagraphAttributes
(int offset, int length, AttributeSet s, boolean replace);
This method is similar to setCharacterAttributes(),
but it allows you to change the attributes of paragraph Elements.
It is up to the implementation of a StyledDocument to define which
Elements are paragraphs. DefaultStyledDocument interprets paragraph
Elements to be the parent Element of the character Element. Invoking
this method does not result in a structure change; only the attributes
of the paragraph Element change.
This article has presented a brief overview of how
the Document interface models its content using the Element interface.
For further reading, I recommend looking at the Swing online API
(javadoc) entries for EditorKit
View is responsible for rendering a particular Element, and EditorKit
is responsible for a ViewFactory
that is able to decide what View should be created based on an Element.