Utilizando o padrão Fábrica para criar frames de testes – Parte II

Neste artigo será feito a adição de mais um frame com novas funcionalidades e demonstraremos o poder do padrão proposto.

Utilizando o padrão Fábrica para criar frames de testes – Parte II

 

O objetivo deste artigo é dar continuidade à idéia proposta na primeira parte de Utilizando o padrão Fábrica para criar frames de testes. O que será feito é a adição de mais um novo frame com novas funcionalidades e demonstrar o poder do padrão proposto, tendo em vista que a única classe a sofrer alteração será a  responsável por representar a Factory.

 

Para melhor entendimento do artigo, é necessário ter uma noção básica do padrão Fábrica.

 

Introdução

Na primeira parte do artigo, quando foi citada a idéia e o por quê de usar o padrão Fábrica, foi proposta a criação de um frame que recebesse uma certa quantidade de panels e permitisse que o desenvolvedor os posicionasse dinamicamente, recebendo as coordenadas para fixá-los posteriormente na aplicação.

 

Será criado o frame e posteriormente configurado na classe factory desenvolvida no artigo anterior. E para ficar clara a idéia do padrão, tente imaginar como seria feita a inserção de uma nova funcionalidade à biblioteca de frames de testes se o mesmo não fosse adotado.

 

O novo frame de testes

A idéia agora é criar um frame que receba uma certa quantidade de panels e permita que o desenvolvedor posicione-os livremente e dinamicamente. Em seguida terá acesso às coordenadas de cada um para posteriormente configurá-las no código.

 

Vale lembrar que esse trabalho todo é feito, como descrito anteriormente, nos casos em que frameworks não são utilizados e há de se configurar a interface gráfica manualmente.

 

A Listagem 01 indica o início do código de JFrameWithDraggablePanel – a nova classe a ser adicionada à Factory.

 

package br.com.jm.jframes;

 

import java.awt.Color;

import java.awt.Point;

import java.awt.event.MouseEvent;

import java.awt.event.MouseListener;

import java.awt.event.MouseMotionListener;

 

import javax.swing.JFrame;

import javax.swing.JPanel;

 

public class JFrameWithDraggablePanel extends JFrameFactory implements MouseMotionListener, MouseListener{

 

      private int xPressed;

      private int yPressed;

      private int xDragged;

      private int yDragged;

     

      private JPanel[] panels;

     

