【问题标题】:Java JTextPane + JScrollPane: de/activate automatic scrollingJava JTextPane + JScrollPane:取消/激活自动滚动
【发布时间】:2012-01-09 13:47:34
【问题描述】:

我目前正在用 Java 编写一个简单的聊天,目前我遇到了这个问题: 我希望我的输出 JTextPane 的行为就像您期望它从良好的聊天中一样,即默认情况下,当新文本到达时文本会自动滚动(使用 outputfield.setCaretPosition(outputdocument.getLength())),但是当用户向上滚动时应该禁用,当然当用户再次滚动到底部时重新启用。

我试着玩弄 ScrollBars 和所有东西,但我似乎找不到检测 ScrollBar 是否在底部的方法。

我通过创建一个 JTextPane、一个 JScrollPane 并将一个放入另一个来创建可滚动的输出区域。

JTextPane outputpane = new JTextPane
...
JScrollPane outputscroll = new JScrollPane(outputpane);
outputscroll.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);

编辑:这是我用来创建组件和显示新消息的代码:

// lots of imports


public class RoomFrame extends JFrame implements ListSelectionListener, ActionListener, Comparable, Runnable
{   
public static final String DEFAULT_FONT = "Courier";
public static final int DEFAULT_FONT_SIZE = 12;

private JTextPane mOutputField;
private JScrollPane mOutputScroll;

private StyledDocument mOutputDocument;


public RoomFrame(...)
{
    super(...);

    createGUI();
    setOutputStyles();
    setVisible(true);

    new Thread(this).start();
}

// ========================================================

private void createGUI()
{
    Color borderhighlightouter = new Color(220, 220, 220);
    Color borderhighlightinner = new Color(170, 170, 170);
    Color bordershadowouter = new Color(120, 120, 120);
    Color bordershadowinner = new Color(170, 170, 170);

    setLayout(new GridBagLayout());
    setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
    setSize(500, 400);


    // -----------------------------
    // Output
    mOutputField = new JTextPane();
    mOutputField.setEditable(false);
    mOutputField.setBackground(new Color(245, 245, 245));
    mOutputDocument = mOutputField.getStyledDocument();

    mOutputScroll = new JScrollPane(mOutputField);

    mOutputScroll.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
    mOutputScroll.setPreferredSize(new Dimension(250, 250));
    mOutputScroll.setMinimumSize(new Dimension(50, 50));
    mOutputScroll.setOpaque(false);
    mOutputScroll.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createEmptyBorder(0, 0, 1, 0), BorderFactory.createBevelBorder(BevelBorder.LOWERED, borderhighlightouter, borderhighlightinner, bordershadowouter, bordershadowinner)));

    getContentPane().add(mOutputScroll);
}

private void setOutputStyles()
{
    Style def = StyleContext.getDefaultStyleContext().getStyle(StyleContext.DEFAULT_STYLE);

    Style regular = mOutputDocument.addStyle("regular", def);
    StyleConstants.setFontFamily(def, DEFAULT_FONT);
    StyleConstants.setFontSize(def, DEFAULT_FONT_SIZE);
    StyleConstants.setFirstLineIndent(def, 100.0f);

    Style nickname = mOutputDocument.addStyle("username", def);
    StyleConstants.setBold(nickname, true);
    StyleConstants.setForeground(nickname, new Color(50, 50, 220));

    Style highlight = mOutputDocument.addStyle("highlight", def);
    StyleConstants.setBold(highlight, true);
    StyleConstants.setBackground(highlight, new Color(150, 0, 0));

    Style join = mOutputDocument.addStyle("join", def);
    StyleConstants.setBold(join, true);
    StyleConstants.setForeground(join, new Color(20, 100, 20));

    Style leave = mOutputDocument.addStyle("leave", def);
    StyleConstants.setBold(leave, true);
    StyleConstants.setForeground(leave, new Color(100, 100, 20));

    Style topic = mOutputDocument.addStyle("topic", def);
    StyleConstants.setBold(topic, true);
    StyleConstants.setUnderline(topic, true);

    Style error = mOutputDocument.addStyle("error", def);
    StyleConstants.setBold(error, true);
    StyleConstants.setForeground(error, new Color(255, 0, 0));

    Style kick = mOutputDocument.addStyle("kick", def);
    StyleConstants.setBold(kick, true);
    StyleConstants.setForeground(kick, new Color(150, 0, 0));
}

private final boolean shouldScroll()
{
    int min = mOutputScroll.getVerticalScrollBar().getValue() + mOutputScroll.getVerticalScrollBar().getVisibleAmount();
    int max = mOutputScroll.getVerticalScrollBar().getMaximum();

    return min == max;
}

// ========================================================

public void displayMessage(String message)
{
    displayMessage(message, "");
}

public void displayMessage(String message, String style)
{
    displayMessage(message, style, true);
}

public void displayMessage(String message, String style, boolean addnewline)
{
    String newline = (addnewline ? "\n" : "");

    style = (style.equals("") ? "regular" : style);
    message = message.replace("\n", " ");

    // check for highlight

    try
    {
        mOutputDocument.insertString(mOutputDocument.getLength(),
                                        String.format("%s%s", message, newline),
                                        mOutputDocument.getStyle(style));
    }
    catch (Exception e) {}

    // if (shouldScroll())
    //  mOutputField.setCaretPosition(mOutputDocument.getLength());
}

public void run()
{
    while (true)
    {
        if (shouldScroll())
        {
            SwingUtilities.invokeLater(
                new Runnable()
                {
                    public void run()
                    {
                        mOutputScroll.getVerticalScrollBar().setValue(mOutputScroll.getVerticalScrollBar().getMaximum());
                    }
                });
        }

        try { Thread.sleep(500); }
        catch (InterruptedException e) { break; }
    }
}

