Duda manejando eventos de teclado en Java

Iniciado por niano, 11 Julio 2019, 12:26 PM

0 Miembros y 1 Visitante están viendo este tema.

niano

Hola.

Soy algo novato en todo esto de las interfaces gráficas y animaciones y tengo algunas dudas con un código en Java que estoy confeccionando. Necesito crear una animación controlada por cuatro teclas del teclado que empiece o se detenga cuando se aprieta un botón con el ratón.

Para eso defino utilizo una clase a la que he llamado Panel:

Código (java) [Seleccionar]

public class Panel extends Canvas implements Runnable, KeyListener{

    //Atributos:
   private int ancho, alto; //el alto y el ancho del panel
    private boolean play=false;//Variable que nos indica si la animación ha comenzado o no.
   //... Más atributos. En general, objetos que deben ser pintados. Y variables que indican lo que
    //tienen que hacer esos objetos durante la animación
   
    //Constructor:
   public Panel(int anch, int alt)
    {
         ancho=anch;
         alto=alt;
         play=false;
         //Se construyen los demás atributos
    }

    public void paint (Graphics G)
    {
         //Aquí se pintan algunos de los atributos del objeto utilizando técnicas de doble bouffering
    }

    public void update (Graphics G)
    {
        paint (G);
    }
   
    public void actualizar()
    {
        //Nada.. Aquí se actualiza la posición de los objetos (atributos de Panel) que deben ser
        //pintados durante la animación
    }

    public void run()
    {
        while (play)
        {
            actualizar();
            repaint();
            try
            {
                Thread.sleep((int)espera);
            }
            catch (InterruptedException E)
            {}
            //Y más cosas que no creo que vengan a cuento.
        }
    }
    public void Iniciar()
    {
        play=true;
        Thread hilo=new Thread(this);
        hilo.start();
    }
    public void Detener()
    {
        play=false;
    }
    //Metodos de KeyListener
    public void keyTyped(KeyEvent e) {
    }
    public void keyPressed(KeyEvent e)
    {
        if (e.getKeyChar()=='a')
        {
            //Se modifican los atributos necesarios para que la animación haga lo que tiene que
            //hacer cuando se pulsa una 'a'
        }
        if (e.getKeyChar()=='s')
        {
            //Lo mismo para la 's'
        }
        if (e.getKeyChar()=='l')
        {
            //...
        }
        if (e.getKeyChar()=='ñ')
        {
            //...
        }
    }

    @Override
    public void keyReleased(KeyEvent e)
    {
        if (e.getKeyChar()=='a')
        {
            //Se modifican los atributos necesarios para que la animación haga lo que tiene que
            //hacer cuando se suelta la 'a'
        }
        if (e.getKeyChar()=='s')
        {
           //...
        }
        if (e.getKeyChar()=='l')
        {
           //...
        }
        if (e.getKeyChar()=='ñ')
        {
            //...
        }
    }

}


Y otra clase, la clase Juego2:

Código (java) [Seleccionar]

public class Juego2 extends JFrame implements ActionListener
{
    private Panel P;
    private JButton boton;
    private boolean play=false;
    private int anchoP=900, altoP=700;

    //El constructor. No creo que tenga mucho interés para lo de mi duda pero lo pongo por si
    //acaso:
    public Juego2()
    {
        P=new Panel (anchoP,altoP);
        boton=new JButton("play");
        setBackground(new Color(240,240,240));
        setLayout(null);       
        boton.addActionListener(this);
       
        getContentPane().add(boton);
        boton.setBounds(20,330,250,30);
       
        getContentPane().add(P);
        P.setBounds(300, 30, anchoP, altoP);
        P.setVisible(true);
       
        addWindowListener(new WindowAdapter(){
            public void windowClosing(WindowEvent e){
                System.exit(0);
            }
        });
    }

    //el main
    public static void main(String[] args) {
        final Juego2 F = new Juego2();
        F.setBounds(0, 0, 1300, 800);
        F.setVisible(true);
    }

    //El actionPerformed de la clase ActionListener
    public void actionPerformed(ActionEvent e) {
        if (e.getSource()==boton)
        {
            if (play)
            {
                play=false;
                boton.setText("play");
                P.Detener();
            }
            else
            {
                play=true;
                boton.setText("pause");
                P.Iniciar();
            }
        }
    }
}


A continuación explicaré el fenómeno que me crea dudas.Cuando el código está listo hago lo siguiente:

