【问题标题】:Scrolling limitation with JScrollPane and JViewport maximum sizes smaller than contentsJScrollPane 和 JViewport 最大尺寸小于内容的滚动限制
【发布时间】:2014-09-04 21:03:32
【问题描述】:

我有一个JFrame,其中包含一个JScrollPane,其中包含一个JPanelJPanel 包含一堆 JTextAreas。 我正在向其中加载大量文本(大约 8k-10k 个字符)。

布局工作正常,虽然滚动有点滞后。

真正的问题是,JPanelJScrollPaneJViewport 似乎有一个硬性 32767 大小限制,所以当任何JTextArea 增长到高于该大小时,它就不能再滚动以显示文本的最后 1/3。

您可以在下面看到该问题的一个最小示例。我使用了 NetBeans JFrame 设计器,所以它可能有点长,但我唯一改变默认值的是 JTextAreas 是 JPanel 的直接子代,滚动条策略和稍大的字体大小:

public class NewJFrame extends javax.swing.JFrame {

/**
 * Creates new form NewJFrame
 */
public NewJFrame() {
    initComponents();
}

/**
 * This method is called from within the constructor to initialize the form.
 * WARNING: Do NOT modify this code. The content of this method is always
 * regenerated by the Form Editor.
 */
@SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">                          
private void initComponents() {

    jScrollPane1 = new javax.swing.JScrollPane();
    jPanel1 = new javax.swing.JPanel();
    jTextArea1 = new javax.swing.JTextArea();
    jTextArea2 = new javax.swing.JTextArea();

    setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);

    jScrollPane1.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
    jScrollPane1.setVerticalScrollBarPolicy(javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);

    jTextArea1.setColumns(20);
    jTextArea1.setFont(new java.awt.Font("Monospaced", 0, 14)); // NOI18N
    jTextArea1.setRows(5);

    jTextArea2.setColumns(20);
    jTextArea2.setFont(new java.awt.Font("Monospaced", 0, 14)); // NOI18N
    jTextArea2.setRows(5);
    jTextArea2.setText("Some long text...");

    javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1);
    jPanel1.setLayout(jPanel1Layout);
    jPanel1Layout.setHorizontalGroup(
        jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
        .addGroup(jPanel1Layout.createSequentialGroup()
            .addComponent(jTextArea1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addComponent(jTextArea2, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
            .addGap(0, 0, 0))
    );
    jPanel1Layout.setVerticalGroup(
        jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
        .addGroup(jPanel1Layout.createSequentialGroup()
            .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                .addComponent(jTextArea1, javax.swing.GroupLayout.DEFAULT_SIZE, 342, Short.MAX_VALUE)
                .addComponent(jTextArea2))
            .addGap(0, 0, 0))
    );

    jScrollPane1.setViewportView(jPanel1);

    javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
    getContentPane().setLayout(layout);
    layout.setHorizontalGroup(
        layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
        .addComponent(jScrollPane1)
    );
    layout.setVerticalGroup(
        layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
        .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 96, Short.MAX_VALUE)
    );

    pack();
}// </editor-fold>                        

/**
 * @param args the command line arguments
 */
