1

¡Hola muy buenas noches!, tengo cierto problema en java que ya me esta desconcertando bastante, ya que no pensé que algo que yo pensaría que sería "relativamente sencillo", resultase en que llevo 1 día y medio pensando en porque el programa no me funciona bien... (Tener en cuenta que casi no se nada de Java), soy un completo novato.

El lenguaje de programación con el que estoy familiarizado es javascript (Bastante familizarizado de hecho), pero mirando ahora a Java y el uso de las librerías graficas awt, me quede ya un poco perdido... tan perdido que no tengo idea de si lo que estoy haciendo esta correctamente organizado y estructurado para lo que estoy haciendo.

Para poner rápido en contexto lo que debo hacer (Es una tarea de la universidad), debo crear un "paint", pero sencillo, el cual me permita dibujar trazos a pulso, (Como si de la herramienta lapiz de paint se tratase), el programa debe permitir cambiar de "herramienta de dibujo", y debe permitir realizar formas primitivas, por ejemplo un rectangulo, un circulo y ademas permitir cambiar el color del trazó y relleno.

Por supuesto, esto de agregar herramientas seleccionarlas cambiar el color de trazado y de relleno y hacer diferentes formas una vez logre hacer que funcione el objeto Graphics2D, las implementare luego de que logre hacer funcionar Graphics2D...

Aquí esta el código de las diferentes clases que tengo ya creadas del programa:

clase principal (magicBoard):

package magicBoard;
import java.awt.*;

public class MagicBoard {

    public static void main(String[] args) {

        Board board = new Board("¡Tablero magico!",Color.orange, 800,600);
        board.settings();

    }

}

Clase board:

package magicBoard;
import java.awt.*;
import javax.swing.*;

public class Board extends JFrame{

    protected Canvas canvas;
    protected String title;
    protected Color color;
    protected Container container;
    protected Dimension screenSize;

    Board(String title, Color color, int width, int height){

        this.canvas = new Canvas(width, height);
        this.title = title;
        this.color = color;
        this.container = getContentPane();
        this.screenSize = java.awt.Toolkit.getDefaultToolkit().getScreenSize();

    }


    public void settings() {

        setTitle(this.title);
        this.setSize(this.canvas.width , this.canvas.height);
        this.canvas.setBackground(this.color);
        container.setLayout(new BorderLayout());
        container.add(this.canvas, BorderLayout.CENTER);
        this.setLocation(
            this.screenSize.width / 2 - this.canvas.width / 2, 
            this.screenSize.height / 2 - this.canvas.height / 2
        );

        this.show(true);
        setDefaultCloseOperation(EXIT_ON_CLOSE);

    }


}

Clase canvas:

package magicBoard;
import java.awt.event.*;
import java.util.EventListener;
import javax.swing.*;

public class Canvas extends JPanel{

    protected EventListener listeners;
    protected Pointer pointer;
    protected int width,height;

    Canvas(int w, int h){

        //Tamaño de la ventana
        this.pointer = new Pointer(0,0);
        this.width = w;
        this.height = h;

        //Pocisiones X y Y actuales del cursor en el canvas
        listeners = new EventManager(this);
        addMouseListener((MouseListener) listeners);
        addMouseMotionListener((MouseMotionListener) listeners);

    }


}

Clase pointer (Ojo, esta clase va a tener mas funciones luego, en ella pondría las funciones de dibujado, de momento estas las implemente fue directamente en la clase que maneja los eventos (Como prueba), luego serán migradas una vez funcionen):

package magicBoard;
import java.awt.*;

public class Pointer {

    protected int x,y;

    Pointer(int x,int y){

        this.x = x;
        this.y = y;

    }

    public void updatePoint(Point p){

        this.x = p.x;
        this.y = p.y;

    }


}

Clase EventManager:

package magicBoard;
import java.awt.*;
import java.awt.event.*;

public class EventManager implements MouseListener,MouseMotionListener {

    protected boolean mouseIsPressed;
    protected Canvas canvas;
    protected Graphics2D ctx;

    public EventManager(Canvas canvas) {

        this.mouseIsPressed = false;
        this.canvas = canvas;
        this.ctx = (Graphics2D) canvas.getGraphics();
        System.out.println((Graphics2D) this.canvas.getGraphics());

    }

    @Override
    public void mouseDragged(MouseEvent e) {

        Point p = e.getPoint();

        if(this.mouseIsPressed) {

            this.ctx.drawLine(this.canvas.pointer.x,this.canvas.pointer.y, p.x,p.y);
            this.canvas.pointer.updatePoint(p);

        }

    }

