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
-
Artigo
-
Artigo
-
Artigo
-
Artigo
-
Artigo