      public JFrameWithDraggablePanel(JPanel[] panel){

            this.xPressed = 0;

            this.yPressed = 0;

            this.xDragged = 0;

            this.yDragged = 0;

           

            this.panels = panel;

            this.configurePanels();

           

            super.setLayout(null);

            for (JPanel current : panels) {

                  super.getContentPane().add(current);

            }

            super.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

super.setExtendedState(JFrame.MAXIMIZED_BOTH);

      }

Listagem 01 – Início do código.

 

A classe, filha de JFrameFactory (como os outros frames retornados), implementa as interfaces MouseMotionListener e MouseListener para que seja possível trabalhar com os panels dinamicamente. Pode-se também implementar KeyListener para permitir que o posicionamento dinâmico dos panels, em tempo de execução, seja feito com as teclas de direção, mas para focar o padrão Factory será feita da forma mais simples.

 

As variáveis xPressed, yPressed, xDragged e yDragged representam as coordenadas x e y do mouse nos instantes em que é pressionado e arrastado, respectivamente.

 

O construtor de JFrameWithDraggablePanel recebe um array de JPanels e o armazena na variável panels. Em seguida chama o método configurePanels() que será descrito mais adiante.

 

Há também uma chamada para setLayout() de JFrame – necessário aqui, pois para que componentes gráficos sejam posicionados livremente é necessário que nenhum gerenciador de layout, até mesmo o gerenciador padrão, esteja atuando.

 

A Listagem 02 continua com o código do frame, que estará em ordem ao longo deste artigo.

 

private void configurePanels(){

      for (JPanel current : panels) {

            current.setSize(current.getPreferredSize());

            current.addMouseListener(this);

            current.addMouseMotionListener(this);

                 

            location(current);

      }

}

     

private void location(JPanel panel){

      panel.setLocation(new Point(xDragged, yDragged));

}

     

private void refreshTitle(JPanel jPanel){

      String position = "x = " + jPanel.getX() + ", y = " + jPanel.getY();

      super.setTitle(position);

}

     

public void start() {

      super.setVisible(true);

}

Listagem 02 – Continuação de JFrameWithDraggablePanel.

 

O método configurePanels() apenas itera pelo array de panels recebidos no construtor e seta alguns atributos importantes, como size. Destaque para a adição dos eventos de MouseMotionListener e MouseListener passando this como argumento. This faz uma referência à própria classe, que já implementa as interfaces citadas.

 

Location() apenas armazena e altera a nova posição do panel de acordo com as coordenadas do mouse após ser arrastado, que serão configuradas nos métodos implementados mais a frente.

 

RefreshTitle() e start() já foram vistos no artigo passado. O primeiro apenas altera o título do JFrame com as coordenadas do panel em foco e o segundo chama setVisible para que o frame seja exibido.

 

Na Listagem 03 serão vistos os métodos implementados de MouseListener e MouseMotionListener e que permitem o posicionamento dinâmico dos panels.

 

public void mouseDragged(MouseEvent e) {

      this.xDragged = e.getXOnScreen() - xPressed;

      this.yDragged = e.getYOnScreen() - yPressed;

     

      location((JPanel)e.getSource());

}

 

public void mouseEntered(MouseEvent e) {

JPanel jPanel = (JPanel)e.getSource();

      this.refreshTitle(jPanel);

      jPanel.setBackground(new Color(231, 231, 211));

}

 

public void mouseExited(MouseEvent e) {

      JPanel jPanel = (JPanel)e.getSource();

      jPanel.setBackground(null);

}

 

public void mousePressed(MouseEvent e) {

      this.xPressed = e.getX();

      this.yPressed = e.getY();

}

 

public void mouseClicked(MouseEvent e) {}

 

public void mouseReleased(MouseEvent e) {

      this.refreshTitle((JPanel)e.getSource());

}

 

public void mouseMoved(MouseEvent e) {}

}

Listagem 03 – Fim de JFrameWithDraggablePanel.

 

Em mouseDragged(), que é o evento gerado após o mouse ser pressionado e arrastado, xDragged e yDragged são armazenadas de acordo com a posição dos mesmos na tela menos o local dentro do panel onde o mouse foi pressionado (cálculo necessário para que o panel seja arrastado sem perder muito de seu alinhamento no primeiro movimento, visto que há um erro mínimo de pixels, notável quando se inicia o posicionamento, mas que pode ser corrigido com cálculos mais precisos e que fogem ao escopo do artigo).

 

A cada pixel arrastado o método ainda chama location para re-posicionar o panel.

 

MouseEntered(), que é o evento gerado quando o mouse entra em um determinado componente, atualiza o título do JFrame chamando refreshTitle(). Ele altera também a cor de fundo do JPanel em foco para destacá-lo dos demais, facilitando a visualização quando se há muitos panels. Essa ação de destaque é desfeita quando o mouse sai do panel como visto em mouseExited().

 

Em mousePressed() as coordenadas do clique do mouse são armazenadas, o que possibilita o cálculo em mouseDragged().

 

MouseReleased() também atualiza o título com as coordenadas do panel em foco para evitar que, ao ser posicionado, o mouse deva sair da área e voltar para serem visualizadas suas novas coordenadas.

 

Com isso tem-se uma classe que atende aos requisitos citados no início desta parte do artigo. É claro, há formas mais encapsuladas e corretas de se trabalhar com esse tipo de funcionalidade, mas como dito anteriormente, a idéia geral é focar no padrão Factory.

 

Atualizando a biblioteca de frames de testes

O desenvolvedor acabou de criar mais um frame; mais uma nova funcionalidade para sua biblioteca. Isso significa que ele deve alterar alguns trechos de código de suas classes? Não. Como foi utilizado o padrão Factory deve-se apenas abrir a classe que representa a fábrica e atualizá-la, para comportar a nova classe criada.

 

