Ограничение прокрутки с максимальными размерами JScrollPane и JViewport меньше, чем содержимое

У меня есть JFrame, содержащий JScrollPane, содержащий JPanel. JPanel содержит кучу JTextArea. Я загружаю в них много текста (около 8-10 тысяч символов).

Макет работает нормально, хотя прокрутка немного тормозит.

Реальная проблема заключается в том, что кажется, что JPanel, JScrollPane и JViewport имеют жесткий предел размера 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, и вы можете видеть, что верхние пиксели из следующей строки едва отображаются.

Я уже пробовал setMaximumSize и setSize на JPanel, JScrollPane и его JViewport, но ничего не изменилось. Я также несколько смущен тем, что, несмотря на наличие 10 тыс. Строк текста, некоторые из которых можно прокрутить достаточно далеко для просмотра, методы getRows() и getSize() возвращают исходные значения.

Как правильно справиться с этой ситуацией, когда я хочу, чтобы прокручиваемый JTextArea был больше 32767?


person nwod    schedule 04.09.2014    source источник
comment
Я не могу поверить, что JViewport может иметь такое ограничение. Я разработал таблицу с миллионом строк, и ее можно было очень хорошо прокручивать. Пожалуйста, предоставьте рабочий пример для лучшей помощи.   -  person Sergiy Medvynskyy    schedule 05.09.2014
comment
Я предполагаю, что жесткий предел будет близок к Interger.MAX_VALUE, исходя из требований Dimension   -  person MadProgrammer    schedule 05.09.2014
comment
Вы можете использовать этот подход, который использует JTable легковесный рендеринг; при необходимости используйте настраиваемый или всплывающий редактор ячеек.   -  person trashgod    schedule 05.09.2014
comment
@SergiyMedvynskyy и @MadProgrammer, я тоже так думал, и JTextArea максимальные значения по умолчанию равны Integer.MAX_VALUE. Однако по какой-то причине значения JPanel и JScrollPane равны 32767. Попытка изменить максимальный или текущий размер в любой момент для меня не имела никакого значения.   -  person nwod    schedule 05.09.2014
comment
@trashgod Я загружаю не очень большой файл (‹20 КБ), просто в нем много строк. Я не уверен, что таблица подойдет для того, что я пытаюсь сделать, но даже в этом случае предлагаемый подход также помещает ее в JScrollPane, поэтому я не понимаю, как это поможет избежать проблемы. В любом случае, я также хотел выяснить, почему я сталкиваюсь с проблемой, так же как я хочу найти решение.   -  person nwod    schedule 05.09.2014
comment
@SergiyMedvynskyy, рабочий пример предоставлен.   -  person nwod    schedule 05.09.2014


Ответы (2)