1) Ejecuto el código. La cosa va bien, todo se construye y se visualiza correctamente.
2) Le doy al play. Y la animación comienza correctamente.
3) Le doy a una de las teclas controladoras y no ocurre nada. ¡Nada de nada!
4) Hago un 'click' de ratón sobre el panel y desde entonces ya sí funciona todo bien. El 'click' tiene que ser sobre el panel, si lo hago sobre otro punto de la ventana o fuera de ella las teclas controladoras siguen inactivas.

¿Alguien sabe por qué pasa esto y cómo puedo solucionarlo? Es decir, me gustaría que las teclas controladoras funcionasen desde que le doy al play, que no fuese necesario hacer ese 'click'.

Muchas gracias de antemano por vuestro tiempo. Un saludo.

rub'n

#1
Tu Panel extiende de Canvas, debería ser de JPanel, ambos se aplica lo mismo para obtener el foco, cuando haces click obtienes el foco del panel que contiene los eventos del KeyListener dog.

Aqui la parte azul representa a ese Pseudo Panel que tienes  :xD



Por ningun lado vi a

Código (java) [Seleccionar]
addKeyListener(this);

lo añadí en el método iniciar, entonces al darle click se habilita el KeyListener y el foco de ese Panel con

Código (java) [Seleccionar]
requestFocus();

quedando

Código (java) [Seleccionar]
public class Panel extends Canvas implements Runnable, KeyListener, ShowData {

   //Atributos:
   private int ancho, alto; //el alto y el ancho del panel
   private boolean play = false;//Variable que nos indica si la animación ha comenzado o no.
   //... Más atributos. En general, objetos que deben ser pintados. Y variables que indican lo que
   //tienen que hacer esos objetos durante la animación
   private int espera = 1000;

   //Constructor:
   public Panel(int anch, int alt) {
       ancho = anch;
       alto = alt;
       play = false;
       //Se construyen los demás atributos
       setBackground(Color.BLUE);

   }

   public void paint(Graphics G) {
       //Aquí se pintan algunos de los atributos del objeto utilizando técnicas de doble bouffering
   }

   public void update(Graphics G) {
       paint(G);
   }

   public void actualizar() {
       //Nada.. Aquí se actualiza la posición de los objetos (atributos de Panel) que deben ser
       //pintados durante la animación
   }

   @Override
   public void run() {
       while (play) {
           actualizar();
           repaint();
           try {
               Thread.sleep(espera);
           } catch (InterruptedException E) {
           }
           //Y más cosas que no creo que vengan a cuento.
       }
   }

   public void iniciar() {
       play = true;
       addKeyListener(this);
       requestFocus();
       Thread hilo = new Thread(this);
       hilo.start();
   }

   public void detener() {
       play = false;
       removeKeyListener(this);
   }

   //Metodos de KeyListener
   public void keyTyped(KeyEvent e) {
   }

   public void keyPressed(KeyEvent e) {
       if (e.getKeyChar() == 'a') {
           //Se modifican los atributos necesarios para que la animación haga lo que tiene que
           //hacer cuando se pulsa una 'a'
           info("Letra A");
       }
       if (e.getKeyChar() == 's') {
           //Lo mismo para la 's'
           info("Letra S");
       }
       if (e.getKeyChar() == 'l') {
           //...
           info("Letra L");
       }
       if (e.getKeyChar() == 'ñ') {
           //...
           info("Letra Ñ");
       }
   }

   @Override
   public void keyReleased(KeyEvent e) {
       if (e.getKeyChar() == 'a') {
           //Se modifican los atributos necesarios para que la animación haga lo que tiene que
           //hacer cuando se suelta la 'a'
           info("Letra A soltada");
       }
       if (e.getKeyChar() == 's') {
           //...
           info("Letra s soltada");
       }
       if (e.getKeyChar() == 'l') {
           //...
           info("Letra l soltada");
       }
       if (e.getKeyChar() == 'ñ') {
           //...
           info("Letra ñ soltada");
       }
   }

}


Juego2

Código (java) [Seleccionar]
public class Juego2 extends JFrame implements ActionListener {

   private Panel panel;
   private JButton boton;
   private boolean play = false;
   private int anchoP = 900, altoP = 700;

