Обновление JLabel не работает должным образом внутри потока SwingWorker

Я пытаюсь обновить JLabel в некотором ожидаемом поведении, например -

COND 1: JLabel должен отображаться в течение 5 секунд, затем он должен быть невидимым в течение следующих 3 секунд, затем он должен отображаться в течение 5 секунд и так далее.

COND 2: JLabel должен отображаться в течение 0,5 секунды, затем он должен быть невидимым в течение следующих 3 секунд.

Вид любой комбинации перестановок, поведение должно работать. Ниже приведен пример кода:

import java.awt.LayoutManager;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.List;

import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingWorker;

public class Main
{
    static CounterTask task;

    public static void main ( String[] args )
    {

        final JLabel label = new JLabel( "TEST ME " );
        JButton startButton = new JButton( "Start" );
        startButton.addActionListener( new ActionListener()
        {
            public void actionPerformed ( ActionEvent e )
            {
                task = new CounterTask( label );
                task.execute();
            }
        } );

        JButton cancelButton = new JButton( "Cancel" );
        cancelButton.addActionListener( new ActionListener()
        {
            public void actionPerformed ( ActionEvent e )
            {
                task.cancel( true );
            }
        } );

        JPanel buttonPanel = new JPanel();
        buttonPanel.add( startButton );
        buttonPanel.add( cancelButton );

        JPanel cp = new JPanel();
        LayoutManager layout = new BoxLayout( cp, BoxLayout.Y_AXIS );
        cp.setLayout( layout );
        cp.add( buttonPanel );
        cp.add( label );

        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
        frame.setContentPane( cp );
        frame.pack();
        frame.setVisible( true );
    }
}

class CounterTask
    extends SwingWorker< Integer, Integer >
{
    int DELAY = 3000;

    JLabel label;

    int WAIT = 5000;

    public CounterTask ( JLabel label )
    {
        this.label = label;
    }

    @Override
    protected Integer doInBackground () throws Exception
    {
        int i = 0;
        int count = 1000;
        while ( !isCancelled() && i < count )
        {
            i++;
            publish( new Integer[] { i } );
            label.setVisible( false );
            Thread.sleep( DELAY );
        }
        return count;
    }

    protected void process ( List< Integer > chunks )
    {
        Integer strContent = chunks.get( chunks.size() - 1 );
        label.setVisible( true );
        label.validate();
        label.setText( "DISPLAYING " + strContent );
        try
        {
            Thread.sleep( WAIT );
        }
        catch ( InterruptedException e )
        {
            e.printStackTrace();
        }
    }

    @Override
    protected void done ()
    {
        if ( isCancelled() )
            System.out.println( "Cancelled !" );
        else
            System.out.println( "Done !" );
    }
}

Я определенно делаю что-то не так, так как эта комбинация работает для некоторых данных, и в основном JLabel становится невидимым повсюду, но если я ставлю SOP, я получаю правильные данные с правильной задержкой и временем ожидания.

Приведенный выше код Swing worker имеет две переменные WAIT , DELAY . WAIT делает JLabel видимым на X секунд, а DELAY делает JLAbel невидимым на Y секунд. Но если попробовать другую завивку и расческу, вы обнаружите, что видимость JLabel не работает должным образом. Это может быть из-за обновлений лейбла EDT или из-за перерисовки. Не уверен в первопричине.

Ожидал :

  1. Хотите, чтобы код работал как SOP или в идеальном случае.
  2. Нужно знать причину неадекватного поведения.

