How can I implement a Zoom Panel with Java Swing libraries

2.8k views Asked by At

I've implemented a JFrame with Java Swing libraries to visualize a map. I'd like to realize a zoom function within this frame. This is the code of my JFrame:

package components;

import java.awt.BorderLayout;
import java.awt.Container;
import javax.swing.JFrame;
import javax.swing.JScrollPane;




public class MyFrame extends JFrame {

private static final long serialVersionUID = 1L;
private Container contentPane;
public static final int startWidth = 1280;
public static final int startHeight = 800;
public static MyPanel EarthPanel;

public MyFrame(){
    initComponents();
}


public void initComponents(){
    contentPane = getContentPane();
    this.setTitle("TLE Graphic Propagator");
    this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    this.setSize(startWidth, startHeight);
    this.setLocation(0,0);
    this.setLayout(new BorderLayout());
    EarthPanel = new MyPanel(startWidth,startHeight);
    JScrollPane scroll = new JScrollPane(EarthPanel,     JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
    MySlider slider = new MySlider();
    contentPane.add(scroll, BorderLayout.CENTER);
    contentPane.add(slider, BorderLayout.SOUTH);
}
}

As you can see above, the frame contains a JPanel in a JScrollPane and a JSlider that I use for zoom function. It follows the code of the panel..

package components;

import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.File;
import javax.imageio.ImageIO;
import javax.swing.JPanel;

public class MyPanel extends JPanel{

private static final long serialVersionUID = 1L;
private BufferedImage img;
private int width, height;
private String path = "images/earth1280x800.jpg";
private static final int UPDATE_RATE = 10;  //#volte al secondo


public MyPanel(int larghezzaFrame, int altezzaFrame){   

    width = larghezzaFrame;
    height = altezzaFrame;

    this.setPreferredSize(new Dimension(width, height));

    img = loadImage();
}


public BufferedImage loadImage() {
    BufferedImage bimg = null;
    BufferedImage ret = null;
    try {
        bimg = ImageIO.read(new File(path));
    } catch (Exception e) {
        e.printStackTrace();
    }
    ret = new BufferedImage(bimg.getWidth(), bimg.getHeight(), BufferedImage.TYPE_INT_ARGB);
    Graphics2D g = ret.createGraphics();
    g.drawImage(bimg, 0, 0, null);
    g.dispose();

    return ret;
}

@Override
protected void paintComponent(Graphics g) {
    setOpaque(false);

    Graphics2D g2d = (Graphics2D)g;
    g2d.scale(MySlider.SCALE, MySlider.SCALE);
    g2d.drawImage(img, 0, 0, null); 
    super.paintComponent(g2d);
}

}

..and the slider's one:

package components;

import java.awt.Dimension;

import javax.swing.JSlider;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

public class MySlider extends JSlider implements ChangeListener{

private static final long serialVersionUID = 1L;
public static double SCALE = 1;

public MySlider(){
    super(JSlider.HORIZONTAL, 100, 400, 100);
    setMajorTickSpacing(50);  
    setMinorTickSpacing(10);  
    setPaintTicks(true);  
    setPaintLabels(true);  
    addChangeListener(this);   
}

@Override
public void stateChanged(ChangeEvent arg0) {
    int value = ((JSlider) arg0.getSource()).getValue();  
    SCALE = value/100.0; 
    MyFrame.EarthPanel.setPreferredSize(new Dimension((int)(MyFrame.startWidth * MySlider.SCALE),(int)(MyFrame.startHeight * MySlider.SCALE)));
    MyFrame.EarthPanel.repaint();
}

}

To run the code, you just need to insert the following Main function:

package main;

import components.MyFrame;

public class MainPersonalFrame {
public static void main(String[] args){
    MyFrame frame = new MyFrame();
    frame.setVisible(true);
}
}

Note: It will be necessary to insert a package "images" in the same path of the source code with an image called "earth1280x800.jpg" into that.

The problem that usually comes out is that when I use the zoom function, scrolling right/left and up/down, sometimes the image is not contained by the frame anymore. I'd like to get an automatic refresh of the scrollbars and that the image doesn't come out from the borders. How should I do it?

Thanks to everyone.

1

There are 1 answers

2
Hovercraft Full Of Eels On BEST ANSWER

I'm not 100% sure what the problem is, but I suspect that an issue is that you're not changing the preferred size of your image JPanel when the image changes size. If so, then a solution would be to override getPreferredSize() in the image displaying JPanel and return a dimension that matches that of the zoomed image.


Edit, no, you sort of do this, but you're not revalidating nor repainting the JScrollPane's viewport, and this is what you must do.

import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URL;

import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSlider;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

public class MainPersonalFrame {
   public static void main(String[] args) {
      MyFrame frame = new MyFrame();
      frame.setVisible(true);
   }
}

class MyFrame extends JFrame {

