Issue
Goal:
Use JList for any purpose.
Problem:
If the first entry is selected, and then you clear the model and add new content, this will be extremely slow, no matter how often you do it. Only once you have selected another entry - one that is not the first - the behavior will be fast=normal again.
Workaround:
Every time before clearing the model, programmatically add two arbitrary entries (two, because the list may have been entirely empty), then select the entry with the highest valid index.
Question:
Can you reproduce the bug? Is it a known bug? If not, could someone please post it so that OpenJDK will be fixed?
Test environment:
Standard Oracle Java release download JDK8 202.
java version "1.8.0_202"
Java(TM) SE Runtime Environment (build 1.8.0_202-b08)
Java HotSpot(TM) 64-Bit Server VM (build 25.202-b08, mixed mode)
Windows 10 Home, Version 1809 (OS Build 17763.437)
Intel(R) Core(TM) i7-6700K CPU @ 4.00 GHz 4.01 GHz
Also reproduced with OpenJDK 11
openjdk version "11.0.2" 2019-01-15
OpenJDK Runtime Environment 18.9 (build 11.0.2+9)
OpenJDK 64-Bit Server VM 18.9 (build 11.0.2+9, mixed mode)
SSCCE
This code is minimal, so it even uses the default LookAndFeel. Originally, I encountered the problem when using L&F "Windows".
package jlistslowdownbugdemo;
import javax.swing.*;
import java.awt.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
final public class Main {
final private static boolean ENABLE_FIX = false;
public static void main(final String[] args) {
SwingUtilities.invokeLater(() -> {
final DefaultListModel<String> valueListModel = new DefaultListModel<>();
final JList<String> valueList = new JList<>(valueListModel);
final DefaultListModel<String> keyListModel = new DefaultListModel<>();
for (String key : Main.DATA.keySet()) {
keyListModel.addElement(key);
}
final JList<String> keyList = new JList<>(keyListModel);
keyList.addListSelectionListener(e -> {
if (e.getValueIsAdjusting()) { // To prevent SUPER-slowdown when the bug kicks. Not an important measure, just for comfortable demoing.
// Workaround for the bug: Add two entries (two, because list may be entirely empty),
// and select the last available entry (in the worst case, this is the 2nd).
// Just so that one is selected that IS NOT THE FIRST.
// This problem AND fix occurred in Oracle JDK 8 and Open JDK 11.
if (ENABLE_FIX) {
valueListModel.addElement(null);
valueListModel.addElement(null); // Make sure you add something else instead of null if your GUI/JList setup requires it.
valueList.setSelectedIndex(valueListModel.size() - 1);
}
valueListModel.clear();
final String key = keyList.getSelectedValue();
if (key != null) {
for (String value : Main.DATA.get(key)) {
valueListModel.addElement(value);
}
}
}
});
final JPanel contentPane = new JPanel(new GridLayout(1, 0, 0, 0));
contentPane.add(new JScrollPane(keyList));
contentPane.add(new JScrollPane(valueList));
final JFrame window = new JFrame();
window.setContentPane(contentPane);
window.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
window.setMinimumSize(new Dimension(1200, 1000));
window.setLocationRelativeTo(null);
window.setVisible(true);
final String msg = "Click&drag in the left list. This rapidly changes the content of the right list.\n" +
"\n" +
"Now select any entry EXCEPT the first entry of the right list. Click&drag again in the left list,\n" +
"it still works just as rapidly. Now click THE FIRST entry of the right list.\n" +
"\n" +
"If you NOW click&drag again in the left list, you will experience BRUTAL slowdown.\n" +
"\n" +
"Once Swing has calmed down, click any entry EXCEPT the first entry of the right list.\n" +
"Click&drag again in the left list - the problem is gone.\n" +
"\n" +
"The built-in workaround (off by default) simulates this.";
JOptionPane.showMessageDialog(window,
msg,
"How to reproduce the Swing bug:",
JOptionPane.INFORMATION_MESSAGE);
});
}
final private static Random RAND = new Random(0);
final private static Map<String, List<String>> DATA = createDataMap();
private static Map<String, List<String>> createDataMap() {
final Map<String, List<String>> ret = new HashMap<>();
for (int i = 0; i < 30; i++) {
final int listSize = 20 + RAND.nextInt(5000);
final String key = generateRandomString('A');
final List<String> value = new ArrayList<>(listSize);
ret.put(key, value);
for (int ii = 0; ii < listSize; ii++) {
value.add(generateRandomString('a'));
}
}
return ret;
}
private static String generateRandomString(final char baseChar) {
final StringBuilder sb = new StringBuilder();
final int len = 4 + RAND.nextInt(17);
for (int i = 0; i < len; i++) {
sb.append((char) (baseChar + RAND.nextInt(26)));
}
return sb.toString();
}
}
(Title edited: Removed ", definitely a Java bug")
Solution
I don't know the source of the problem yet (hence this is being answered as a "community wiki" and not yet as a personal answer), but the problem goes away if you set the JList's visible row count and prototype cell value (which is Swing best-practice).
Note that removing the value list's selection listeners before changing the model, and then re-adding them after changing the model, does not help your problem. My gut feeling is that it is an issue of rendering and sizing.
Working code:
import javax.swing.*;
import java.awt.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
final public class Foo {
public static void main(final String[] args) {
SwingUtilities.invokeLater(() -> {
final DefaultListModel<String> valueListModel = new DefaultListModel<>();
final JList<String> valueList = new JList<>(valueListModel);
final DefaultListModel<String> keyListModel = new DefaultListModel<>();
for (String key : Foo.DATA.keySet()) {
keyListModel.addElement(key);
}
final JList<String> keyList = new JList<>(keyListModel);
keyList.addListSelectionListener(e -> {
if (e.getValueIsAdjusting()) {
valueListModel.clear();
final String key = keyList.getSelectedValue();
if (key != null) {
for (String value : Foo.DATA.get(key)) {
valueListModel.addElement(value);
}
}
}
});
keyList.setVisibleRowCount(25);
valueList.setVisibleRowCount(25);
String prototypeFormat = "%100s";
keyList.setPrototypeCellValue(String.format(prototypeFormat, " "));
valueList.setPrototypeCellValue(String.format(prototypeFormat, " "));
final JPanel contentPane = new JPanel(new GridLayout(1, 0, 0, 0));
contentPane.add(new JScrollPane(keyList));
contentPane.add(new JScrollPane(valueList));
final JFrame window = new JFrame();
window.setContentPane(contentPane);
window.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
// window.setMinimumSize(new Dimension(1200, 1000));
window.pack();
window.setLocationRelativeTo(null);
window.setVisible(true);
});
}
final private static Random RAND = new Random(0);
final private static Map<String, List<String>> DATA = createDataMap();
private static Map<String, List<String>> createDataMap() {
final Map<String, List<String>> ret = new HashMap<>();
for (int i = 0; i < 30; i++) {
final int listSize = 20 + RAND.nextInt(5000);
final String key = generateRandomString('A');
final List<String> value = new ArrayList<>(listSize);
ret.put(key, value);
for (int ii = 0; ii < listSize; ii++) {
value.add(generateRandomString('a'));
}
}
return ret;
}
private static String generateRandomString(final char baseChar) {
final StringBuilder sb = new StringBuilder();
final int len = 4 + RAND.nextInt(17);
for (int i = 0; i < len; i++) {
sb.append((char) (baseChar + RAND.nextInt(26)));
}
return sb.toString();
}
}
Answered By - DontKnowMuchBut Getting Better
Answer Checked By - Cary Denson (JavaFixing Admin)