【问题标题】:Why does a fireContentsChanged call of a JList freezes the entire GUI?为什么 JList 的 fireContentsChanged 调用会冻结整个 GUI?
【发布时间】:2026-02-03 01:30:01
【问题描述】:

在使用 Java 中的自定义单元格渲染器重新渲染 JTable 时,我遇到了冻结 SWING GUI 的一些问题。所以我问了“Why does a JTable view update block the entire GUI?”这个问题。答案指出了这样一个事实,即 JList 不修改 JTable 并覆盖 doLayout 可能是更好的选择。所以我用JList 实现了这个例子并遇到了同样的问题:在生成数据时,一切正常,进度条移动。但是当视图更新时,程序冻结,进度条停止移动。

请注意,sleep 语句只是为了让生成花费更长、更实际的时间(通过JDBC 读取数千个数据集并从中创建对象需要很多时间)。可以删除它并增加生成项目的数量。但是您可以清楚地看到,HTML 渲染非常缓慢。但我需要这种颜色和两条线(如果不一定有这么多不同的颜色)。

那么你能告诉我,我的错误在哪里吗?我认为,EDT 和其他工作是通过单独的线程分开的,我看不到任何错误。

更新:我环顾四周,发现了这个问题“https://*.com/a/20813122/2429611”。有说:

更有趣的问题是如何避免 UI 阻塞,但我认为仅使用 Swing 是不可能的,您必须实现一些延迟加载或批量渲染。

这意味着我无法解决我的问题。这是正确的吗?


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);
            }
        });
    }

}

【问题讨论】:

    标签: java multithreading swing user-interface


    【解决方案1】:

    存在三个问题(重新创建、重置模型和自定义渲染器停止工作)

    1. JList (JComboBox hasn't) has an issue by removing more than 999 items,你得给JList设置一个新模型

    2. 参见重要的 ComboBoxModel 扩展 AbstractListModel 为 setElementAt 实现 MutableComboBoxModel(以保持当前选择)

    3. public void fireContentsChanged() {的用法不对,看不出用这种方式的理由,又要换电流了,重置模型

    。例如成功 atr 运行时并通过递归测试/如果事件(触发)

       setModel(new DefaultListModel(list.toArray()) {
    
          protected void fireContentsChanged(Object obj, int i, int j) {
            if (!isFired)
              super.fireContentsChanged(obj, i, j);
          }
    
        });
    

    【讨论】:

    • 我忘了这个模型不完整
    最近更新 更多