   private static final long serialVersionUID = 1L;
   private Container contentPane;
   public static final int startWidth = 1280;
   public static final int startHeight = 800;

   // !! This should be private and not static
   private MyPanel earthPanel;

   // !! this should be a field
   private JScrollPane scroll;

   public MyFrame() {
      initComponents();
   }

   public void initComponents() {
      contentPane = getContentPane();
      this.setTitle("TLE Graphic Propagator");
      this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

      // !! avoid setting sizes if possible
      this.setSize(startWidth, startHeight);
      this.setLocation(0, 0);
      this.setLayout(new BorderLayout());
      earthPanel = new MyPanel(startWidth, startHeight);

      // !! again, scroll is now a field
      scroll = new JScrollPane(earthPanel,
            JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
            JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
      MySlider slider = new MySlider(this);
      contentPane.add(scroll, BorderLayout.CENTER);
      contentPane.add(slider, BorderLayout.SOUTH);
   }

   // !! to avoid having classes directly manipulate other class's fields
   public void setEarthPanelSize(Dimension size) {
      earthPanel.setPreferredSize(size);
   }

   // !! allow other classes the ability to revalidate/repaint viewport
   public void revalidateViewport() {
      scroll.getViewport().revalidate();
      scroll.getViewport().repaint();
   }
}

class MyPanel extends JPanel {

   private static final long serialVersionUID = 1L;
   private BufferedImage img;
   private int width, height;

   // !! changes so that I can run your program
   // !! with an internet image
   // !! private String path = "images/earth1280x800.jpg";
   private String urlPath = "http://image.desk7.net/"
         + "Space%20Wallpapers/1422_1280x800.jpg";

   // !! private static final int UPDATE_RATE = 10; // !! never used

   public MyPanel(int larghezzaFrame, int altezzaFrame) {
      setOpaque(false); // !! this should be here
      width = larghezzaFrame;
      height = altezzaFrame;

      this.setPreferredSize(new Dimension(width, height));

      img = loadImage();
   }

   public BufferedImage loadImage() {
      BufferedImage bimg = null;
      BufferedImage ret = null;
      try {
         URL imgUrl = new URL(urlPath);
         // !! bimg = ImageIO.read(new File(path)); // !!
         bimg = ImageIO.read(imgUrl); // !! use image available to all
         // !! } catch (Exception e) {
         // e.printStackTrace();
      } catch (IOException e) {
         // !! use more specific exception
         e.printStackTrace();
      }
      ret = new BufferedImage(bimg.getWidth(), bimg.getHeight(),
            BufferedImage.TYPE_INT_ARGB);
      Graphics2D g = ret.createGraphics();
      g.drawImage(bimg, 0, 0, null);
      g.dispose();

      return ret;
   }

   @Override
   protected void paintComponent(Graphics g) {
      // !! this shouldn't be in paintComponent:
      // !! setOpaque(false); 

      Graphics2D g2d = (Graphics2D) g;
      g2d.scale(MySlider.SCALE, MySlider.SCALE);
      g2d.drawImage(img, 0, 0, null);
      super.paintComponent(g2d);
   }

}

class MySlider extends JSlider implements ChangeListener {

   private static final long serialVersionUID = 1L;
   public static double SCALE = 1;
   private MyFrame myFrame;

   public MySlider(MyFrame myFrame) {
      super(JSlider.HORIZONTAL, 100, 400, 100);
      this.myFrame = myFrame;
      setMajorTickSpacing(50);
      setMinorTickSpacing(10);
      setPaintTicks(true);
      setPaintLabels(true);
      addChangeListener(this);
   }

   @Override
   public void stateChanged(ChangeEvent arg0) {
      int value = ((JSlider) arg0.getSource()).getValue();
      SCALE = value / 100.0;
      int w = (int) (MyFrame.startWidth * MySlider.SCALE);
      int h = (int) (MyFrame.startHeight * MySlider.SCALE);
      Dimension size = new Dimension(w, h);
      myFrame.setEarthPanelSize(size); // !!
      myFrame.revalidateViewport(); // !!
      // !! MyFrame.earthPanel.repaint(); // No, don't do this
   }
}