Почему я иногда получаю пустые JLists после обновления содержимого с помощью модели списка?

#java #swing #model #jlist

#java #swing #Модель #jlist

Вопрос:

У меня повторяющаяся проблема, когда у меня есть 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);
    }

}
  

Любые указатели были бы очень кстати.

Ответ №1:

Посмотрите на этот код:

 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. Работает гладко.

Комментарии:

1. да, но этот режим ожидания не затрагивает EDT или основной поток, он приостанавливает рабочий поток Swing. Так что никаких зависаний или пустых экранов.

2. 1 Я считаю, что вот как правильно делегировать такой рабочий процесс.

3. Похоже, это именно то, что я искал. Должен признаться, я не смог увидеть проблему с обновлением swing из разных потоков, но я прочитаю о том, как работают потоки swing. Спасибо.

Ответ №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();
  

Вот почему важно сохранить его, как это хорошо объяснено в документе class:

javax.swing.Timer Имеет две функции, которые могут немного упростить использование с графическими интерфейсами. Во-первых, его метафора обработки событий знакома программистам с графическим интерфейсом пользователя и может немного упростить работу с потоком отправки событий. Во-вторых, его автоматическое совместное использование потоков означает, что вам не нужно предпринимать специальных шагов, чтобы избежать появления слишком большого количества потоков. Вместо этого ваш таймер использует тот же поток, который использовался для мигания курсоров, отображения всплывающих подсказок и так далее.

Комментарии:

1. 1, download.oracle.com/javase/7/docs/api/javax/swing/…

2. Хотя в моем примере использовался простой таймер, в моем реальном коде мне нужен отдельный поток для выполнения обновления, поскольку он собирает данные более сложным способом. Существует ли эквивалент Timer для создания отдельного потока, который не конфликтует с механизмом обновления Swing?

3. вы должны искать SwingWorker, как опубликовано @ gasan, но обратите внимание на ошибку Executor SwingWorker для Java <1.6.022 bugs.sun.com /…

Ответ №3:

это никогда не вызывает void updateList (…), но внутри я пропустил sleep(int) , потому что Swing лучше и требуется использовать java.swing.Таймер http://download.oracle.com/javase/tutorial/uiswing/misc/timer.html

Комментарии:

1. Да, это так, но очень запутанным образом. Но вы правы, обновление списка должно производиться на EDT .

2. 1 для таймера. Таймер запускается в потоке swing repaint и, как результат, корректно обновляет пользовательский интерфейс при каждом запуске.

3. В любом случае я согласен с @sthupahsmaht 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 связана с частотой добавления элементов, поэтому, чтобы избежать этого, необходимо уменьшить время hass в моем коде выше, я использую менее 1 секунды t.sleep (100), и это работает нормально, меньшая задержка потока может вызвать сбой