   //El constructor. No creo que tenga mucho interés para lo de mi duda pero lo pongo por si
   //acaso:
   public Juego2() {
       panel = new Panel(anchoP, altoP);
       boton = new JButton("play");

       setBackground(new Color(240, 240, 240));
       setLayout(null);
       setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
       setBounds(0, 0, 1300, 800);
       setVisible(true);

       boton.addActionListener(this);

       getContentPane().add(boton);
       boton.setBounds(20, 330, 250, 30);

       getContentPane().add(panel);
       panel.setBounds(300, 30, anchoP, altoP);
       panel.setVisible(true);

       addWindowListener(new WindowAdapter() {
           @Override
           public void windowClosing(WindowEvent e) {
               final int opc = JOptionPane.showConfirmDialog(null,
                       "Información",
                       "Desea salir?",
                       0);
               if(opc == 0) {
                   System.exit(0);
               }
           }
       });

   }

   //el main
   public static void main(String[] args) {
       new Thread(Juego2::new).start();
   }

   //El actionPerformed de la clase ActionListener
   public void actionPerformed(ActionEvent e) {
       if (e.getSource() == boton) {
           if (play) {
               play = false;
               boton.setText("play");
               panel.detener();
           } else {
               play = true;
               boton.setText("pause");
               panel.iniciar();
           }
       }
   }
}


Y una interface para los logs

Código (java) [Seleccionar]
/**
* logs
*/
public interface ShowData {

   default Logger getLogger() {
       return LoggerFactory.getLogger(getClass().getSimpleName());
   }

   default <T> void info(final T s) {
       getLogger().info(s.toString());
   }

   default <T> void error(final T s) {
       getLogger().warn(s.toString());
   }
}



rubn0x52.com KNOWLEDGE  SHOULD BE FREE!!!
If you don't have time to read, you don't have the time (or the tools) to write, Simple as that. Stephen

niano

Hola rub'n.

Antes de nada muchas gracias por tu respuesta, me ha sido muy útil. He hecho los cambios que me has indicado y ya funciona todo perfectamente. No obstante, me gustaría hacer un par de preguntas y observaciones si no te importa.

Cita de: rub'n en 11 Julio 2019, 16:08 PM

Tu Panel extiende de Canvas, debería ser de JPanel, ambos se aplica lo mismo para obtener el foco, cuando haces click obtienes el focu del panel que contiene los eventos del KeyListener dog.


¿Te importaría indicarme, aunque sea por encima, qué ventajas tiene la clase JPanel sobre la Canvas? Según tengo entendido la clase JPanel es más nueva y todo eso. Además, es cierto que con la Canvas sigue sin funcionar el asunto. Lo que sucede es que la clase JPanel creo que no la controlo demasiado. Tampoco sé lo que es un dog  :huh:

En otro ejercicio que me planteé parecido a este, si utilizaba la clase JPanel me empezaban a salir componentes como botones y cuadros de texto sobre el panel si hacía algo con ellos, cuando en un principio debían estar fuera. ¿Qué te parece?¿Sabrías decirme por qué me podía estar pasando eso? Con la Canvas no me pasaba y por eso renegué de la clase JPanel. Leeré algo más sobre la JPanel a ver si me familiarizo un poco más con ella.

Cita de: rub'n en 11 Julio 2019, 16:08 PM
Por ningun lado vi a

Código (java) [Seleccionar]
addKeyListener(this);

lo añadí en el método iniciar, entonces al darle click se habilita el KeyListener y el foco de ese Panel con

Código (java) [Seleccionar]
requestFocus();

Sí, tienes toda la razón, se me olvidó lo del addKeyListener cuando escribí el mensaje, aunque sí que lo tenía en mi código, dentro del constructor de la clase Panel.

Supongo que la clave para solucionar el problema está en lo del requestFocus(). No conocía ese método. Si lo quito los métodos de la KeyListener no se activan, por muchos 'clicks' que haga. Sin embargo, si utilizo una Canvas, es indiferente ponerlo o no ponerlo. Para que se ejecuten los métodos del KeyListener hay que hacer 'click'. ¿Sabes por qué? ¿O sabes dónde puedo encontrar información sobre todo esto? Me temo que el material que tengo es bastante corto para responderme a todas las preguntas que me surgen cuando pongo las cosas en práctica.

Por otro lado, dentro del detener:
.
Cita de: rub'n en 11 Julio 2019, 16:08 PM
Código (java) [Seleccionar]

public void detener() {
    play = false;
    removeKeyListener(this);
}


¿Qué hace ese removeKeyListener? ¿Es necesario? No lo había visto nunca, en mi corta experiencia en estos asuntos.

Y para finalizar, añadir que me has dado una buena idea con la modificación del windowClosing para que el programa confirme que realmente se quiere salir. Empiezo ahora también con los cuadros de diálogo. A ver si poco a poco me voy aclarando.

Y eso, que muchísimas gracias por todo, en serio. Un saludo.