person Abhishek Choudhary    schedule 07.09.2013    source источник
comment
Сон в EDT не поможет вам, процесс выполняется внутри EDT. Изменение состояния метки в doInBackground также является нарушением правил одиночного потока Swing, нет, он ведет себя именно так, как вы его закодировали. О, и у вас есть состояние гонки между методами doInBackground и process   -  person MadProgrammer    schedule 07.09.2013
comment
Спасибо за вклад, так что это означает, что мне нужно найти способ ожидания внутри EDT, и это не может быть обычный поток. Во-вторых, что касается состояния гонки, я ожидал, что поток избегает любой гонки, можете ли вы дать небольшую подсказку.   -  person Abhishek Choudhary    schedule 07.09.2013
comment
нет, вы никогда не ждете EDT: вместо этого поместите всю логику в doInBackground и опубликуйте состояние метки   -  person kleopatra    schedule 07.09.2013
comment
Более того, было бы разумно вместо setVisible(true/false) использовать просто setText(""/"newValue"). Так что вам действительно не нужно revalidate()/repaint() каждый раз. И просто переберите chunks, чтобы отобразить содержимое в JLabel, вместо того, чтобы явно брать верхнее значение из списка и отображать то же самое в JLabel, т.е. просто поместите только эти две строки в метод process() for (Integer chunk : chunks) label.setText( "DISPLAYING " + chunk.toString() ); :-) +1 для предоставления исполняемого пример :-)   -  person nIcE cOw    schedule 07.09.2013
comment
все комментарии выше учтены и проблема решена :-)   -  person Abhishek Choudhary    schedule 07.09.2013


Ответы (2)


SwingWorker — это излишество для просто мигающей метки. Попробуйте следующий подход на основе таймера, он вообще не требует магии потоков:

public class Main {
private static Timer setVisibleTimer;
private static Timer setInvisibleTimer;

public static void main(String[] args) {

    final JLabel label = new JLabel("TEST ME ");
    JButton startButton = new JButton("Start");
    startButton.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
            setVisibleTimer = new Timer(8000, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    label.setVisible(true);
                }
            });
            setInvisibleTimer = new Timer(5000, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    label.setVisible(false);
                }
            });
            setVisibleTimer.start();
            setInvisibleTimer.start();
        }
    });

    JButton cancelButton = new JButton("Cancel");
    cancelButton.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
            label.setVisible(true);
            if (setVisibleTimer != null){
                setInvisibleTimer.stop();
                setVisibleTimer.stop();
            }
        }
    });

    JPanel buttonPanel = new JPanel();
    buttonPanel.add(startButton);
    buttonPanel.add(cancelButton);

    JPanel cp = new JPanel();
    LayoutManager layout = new BoxLayout(cp, BoxLayout.Y_AXIS);
    cp.setLayout(layout);
    cp.add(buttonPanel);
    cp.add(label);

    JFrame frame = new JFrame();
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setContentPane(cp);
    frame.pack();
    frame.setVisible(true);
}
}
person Jk1    schedule 07.09.2013
comment
Nb вы можете изменить задержку таймера из события actionPerformed, это требует немного дополнительной работы;) - person MadProgrammer; 07.09.2013

нет, вы никогда не ждете EDT: вместо этого поместите всю логику в doInBackground и опубликуйте состояние метки

Чтобы дополнить мой комментарий небольшим количеством кода (можно сделать в таймере, но на самом деле мне нравятся рабочие :)

static class LabelState {
    int count;
    boolean visible;

    public LabelState(boolean visible, int count) {
        this.count = count;
        this.visible = visible;
    }
}
static class CounterTask extends SwingWorker<Void, LabelState> {
    int DELAY = 3000;

    JLabel label;

    int WAIT = 5000;

    public CounterTask(JLabel label) {
        this.label = label;
    }

    @Override
    protected Void doInBackground() throws Exception {
        int i = 0;
        int count = 1000;
        while (!isCancelled() && i < count) {
            i++;
            publish(new LabelState(false, i));
            Thread.sleep(DELAY);
            i++;
            publish(new LabelState(true, i));
            Thread.sleep(WAIT);
        }
        return null;
    }

    protected void process(List<LabelState> chunks) {
        LabelState strContent = chunks.get(chunks.size() - 1);
        label.setVisible(strContent.visible);
        label.validate();
        label.setText("DISPLAYING " + strContent.count);
    }

    @Override
    protected void done() {
        if (isCancelled())
            System.out.println("Cancelled !");
        else
            System.out.println("Done !");
    }
}
person kleopatra    schedule 07.09.2013
comment
+1, мне понравилась эта идея, хотя почему бы просто не использовать setText("") вместо hiding and showing JLabel снова и снова :-) - person nIcE cOw; 07.09.2013