0

I had some problems with freezing SWING GUIs when re-rendering a JTable with a custom cell renderer in Java. So I asked the question "Why does a JTable view update block the entire GUI?". The answers pointed to the fact, that a JList without modifying JTable and overwriting doLayout might be a better choice. So I implemented the example with a JList and ran into the same problem: while generating data, everything works fine and the progress bar moves. But when the view is updated, the program freezes and the progress bar stops moving.

Please note, that the sleep statement is there only to let the generation take a longer, more realistic time (reading thousands of data sets via JDBC and create objects out of them takes a lot time). One could remove it and increment the number of generated items. But you can clearly see, that the HTML rendering is quite slow. But I need this colors and the two lines (if not necessarily so many different colors).

So could you please tell me, where my mistake is? I think, that EDT and other work is separated through separate threads and I cannot see any mistke.

Update: I looked around at SO and found this question "https://stackoverflow.com/a/20813122/2429611". There is said:

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.

This would mean, that I cannot solve my problem. Is this correct?


package example;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.List;
import javax.swing.AbstractAction;
import javax.swing.AbstractListModel;
import javax.swing.Box;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JScrollPane;
import javax.swing.ListCellRenderer;
import javax.swing.SwingUtilities;

public class ListExample  extends AbstractListModel {

    static List<DemoObject> internalList = new ArrayList<>();

        @Override
        public int getSize() {
            return internalList.size();
        }

        @Override
        public DemoObject getElementAt(int index) {
            return internalList.get(index);
        }

        public void fireContentsChanged() {
            SwingUtilities.invokeLater(new Runnable() {

                @Override
                public void run() {
                    fireContentsChanged(this, 0, -1);
                }
            });
        }

    static class MyCellRenderer extends JLabel implements ListCellRenderer<ListExample.DemoObject> {
        public MyCellRenderer() {
            setOpaque(true);
        }

        @Override
        public Component getListCellRendererComponent(JList<? extends ListExample.DemoObject> list,
                                                      ListExample.DemoObject value,
                                                      int index,
                                                      boolean isSelected,
                                                      boolean cellHasFocus) {

            setText("<html>" + value.toString() 
                    + "<br/>" 
                    + "<span bgcolor=\"#ff0000\">Line 2; Color = " + value.c + "</span>");

            Color background;
            Color foreground;

            // check if this cell represents the current DnD drop location
            JList.DropLocation dropLocation = list.getDropLocation();
            if (dropLocation != null
                    && !dropLocation.isInsert()
                    && dropLocation.getIndex() == index) {

                background = Color.BLUE;
                foreground = Color.WHITE;

            // check if this cell is selected
            } else if (isSelected) {
                background = Color.RED;
                foreground = Color.WHITE;

            // unselected, and not the DnD drop location
            } else {
                background = value.c; //Color.WHITE;
                foreground = Color.BLACK;
            };

            setBackground(background);
            setForeground(foreground);

            return this;
        }
    }

    static class DemoObject {

        String str;
        Color c;

        public DemoObject(String str, int color) {
            this.str = str;
            this.c = new Color(color);
        }

        @Override
        public String toString() {
            return str;
        }

    }

    static JPanel overlay;

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                JFrame frame = new JFrame("Example");
                frame.setLayout(new BorderLayout(4, 4));

                // Add JTable
                final ListExample model = new ListExample();
                JList list = new JList(model);
                list.setCellRenderer(new MyCellRenderer());
                frame.add(new JScrollPane(list), BorderLayout.CENTER);

                // Add button
                Box hBox = Box.createHorizontalBox();
                hBox.add(new JButton(new AbstractAction("Load data") {

                    @Override
                    public void actionPerformed(ActionEvent e) {
                        new Thread(new Runnable() {

                            @Override
                            public void run() {
                                overlay.setVisible(true);
                                internalList.clear();

                                System.out.println("Generating data ...");

                                SecureRandom sr = new SecureRandom();
                                for (int i = 0; i < 10000; i++) {
                                    internalList.add(
                                            new DemoObject(
                                                    "String: " + i + " (" + sr.nextFloat() + ")",
                                                    sr.nextInt(0xffffff)
                                            )
                                    );

                                    // To create the illusion, that data are
                                    // fetched via JDBC (which takes a little
                                    // while), this sleep statement is embedded
                                    // here. In a real world scenario, this wait
                                    // time is caused by talking to the database
                                    // via network
                                    if (i%10 == 0) {
                                        try {
                                            Thread.sleep(1);
                                        } catch (Exception e) {
                                        }
                                    }
                                }

                                System.out.println("Updating view ...");

                                model.fireContentsChanged();
                                overlay.setVisible(false);

                                System.out.println("Finished.");
                            }
                        }).start();
                    }
                }));
                hBox.add(Box.createHorizontalGlue());
                frame.add(hBox, BorderLayout.NORTH);

                // Create loading overlay
                overlay = new JPanel(new FlowLayout(FlowLayout.CENTER)) {

                    @Override
                    protected void paintComponent(Graphics g) {
                        g.setColor(new Color(0, 0, 0, 125));
                        g.fillRect(0, 0, getWidth(), getHeight());
                        super.paintComponent(g);
                    }
                };
                overlay.setOpaque(false);
                overlay.setBackground(new Color(0, 0, 0, 125));
                JProgressBar bar = new JProgressBar();
                bar.setIndeterminate(true);
                overlay.add(bar);

                frame.setGlassPane(overlay);
                frame.getGlassPane().setVisible(false);

                // Create frame
                frame.setSize(600, 400);
                frame.setVisible(true);
            }
        });
    }

}
Al Fahad
  • 2,378
  • 5
  • 28
  • 37
mythbu
  • 786
  • 1
  • 7
  • 20

1 Answers1

1

there are three problems (recreating, reseting the model, and custom Renderer stoped to works)

  1. JList (JComboBox hasn't) has an issue by removing more than 999 items, you have to set a new model to JList

  2. see important for ComboBoxModel extends AbstractListModel implements MutableComboBoxModel for setElementAt(to hold current selection)

  3. usage of public void fireContentsChanged() { is wrong, don't see reason to use this way, again is about to replace current, reset the model

. e.g. with success atr runtime and by recrusive testing for/if event (fired)

   setModel(new DefaultListModel(list.toArray()) {

      protected void fireContentsChanged(Object obj, int i, int j) {
        if (!isFired)
          super.fireContentsChanged(obj, i, j);
      }

    });
Community
  • 1
  • 1
mKorbel
  • 109,525
  • 20
  • 134
  • 319