O padrão cuida da instanciação de classes, deixando o cliente a parte dessa tarefa; mesmo porque isto não é de interesse do mesmo. Ou seja, para o cliente não importa se uma nova funcionalidade foi criada. Ele deve apenas ter conhecimento de que agora a biblioteca de frames suporta, caso seja de sua necessidade, uma nova funcionalidade, que nesse caso é a de receber um array de panels e permitir seu posicionamento dinâmico a fins de obtenção das coordenadas finais.

 

A Listagem 04 mostra a única alteração feita na classe JFrameFactory (criada na primeira parte do artigo).

 

...início do código...

return new JFrameWithPanel(jPanel);

}

     

if(object instanceof JPanel[]){

            JPanel[] jPanel =(JPanel[])object;

            return new JFrameWithDraggablePanel(jPanel);

      }

           

return null;

Listagem 04 – trecho de código a ser alterado em JFrameFactory.

 

Feito isso, a factory agora entende que, ao receber um array de JPanel irá instanciar JFrameWithDraggablePanel ao invés de retornar null pelo fato de anteriormente não reconhecer o objeto.

 

Testando as alterações na fábrica

A Listagem 05 mostra apenas uma classe de testes que cria três painéis simples e os passa para a factory que se encarregará de escolher quem irá tratá-los. Nesse caso JFrameWithDraggablePanel.

 

import java.awt.BorderLayout;

import java.awt.Dimension;

 

import javax.swing.BorderFactory;

import javax.swing.JLabel;

import javax.swing.JPanel;

import javax.swing.JScrollPane;

import javax.swing.JTextArea;

import javax.swing.JTextField;

import javax.swing.UIManager;

 

import br.com.jm.factory.JFrameFactory;

 

public class Teste_Factory {

      public static void main(String[] args){

            try{

                  UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());

            } catch(Exception exception){

                 

            }

           

            JPanel jPanel = new JPanel(new BorderLayout());

            jPanel.add(new JLabel("teste-factory-part2"), BorderLayout.NORTH);

            jPanel.add(new JLabel("teste-1:"), BorderLayout.WEST);

            jPanel.add(new JTextField(), BorderLayout.CENTER);

            jPanel.add(new JLabel("teste-fim"), BorderLayout.SOUTH);

            jPanel.setPreferredSize(new Dimension(400,100));

            jPanel.setBorder(BorderFactory.createTitledBorder("jPanel1"));

           

            JPanel jPanel2 = new JPanel(new BorderLayout());

            jPanel2.add(new JLabel("teste-factory-part2"), BorderLayout.NORTH);

            jPanel2.add(new JLabel("teste-2:"), BorderLayout.WEST);

            jPanel2.add(new JLabel("teste-2-fim"), BorderLayout.SOUTH);

            jPanel2.setPreferredSize(new Dimension(400,100));

            jPanel2.setBorder(BorderFactory.createTitledBorder("jPanel2"));

           

            JPanel jPanel3 = new JPanel(new BorderLayout());

            jPanel3.add(new JLabel("teste-factory-part2"), BorderLayout.NORTH);

            jPanel3.add(new JLabel("teste-3:"), BorderLayout.WEST);

            jPanel3.add(new JScrollPane(new JTextArea()), BorderLayout.CENTER);

            jPanel3.add(new JLabel("teste-3-fim"), BorderLayout.SOUTH);

            jPanel3.setPreferredSize(new Dimension(400,500));

            jPanel3.setBorder(BorderFactory.createTitledBorder("jPanel3"));

           

            JFrameFactory jFrame = JFrameFactory.createJFrame(new JPanel[]{jPanel, jPanel2, jPanel3});

            jFrame.start();

      }

}

Listagem 05 – Classe de testes.

 

Conclusão

Neste artigo foi demonstrada uma das vantagens em se utilizar um padrão de projeto. Inicialmente tinha-se uma biblioteca de frames de testes e foi interessante criar mais uma funcionalidade para a mesma. Pode-se focar na criação do novo frame sem se preocupar com o que já foi criado anteriormente, necessitando apenas adicionar poucas linhas de código à fábrica. Isso permite que o desenvolvedor crie funcionalidades extras à medida que vão surgindo as necessidades do projeto.

 

Um grande abraço,

Adriano S. Castro

Artigos relacionados