【问题标题】:Why do I sometimes get blank JLists after updating contents through the list model?为什么通过列表模型更新内容后有时会得到空白的 JLists?
【发布时间】:2011-08-20 03:39:08
【问题描述】:

我有一个反复出现的问题,我有一个希望用新内容更新的 JList。我正在使用 DefaultListModel,它提供了向列表添加新内容的方法,但是在使用这些方法时,我发现某些调用比例会导致完全空白的 JList。更新是否有效似乎是随机的,与发送的数据无关。

下面是一个演示问题的简单程序。它只是生成一个不断增加的列表来更新 JList,但在运行时列表内容似乎随机出现和消失。

据我所知,我正在遵循正确的 API 来执行此操作,但我想我肯定缺少一些基本的东西。

import java.awt.BorderLayout;
import javax.swing.*;

public class ListUpdateTest extends JPanel {

    private JList list;
    private DefaultListModel model;

    public ListUpdateTest () {
        model = new DefaultListModel();
        list = new JList(model);

        setLayout(new BorderLayout());

        add(new JScrollPane(list),BorderLayout.CENTER);
        new UpdateRunner();
    }

    public void updateList (String [] entries) {
        model.removeAllElements();
        for (int i=0;i<entries.length;i++) {
            model.addElement(entries[i]);
        }
    }

    private class UpdateRunner implements Runnable {

        public UpdateRunner () {
            Thread t = new Thread(this);
            t.start();
        }

        public void run() {

            while (true) {
                int entryCount = model.size()+1;

                System.out.println("Should be "+entryCount+" entries");

                String [] entries = new String [entryCount];

                for (int i=0;i<entries.length;i++) {
                    entries[i] = "Entry "+i;
                }

                updateList(entries);

                try {
                    Thread.sleep(1000);
                } 
                catch (InterruptedException e) {}
            }
        }   
    }

    public static void main (String [] args) {

        JDialog dialog = new JDialog();
        dialog.setContentPane(new ListUpdateTest());
        dialog.setSize(200,400);
        dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
        dialog.setModal(true);
        dialog.setVisible(true);
        System.exit(0);
    }

}

非常欢迎任何指点。

【问题讨论】:

    标签: java swing model jlist


    【解决方案1】:

    从不调用 void updateList(...) 但在里面我错过了sleep(int),因为 Swing 更好,并且需要使用 java.swing.Timer http://download.oracle.com/javase/tutorial/uiswing/misc/timer.html

    【讨论】:

    • 是的,但是以一种非常复杂的方式。但你是对的,更新列表应该在EDT 上完成。
    • +1 表示计时器。计时器在摆动重绘线程中运行,因此,每次触发时都会正确更新 UI。
    • 我同意@sthupahsmaht 无论如何 +1 的答案。
    【解决方案2】:

    是的,您应该确保它在 EDT 上运行。 我想知道你有没有注意到任何异常顺便说一句? 我在第一次运行时得到了一个。

    改为使用的代码(删除 UpdateRunner 并将其转换为 javax.swing.Timer):

            Timer t = new Timer(1000, new ActionListener() {    
                @Override
                public void actionPerformed(ActionEvent e)
                {
                    int entryCount = model.size()+1;    
                    System.out.println("Should be "+entryCount+" entries");    
                    String [] entries = new String [entryCount];    
                    for (int i=0;i<entries.length;i++) {
                        entries[i] = "Entry "+i;
                    }    
                    updateList(entries);
                }
            });
            t.setRepeats(true);
            t.start();
    

    这就是使用它的原因,因为它在the class doc 中有很好的解释:

    javax.swing.Timer 有两个功能可以使它更容易与 GUI 一起使用。首先,它的事件处理隐喻对于 GUI 程序员来说是熟悉的,并且可以使处理事件调度线程更简单一些。其次,它的自动线程共享意味着您不必采取特殊步骤来避免产生太多线程。相反,您的计时器使用用于使光标闪烁、显示工具提示等的同一线程。

    【讨论】:

    • 虽然我的示例只使用了一个简单的计时器,但在我的真实代码中,我需要一个单独的线程来进行更新,因为它以更复杂的方式收集数据。是否有与 Timer 等效的方法来创建不与 Swing 更新机制冲突的单独线程?
    • 你必须寻找 @gasan 发布的 SwingWorker,但请注意 Executor + SwingWorker for Java bugs.sun.com/…
    【解决方案3】:

    看这段代码:

    import java.awt.BorderLayout;
    import javax.swing.*;
    import javax.swing.SwingWorker;
    import java.util.Arrays;
    import java.util.List;
    public class ListUpdateTest extends JPanel {
    
        private JList list;
        private DefaultListModel model;
    
        public ListUpdateTest () {
            model = new DefaultListModel();
            list = new JList(model);
    
            setLayout(new BorderLayout());
    
            add(new JScrollPane(list),BorderLayout.CENTER);
            (new UpdateRunner()).execute();
        }
    
        public void updateList (List<String> entries) {
            model.removeAllElements();
            for (String entry : entries) {
                model.addElement(entry);
            }
        }
        private class UpdateRunner extends SwingWorker<List<String>, List<String>>{
    
            @Override
            public List<String> doInBackground() {
                while (true) {
                    int entryCount = model.size()+1;
    
                    System.out.println("Should be "+entryCount+" entries");
    
                    String [] entries = new String [entryCount];
    
                    for (int i=0;i<entries.length;i++) {
                        entries[i] = "Entry "+i;
                    }
    
                    publish(Arrays.asList(entries));
    
                    try {
                        Thread.sleep(1000);
                    }
                    catch (InterruptedException e) {}
                }
                return null;
            }
            @Override
            protected void process(List<List<String>> entries) {
                for (List<String> entry : entries) {
                    updateList(entry);
                }
            }
            @Override
            protected void done() {
                updateList(Arrays.asList("done"));
            }
        }
    
        public static void main (String [] args) {
    
            JDialog dialog = new JDialog();
            dialog.setContentPane(new ListUpdateTest());
            dialog.setSize(200,400);
            dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
            dialog.setModal(true);
            dialog.setVisible(true);
            System.exit(0);
        }
    
    }
    

    由 SwingWorker 实现。工作顺利。

    【讨论】:

    • 是的,但是睡眠不会触及 EDT 或主线程,它会暂停 Swing Worker 线程。所以没有冻结或空屏。
    • +1 我相信这是正确委派此类工作流程的方法。
    • 这似乎正是我想要的。我必须承认我没有看到从不同线程更新摆动的问题,但我将阅读摆动线程的工作方式。谢谢。
    【解决方案4】:

    虽然尝试在我的程序中实现上述答案,但它不起作用,所以我想出了下面的简单方法,它可以毫无故障地添加到 jList。

    public static void updateList (String entries, DefaultListModel model) {
        try {
            AddElement t = new AddElement(entries, model);
            t.sleep(100); t.stop();
        } catch (InterruptedException ex) {
            Logger.getLogger(Others.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
    
     static class AddElement extends Thread {
    
         public AddElement(String entries, DefaultListModel model) {
             model.addElement(entries);
         }
    
     }
    

    要将元素添加到模型中,只需执行此操作或循环调用 updateList

    int entryCount = model.size()+1; 
    updateList("Entry "+entryCount, model);
    

    jList 中出现故障的实际原因是由于添加元素的时间率,因此为了避免它在我上面的代码中减少时间,我使用的时间少于 1 秒 t.sleep (100) 并且工作正常下线程延迟可能会导致故障

    【讨论】: