terça-feira, 17 de julho de 2012

Drag'n Drop de elementos gráficos (Java-AWT)

Um rapaz me perguntou como seria fazer um drag'n drop (na mão) de um elemento gráfico em uma tela.
Então resolvi postar uma resposta aqui no blog. :)
Como ele não especificou a linguagem, vou fazer em Java/AWT (será que alguém ainda usa isso?!? :P ), que foi a linguagem que mais trabalhei nos últimos 10 anos...

Primeiramente, vamos criar uma janela de tamanho fixo (no caso, 300x300):

//Herda de Frame (Janela)
public class MainClass extends Frame {

    //Construtor da classe
    public MainClass() {
        //Tamanho da janela
        this.setSize(300, 300);
        //Título da janela
        this.setTitle("Exemplo Gráfico");
    }

    //Método principal que inicia a aplicação
    public static void main(String arg[]) {
        //Instância o objeto
        MainClass main = new MainClass();
        //Exibe a tela
        main.setVisible(true);
    }
}

Para evitar que a janela fique "ad-eterno" na tela, mesmo clicando no "X", vamos implementar a função de fechar da janela em seu construtor:

...
    //Construtor da classe
    public MainClass() {
         ...
         //"Escutador" de eventos de janela
         this.addWindowListener(new WindowAdapter() {
             //Método que é executado quando o usuário clicar no botão de fechar da janela (ou algum evento que tenha a mesma funcionalidade, como o Ctrl+X no MS-Windows)
            @Override
            public void windowClosing(WindowEvent e) {
                //Encerra a aplicação
                System.exit(0);
            }
        });
        ...    
    }

...

Legal, temos uma janela feita em Java! :)

Vamos para o segundo passo: criamos um objeto gráfico (neste caso, um retângulo) como uma propriedade da classe e instanciamos esta em seu construtor:

public class MainClass extends Frame {
    private Rectangle rect;
    ...
    public MainClass() {
          ...
          //Retângulo a ser desenhado
          rect = new Rectangle(50, 50, 60, 30);
          ...
    }
    ...
}

E agora vamos desenhar este retângulo na tela, reescrevendo os métodos paint e update:

...

   //Para evitar o "pisca-pisca" (efeito de blink) da tela
    @Override
    public void update(Graphics g) {
        this.paint(g);
    }
   
    //Desenha um retângulo vermelho em um fundo preto (retângulo do tamanho da tela)
    @Override
    public void paint(Graphics g) {
        //Pinta a tela de preto
        g.setColor(Color.BLACK);
        g.fillRect(0, 0, this.getWidth(), this.getHeight());
        //Pinta um retângulo vermelho
        g.setColor(Color.RED);
        g.fillRect((int)rect.getX(), (int)rect.getY(),
                (int)rect.getWidth(), (int)rect.getHeight());
    }
...


Executamos e temos um retângulo vermelho em um fundo preto (eu já disse isso em algum lugar?). :)

Nesta terceira etapa, precisamos identificar os eventos de drag'n drop, que são (eu acho...):
1) O evento de botão do mouse pressionado para obter o efeito de "pegar" o retângulo;
2) O evento de soltar o botão do mouse para obter o efeito de "largar" o retângulo;
3)  E o evento de movimento do mouse, para que o retângulo acompanhe o "ponteiro" do mouse enquanto ele não for solto;

Com isso, a classe vai ficar mais ou menos assim:


import java.awt.*;
import java.awt.event.*;


//Herda de Frame (Janela)
public class MainClass extends Frame {

    //Retângulo a ser desenhado
    private Rectangle rect;
    //Flag que indica se o objeto esta sendo "arrastado"
    private boolean elementDragged;
    //Ponto (x,y) da tela onde o ponteiro do mouse estava inicialmente
    private Point initial;
 
    //Construtor da classe  
    public MainClass() {
        //Retângulo a ser desenhado
        rect = new Rectangle(50, 50, 50, 50);
        //Tamanho da janela
        this.setSize(300, 300);
        //Título da Janela
        this.setTitle("Exemplo Gráfico");
        this.addEventListener();
    }
   
     //Método privado que adiciona os eventos
    private void addEventListener() {
        //"Escutador" de eventos de janela
        this.addWindowListener(new WindowAdapter() {
             //Método que é executado quando o usuário clicar no botão de fechar da janela (ou algum evento que tenha a mesma funcionalidade, como o Ctrl+X no MS-Windows)
            @Override
            public void windowClosing(WindowEvent e) {
                System.exit(0);
            }
        });
        //"Escutador" de eventos do mouse
        this.addMouseListener(new MouseAdapter() {
            @Override
            public void mousePressed(MouseEvent e) {
                mousePressedEvent(e);
            }           
            @Override
            public void mouseReleased(MouseEvent e) {
                mouseReleasedEvent(e);
            }
        });
        //"Escutador" de eventos de movimento do mouse
        this.addMouseMotionListener(new MouseMotionAdapter() {
            @Override
            public void mouseDragged(MouseEvent e) {
                mouseDraggedEvent(e);
            }
        });
    }
   
    //Quando o botão do mouse é pressionado, este método é executado
    private void mousePressedEvent(MouseEvent e) {
        //Se o ponteiro do mouse esta dentro do retângulo...
        if (rect.contains(e.getPoint())) {
            //Obtenho o ponto inicial do ponteiro do mouse
            initial = new Point((int)(e.getPoint().getX() - rect.getX()),
                    (int)(e.getPoint().getY() - rect.getY()));
            //E digo que o retângulo pode ser "arrastado"
            elementDragged = true;
        }       
    }
   
    //Quando o mouse é "arrastado", este método é executado
    private void mouseDraggedEvent(MouseEvent e) {
        //Se o retângulo pode ser "arrastado"
        if (elementDragged) {
            //Faço ele movimentar-se de acordo com a posição do ponteiro do mouse
            rect.setLocation((int)((e.getPoint().getX() - initial.getX())),
                    (int)(e.getPoint().getY() - initial.getY()));
            //Redesenho a tela
            this.repaint();
        }
    }           
 
   //Quando o botão do mouse é "solto", este método é executado
    public void mouseReleasedEvent(MouseEvent e) {
        //Digo que o retângulo não esta mais "arrastavel"
        elementDragged = false;
        //E "zero" a posição inicial
        initial = null;
    }
   
    //Para evitar o "pisca-pisca" (efeito de blink) da tela
    @Override
    public void update(Graphics g) {
        this.paint(g);
    }
   
     //Desenha um retângulo vermelho em um fundo preto (retângulo do tamanho da tela)
    @Override
    public void paint(Graphics g) {
        //Pinta a tela de preto
        g.setColor(Color.BLACK);
        g.fillRect(0, 0, this.getWidth(), this.getHeight());
        //Pinta um retângulo vermelho
        g.setColor(Color.RED);
        g.fillRect((int)rect.getX(), (int)rect.getY(),
                (int)rect.getWidth(), (int)rect.getHeight());
    }
 
    //Método principal que inicia a aplicação (tudo começa aqui... :) )
    public static void main(String arg[]) {
        //Instância a classe
        MainClass main = new MainClass();
        //Exibe a tela
        main.setVisible(true);
    }
}

Parece que funciona... (eu testei no MacOSX 10.7.4, com o JDK 1.6.0_33)

Até mais!

Nenhum comentário: