Java graphics events not occurring in the order I

2019-08-09 00:43发布

问题:

So I'm making a graphical card game. Each card is a JPanel with a button and two images associated with it. I have a flip method, which is the first thing I call in my action listener when a card is clicked on.

public void flip()
{
    if(b1.getIcon() == card2) b1.setIcon(card1);
    else b1.setIcon(card2);
    revalidate();
    repaint();
}

However, for some reason the card doesnt flip (meaning the icons don't change) until some point after I call this method. For example, when I put a Thread.sleep after I call flip, I would assume that my program would pause after flip is completed, but it doesnt! It sleeps with the images not yet switched, and only switches them some at point after the sleep time has ended.

This is causing some major problems when I have a human play an AI in this card game, because AI events are happening before cards flip on the screen, even though I am calling flip() before I do any AI code. Can anyone clue me in as to what is going on here?

回答1:

Repaint is a request to Swing to redraw not a call to redraw. Under the covers repaint() is posting a repaint job onto a queue of events. Along with that repaint job things like mouse moves, mouse clicks, keyboard activites, etc are posted there too. The Swing thread comes by and periodically executes jobs from that queue to redraw the UI, deliver mouse and keyboard events to components, etc. In fact the swing thread delivers these events quite frequently, but it just depends on how much work your UI is doing on that Swing thread. When it's out delivering mouse clicks, keyboard events, and redraws it can't read from that queue. So if your code takes a long time to repsond to any event the ability for swing to respond timely to new events is reduced or all together blocked.

That's why if you execute a blocking service call over the network with the Swing event dispatch thread your UI stops drawing until that call returns. This is why its important to move long running jobs like network calls off the Swing Thread, and use SwingUtilities.invokeLater() when the call comes back so you can update the UI on the Event thread. (invokeLater() does this by posting a job onto the Swing event queue we talked about above).

This is also why it's a terrible idea to call sleep on the Swing event thread. If you put that thread to sleep it can't service events from the queue. And the repaint() you requested isn't going to be done while the Swing thread is sleeping.

First remove your sleep call. Second. Don't write code that depends on painting to finish before executing more logic. Repaint and revalidate are special calls, and Swing will combine requests to repaint/revalidate so it doesn't redraw 10 times in a row (wasting vital cpu time).

Swing can't redraw your panel until you let the thread that called you leave the method it first called you back on. The sooner the thread leaves that method the sooner your repaint() will happen.

You haven't explained why you want the repaint to happen immediately so I can't give you any advice on how to structure your code so you don't care about it. But I'm telling you, you shouldn't care that the repaint hasn't happened yet.



回答2:

Totally agree with @chubbsondubs. I just wanted to add that if your goal is to have some event happen some time after flip is called, consider using a swing timers. As you are painfully aware, calling Sleep does not have the desired effect. But if instead, you

  • create a timer for say 1 second
  • create an action to be performed when the timer fires
  • start the timer

then you can create the delay that you want without blocking the event dispatch thread.

public void flip()
{
    if(b1.getIcon() == card2) b1.setIcon(card1);
    else b1.setIcon(card2);
    revalidate();
    repaint();

    javax.swing.Timer t = new javax.swing.Timer(1000, new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          // do something interesting
        }
     });
    t.repeats(false);
    t.start();
}