移动插入符号时保持 JScrollPane 的 (JTextPane) 滚动条到位?

Keeping a JScrollPane's (JTextPane) scroll bar in place when moving the Caret?

我在 JScrollPane 中有一个 JTextPane,还有一个将插入符号的位置从文档顶部移动到底部的 JButton。

我的目标是在单击按钮时保持滚动条的当前值。我试图通过在移动插入符号之前保存滚动条的值,然后在移动插入符号之后重置滚动条的值来实现这一点。

这是奇怪的部分:如果我在重置滚动条的值之前包含一个 JOptionPane(对话框弹出窗口),它就可以工作。没有弹出对话框,滚动条保留在底部。造成这种差异的对话框是做什么的?

注意:我测试过的一件事是,当对话框打开时,它会将焦点从文本窗格中移开。但是我尝试在移动插入符号后手动将焦点从文本窗格中移开,并且我尝试将所有组件设置为不可聚焦,但这些都没有什么不同。

我被难住了。祝你好运,谢谢!

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 代码在 Event Dispatch Thread (EDT) 上执行。有时某些方法会将代码添加到 EDT 的末尾,因此代码不会按您期望的顺序执行。

我猜 setCaretPosition(...) 方法是这些方法之一,这意味着您重置滚动条值的代码实际上是在导致滚动条滚动的 setCaretPosition() 方法的逻辑之前执行的。

您可以将代码添加到 EDT 的末尾,如下所示:

//scrollBar.setValue(scrollPos);
SwingUtilities.invokeLater(() -> scrollBar.setValue(scrollPos));

将 JOptionPane 代码放入其中会以某种方式影响 EDT 和执行顺序。