Question

If I update a JList with a long number of html formatted items then the controls stop responding and indicators won't update. This makes sense, the event thread is busy. The title can still be set though. Why is this?

Here's some (long) code demonstrating this:

import java.awt.*;
import java.awt.event.*;
import java.lang.reflect.InvocationTargetException;
import java.util.Timer;
import java.util.TimerTask;
import javax.swing.*;

public class JListTest extends JFrame {

    class TestListModel extends AbstractListModel<String> {

        private static final long serialVersionUID = JListTest.serialVersionUID;
        private boolean useHtml;
        private String[] formattedList = new String[] {};

        public int getSize() {
            return formattedList.length;
        }

        public String getElementAt(int index) {
            return formattedList[index];
        }

        public void setUseHtml(boolean useHtml) {
            this.useHtml = useHtml;
        }

        public String getNewListItem() {
            if (useHtml) {
                return "<html><div style='padding:2px"
                      + ";background-color:#EDF5F4;color:black'><div style='padding:2px;font-weight:500;'>"
                      + "Item " + (100 * Math.random())
                      + "</div>"
                      + "This will change!"
                      + "</div></html>";
            } else {
                return "Item " + (100 * Math.random());
            }
        }

        public void updateItems() {
            formattedList = new String[] {"<html><h1>Loading!</h1></html>"};
            fireContentsChanged(this, 0, 1);

            Thread buildItems = new Thread() {
                @Override
                public void run() {
                    final String[] tempList = new String[3000];
                    for (int i=0; i<tempList.length; i++) {
                        tempList[i] = getNewListItem();
                    }
                    // Just show the string bashing's done
                    try {
                        SwingUtilities.invokeAndWait(new Runnable() {
                            public void run() {
                                formattedList = new String[] {"<html><h1>Updating!</h1></html>"};
                                fireContentsChanged(TestListModel.this, 0, 1);
                            }
                        });
                    } catch (InvocationTargetException e) {
                        e.printStackTrace();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    // Update
                    SwingUtilities.invokeLater(new Runnable() {
                        public void run() {
                            formattedList = tempList;
                            fireContentsChanged(TestListModel.this, 0, formattedList.length);
                        }
                    });
                }
            };
            buildItems.start();
        }
    }

    protected static final long serialVersionUID = 1L;

    public JListTest() {

        JPanel controlPanel = new JPanel();
        JButton updaterControl = new JButton("Add 3000");
        final JCheckBox useHtmlControl = new JCheckBox("Use HTML");
        final TestListModel model = new TestListModel();
        JList<String> list = new JList<String>(model);
        JScrollPane scrollPane = new JScrollPane(list);
        final JLabel durationIndicator = new JLabel("0");

        controlPanel.add(useHtmlControl, BorderLayout.WEST);
        controlPanel.add(updaterControl, BorderLayout.EAST);

        getContentPane().add(controlPanel, BorderLayout.PAGE_START);
        getContentPane().add(scrollPane, BorderLayout.CENTER);
        getContentPane().add(durationIndicator, BorderLayout.PAGE_END);

        useHtmlControl.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                model.setUseHtml(useHtmlControl.isSelected());
            }
        });
        useHtmlControl.setSelected(false);

        updaterControl.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                model.updateItems();
            }
        });

        Timer counter = new Timer();
        counter.schedule(new TimerTask() {
            @Override
            public void run() {
                String previousCounter = durationIndicator.getText();
                String newCounter = Integer.toString(
                    Integer.parseInt(previousCounter) + 1);
                durationIndicator.setText(newCounter);
                setTitle(newCounter);
            }
        }, 0, 100);
    }

    public static void main(String args[]) {
        JListTest jlt = new JListTest();
        jlt.pack();
        jlt.setSize(300, 300);
        jlt.setVisible( true );
    }
}
Was it helpful?

Solution

The answer is pretty obvious - because Window title is not a Swing component, it's OS native entity.

So changes don't have to go through Swing Event Queue, but go to XDecoratedPeer.updateWMName directly in case of Unix and to some other class in other OSes.

The more interesting question would be how to avoid that UI blocking, but I don't think that's possible with just Swing, you'll have to implement some lazy loading, or rendering in batches.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top