[Resolvido]:Java2D: comportamento inesperado ao rolar JPanel depois de usar AffinneTransform

1 resposta
TerraSkilll

Título Anterior: Java2D: comportamento inesperado ao rolar JPanel depois de usar AffinneTransform (zoom)

Boa tarde a todos.

Estou com um projeto em Java2D e, dentre as coisas que estou tentando fazer, está um mecanismo para zoom em um painel. O scale funciona adequadamente, mas como o painel está dentro de um ScrollPane, ao rolar os elementos a serem desenhados são desenhados incorretamente, ficando cortados quando são rolados para fora da tela. A rolagem funciona normalmente se eu não colocar o AffineTransform. Alguns outros problemas ocorrem junto com este, como o botão "Zoom" sendo desenhado do lado direito às vezes, mas creio que são problemas relacionados. Segue código do Painel:
public class DrawPanel extends JPanel implements MouseListener{
	private ArrayList<MyItem> itens;
	private float zoom = 1;

	public DrawPanel() {
		super();
		itens = new ArrayList<MyItem>(30);
		setSize(1200, 1200);
		setPreferredSize(getSize());

		addMouseListener(this);
	}

	@Override
	protected void paintComponent(Graphics g){
		super.paintComponent(g);		
		Graphics2D g2 = (Graphics2D) g.create();
		g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

		AffineTransform at = new AffineTransform();  
		at.scale(zoom, zoom);
		g2.setTransform(at); // sem esta linha, a rolagem não apresenta problemas

		setBackground(Color.WHITE);

		for(MyItem my : itens){
			my.desenhar(g2);
		}

		g2.setStroke(new BasicStroke(3));
		g2.drawRect(40, 40, 50, 50); //  para efeito de testes
		g2.drawRect(30, 30, 50, 50); //  para efeito de testes

		g2.dispose();
	}

	public void adicionar(MyItem my){
		itens.add(my);
	}
	
	private void selecionar(Point2D ponto){
		for(MyItem my : itens){
			my.selecionar(ponto);
		}
	}

	public void redesenhar(){
		repaint();
	}

	public void setZoom(float zoom){
		this.zoom = zoom;
		this.setPreferredSize(new Dimension((int) (1200 * zoom), (int) (1200 * zoom)));
		redesenhar();
	}

	private Point2D traduce(Point2D ponto){
		ponto.setLocation(ponto.getX() / zoom, ponto.getY() / zoom);
		return ponto;
	}

	@Override
	public void mousePressed(MouseEvent evt) {
		selecionar(traduce(evt.getPoint()));
		redesenhar();
	}

	@Override
	public void mouseClicked(MouseEvent evt) {}
	@Override
	public void mouseEntered(MouseEvent evt) {}
	@Override
	public void mouseExited(MouseEvent evt) {}
	@Override
	public void mouseReleased(MouseEvent evt) {}
}
MyItem, MyCircle e MyRectangle são classes simples, que basicamente armazenam e desenham as formas (um círculo e um retângulo). O código do JFrame é este:
package gui;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
import elements.MyCircle;
import elements.MyRectangle;

public class Screen extends JFrame {
	private DrawPanel painel;
	private JButton bAddCircle;
	private JButton bAddRect;
	private JButton bZoom;
	private JScrollPane sp;

	public Screen(){
		super("Teste de Zoom");
		this.setLayout(new BorderLayout());
		painel = new DrawPanel();
		setMinimumSize(new Dimension(300, 300));

		bAddCircle = new JButton("Adicionar Círculo");
		bAddRect = new JButton("Adicionar Retângulo");
		bZoom = new JButton("Zoom");

		sp = new JScrollPane(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
		sp.setViewportView(painel);
		sp.setAutoscrolls(true);

		getContentPane().add(sp, BorderLayout.CENTER);
		getContentPane().add(bAddCircle, BorderLayout.NORTH);
		getContentPane().add(bAddRect, BorderLayout.SOUTH);
		getContentPane().add(bZoom, BorderLayout.EAST);

		setExtendedState(JFrame.MAXIMIZED_BOTH);

		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

		bAddCircle.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent arg0) {
				adicionarCirculo();
			}
		});
		
		bAddRect.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent arg0) {
				adicionarRetangulo();
			}
		});

		bZoom.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent arg0) {
				float f = Float.parseFloat(JOptionPane.showInputDialog("Zoom:"));
				painel.setZoom(f);
			}
		});
	}

	private void adicionarCirculo(){
		painel.adicionar(new MyCircle());
		painel.redesenhar();
	}

	private void adicionarRetangulo(){
		painel.adicionar(new MyRectangle());
		painel.redesenhar();
	}

	public static void main(String[] args){
		new Screen().setVisible(true);
	}
}
O fonte completo está em anexo. É um projeto do Eclipse. Eis uma imagem do problema:

[IMG]http://img577.imageshack.us/img577/47/rolagemantes.th.png[/IMG]
[IMG]http://img850.imageshack.us/img850/6143/rolagemdepois.th.png[/IMG]

Agradeço desde já a atenção.

Abraço.

1 Resposta

TerraSkilll
Bom dia a todos. É bom quando a gente descobre as coisas! :D :D :D :D :D Consegui resolver este caso. A solução é tecnicamente simples, mas para chegar nela foi um bocado de tempo. O código final do paintComponent ficou:
@Override
	protected void paintComponent(Graphics g){
		super.paintComponent(g);		
		Graphics2D g2 = (Graphics2D) g.create();
		g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

		AffineTransform at = new AffineTransform();  
		at.scale(zoom, zoom);
		//g2.setTransform(at); // tira-se essa linha
		g2.transform(at); //  adiciona-se essa

		setBackground(Color.WHITE);

		for(MyItem my : itens){
			my.desenhar(g2);
		}

		g2.setStroke(new BasicStroke(3));
		g2.drawRect(40, 40, 50, 50); //  para efeito de testes
		g2.drawRect(30, 30, 50, 50); //  para efeito de testes

		g2.dispose();
	}
Depois de um longo "ragestorm" (porque o "brainstorm" já tinha acabado faz tempo), fui pego pelo óbvio: por que há dois métodos (setTransform e transform) para fazer a mesma coisa? A resposta é simples: não há. Nada como ir na fonte para obter um pouco de conhecimento: [url]http://docs.oracle.com/javase/6/docs/api/java/awt/Graphics2D.html#transform%28java.awt.geom.AffineTransform%29[/url].

O método que aplica a transformação no contexto gráfico g2 é o tranform(). O setTransform() sobrescreve todas as tranformações do contexto gráfico, o que pode levar a comportamentos inesperados (como os que eu relatei). Pela documentação, o objetivo do setTransform é restaurar as transformações originais depois que toda a renderização é concluída.

Este é um dos raros casos em que, infelizmente, os nomes dos métodos não ajudaram muito. Junte isso a um pouco de falta de atenção e...

Abraços.

Criado 4 de setembro de 2012
Ultima resposta 5 de set. de 2012
Respostas 1
Participantes 1