public static void main(String args[]) {
    final NewJFrame f = new NewJFrame();
    /* Set the Nimbus look and feel */
    //<editor-fold defaultstate="collapsed" desc=" Look and feel setting code (optional) ">
    /* If Nimbus (introduced in Java SE 6) is not available, stay with the default look and feel.
     * For details see http://download.oracle.com/javase/tutorial/uiswing/lookandfeel/plaf.html 
     */
    try {
        for(javax.swing.UIManager.LookAndFeelInfo info : javax.swing.UIManager.getInstalledLookAndFeels())
            if("Nimbus".equals(info.getName())) {
                javax.swing.UIManager.setLookAndFeel(info.getClassName());
                break;
            }
    }catch(ClassNotFoundException ex) {
        java.util.logging.Logger.getLogger(NewJFrame.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
    }catch(InstantiationException ex) {
        java.util.logging.Logger.getLogger(NewJFrame.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
    }catch(IllegalAccessException ex) {
        java.util.logging.Logger.getLogger(NewJFrame.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
    }catch(javax.swing.UnsupportedLookAndFeelException ex) {
        java.util.logging.Logger.getLogger(NewJFrame.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
    }
    //</editor-fold>

    /* Create and display the form */
    java.awt.EventQueue.invokeLater(new Runnable() {
        @Override
        public void run() {
            f.setVisible(true);
        }
    });
    StringBuilder txt = new StringBuilder();
    for(int i=0; i<10000; i++)
        txt.append(i).append("\n");
    f.jTextArea1.setText(txt.toString());
    txt = new StringBuilder();
    txt.append("JTextArea height: ").append(f.jTextArea1.getHeight()).append('\n');
    txt.append("JTextArea rows: ").append(f.jTextArea1.getRows()).append('\n');
    txt.append("JScrollPane height:").append(f.jScrollPane1.getHeight()).append('\n');
    txt.append("JViewport height:").append(f.jScrollPane1.getViewport().getHeight()).append('\n');
    txt.append("JPanel height:").append(f.jPanel1.getHeight()).append('\n');
    f.jTextArea2.setText(txt.toString());
}
// Variables declaration - do not modify                     
private javax.swing.JPanel jPanel1;
private javax.swing.JScrollPane jScrollPane1;
private javax.swing.JTextArea jTextArea1;
private javax.swing.JTextArea jTextArea2;
// End of variables declaration                   
}

如果你运行这个并滚动到底部,你会看到计数达到 10 000,但它只达到 1637,你可以看到下一行的顶部像素几乎没有出现。

我已经在JPanelJScrollPane 和它的JViewport 上尝试了setMaximumSizesetSize,但没有任何改变。我也有点困惑,即使有 10k 行文本,其中一些可以滚动到足以查看,getRows()getSize() 方法返回原始值。

当我想要一个大于 32767 的可滚动JTextArea 时,处理这种情况的正确方法是什么?

【问题讨论】:

  • 我不敢相信 JViewport 会有这样的限制。我开发了一个有一百万行的表格,它可以很好地滚动。请提供一个工作示例以获得更好的帮助。
  • 根据Dimension的要求,我想硬限制将接近Interger.MAX_VALUE
  • 你可以使用这个approach,它利用了JTable flyweight 渲染;根据需要使用自定义或弹出单元格编辑器。
  • @SergiyMedvynskyy 和@MadProgrammer,我也这么认为,JTextArea 默认最大值为Integer.MAX_VALUE。但是,出于某种原因,JPanelJScrollPane 的值是 32767。在任何时候尝试更改它们的最大尺寸或当前尺寸对我来说根本没有任何区别。
  • @trashgod 我正在加载的文件不是很大(JScrollPane,所以我不知道这将如何避免这个问题。无论哪种方式,我也想找出我遇到问题的原因,就像我想找到解决方案一样。

标签: java swing jpanel jscrollpane jviewport


【解决方案1】:

您的示例未正确同步,因为它更新了initial thread 上的jTextArea2。请注意,JTextArea#setText() 不再是线程安全的。下面的示例调用EditorKit#read(),正如here 建议的那样,加载相同的27 MB、176 K 行文件here。这需要几秒钟,大约是 JTable 方法的两倍,但滚动是相当的。

import java.awt.EventQueue;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;

/**
 * @see https://stackoverflow.com/a/25691384/230513
 */
public class Test {

    private static final String NAME = "/var/log/install.log";

    private void display() {
        JFrame f = new JFrame("Test");
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        JTextArea text = new JTextArea(24, 32);
        try {
            text.read(new BufferedReader(new FileReader(NAME)), null);
        } catch (IOException ex) {
            ex.printStackTrace(System.err);
        }
        f.add(new JScrollPane(text));
        f.pack();
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(() -> {
            new Test().display();
        });
    }
}

【讨论】:

  • 很抱歉,但这与我正在尝试做的事情或我给出的示例几乎没有关系。我看不出线程安全是如何重置JTextArea 中的文本的问题——我根本没有使用append()。即使我是,我也只有主线程写入JTextArea,所以我不明白为什么会有同步问题。您的示例没有表现出我的问题,但它也缺少我所拥有的布局,所以这并没有真正的帮助。
  • 糟糕,append() 是对的,但 setText() 也有同样的问题;更新。你可以使用任何你想要的布局。该示例显示滚动窗格可以处理比“大约 8k-10k 个字符”更多的文本。
  • +1 表示SSCCE,没有所有生成的代码。 but it also lacks the layout I have 布局是问题。谁知道 GroupLayout 在做什么(我当然不知道)?你为什么要创建一个带有滚动窗格/面板/文本区域的结构?通常,文本区域直接添加到滚动窗格中。摆脱所有组布局代码和面板。然后只需将文本区域添加到滚动窗格的视口。然后将滚动窗格添加到 BorderLayout.WEST,将第二个文本区域添加到 BorderLayout.EAST。
  • @nwod, I'm not using append() at all 为什么不呢?它的效率要高得多(除非您像垃圾神那样直接阅读文本区域)。首先使用您的方法创建缓冲区并通过将大量文本附加到缓冲区来浪费内存。然后 setText() 方法将解析所有这些文本以寻找新的行字符串。
  • @camickr It is far more efficient 怎么样?无论如何,Java中的字符串连接都是用StringBuilder实现的。即使我只是做了str+"ing",编译器也会隐式地从锤子空间中拉出一个来进行连接。如果你看一下,你会发现JTextArea 中的默认Document 实现为每个insertString() 调用显式地创建了一个,这就是JTextArea#append() 最终所做的。我使用StringBuilder,因为它对我来说更方便,但在这种情况下它也更有效,因为它避免了额外的函数调用和实例。
【解决方案2】:

这个问题与线程安全无关。经过大量挖掘,我发现 JPanel 的外观和感觉的底层窗口管理器实现有一个硬编码的 32767 大小限制,所以它位于 JScrollPane 中并不重要。这个想法是为了避免对个人 JScrollPanes 进行大量额外管理,但限制使其不可避免。

JPanel 的内容移动到他们自己的JscrollPanes 中解决了这个问题。由于我不知道的原因,滚动仍然有点滞后。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-09-30
    • 1970-01-01
    • 2016-10-27
    • 2018-07-20
    • 2017-09-08
    • 1970-01-01
    相关资源
    最近更新 更多