1

I am creating a Tetris clone as a personal project. In order to switch between the menus, I am using a CardLayout, but I have come across a problem. When I switch from one menu to the other, the focus does not transfer to the other JPanel. How can I fix this?

Here is my JFrame:

package com.cgp.tetris;

import java.awt.CardLayout;
import java.awt.Dimension;
import java.awt.Toolkit;

import javax.swing.JFrame;
import javax.swing.JPanel;

public class TetrisFrame extends JFrame {
    private static final long serialVersionUID = 1L;
    TetrisMenu tm = new TetrisMenu();
    static SinglePlayerMenu spm = new SinglePlayerMenu();
    public static CardLayout cl = new CardLayout();
    static JPanel parentComponent = new JPanel(cl);

    public static void main(String[] args) {
        new TetrisFrame();
    }

    public TetrisFrame() {
        setTitle("Tetris");
        setSize(new Dimension(646, 604));
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setVisible(true);
        setResizable(false);
        Dimension d = Toolkit.getDefaultToolkit().getScreenSize();
        setLocation((d.width / 2) - 323, (d.height / 2) - 302);
        parentComponent.add(tm, "tm");
        parentComponent.add(spm, "spm");
        cl.show(parentComponent, "tm");
        add(parentComponent);
    }

    public static void replace() {
        cl.show(parentComponent, "spm");
        spm.startClip();
    }
}

Here is my first JPanel/menu:

package com.cgp.tetris;

import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.UnsupportedAudioFileException;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JPanel;
import javax.swing.KeyStroke;

public class TetrisMenu extends JPanel implements Runnable {
    private static final long serialVersionUID = 1L;
    private Thread thread;
    private BufferedImage titletop, titlebottom, selector;
    private boolean left = true, right = false;
    private AudioInputStream themestream;
    private Clip clip;

    public TetrisMenu() {
        super();
    }

    public void run() {
        requestFocus(true);
        loadImages();
        sound();
        bind();
        while (true) {
            repaint();
        }
    }