    @Override
    public void mouseMoved(MouseEvent e) {
        // TODO Auto-generated method stub

    }

    @Override
    public void mouseClicked(MouseEvent ev) {
        // TODO Auto-generated method stub

    }

    @Override
    public void mouseEntered(MouseEvent ev) {
        // TODO Auto-generated method stub

    }

    @Override
    public void mouseExited(MouseEvent ev) {
        // TODO Auto-generated method stub

    }

    @Override
    public void mousePressed(MouseEvent ev) {

        this.canvas.pointer.updatePoint(ev.getPoint());
        this.mouseIsPressed = true;

    }

    @Override
    public void mouseReleased(MouseEvent ev) {

        this.mouseIsPressed = false;

    }

}

El problema se da en la clase EventManager, donde al tratar de asignar una instancia nueva de tipo Graphics2D al atributo ctx de la clase EventManager, esta se guarda como null, dando como resultado que al iniciarse el programa dará este error:

Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException
at magicBoard.EventManager.mouseDragged(EventManager.java:27)

Mi pregunta es...

Si el atributo de la clase EventManager llamado canvas no es null (Ya que lo comprobé imprimiendolo en consola), entonces... ¿por qué no puedo obtener correctamente por parte de este objeto un objeto Graphics2D en el constructor de la clase??

Nota importante: Intente simplemente hacer lo siguiente en el método mouseDragged y funciona! (Existe la referencia).

En vez de:

public class EventManager implements MouseListener,MouseMotionListener {

    protected boolean mouseIsPressed;
    protected Canvas canvas;
    protected Graphics2D ctx;

    public EventManager(Canvas canvas) {

        this.mouseIsPressed = false;
        this.canvas = canvas;
        this.ctx = (Graphics2D) canvas.getGraphics();
        System.out.println((Graphics2D) this.canvas.getGraphics());

    }

    @Override
    public void mouseDragged(MouseEvent e) {

        Point p = e.getPoint();
        if(this.mouseIsPressed) {

            this.ctx.drawLine(this.canvas.pointer.x,this.canvas.pointer.y, p.x,p.y);
            this.canvas.pointer.updatePoint(p);

        }

    }
//Resto de código

Hacer esto:

   public class EventManager implements MouseListener,MouseMotionListener {

        protected boolean mouseIsPressed;
        protected Canvas canvas;

        public EventManager(Canvas canvas) {

            this.mouseIsPressed = false;
            this.canvas = canvas;

        }

        @Override
        public void mouseDragged(MouseEvent e) {

            Point p = e.getPoint();
            Graphics2D ctx = (Graphics2D) this.canvas.getGraphics();
            if(this.mouseIsPressed) {

                ctx.drawLine(this.canvas.pointer.x,this.canvas.pointer.y, p.x,p.y);
                this.canvas.pointer.updatePoint(p);

            }

        }
    //Resto de código

sin embargo me gustaría tener la referencia digamos lo así... Global, para poder que todas los métodos puedan acceder a un único objeto y no tener que estar creando uno por cada método...

Esto en javascript podría solucionarse fácilmente simplemente asignando el objeto en el constructor, pero esto no parece funcionar correctamente en java o no lo estoy haciendo bien... (También intente hacer el atributo ctx de tipo public y no funciono, sigue siendo null la referencia)... alguna manera de hacerlo global sin que la referencia termine siendo null para poderla usar de manera global en mis metodos??

Riven
  • 5,728
  • 2
  • 7
  • 27
  • 1
    Posible duplicado de [¿Cuál es la solución a todos los errores NullPointerException presentes, pasados y futuros?](https://es.stackoverflow.com/questions/42977/cu%c3%a1l-es-la-soluci%c3%b3n-a-todos-los-errores-nullpointerexception-presentes-pasados) – Trauma Apr 21 '19 at 06:52

2 Answers2

2

Antes de nada: ni idea de Java. Pero, con un buscador a mano ...

Primero, consultamos la documentación:

public Graphics getGraphics()

Creates a graphics context for this component. This method will return null if this component is currently not displayable.

Returns:
a graphics context for this component, or null if it has none

...umm... ... si no es posible mostrar el componente. Vale, ya nos vamos orientando. Ahora, miramos tu código ...

public class Board extends JFrame {
  ...
  Board( String title, Color color, int width, int height ) {
    this.canvas = new Canvas( width, height );
    this.title = title;
    this.color = color;
    ...
  }
  public void settings( ) {
    ...
    container.add( this.canvas, BorderLayout.CENTER );
    ...
  }
  ...
}

Vemos que primero creas el Canvas y después, en otro método distinto (settings( )), lo añades al Container ... claro, con toda la lógica del mundo. No lo puedes añadir antes de crearlo, ¿ verdad ?

Ahora, si miramos tu clase Canvas ... mas concretamente, su constructor:

public class Canvas extends JPanel {
  ...
  Canvas( int w, int h ) {
    ...
    listeners = new EventManager( this );
    ...
  }
}

Ok, creas un EventManager y le pasas tu propia instancia this ... ¡ Un momento ! ¿ No vimos ya que primero creas la instancia de Canvas y después la añadías ?

Ahí tienes tu problema: en el momento en el que llamas al constructor de EventManager, tu Canvas no es mostrable. Y, según la documentación que vimos al principio, canvas.getGraphics( ) devolverá null.

Me temo que tendrás que cambiar la lógica de esa parte de tu aplicación. Lamento no poder ser de mas ayuda a partir de aquí, ya dije que ni idea de java :-)

Trauma
  • 25,297
  • 4
  • 37
  • 60
0

Puedes inicializar las variables que requieres cuando el mouse entra al Canvas, en lugar de hacerlo en el constructor.

public class EventManager implements MouseListener,MouseMotionListener {

