JDK-4476714 : DefaultCaret causes spurious scrolling in multi-line text components
  • Type: Bug
  • Component: client-libs
  • Sub-Component: javax.swing
  • Affected Version: 1.3.0
  • Priority: P4
  • Status: Closed
  • Resolution: Duplicate
  • OS: generic
  • CPU: generic
  • Submitted: 2001-06-29
  • Updated: 2003-08-29
  • Resolved: 2003-08-29
Related Reports
Duplicate :  
Relates :  
Description

Name: bsC130419			Date: 06/29/2001


C:\>java -version
java version "1.3.0"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.3.0-C)
Java HotSpot(TM) Client VM (build 1.3.0-C, mixed mode)

Due to the way in which DefaultCaret attempts to scroll itself into view, there
are some unusual behaviors that occur when updating viewports that contain
multi-line text components (such as JTextArea).  The protected method
adjustVisibility(Rectangle nloc) is called when the text area is modified,
which causes the scroll pane to scroll to each text area in the viewport.  This
behavior is especially noticeable when information in the text area changes
asynchronously or programmatically.

The following code illustrates this behavior:
------------------------------8<-------------------------------
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.text.*;

public class ScrollTest extends JFrame implements ChangeListener
{
    JTextArea area1 = new JTextArea("1", 8, 20);
    JTextArea area2 = new JTextArea("2", 8, 20);
    JTextArea area3 = new JTextArea("3", 8, 20);
    JScrollPane scrollPane = new JScrollPane();

    public ScrollTest()
    {
        this.setLocation(100, 100);
        this.setSize(300, 300);

        JPanel panel = new JPanel(new GridBagLayout());
        GridBagConstraints gbc = new GridBagConstraints();
        gbc.gridx = 0;
        gbc.gridy = GridBagConstraints.RELATIVE;
        panel.add(area1, gbc);
        panel.add(area2, gbc);
        panel.add(area3, gbc);

        scrollPane.setViewportView(panel);
        scrollPane.getVerticalScrollBar().getModel().addChangeListener(this);

        this.getContentPane().add(scrollPane, BorderLayout.CENTER);

        JButton button = new JButton("update");
        this.getContentPane().add(button, BorderLayout.SOUTH);
        button.addActionListener(new ActionListener()
        {
            public void actionPerformed(ActionEvent event)
            {
                area1.setText("1\n\n\n\n\n\n\n\n\n\n\n\nWahhoooo!");
                area2.setText("2\n\n\n\n\n\n\n\n\n\n\n\nWahhoooo!");
                area3.setText("3\n\n\n\n\n\n\n\n\n\n\n\nWahhoooo!");
            }
        });
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }

    public void stateChanged(ChangeEvent event)
    {
        System.out.println(scrollPane.getVerticalScrollBar().getModel());
    }

    public static void main(String[] args)
    {
        new ScrollTest().setVisible(true);
    }
}
------------------------------8<-------------------------------
Clicking the button will cause the viewport to scroll such that the last line
of the bottom-most text area is visible.  Note that the individual JTextArea
components are NOT in JScrollPane containers.

The output from the console is this:
javax.swing.DefaultBoundedRangeModel[value=0, extent=243, min=0, max=408,
adj=false]
javax.swing.DefaultBoundedRangeModel[value=0, extent=243, min=0, max=663,
adj=false]
javax.swing.DefaultBoundedRangeModel[value=199, extent=243, min=0, max=663,
adj=false]
javax.swing.DefaultBoundedRangeModel[value=420, extent=243, min=0, max=663,
adj=false]

This shows the problem in its essence.  The last three model changes are caused
by the DefaultCaret class when it detects a change in the text component and
attempts to scroll the viewport so the changed component is visible.  This
wreaks havoc since it will cause the viewport to scroll even if someone is
doing data entry in another field.  The adjustments are asynchronous and
controlled by inaccessible methods in DefaultCaret.

I have come up with two workarounds.  Simplest is to enclose all of the
JTextAreas in a JScrollPane.  This seemed a little draconian, so I tried
another route.  By extending DefaultCaret, I was able to skirt the problem
sufficiently:

------------------------------8<-------------------------------
        area1.setCaret(new NoScrollCaret());
        area2.setCaret(new NoScrollCaret());
        area3.setCaret(new NoScrollCaret());
        .
        .
    class NoScrollCaret extends DefaultCaret
    {
        protected void adjustVisibility(Rectangle rect)
        {
            JTextComponent component = this.getComponent();
            if (component.getParent().getClass() == JViewport.class)
                super.adjustVisibility(rect);
        }
    }
------------------------------8<-------------------------------
In this manner, if the immediate parent of a text component is not a viewport,
the adjustment is not made.
(Review ID: 127325) 
======================================================================

Comments
EVALUATION I'm moving this over to an RFE. I believe the submitter is asking for API to disable automatic scrolling. scott.violet@eng 2001-07-10 Actually, I think the problem is that we are doing automatic scrolling even if our direct parent is not a JViewPort. This can cause odd behavior when there is a JViewPort containing us further up in the containment hierarchy. I'm moving this back to a bug. ###@###.### 2001-11-14 Name: anR10225 Date: 08/29/2003 I saw several examples where JTextArea was put into JPanel and then into JScrollPane and it was expected that scroll pane adjusts visibility on caret movements. So we can't disable adjusting visibility when a text area is not directly in the JViewport due to compatibility reason. There is another mechanism will be introduced which allows to disable DefaultCaret updates and thus scrolling. It is 'updatePolicy' property of the DefaultCaret. See the evaluation of the bug 4201999 for details. This bug will be closed as duplicate of 4201999. ======================================================================
11-06-2004

WORK AROUND Name: bsC130419 Date: 06/29/2001 I have come up with two workarounds. Simplest is to enclose all of the JTextAreas in a JScrollPane. This seemed a little draconian, so I tried another route. By extending DefaultCaret, I was able to skirt the problem sufficiently: ------------------------------8<------------------------------- area1.setCaret(new NoScrollCaret()); area2.setCaret(new NoScrollCaret()); area3.setCaret(new NoScrollCaret()); . . class NoScrollCaret extends DefaultCaret { protected void adjustVisibility(Rectangle rect) { JTextComponent component = this.getComponent(); if (component.getParent().getClass() == JViewport.class) super.adjustVisibility(rect); } } ------------------------------8<------------------------------- In this manner, if the immediate parent of a text component is not a viewport, the adjustment is not made. ======================================================================
11-06-2004