I have a JTextPane within a JScrollPane, and a JButton that moves the position of the caret from the top of the document to the bottom.
My goal is to maintain the scroll bar's current value when clicking the button. I'm attempting to do this by saving the scroll bar's value before moving the caret, and then resetting the scroll bar's value after moving the caret.
Here's the weird part: If I include a JOptionPane (dialog popup) right before resetting the scroll bar's value, it works. Without the dialog popup, the scroll bar remains at the bottom. What is the dialog doing that accounts for this difference?
Note: One thing I tested is that when the dialog opens, it takes the focus off of the text pane. But I tried manually moving the focus off of the text pane after moving the caret, AND I tried setting all of the components to non-focusable, and neither of those made a difference.
I'm stumped. Good luck and thanks!
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JOptionPane;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollBar;
import javax.swing.JScrollPane;
import javax.swing.JTextPane;
import javax.swing.text.DefaultCaret;
public class ScrollTest2 implements Runnable {
public static void main(String[] args) {
EventQueue.invokeLater(new ScrollTest2());
}
private JFrame frame;
private JScrollBar scrollBar;
private JTextPane textPane;
@Override
public void run() {
frame = new JFrame("Scroll Test 2");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(createMainPanel(), BorderLayout.CENTER);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
private JPanel createMainPanel() {
JPanel mainPanel = new JPanel();
mainPanel.setPreferredSize(new Dimension(250, 100));
textPane = new JTextPane();
textPane.setEditable(false);
textPane.setText("hello\nhello\nhello\nhello\nhello\nhello\nhello\nhello\nhello\nhello");
textPane.setFont(new Font("Courier New", Font.BOLD, 12));
JScrollPane scrollPane = new JScrollPane(textPane);
scrollPane.setPreferredSize(new Dimension(80, 80));
mainPanel.add(scrollPane);
scrollBar = scrollPane.getVerticalScrollBar();
JButton button = new JButton("Click me");
button.addActionListener(new ButtonListener());
mainPanel.add(button);
return mainPanel;
}
public class ButtonListener implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
// Get the scroll bar's position
int scrollPos = scrollBar.getValue();
// The scroll bar is sent to the bottom.
textPane.setCaretPosition(0);
textPane.setCaretPosition(textPane.getDocument().getLength());
/**
* Comment out this line to test.
* When this line is present, the scroll bar is set to its original position (correct).
* When this line is commented out, the scroll bar remains at the bottom (incorrect).
*/
JOptionPane.showMessageDialog(frame, "test", "dialog", JOptionPane.PLAIN_MESSAGE);
// Set scroll bar to its original position.
scrollBar.setValue(scrollPos);
}
}
}
Swing code executes on the Event Dispatch Thread (EDT)
. Sometimes some methods will add code to the end of the EDT so the code doesn't execute in the order you expect.
I'm guessing the setCaretPosition(...)
method is one of these methods which means your code to reset the scrollbar value is actually executed before the logic from the setCaretPosition() method which causes the scrollbar to scroll.
You can add code to the end of the EDT as follows:
//scrollBar.setValue(scrollPos);
SwingUtilities.invokeLater(() -> scrollBar.setValue(scrollPos));
Throwing the JOptionPane code in there somehow affects the EDT and the order of execution.