public void valueChanged(ListSelectionEvent event)
{

}

public void actionPerformed(ActionEvent event)
{

}

public final int compareTo(Object o) { return this.toString().compareTo(o.toString()); }
}


编辑:感谢 fireshadow52 与 another similar question 的链接,我终于让它完全按照我想要的方式工作:

private boolean isViewAtBottom()
{
    JScrollBar sb = mOutputScroll.getVerticalScrollBar();
    int min = sb.getValue() + sb.getVisibleAmount();
    int max = sb.getMaximum();
    System.out.println(min + " " + max);
    return min == max;
}

private void scrollToBottom()
{
    SwingUtilities.invokeLater(
        new Runnable()
        {
            public void run()
            {
                mOutputScroll.getVerticalScrollBar().setValue(mOutputScroll.getVerticalScrollBar().getMaximum());
            }
        });
}

public void displayMessage(String message, String style, boolean prependnewline)
{
    String newline = (prependnewline ? "\n" : "");
    boolean scroll = isViewAtBottom() && prependnewline;

    style = (style.equals("") ? "regular" : style);
    message = message.replace("\n", " ");

    try
    {
        mOutputDocument.insertString(mOutputDocument.getLength(),
                                        String.format("%s%s", newline, message),
                                        mOutputDocument.getStyle(style));
    }
    catch (Exception e) {}

    if (scroll)
        scrollToBottom();
}

再次感谢您的帮助!

【问题讨论】:

  • 你的意思是自动滚动到底部???
  • 如你所说, setCaretPosition(Component.getDocument().getLength());会做的工作。每次有新数据进入时只需调用它。否则首先我尝试使用这种技术:stackoverflow.com/questions/8445413/…,这很好,但不如你上面已经提到的简单文本行
  • 我的意思是当 ScrollBar 位于底部时,文本才应该滚动。如果您只使用 setCaretPosition(Component.getDocument().getLength());即使用户向上滚动,它也会一直滚动到底部
  • 看看this question
  • 天哪,谢谢!终于成功了!

标签: java swing jtextpane scrollpane


【解决方案1】:

如果您只想在底部时滚动,那么这个应该提供帮助。

使用此方法检查滚动条是否在末尾(或底部),如果是,则使用setCaretPosition(Component.getDocument().getLength());自动向下滚动:

public boolean shouldScroll() {
    int minimumValue = scrollPane.getVerticalScrollBar().getValue() + scrollPane.getVerticalScrollBar().getVisibleAmount();
    int maximumValue = scrollPane.getVerticalScrollBar().getMaximum();
    return maximumValue == minimumValue;
}

我在使用 Google 时发现了类似的结果,这使我找到了一种与此类似的方法,它按要求工作。

编辑:确保它在invokeLater() 内完成,因为它需要在滚动完成之前更新。

我使用的完整示例:

public class ScrollTest extends Thread {

    private JFrame frame;

    private JTextArea textArea;

    private JScrollPane scrollPane;

    public static void main(String[] arguments) {
            new ScrollTest().run();
    }

    public ScrollTest() {
            textArea = new JTextArea(20, 20);
            scrollPane = new JScrollPane(textArea);

            frame = new JFrame("Test");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.add(scrollPane);
            frame.pack();
            frame.setVisible(true);
    }

    public void run() {
            while (true) {
                    textArea.append("" + Math.random() + "\n");
                    if (shouldScroll()) {
                            SwingUtilities.invokeLater(new Runnable() {

                                    @Override
                                    public void run() {
                                            scrollPane.getVerticalScrollBar().setValue(scrollPane.getVerticalScrollBar().getMaximum());
                                    }

                            });
                    }
                    try {
                            Thread.sleep(500);
                    } catch (Exception exception) {
                            exception.printStackTrace();
                    }
            }
    }

    public boolean shouldScroll() {
            int minimumValue = scrollPane.getVerticalScrollBar().getValue() + scrollPane.getVerticalScrollBar().getVisibleAmount();
            int maximumValue = scrollPane.getVerticalScrollBar().getMaximum();
            return maximumValue == minimumValue;
    }

}

【讨论】:

  • 这似乎也不起作用。如果我像if (shouldScroll()) mOutputField.setCaretPosition(mOutputDocument.getLength()); 一样使用它,它根本不会滚动。我是否必须手动设置滚动条的最大值?编辑:嗯,我应该在哪里使用 invokeLater()?我还是新手。
  • 我已经更新了我的帖子以包含我使用的示例,它工作正常。
  • 现在它不会显示任何文本。我想这可能是因为。)我使用 JTextPane 和。)方法 displayMessage(...) 使用mOutputDocument.insertString(mOutputDocument.getLength(), String.format("%s%s", message, newline), mOutputDocument.getStyle(style)); 来显示新文本?编辑:好的,问题是因为它没有在线程中运行。现在它显示了文本,但仍然没有像它应该的那样滚动......
  • 我认为这是因为您使用了 JTextPane。我用textArea.getDocument().insertString(textArea.getDocument().getLength(), "" + Math.random() + "\n", null); 替换了textArea.append(...),它仍然可以正常工作。您介意发布确切您正在使用的内容吗?
  • 我编辑了这篇文章。它仅包含与新文本输出相关的内容。
【解决方案2】:

JScrollBar 有 getValue() 方法。只需在 SwingUtilities.invokeLater() 中对其进行变形,以便在添加文本后调用。

【讨论】:

  • 我不太明白这对我的问题有什么帮助
  • 在添加内容之前存储垂直滚动条的值并在添加后将其设置回来。
【解决方案3】:

您可以使用AdjustmentListener 来调节滚动,如example 所示。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2010-11-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-01-09
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多