调整文本大小时保持底部的 JScrollPane (JTextPane) 滚动条?

Keeping a JScrollPane's (JTextPane) scroll bar at the bottom when resizing text?

非常奇怪的问题:我在 JScrollPane 中有一个 JTextPane,还有一个用于调整文本大小的 JSlider。如果我增加文本大小,滚动条会向上移动(正常)。我的目标是将滚动条保持在底部,if 它在调整文本大小之前位于底部。奇怪的是,如果我在 ChangeListener 的末尾放入 JOptionPane(对话框),我只能让它工作。如果我不包含对话框(注释掉第 92 行),当增加文本大小时滚动条仍然向上移动,显然忽略了第 100 行。

另一件事:如果我 单击 更改文本大小,对话框 window 会崩溃,弹出多次并搞砸滑块。但是,如果我使用 Tab 到 select 滑块,然后使用 箭头键 上下移动它,对话框 window 正常运行,滚动条将按照我的意愿运行。

所以,这里其实有两个谜团:

  1. 为什么 将滚动条发送到底部的第 100 行似乎只有在其前面有对话框弹出窗口(第 92 行)时才有效?
  2. 为什么 dialog/slider 在使用鼠标移动滑块时会变得很乱,但在使用 Tab+箭头键时却不会?

这快把我逼疯了。祝你好运,谢谢!

import java.awt.*;
import javax.swing.*;
import javax.swing.event.*;



public class ScrollTest extends JFrame {
   
   private JPanel mainPanel;
   private JTextPane textPane;
   private JScrollPane scrollPane;
   private JScrollBar scrollBar;
   private JSlider textSlider;
   private JFrame dialogFrame;
   
   public static void main(String[] args) {
   
      java.awt.EventQueue.invokeLater(
         new Runnable() {
            public void run() {
            
               ScrollTest test = new ScrollTest();
               test.pack();
               test.setLocationRelativeTo(null);
               test.setVisible(true);
            }
         });
   }
   
   public ScrollTest() {
   
      super("Scroll Test");
      this.setDefaultCloseOperation(EXIT_ON_CLOSE);
      
      mainPanel = new JPanel();
      mainPanel.setPreferredSize(new Dimension(250, 100));
      this.add(mainPanel);
      
      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, 10));
      
      scrollPane = new JScrollPane(textPane);
      scrollPane.setPreferredSize(new Dimension(80, 80));
      mainPanel.add(scrollPane);
      
      scrollBar = scrollPane.getVerticalScrollBar();
      
      textSlider = new JSlider(10, 16, 10);
      textSlider.setMajorTickSpacing(2);
      textSlider.setPreferredSize(new Dimension(120, 40));
      textSlider.setPaintLabels(true);
      textSlider.setSnapToTicks(true);
      mainPanel.add(textSlider);
      
      dialogFrame = new JFrame();
      
      textSlider.addChangeListener(
         new ChangeListener() { 
            public void stateChanged(ChangeEvent e) {
               
               /**
                *  Determine whether the scroll bar is at the bottom.
                *  Must be done this way because getValue() returns 
                *  the position at the top of the knob.
                */
               int value = scrollBar.getValue();
               int max = scrollBar.getMaximum() - scrollBar.getVisibleAmount();
               boolean startedAtBottom = value == max;
               
               /**
                *  Resize the text.
                *  Increasing the text size moves the scroll bar up.
                */
               switch (textSlider.getValue()) {
                  case 10: textPane.setFont(textPane.getFont().deriveFont(10f));
                     break;
                  case 12: textPane.setFont(textPane.getFont().deriveFont(12f));
                     break;
                  case 14: textPane.setFont(textPane.getFont().deriveFont(14f));
                     break;
                  case 16: textPane.setFont(textPane.getFont().deriveFont(16f));
                     break;               
               }
               
               /**
                *  IMPORTANT: line 92 seems to determines whether line 100
                *  works properly (for some reason).
                *  Comment out line 92 to test.
                */
               JOptionPane.showMessageDialog(dialogFrame, "test", "dialog", JOptionPane.PLAIN_MESSAGE);
               
               /**
                *  Send the scroll bar to the bottom ONLY if it was at 
                *  the bottom before the text was resized.
                *  If line 92 is commented out, this doesn't work when 
                *  increasing text size.
                */
               if (startedAtBottom) scrollBar.setValue(scrollBar.getMaximum());
            }
         });
   }
}

我修改了您的代码以创建以下 GUI。

我所做的主要更改是在 stateChanged 方法中。我添加了滑块停止移动和 JFrame 包的测试。 JFrame 包确保所有 Swing 组件都已正确调整大小。

这是完整的可运行代码。

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Font;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollBar;
import javax.swing.JScrollPane;
import javax.swing.JSlider;
import javax.swing.JTextPane;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

public class ScrollTest implements Runnable {

    public static void main(String[] args) {
        EventQueue.invokeLater(new ScrollTest());
    }
    
    private JFrame frame;

    private JScrollBar scrollBar;
    
    private JTextPane textPane;

    @Override
    public void run() {
        frame = new JFrame("Scroll Test");
        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, 10));

        JScrollPane scrollPane = new JScrollPane(textPane);
        scrollPane.setPreferredSize(new Dimension(80, 80));
        mainPanel.add(scrollPane);

        scrollBar = scrollPane.getVerticalScrollBar();

        JSlider textSlider = new JSlider(10, 16, 10);
        textSlider.setMajorTickSpacing(2);
        textSlider.setPreferredSize(new Dimension(120, 40));
        textSlider.setPaintLabels(true);
        textSlider.setSnapToTicks(true);
        textSlider.addChangeListener(new FontSizeListener());
        mainPanel.add(textSlider);

        return mainPanel;
    }

    public class FontSizeListener implements ChangeListener {

        @Override
        public void stateChanged(ChangeEvent event) {
            JSlider slider = (JSlider) event.getSource();
            
            if (!slider.getValueIsAdjusting()) {
                /**
                 * Determine whether the scroll bar is at the bottom. Must be done this way
                 * because getValue() returns the position at the top of the knob.
                 */
                int value = scrollBar.getValue();
                int max = scrollBar.getMaximum() - scrollBar.getVisibleAmount();
                boolean startedAtBottom = value == max;

                /**
                 * Resize the text. Increasing the text size moves the scroll bar up.
                 */
                textPane.setFont(textPane.getFont().deriveFont((float) slider.getValue()));
                frame.pack();

                /**
                 * Send the scroll bar to the bottom ONLY if it was at the bottom before the
                 * text was resized. If line 92 is commented out, this doesn't work when
                 * increasing text size.
                 */
                if (startedAtBottom) {
                    max = scrollBar.getMaximum() + scrollBar.getVisibleAmount();
                    scrollBar.setValue(max);
                }
            }
            
        }

    }

}