Text Components and Editors
The model for a JTextComponent is an object of type Document, separate from the component you see.
Why a Silent Listener Is Hard to Debug
You have a text component on the screen, and you want your program to react whenever the text in it changes. You write a listener, attach it, and nothing happens. The window looks fine, and the code looks reasonable, so where should you start looking? This article answers that question by showing that a text component is not the object that holds the text. Behind the component sits a separate object, and a listener attached in the wrong place will never hear about the changes you care about.
The lesson's trace keeps one question in mind as you follow the code: where does the text component keep the information it is responsible for? The screen shows a text component, but the trace is designed to make you look past the screen to the object behind it. Keep that question in mind as we go.
Two Objects Behind One Text Field
A JTextComponent is the piece you see and interact with. The Document is a separate object that the component relies on as its model. The model for a JTextComponent is an object of type Document, separate from the component you see.
The two objects have distinct jobs. The component handles what appears on screen and how the user interacts with it. The Document holds the model, which is the object the component depends on for its text. This separation is the heart of the model-view-controller idea that this chapter builds on. The practical consequence is simple: if you want to observe the text itself, rather than the component that displays it, you need a way to reach the Document.
The diagram shows the containment in one direction: the component you see is paired with the Document it depends on. Read the arrow as reliance, not as a claim about every internal detail of the component. Your job when reasoning about a listener is to remember that the text lives in the Document, and the component is the view of it.
Reading the Chain One Step at a Time
The source gives one line for attaching a listener. It is worth reading slowly, because the line is a chain of three steps, and each step operates on a different object.
textComponent: the text component you already havegetDocument(): asks the component for its Document model and returns that objectaddDocumentListener(listener): called on the returned Document, registering the listener with it
The first part names the text component. The second part, getDocument(), is called on that component and hands back its Document. The third part, addDocumentListener(listener), is called on the returned Document, not on the component. The order of the chain is the key detail. The chain first moves from the component to its model, and only then registers the listener. A listener attached this way is attached to the model, which is why the source describes it as a way to be notified of changes in the model.
Tracing Where the Listener Lands
Now trace the registration as a sequence of messages between the objects involved. Your code starts the process, the component supplies its model, the model receives the registration, and later the model notifies the listener when a change happens. Notice that the component never receives the listener in this path.
Read the diagram from top to bottom. Messages 1 and 2 retrieve the model. Message 3 is the registration, and it lands on the Document. Message 4 is the notification path: once a change happens in the model, the Document notifies the listeners attached to it, and the listener's own code then runs. The source describes this path at the level of which object is notified, so treat the notification step as the link to verify when you debug, rather than assuming any particular callback names.
What do you think happens?
A listener is registered with textComponent.getDocument().addDocumentListener(listener). Which object actually receives the listener?
Reveal answer
The Document returned by getDocument()
The addDocumentListener method belongs to the Document. The chain first retrieves the model, and the listener is registered on that model. This is why the listener is notified of changes in the model.
Finding the Step Where Results Diverge
When a listener does not react, do not guess at the whole program. Split the chain into its three steps and check each one. The first step where your expectation and the actual object disagree is the place to investigate. A listener that seems silent usually comes down to one question: at which step did the program stop doing what you expected? That step might be fetching the right object, registering the listener, or the change itself.
Common mistake Assuming the listener was attached to the text component
What it looks likeReading textComponent.addDocumentListener(listener) as if it were valid and expecting the component to report every change.
Why it failsThe
addDocumentListenermethod belongs to the Document. The registration is made on the model, not on the component on screen.Do this insteadInsert the getDocument() call so the registration is made on the Document: textComponent.getDocument().addDocumentListener(listener).
Common mistake Checking only the final line and not the intermediate object
What it looks likeConfirming that addDocumentListener is called with the right listener, but never checking which object getDocument() returned.
Why it failsThe registration can be correct in form but still land on an object you did not intend, so the listener never hears about the changes you expected.
Do this insteadConfirm that the reference you hold is the text component you meant, that getDocument() produced the Document you expected, and that addDocumentListener was called on that Document with the listener you intended.
Common mistake Treating the notification as an assumed detail instead of a verified link
The source does not list the callback methods a DocumentListener defines, and it does not claim that every change is reported in every situation. When a listener seems silent, verify the notification link itself, namely whether the change actually happens in the Document you registered on, rather than assuming the details of the listener interface.
Practice: Naming the Object at Each Step
Given the line textField.getDocument().addDocumentListener(myListener), state which object each of the three parts refers to, and name the object that stores the listener. Then describe, in one sentence, what would be wrong if the line were written as textField.addDocumentListener(myListener) and what that mistake would look like to a learner watching the screen.
Hints
- Start with the leftmost part and ask what object it names.
- Ask what
getDocument()returns and which object the next call is made on. - Recall that the source says the listener is notified of changes in the model.
Key Takeaways
- A
JTextComponentis the piece you see and interact with, while its Document is a separate model object that holds the text the component relies on. - The line
textComponent.getDocument().addDocumentListener(listener) runs in three steps: it names the component, retrieves the Document withgetDocument(), and registers the listener on that Document. - A listener registered through the chain is attached to the model, so it is notified of changes in the Document rather than of events on the component itself.
- When a listener is silent, check each step of the chain in order and find the first point where the actual object differs from what you expected.