[an error occurred while processing this directive]

Page One
Page Two
The Databank
What Is Swing?
Special Report
IDE Roundup
Swing and the Web
Swing Text
Tech Topics
Tips and Tricks
The PLAF Papers
Call 911
The Archive
JFC Home
Download Swing
Swing API Docs
Download JDK
JDK Docs
Java Tutorial
The Swing Connection Swing Text

The Element Interface
Use 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 interface.

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


About the Element Interface

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

    Element getParentElement();
    int getElementCount();
    int getElement(int index);
    boolean isLeaf();
    Document getDocument();
    String getName();
    AttributeSet getAttributes();
    int getStartOffset();
    int getEndOffset();

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() returns true.

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() and getRootElements(), 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 is.

The PlainDocument class

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

Figure 1
How a PlainDocument models two lines of text


Figure 2 shows how how those same two lines of text might map to actual content:

Figure 2
Mapping text lines to actual content

Inserting text into a PlainDocument

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 method insertString(), using this syntax:

    document.insertString(2, "\n", null);

After invoking the insertString()method, the Element structure would look like the one shown in Figure 3.

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

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

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

Figure 5
Removing text

The Default StyledDocument Class

The DefaultStyledDocument 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 6
Removing text

Figure 7 shows how those same Elements might map to content.

Figure 7
Mapping to content


Inserting text into a DefaultStyledDocument

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.

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(), as follows:

    styledDocument.insertString(2, "\n", 

This operation would have the result shown in Figure 9.

Figure 9
Inserting three styled lines into two paragraphs

It's important to note that the AttributeSet passed to insertString() matches that of the attributes of Style 1. If the AttributeSet passed to insertString() did not match, the result would be the situation shown in Figure 10.

Figure 10
The result of mismatched attributes

Removing text from a DefaultStyledDocument

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

Figure 11
Removing styled text

If the attributes did not match, we would get the results shown in Figure 12.

Figure 12
The result of more mismatched attributes

The StyledDocument Class

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

The setCharacterAttributes() 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.

Changing Paragraph Attributes in a StyledDocument

The StyledDocument class provides a method named setParagraphAttributes(), 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.

Further Reading

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 and View. 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. Happy modeling.

[an error occurred while processing this directive]