    private void sound() {
        File theme = new File("res/theme.wav");
        try {
            themestream = AudioSystem.getAudioInputStream(theme);
        } catch (UnsupportedAudioFileException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        DataLine.Info info = new DataLine.Info(Clip.class, themestream.getFormat());
        clip = null;
        try {
            clip = (Clip) AudioSystem.getLine(info);
        } catch (LineUnavailableException e) {
            e.printStackTrace();
        }
        try {
            clip.open(themestream);
        } catch (LineUnavailableException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        clip.start();
    }

    private void bind() {
        InputMap im = getInputMap();
        ActionMap am = getActionMap();

        im.put(KeyStroke.getKeyStroke("LEFT"), "left");
        am.put("left", new AbstractAction() {
            private static final long serialVersionUID = 1L;

            public void actionPerformed(ActionEvent e) {
                left = true;
                right = false;
            }
        });

        im.put(KeyStroke.getKeyStroke("RIGHT"), "right");
        am.put("right", new AbstractAction() {
            private static final long serialVersionUID = 1L;

            public void actionPerformed(ActionEvent e) {
                System.out.println("2");
                right = true;
                left = false;
            }
        });

        im.put(KeyStroke.getKeyStroke("SPACE"), "space");
        am.put("space", new AbstractAction() {
            private static final long serialVersionUID = 1L;

            public void actionPerformed(ActionEvent e) {
                if (left) {
                    TetrisFrame.replace();
                    clip.stop();
                    thread.stop();a
                }
            }
        });
    }

    private void loadImages() {
        try {
            titletop = ImageIO.read(new File("res/tetrispic.png"));
            titlebottom = ImageIO.read(new File("res/titlebottom.png"));
            selector = ImageIO.read(new File("res/selector.png"));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void addNotify() {
        super.addNotify();
        thread = new Thread(this);
        thread.start();
    }

    public void paint(Graphics g) {
        super.paint(g);

        g.drawImage(titletop, 0, 0, 640, 440, null);
        g.drawImage(titlebottom, 0, 440, 640, 136, null);
        if (left) {
            g.drawImage(selector, 36, 452, 16, 24, null);
        } else if (right) {
            g.drawImage(selector, 356, 452, 16, 24, null);
        }


    }
}

Here is my second JPanel/menu:

package com.cgp.tetris;

import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.UnsupportedAudioFileException;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JPanel;
import javax.swing.KeyStroke;

public class SinglePlayerMenu extends JPanel implements Runnable {
    private static final long serialVersionUID = 1L;
    private Thread thread;
    private AudioInputStream themestream;
    private Clip clip;
    private BufferedImage base, bi1, bi2, bi3, bi4, bi1s, bi2s, bi3s, bi4s;
    private boolean typeselected = false, b1 = true, b2 = false, b3 = true, b4 = false, b5 = false, b6 = false;

    public SinglePlayerMenu() {
        super();
    }

    public void paint(Graphics g) {
        super.paint(g);

        g.drawImage(base, 0, 0, 640, 576, null);

        if (b1) {
            g.drawImage(bi1s, 80, 156, 240, 40, null);
        } else if (!b1) {
            g.drawImage(bi1, 80, 156, 240, 40, null);
        }
        if (b2) {
            g.drawImage(bi2s, 324, 156, 236, 40, null);
        } else if (!b2) {
            g.drawImage(bi2, 324, 156, 236, 40, null);
        }
    }

    public void run() {
        sound();
        loadImages();
        bind();
        while (true) {
            repaint();
        }
    }

    private void bind() {
        InputMap im = getInputMap();
        ActionMap am = getActionMap();

        im.put(KeyStroke.getKeyStroke("LEFT"), "left");
        am.put("left", new AbstractAction() {
            private static final long serialVersionUID = 1L;

            public void actionPerformed(ActionEvent e) {
                if (!typeselected) {
                    b1 = true;
                    b2 = false;
                }
            }
        });

        im.put(KeyStroke.getKeyStroke("RIGHT"), "right");
        am.put("right", new AbstractAction() {
            private static final long serialVersionUID = 1L;

            public void actionPerformed(ActionEvent e) {
                if (!typeselected) {
                    b2 = true;
                    b1 = false;
                }
            }
        });
    }

    private void loadImages() {
        try {
            base = ImageIO.read(new File("res/base.png"));
            bi1 = ImageIO.read(new File("res/1.png"));
            bi2 = ImageIO.read(new File("res/2.png"));
            bi3 = ImageIO.read(new File("res/3.png"));
            bi4 = ImageIO.read(new File("res/4.png"));
            bi1s = ImageIO.read(new File("res/1s.png"));
            bi2s = ImageIO.read(new File("res/2s.png"));
            bi3s = ImageIO.read(new File("res/3s.png"));
            bi4s = ImageIO.read(new File("res/4s.png"));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void sound() {
        File theme = new File("res/playtheme.wav");
        try {
            themestream = AudioSystem.getAudioInputStream(theme);
        } catch (UnsupportedAudioFileException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        DataLine.Info info = new DataLine.Info(Clip.class, themestream.getFormat());
        clip = null;
        try {
            clip = (Clip) AudioSystem.getLine(info);
        } catch (LineUnavailableException e) {
            e.printStackTrace();
        }
        try {
            clip.open(themestream);
        } catch (LineUnavailableException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void addNotify() {
        super.addNotify();
        thread = new Thread(this);
        thread.start();
    }

    public void startClip() {
        clip.loop(Clip.LOOP_CONTINUOUSLY);
    }
}

Thanks in advance!

kleopatra
  • 51,061
  • 28
  • 99
  • 211
Cg2916
  • 1,117
  • 6
  • 23
  • 36
  • 1
    For better help sooner, post an [SSCCE](http://sscce.org/). – Andrew Thompson Nov 27 '11 at 02:32
  • For some reason, the files won't package correctly into the JAR. Just a minute. – Cg2916 Nov 27 '11 at 02:36
  • How do I package files into a JAR? – Cg2916 Nov 27 '11 at 02:40
  • Why are you asking about packaging a Jar? – Hovercraft Full Of Eels Nov 27 '11 at 03:00
  • I'm trying to create a SSCCE, but for some reason, the images I'm using won't show up when I run the jar. – Cg2916 Nov 27 '11 at 03:01
  • 1
    What do you need images for? All you want is a small app that uses cardlayout and tries to get focus, ***that's it***. There's no need for images or any of that nonsense unrelated to the problem. – Hovercraft Full Of Eels Nov 27 '11 at 03:08
  • That's my whole GUI. You can't tell if it's working or not without the images. – Cg2916 Nov 27 '11 at 03:09
  • 1
    *"You can't tell if it's working or not without the images."* I find that very hard to believe. Most focusable elements to which you can add an image also accept plain text. If it is the case that images are really required, generate some images in code as shown in the [Nested Layout Example](http://stackoverflow.com/questions/5621338/about-swing-and-jtable/5630271#5630271). The images do not even require a (nice) gradient like shown there - separate solid colors will be fine. – Andrew Thompson Nov 27 '11 at 03:13
  • Oh wow. Eclipse is really giving me grief. I tried creating an SSCCE with just filled-in background colors (no images), and it STILL won't work. – Cg2916 Nov 27 '11 at 03:25

4 Answers4

7

Have you tried giving the component of interest a ComponentListener, and in its componentShown(...) request the focus via requestFocusInWindow()? Something like:

  myComponent.addComponentListener(new ComponentAdapter() {

     @Override
     public void componentShown(ComponentEvent cEvt) {
        Component src = (Component) cEvt.getSource();
        src.requestFocusInWindow();
     }

  });

Of course the component (a JPanel in your case) needs to have its focusable property set to true via

myComponent.setFocusable(true);

For example:

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

@SuppressWarnings("serial")
public class CardLayoutFocus extends JPanel {
   private static final int PREF_W = 300;
   private static final int PREF_H = 150;
   private static final int COUNT = 4;
   private CardLayout cardlayout = new CardLayout();
   private JPanel cardHolder = new JPanel(cardlayout );

   public CardLayoutFocus() {
      setLayout(new BorderLayout());
      add(cardHolder, BorderLayout.CENTER);
      for (int i = 0; i < COUNT; i++) {
         String labelString = "Card " + i;
         cardHolder.add(createCardLabel(labelString), labelString);
      }

      int timerDelay = 1000;
      new Timer(timerDelay , new ActionListener() {
         public void actionPerformed(ActionEvent arg0) {
            cardlayout.next(cardHolder);
         }
      }).start();
   }

   private JLabel createCardLabel(String labelString) {
      final JLabel label = new JLabel(labelString, SwingConstants.CENTER);
      label.setName(labelString);
      label.setOpaque(true);
      label.setFocusable(true);
      label.addFocusListener(new FocusListener() {

         @Override
         public void focusLost(FocusEvent arg0) {
            label.setBackground(null);
         }

         @Override
         public void focusGained(FocusEvent arg0) {
            label.setBackground(Color.pink);
         }
      });
      label.addComponentListener(new ComponentAdapter() {

         @Override
         public void componentShown(ComponentEvent cEvt) {
            Component src = (Component) cEvt.getSource();
            src.requestFocusInWindow();
         }

      });
      return label;
   }

   @Override
   public Dimension getPreferredSize() {
      return new Dimension(PREF_W, PREF_H);
   }

   private static void createAndShowGui() {
      CardLayoutFocus mainPanel = new CardLayoutFocus();

      JFrame frame = new JFrame("Pink if has the focus");
      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      frame.getContentPane().add(mainPanel);
      frame.pack();
      frame.setLocationByPlatform(true);
      frame.setVisible(true);
   }

   public static void main(String[] args) {
      SwingUtilities.invokeLater(new Runnable() {
         public void run() {
            createAndShowGui();
         }
      });
   }
}
Hovercraft Full Of Eels
  • 283,665
  • 25
  • 256
  • 373
4

Add a ComponentListener so you can request the window to put it in focus. Replace your existing constructor with this:

public SinglePlayerMenu() {
    super();
    this.addComponentListener( new ComponentAdapter() {
        @Override
        public void componentShown( ComponentEvent e ) {
            SinglePlayerMenu.this.requestFocusInWindow();
        }
    });
}
Hovercraft Full Of Eels
  • 283,665
  • 25
  • 256
  • 373
blackcompe
  • 3,180
  • 16
  • 27
3

This is usually achieved by calling requestFocusInWindow().

Andrew Thompson
  • 168,117
  • 40
  • 217
  • 433
3

Card Layout Focus handles this by placing focus on the first component on the panel.

camickr
  • 321,443
  • 19
  • 166
  • 288