    protected boolean mouseIsPressed;
    protected Canvas canvas = null;
    protected Graphics2D ctx = null;

    public EventManager() {
        this.mouseIsPressed = false;
    }
    @Override
    public void mouseEntered(MouseEvent e) {
        if (canvas == null) {
            canvas = (Canvas)e.getSource();
            ctx = (Graphics2D)canvas.getGraphics();
        }
        //el resto de lo que requieras hacer en este método
    }
 }

Otra opción es que obtengas la información en cada evento. Esto sería otra opción:

public class EventManager implements MouseListener,MouseMotionListener {

    protected boolean mouseIsPressed;


    protected Canvas dameCanvasDelEvento(MouseEvent e) {
        return (Canvas)e.getSource();
    }
    protected Graphics2D dameGraficosDeCanvas(Canvas canvas) {
        return (Graphics2D)canvas.getGraphics();
    }
    protected Graphics2D dameGraficosDeEvento(MouseEvent e) {
        return dameGraficosDeCanvas(dameCanvasDelEvento(e));
    }

    @Override
    public void mouseDragged(MouseEvent e) {
        Canvas canvas = dameCanvasDelEvento(e);
        Graphics2D ctx = dameGraficosDeCanvas(canvas);

        Point p = e.getPoint();
        if(this.mouseIsPressed) {

            this.ctx.drawLine(this.canvas.pointer.x,this.canvas.pointer.y, p.x,p.y);
            this.canvas.pointer.updatePoint(p);
        }
    }
}

Otra opción pudiera ser que Canvas en sí manejara los eventos y así no tendrías que requerir de las variables globales ya que estarías a dentro de. Ejemplo:

public class Canvas extends JPanel{

protected EventListener listeners;
protected Pointer pointer;
protected int width,height;
protected boolean mouseIsPressed = false; ///aquí pudiéramos poner esta

protected ManejadorDeMouse manejadorDeMouse;


Canvas(int w, int h){

    //Tamaño de la ventana
    this.pointer = new Pointer(0,0);
    this.width = w;
    this.height = h;

    //Pocisiones X y Y actuales del cursor en el canvas
    listeners = new EventManager(this);
    iniciaManejadorDeMouse();
}
private void iniciaManejadorDeMouse {
    manejadorDeMouse = new ManejadorDeMouse();
    addMouseListener(manejadorDeMouse);
    addMouseMotionListener(manejadorDeMouse);
}

protected Graphics2D get2d() {
   return (Graphics2D)getGraphics();
}

protected class ManejadorDeMouse implements MouseListener, MouseMotionListener {

@Override
public void mouseDragged(MouseEvent e) {
   /* Esta es subclase de Canvas. Por lo que tenemos acceso
      a todo Canvas y a sus métodos.
   */
    Point p = e.getPoint();
    if(mouseIsPressed) {
       get2d().drawLine(pointer.x,pointer.y, p.x,p.y);
       pointer.updatePoint(p);
    }
}


}
AlbertoLopez
  • 1,541
  • 2
  • 7
  • 13
  • Gracias!, tu forma me ha funcionado, sin embargo la modifique un poco, ya que el estar obteniendo las Graphics en mouseDragged alenta un poco el dibujado, así que la puse en otra parte. – Riven Apr 21 '19 at 06:02
  • me da mucho gusto. Saludos – AlbertoLopez Apr 21 '19 at 06:14