Thread sleep inside of actionPerformed method

£可爱£侵袭症+ 提交于 2019-12-29 08:55:12

问题


First of all I want to say I'm aware this aproach is wrong so I'm asking this question because of pure curiousity. Lets say I have a swing application like this:

import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

public class ThreadSleeping {
    JFrame frame = new JFrame();
    JPanel panel = new JPanel();
    JButton button = new JButton("Load");
    JLabel label = new JLabel();

    public ThreadSleeping() {
        panel.add(button);

        button.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent arg0) {
                label.setIcon(new ImageIcon(
                        "C:/Users/Public/Pictures/Sample Pictures/Tulips.jpg"));
                System.out.println("Tulips painted");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                label.setIcon(new ImageIcon(
                        "C:/Users/Public/Pictures/Sample Pictures/Koala.jpg"));
                System.out.println("Koala painted");

            }
        });

        frame.add(panel, BorderLayout.NORTH);
        frame.add(label, BorderLayout.CENTER);
        frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        frame.setSize(1024, 768);
        // frame.pack();
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                new ThreadSleeping();
            }
        });
    }
}

Basically when I click a Load button I expect that Tulips.jpg image displays then GUI freezes for a 2 seconds and after that I expect that Koala.jpg image displays. But what happens is that: I click on button, GUI freezes for a 2 seconds and Koala.jpg displays. No Tulips.jpg before that. But thing that confuses me is when I put those System.out.println("Tulips painted"); and System.out.println("Koala painted");. So when I click on button it prints "Tulips painted" and after 2 seconds "Koala painted". Can someone tell me whats going on here? Regards.


回答1:


  1. works in this case, because you programatically freeze ouf Swing GUI, but there is/aren't another update(s), ot another JComponent(s)

  2. doens't works in the case that there are a few another updated to the Swing GUI, Thread.sleep(int) freeze Event Dispatch Thread,

  3. by default all updates to the JComponents XxxModels never will be visible on the JComponents view

  4. example until sleep ended you'll lost all updated to the GUI




回答2:


The point I intended to make in my comment:

if you sleep the edt, the resulting mis-behaviour is basically unpredictable.

Unpredictable in the sense that you can't know what will happen or not. All we can do, is guess ...

The technical reason is that most ui updates don't happen immediately but are scheduled: we can't really know what's waiting in the line behind us. Remember: it's only one lane, and when in the actionPerformed, it's we that are sitting in it.

For educational reasons, the code below is the original code with a couple of lines to un/comment to demonstrate different scenarios.

  • [0] resetting the icon before/after sleeping: as you already noticed, the first doesn't show even though the property is taken. Technical reason: visual update happens via label.repaint() which is scheduled on the EDT for latter processing (as its api doc states)
  • [1] skimming the api doc, we notice another method: paintImmediately(...) which is documented to do exactly what it's name says and allowed - as we are on the EDT - is allowed to be called. Looks like success, the yellow icon shows up.
  • [2] but wait: being in the center of a Borderline, the label fills that area anyway, independent of whether or not it has an icon. Let's try to put it into a region that requires a re-layout, as f.i. into the south. Back to square [0], yellow icon not showing.
  • [3] looking into the source of setIcon(..) reveals that layout is ... scheduled again. We learned in square [1] that we can force thingies to happen immediately, in case of layout that would be the pair invalidate() / validate(). Bingo, yellow icon even when in south.
  • [4] nasty subclass which schedules the icon property setting (note: while contrived here there is nothing in its contract that hinders subclasses to do it!). As the property isn't even set, yellow isn't showing, back to square [0]

At the end of the day (but before going to sleep the EDT :-), there is simply no way to reliably predict the visual outcome during the sleep. And visuals are only the tip of the ice...

/**
 * Unpredictable behaviour when sleeping the EDT.
 * http://stackoverflow.com/q/15600203/203657
 * 
 * [0] run as-is: property set but yellow not showing
 * [1] uncomment paintImmediately: yellow showing in center
 * [2] add label to south: yellow not showing
 * [3] force immediate in-/validation: yellow showing in south
 * [4] subclass label with invoked property setting: 
 *       property not set, yellow not showing
 * 
 */
public class ThreadSleeping {
    JFrame frame = new JFrame();
    JPanel panel = new JPanel();
    JButton button = new JButton("Load");
    JLabel label = new JLabel() {
// [4] subclass implemented to schedule the property setting         
//        @Override
//        public void setIcon(final Icon icon) {
//            SwingUtilities.invokeLater(new Runnable() {
//                public void run() {
//                    superSetIcon(icon);
//                    
//                }
//            });
//        }
//        
//        protected void superSetIcon(Icon icon) {
//            super.setIcon(icon);
//        }
//        
    };

    public ThreadSleeping() {
        panel.add(button);

        button.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent arg0) {
                Icon firstIcon = new FixedIcon(Color.YELLOW, 100, 100);
                Icon secondIcon = new FixedIcon(Color.RED, 500, 500);
                label.setIcon(firstIcon);
                // check if the property is already set
                System.out.println(label.getIcon());
                // following lines try to force the output before going to sleep
                // [3] paintImmediately + force immediate re-layout 
                //  label.invalidate();
                //  label.getParent().validate();
                // {1] paintImmediately (fine for center, no effect for south)
                // ((JComponent) label.getParent()).paintImmediately(0, 0, 5000, 5000);
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                label.setIcon(secondIcon);

            }
        });

        frame.add(panel, BorderLayout.NORTH);
        // un/comment one of the following, placing the
        // label either in CENTER (= sized to fill)
        frame.add(label, BorderLayout.CENTER);
        // [2] or in SOUTH (= sized to pref)
        // frame.add(label, BorderLayout.SOUTH);
        frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        frame.setSize(1024, 768);
        frame.setVisible(true);
    }

    /**
     * Simple fixed size Icon filling its area.
     */
    public static class FixedIcon implements Icon {

        private Color background;
        private int width;
        private int height;

        public FixedIcon(Color background, int width, int height) {
            this.background = background;
            this.width = width;
            this.height = height;
        }
        @Override
        public void paintIcon(Component c, Graphics g, int x, int y) {
            g.setColor(background);
            g.fillRect(0, 0, width, height);
        }

        @Override
        public int getIconWidth() {
            return width;
        }

        @Override
        public int getIconHeight() {
            return height;
        }
        @Override
        public String toString() {
            return "[" + background + " w/h " + width + "/" + height + "]";
        }


    }
    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                new ThreadSleeping();
            }
        });
    }
}


来源:https://stackoverflow.com/questions/15600203/thread-sleep-inside-of-actionperformed-method

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!