Ваш пример неправильно синхронизирован, поскольку он обновляет jTextArea2 в начальном потоке . Обратите внимание, что JTextArea#setText() больше не является потокобезопасным. В приведенном ниже примере вызывается EditorKit#read(), как предлагается здесь, для загрузки того же исследованного строчного файла размером 27 МБ, 176 КБ здесь. Это занимает несколько секунд, примерно в два раза дольше, чем подход 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();
        });
    }
}
person trashgod    schedule 05.09.2014
comment
Мне очень жаль, но это не имеет ничего общего с тем, что я пытаюсь сделать, или с примером, который я привел. Я не понимаю, почему потокобезопасность является проблемой для сброса текста в JTextArea - я вообще не использую append(). Даже если бы я был, у меня только основной поток записывает в JTextArea, поэтому я не понимаю, почему возникла проблема с синхронизацией. В вашем примере нет моей проблемы, но в нем также отсутствует макет, который у меня есть, так что это не очень полезно. - person nwod; 06.09.2014
comment
Ой, ты прав насчет append(), но у setText() та же проблема; обновлено. Вы можете использовать любой макет, какой захотите. Пример показывает, что панель прокрутки может обрабатывать значительно больше текста, чем примерно 8-10 тысяч символов. - person trashgod; 06.09.2014
comment
+1 за SSCCE без всего сгенерированного кода. but it also lacks the layout I have Проблема в макете. Кто знает, что делает GroupLayout (я точно не знаю)? Зачем вам когда-либо создавать структуру с областью прокрутки / панелью / текстовым полем? Обычно текстовая область добавляется непосредственно в область прокрутки. Избавьтесь от кода макета группы и панели. Затем просто добавьте текстовую область в область просмотра области прокрутки. Затем добавьте панель прокрутки в BorderLayout.WEST и вторую текстовую область в BorderLayout.EAST. - person camickr; 06.09.2014
comment
@nwod, I'm not using append() at all а почему бы и нет? Это намного эффективнее (если вы не читаете прямо в текстовой области, как продемонстрировал trashgod). Используя свой подход, вы сначала создаете буфер и тратите впустую память, добавляя в буфер большие объемы текста. Затем метод setText () проанализирует весь этот текст в поисках новых строк. - person camickr; 06.09.2014
comment
@camickr It is far more efficient как? Конкатенация строк в java в любом случае реализована с помощью StringBuilder. Даже если бы я просто сделал str+"ing", компилятор неявно вытащил бы один из молотков, чтобы выполнить конкатенацию. Если вы посмотрите, то обнаружите, что реализация Document по умолчанию в JTextArea создает один явно для каждого insertString() вызова, что JTextArea#append() в конечном итоге и делает. Я использую StringBuilder, потому что это более удобно для меня, но в этом случае он также более эффективен, поскольку позволяет избежать дополнительных вызовов функций и экземпляров. - person nwod; 06.09.2014
comment
@camickr Кроме того, JTextArea#setText() не анализирует весь этот текст в поисках новых строк. На самом деле это просто метод суперкласса JTextComponent#setText(), который вызывает методы insertString() или replace() базовой модели документа. - person nwod; 06.09.2014
comment
@trashgod Мне очень жаль, но я до сих пор не понимаю, насколько потокобезопасность является проблемой. В любом случае, я попытался вызвать все методы swing в потоке AWT и реализовать их по-вашему, но проблема не устранена. Я, очевидно, не могу использовать любой макет, который хочу, потому что моя проблема вызывает. Я был бы очень признателен, если бы вы могли показать мне свое решение на моем примере, потому что, как мне кажется, это действительно проблема компоновки, а не потокобезопасность. По разным причинам мне нужно несколько JTextArea, которые прокручиваются вместе, и я хочу выяснить, почему мой макет, созданный NetBeans, нарушает прокрутку JScrolPane. - person nwod; 06.09.2014
comment
@nwod I obviously can't use whatever layout I want because mine is causing the problem. да, можно. Вы создаете графический интерфейс, и существует несколько подходов к созданию макета. Ваш текущий подход в настоящее время не изменяет размеры компонентов так, как вы ожидаете, поэтому макет неправильный. Способ решить проблему - исправить макет. - person camickr; 06.09.2014
comment
@nwod, это неэффективно, потому что если вы читаете большой файл, у вас есть две копии файла в памяти. Один в StringBuffer и один в текстовой области Document. Если вы загружаете документ по одной строке за раз, у вас будет только одна копия документа. because it is more convenient for me, Как это удобнее? Вместо использования метода append(...) в буфере вы используете его в текстовой области. Это меньше кода, потому что вам не нужно создавать буфер или использовать метод setText (). Мне кажется, что меньше кода удобнее. - person camickr; 06.09.2014
comment
@camickr предлагает очень практичный подход к правильной потоковой передаче; аналогичное мнение приводится здесь. Как только вы исправите резьбу, вы можете сосредоточиться на макете. В моем примере используется значение по умолчанию BorderLayout.CENTER, но GridLayout также будет работать. Если вы хотите повозиться со свойствами генерации кода редактора графического интерфейса, ограничьте объем проблемы, как показано здесь, и рассмотрите вложенные макеты. - person trashgod; 06.09.2014
comment
@camickr Извините, вы, кажется, сбиваете с толку мой минимальный рабочий пример, демонстрирующий мою проблему с фактическим приложением, которое я пишу. Мне удобнее использовать StringBuiolder, потому что я выполняю дополнительную обработку файла, который является двоичным файлом, прежде чем помещать что-либо в JTextAreas. В любом случае у меня будет копия необработанного файла в памяти, потому что мне нужно выполнить с ним другую обработку. Опять же, это небольшой файл, он просто должен отображать много строк в графическом интерфейсе. - person nwod; 07.09.2014
comment
@camickr Я задал свой вопрос, надеясь узнать, почему мой макет не работает. Вместо этого я получаю совет, как мне просто попробовать что-то еще и забыть об этом ... Насколько я знаю, следующее, что я попытаюсь сделать, потерпит неудачу, потому что я делаю ту же ошибку, что бы это ни было, я конечно не знаю. Я задаю вопрос о верстке и получаю лекцию по потоковой безопасности. Я уже пробовал использовать исправленную многопоточность, и это не устраняет проблему макета и не отвечает на вопрос, что именно происходит не так. - person nwod; 07.09.2014
comment
@nwod, I asked my question hoping to find out why my layout doesn't work - GroupLayout используется IDE. Большинство программистов не передают код GroupLayout и поэтому не понимают деталей того, как работает GroupLayout. Нет причин использовать GroupLayout, и я уже дал вам рабочее решение. I'm asking a question about layout and getting a lecture on thread-safety. да, потому что до тех пор, пока проблема не будет решена, мы не знаем, в чем проблема, и мы должны рассмотреть все возможности, начиная с очевидного. For all I know, the next thing I try to do will fail вот как ты учишься. - person camickr; 07.09.2014

Проблема не имела ничего общего с безопасностью потоков. После долгих поисков я обнаружил, что основная реализация оконного менеджера для Look And Feel JPanel имеет жестко запрограммированный предел размера 32767, поэтому не имело значения, что он находился в JScrollPane. Идея заключалась в том, чтобы избежать лишнего управления отдельными JScrollPane, но ограничение делает это неизбежным.

Перемещение содержимого JPanel в отдельные JscrollPanes решило проблему. Прокрутка все еще немного тормозит по неизвестным мне причинам.

person nwod    schedule 03.04.2015