I have a scrollable JPanel, with some number drawn through the "drawString" method. When I click the zoom in button, the repaint method is invoked and the number becomes larger, as expected. But when I try to scroll to the right, part of the number is either missing or is painted in its original size instead of the new zoomed size. Below is the minimal reproducible code.
package sometest;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JMenuBar;
import javax.swing.JScrollPane;
import javax.swing.JViewport;
public class SomeTest implements Runnable
{
public static JButton in;
public static JButton out;
public static void main(String[] args)
{
SomeTest st = new SomeTest();
st.run();
}
@Override
public void run()
{
JMenuBar mb = new JMenuBar();
in = new JButton("Zoom In");
out = new JButton("Zoom Out");
JFrame f = new JFrame("Example");
Visualization v = new Visualization();
JScrollPane sp = new JScrollPane(v);
MouseAdapter mouseAdapter = new MouseAdapter()
{
private Point origin;
@Override
public void mousePressed(MouseEvent e)
{
origin = new Point(e.getPoint());
}
@Override
public void mouseDragged(MouseEvent e)
{
JViewport vp = null;
if (origin != null)
{
vp = sp.getViewport();
}
if (vp != null)
{
int deltaX = origin.x - e.getX();
int deltaY = origin.y - e.getY();
Rectangle view = vp.getViewRect();
view.x = deltaX;
view.y = deltaY;
v.scrollRectToVisible(view);
}
}
};
sp.getViewport().addMouseListener(mouseAdapter);
sp.getViewport().addMouseMotionListener(mouseAdapter);
f.add(sp);
f.setJMenuBar(mb);
mb.add(in);
mb.add(out);
f.setContentPane(sp);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
package sometest;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.AffineTransform;
import javax.swing.JPanel;
import javax.swing.Scrollable;
import javax.swing.SwingUtilities;
import static sometest.SomeTest.in;
import static sometest.SomeTest.out;
public class Visualization extends JPanel implements Scrollable, ActionListener
{
private double zoomFactor = 1;
private double prevZoomFactor = 1;
private boolean zoomer;
private double xOffset = 0;
private double yOffset = 0;
public Visualization()
{
in.addActionListener(this);
out.addActionListener(this);
Font currentFont = getFont();
Font newFont = currentFont.deriveFont(currentFont.getSize() * 18F);
setFont(newFont);
setBackground(Color.WHITE);
}
@Override
public void actionPerformed(ActionEvent e)
{
if(e.getSource()==in)
{
zoomer = true;
zoomFactor *= 1.1;
repaint();
}
if(e.getSource()==out)
{
zoomer = true;
zoomFactor /= 1.1;
repaint();
}
}
@Override
protected void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
if(zoomer)
{
AffineTransform at = new AffineTransform();
Dimension center = getPreferredScrollableViewportSize();
Double xRel = center.getWidth()/2;
Double yRel = center.getHeight()/2;
double zoomDiv = zoomFactor / prevZoomFactor;
xOffset = (zoomDiv) * (xOffset) (1 - zoomDiv) * xRel;
yOffset = (zoomDiv) * (yOffset) (1 - zoomDiv) * yRel;
at.translate(xOffset, yOffset);
at.scale(zoomFactor, zoomFactor);
prevZoomFactor = zoomFactor;
g2d.transform(at);
zoomer = false;
}
g2d.drawString("1234567890", 500, 200);
g2d.dispose();
}
@Override
public Dimension getPreferredSize()
{
if(zoomer)
{
return new Dimension((int)(2000*zoomFactor), (int)(750*zoomFactor));
}
else
{
return new Dimension(2000, 750);
}
}
@Override
public Dimension getPreferredScrollableViewportSize()
{
return new Dimension(1550, 750);
}
@Override
public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction)
{
return 32;
}
@Override
public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction)
{
return 32;
}
@Override
public boolean getScrollableTracksViewportWidth()
{
return false;
}
@Override
public boolean getScrollableTracksViewportHeight()
{
return false;
}
}
When I zoom in and scroll, I'm expecting the drawn number to remain the same size. As far as I know, the repaint method is supposed to automatically handle cases such as when scrolling or resizing. Have I misused the repaint method or is my problem something entirely different?
CodePudding user response:
It might be better to use the AffineTransform#concatenate(AffineTransform) method to combine the AffineTransform
for zoom and the AffineTransform
for translation.
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
AffineTransform scrollTransform = g2d.getTransform();
scrollTransform.concatenate(zoomTransform);
g2d.setTransform(scrollTransform);
g2d.drawString("1234567890", 500, 200);
g2d.dispose();
}
SomeTest2.java
// package sometest;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.AffineTransform;
import javax.swing.*;
public class SomeTest2 {
public JComponent makeUI() {
Visualization v = new Visualization();
MouseAdapter mouseAdapter = new DragScrollListener();
v.addMouseListener(mouseAdapter);
v.addMouseMotionListener(mouseAdapter);
JButton in = new JButton("Zoom In");
in.addActionListener(e -> v.setZoomFactor(1.1));
JButton out = new JButton("Zoom Out");
out.addActionListener(e -> v.setZoomFactor(1 / 1.1));
JMenuBar mb = new JMenuBar();
mb.add(in);
mb.add(out);
EventQueue.invokeLater(() -> v.getRootPane().setJMenuBar(mb));
return new JScrollPane(v);
}
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
JFrame f = new JFrame();
f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
f.getContentPane().add(new SomeTest2().makeUI());
f.setSize(640, 480);
f.setLocationRelativeTo(null);
f.setVisible(true);
});
}
}
class Visualization extends JPanel {
private final AffineTransform zoomTransform = new AffineTransform();
private final Rectangle rect = new Rectangle(2000, 750);
public Visualization() {
Font currentFont = getFont();
Font newFont = currentFont.deriveFont(currentFont.getSize() * 18F);
setFont(newFont);
setBackground(Color.WHITE);
}
public void setZoomFactor(double zoomFactor) {
zoomTransform.scale(zoomFactor, zoomFactor);
revalidate();
repaint();
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
AffineTransform scrollTransform = g2d.getTransform();
scrollTransform.concatenate(zoomTransform);
g2d.setTransform(scrollTransform);
g2d.drawString("1234567890", 500, 200);
g2d.dispose();
}
@Override
public Dimension getPreferredSize() {
Rectangle r = zoomTransform.createTransformedShape(rect).getBounds();
return new Dimension(r.width, r.height);
}
}
class DragScrollListener extends MouseAdapter {
private final Point origin = new Point();
@Override
public void mouseDragged(MouseEvent e) {
Component c = e.getComponent();
Container p = SwingUtilities.getUnwrappedParent(c);
if (p instanceof JViewport) {
JViewport viewport = (JViewport) p;
Point cp = SwingUtilities.convertPoint(c, e.getPoint(), viewport);
Point vp = viewport.getViewPosition();
vp.translate(origin.x - cp.x, origin.y - cp.y);
((JComponent) c).scrollRectToVisible(new Rectangle(vp, viewport.getSize()));
origin.setLocation(cp);
}
}
@Override
public void mousePressed(MouseEvent e) {
Component c = e.getComponent();
Container p = SwingUtilities.getUnwrappedParent(c);
if (p instanceof JViewport) {
JViewport viewport = (JViewport) p;
Point cp = SwingUtilities.convertPoint(c, e.getPoint(), viewport);
origin.setLocation(cp);
}
}
}