Complex Swing Components and the Model-View-Controller Pattern
The model for a JTextComponent is an object of type Document, separate from the component you see.
A Listener That Never Fires
Imagine you have written a listener for a text box, and you have attached it, but nothing happens when the text changes. The code looks reasonable, the screen shows a working text component, and yet the listener seems silent. Debugging a listener like this 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.
This article gives you the vocabulary to name each step, so you can point to the exact place where a result diverged. The key idea is that the text component you see on the screen is not the whole story. Behind it is a separate object that holds the text, and listeners are attached to that object rather than to the visible component.
What do you think happens?
When you register a listener through a text component, which object actually receives it?
Reveal answer
The Document object returned by getDocument()
The source states that the model for a JTextComponent is an object of type Document, and that the addDocumentListener method belongs to the Document, not to the text component. The listener is therefore registered on the model.
Two Objects Behind One Text Box
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 two are distinct objects with distinct jobs. The component is responsible for presenting and interacting with text, while the Document is where the information the component is responsible for is kept.
The model of a JTextComponent is an object of type Document. It is separate from the component you see, and it is the object you reach when you want to observe the text itself rather than the screen representation of it.
This separation is the heart of the model-view-controller idea that this chapter builds on. Keeping the stored information in one object and the presentation in another means you can attach your interest to the model directly. When you want to observe the model rather than the component, you need a way to reach the Document itself. The diagram below shows how the component and its Document are connected.
Reading the Registration Chain
The source gives one line for attaching a listener. It is worth reading slowly, because each part of the line hands a different object to the next part.
textComponent: the text component you already havegetDocument(): asks the component for its Document model and returns that objectaddDocumentListener(listener): called on the returned Document, registers the listener with it
Read from left to right, the line is a chain of three steps. The first part names the text component you already hold. The second part, getDocument(), is a call on that component that returns its Document. The third part, addDocumentListener(listener), is a call on the returned Document, not on the text component. The order matters: the chain first moves from the component to its model, and only then registers the listener on that model.
Tracing the chain for a new text field
You have a text field named nameField and a listener named nameWatcher. You write nameField.getDocument().addDocumentListener(nameWatcher). Trace which object each part of the line refers to.
- 1Step 1: the first part
nameFieldis the text component you already have. It is the view, the piece you see and interact with. - 2Step 2: the second part
getDocument()is called onnameField. It returns the Document model thatnameFieldrelies on for its text. The result is a Document object, not a component. - 3Step 3: the third part
addDocumentListener(nameWatcher)is called on the Document returned in step 2. The listener is therefore registered with the model, which is what the source means when it says the listener is notified of changes in the model.
The listener nameWatcher is attached to the Document of nameField, not to nameField itself.
How a Change Reaches Your Listener
Once the listener is registered, the path from a change to the listener's code is a short sequence. A change happens in the model. The Document then notifies the listeners attached to it, and the listener's own code runs. Notice that the component is not the one sending the notification in this path; the Document is.
Finding Where the Result Diverges
When a listener does not react, split the chain into its three steps and check each one. Your goal is to find the first step where what you expected and what the program actually holds disagree. Work through the checks in order, because an error at an early step makes every later step look wrong.
Common mistake Calling addDocumentListener on the text component
What it looks likenameField.addDocumentListener(nameWatcher)Why it failsThe
addDocumentListenermethod belongs to the Document, not to the text component. The chain must first move from the component to its model.Do this insteadnameField.getDocument().addDocumentListener(nameWatcher)Common mistake Assuming the listener is stored on the visible component
Why it failsThe source states that the model is a separate object of type Document. A listener attached this way is attached to the model, so looking for it on the component will not show you the registration.
Do this insteadInspect the object returned by getDocument() when you want to see where the listener lives.
Common mistake Checking only the final step when the listener is silent
Why it failsAn error may be earlier in the chain, such as holding the wrong component reference or receiving an unexpected Document. Checking only the last call hides the real divergence.
Do this insteadCheck the reference, then the result of getDocument(), then the target of addDocumentListener, in that order.
Trace the Chain Yourself
A learner writes the line commentField.getDocument().addDocumentListener(commentWatcher) but the watcher never reacts when text is typed into commentField. Name the object that the listener is registered on, and state which of the three steps you would check first if you believed the reference commentField pointed to the wrong component.
Hints
- Recall that
getDocument()returns the model, andaddDocumentListeneris called on that returned object. - If the reference points to the wrong component, the first step is already wrong, so every later step inherits that error.
Key Takeaways
- A
JTextComponentis the visible view, and its model is a separate object of type Document. - In
textComponent.getDocument().addDocumentListener(listener), the first part names the component,getDocument()returns the Document model, andaddDocumentListeneris called on that Document to register the listener. - A listener attached through a text component is attached to the Document, which notifies it when the model changes.
- When a listener is silent, check the chain step by step and investigate the first step where the actual object differs from what you expected.