Search code examples
javaswingjpanelpaintjlabel

previously displayed images disappear after mouse-click


I'm programming a gui for a network-able battleships-like game (yes me too). If I right-click to set a waypoint all the mapLabels disappear and I don't get it, since all the right-click should do is to send some string to the server and not in any way influence the display itself. If I call panel.repaint(); pane.validate(); after setting a waypoint everything is displayed again, but performance is coming close to impossibly slow like that.

I realize, this is a lot of code, but I can't imagine the problem beeing in the FieldListener, though it must be. Therefore I have no idea what to post. If you want to see something else, you could ask for it...

Here's the part of our code, that most probably is responsible for the problem:

/**
 * The listener for a field.
 * @author Andris
 *
 */
private class FieldListener implements MouseListener {

    private int[] xy;
    private int[] pressedPos = new int[] {-1,-1};


    @Override
    public void mousePressed(MouseEvent evt) {
        Field field = map.getField(xy[0],xy[1]);
        pressedPos = xy.clone();
        switch(evt.getButton()){
        case MouseEvent.BUTTON1://-------well, BUTTON1......
            break;
        case MouseEvent.BUTTON3:
            switch(selBtn){
            case 2://---------------this is the case
                client.Out("some string");// this sends to the server, nothing else...
                break;
            }
            break;
        }
    }

after the server answers this is executed (in totally different classes from different packages, and all the disappearing fields are private, though):

public class Server {
   client.Out(cfg.chat_server+"you have set a waypoint at ("+x+","+y+").");

public class ChatFrame extends JPanel {
    public void Out(String nextString){
        text.append(" "+nextString);
        text.append("\n");
        JScrollBar scroll = display.getVerticalScrollBar();
        if(!scroll.getValueIsAdjusting())scroll.setValue(Integer.MAX_VALUE);
    }

Here's the custom paint method (maybe this as ImageObserver is wrong):

private class MapLabel extends JLabel{ //------------- in MapFrame

    private Field field;
    private int[] xy;

    public void paint(Graphics g){
        Image image = getImageNsetToolTip();
        Graphics2D g2D=(Graphics2D)g;
        g2D.drawImage(image, 0, 0, actImgSize, actImgSize, this);
        g2D.setClip(0, 0, actImgSize, actImgSize);
        super.paint(g2D);
    }

    /**
     * gets the proper image and sets the tool-tip
     * @return
     */
    private Image getImageNsetToolTip(){
        Image result;
        String toolTip = "("+xy[0]+","+xy[1]+")";
        TileType type = field.getType();
        switch(type){
        case harbor:
            result=harborImg[0];
            break;
        case land: 
//------------------------------etc...


        this.setToolTipText(toolTip);
        return result;
    }

Here's some of the rest:

...lots of imports...

/**
 * This is the frame in which the GameMap is displayed.
 * @author Andris
 *
 */
@SuppressWarnings("serial")
public class MapFrame extends JPanel {

...lots of variables...

    /**
     * Creates the frame, but doesn't make it visible yet.
     * @param window the GameWindow in which this frame will be embedded.
     * @param client the client who runs this.
     */
    public MapFrame(GameWindow window, Client client){

...lots of variables initialized...

        panel = new JPanel();
        pane = new JScrollPane(panel,
                JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
                JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);


        buttons = new JButton[nButtons];
        buttonListener = new ButtonListener();
        for(int i=0; i<nButtons; i++){
            buttons[i] = new JButton(buttonTexts[i]);
            buttons[i].setName(buttonTexts[i].replace(' ', '_'));
            buttons[i].addActionListener(buttonListener);
            buttonPanel.add(buttons[i]);
        }

Solution

  • I finally figured it out:
    There are quite a few threads running because the server, the client, each connection and what-not are running in their own threads.
    This can cause the swing event-queue to act up, with the result, that things are not executed when they are supposed to.
    This usually happens only, if there are many other threads (thus the impossibility to make a SSCCE).
    In order to prevent this every method like validate() or repaint() should be invoked as invokeLater (and therefore be handled by the event-queue properly), especially if they are called from a different thread.
    So this is what I wrote everywhere:

        SwingUtilities.invokeLater(new Runnable(){
            @override
            public void run(){
                panel.validate();
                panel.repaint();
                pane.validate();
            }
        });
    

    or:

        SwingUtilities.invokeLater(new Runnable(){
            @override
            public void run(){
                mapLabels[x][y].repaint();
            }
        });