Thread sleep inside of actionPerformed method

后端 未结 2 1610
囚心锁ツ
囚心锁ツ 2020-12-21 09:01

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:



        
相关标签:
2条回答
  • 2020-12-21 09:15

    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();
                }
            });
        }
    }
    
    0 讨论(0)
  • 2020-12-21 09:19
    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

    0 讨论(0)
提交回复
热议问题