0

I'm trying to write a simple class that extends from JPanel that can zoom in and zoom out on an image and then uses a JScrollPane at the top level to allow someone to scroll back and forth across the zoomed image. I've having trouble with the JScrollPane portion.

When clicking one of the zoom buttons (whether it be in, out, or reset), the button must be clicked twice in order for the JScrollBars to appear(or in the case of the reset button, disappear). Even after they've appear if you continue to zoom in or out the bars do not update according to the new level of zoom. Resizing the window fixes this issue but I'm looking for a more concrete solution.

ImagePanel Code:

package com.zephyr.graphics;

import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;
import javax.swing.JPanel;

public class ImagePanel extends JPanel implements PropertyChangeListener{

    private File imageFile;

    private BufferedImage image;

    private double zoomMultiplier;

    public ImagePanel()
    {
        imageFile = null;

        image = null;

        zoomMultiplier = 1;
    }

    public ImagePanel(File imageFile)
    {
        this.imageFile = imageFile;

        try {
            image = ImageIO.read(imageFile);
        } catch (IOException e) {
            image = null;
        }

        zoomMultiplier = 1;
    }

    public void setImageFile(File imageFile)
    {
        this.imageFile = imageFile;

        if(imageFile == null)
        {
            image = null;
        }
        else
        {
            try {
                image = ImageIO.read(imageFile);
            } catch (IOException e) {
                image = null;
            }
        }

        zoomMultiplier = 1;

        this.revalidate();
        this.repaint();
    }

    public File getImageFile()
    {
        return imageFile;
    }

    public void paintComponent(Graphics g)
    {
        g.clearRect(0, 0, this.getWidth(), this.getHeight());

        if(image == null)
        {
            this.setPreferredSize(new Dimension(this.getParent().getWidth(), this.getParent().getHeight()));
            g.setColor(Color.black);
            g.fillRect(0, 0, this.getWidth(), this.getHeight());
        }
        else
        {
            double preferredWidth = this.getParent().getWidth() * zoomMultiplier;
            double preferredHeight = this.getParent().getHeight() * zoomMultiplier;

            this.setPreferredSize(new Dimension((int)preferredWidth, (int)preferredHeight));

            Image scaled = image.getScaledInstance((int)preferredWidth, (int)preferredHeight, Image.SCALE_DEFAULT);

            g.drawImage(scaled, 0, 0, null);
        }
    }

    @Override
    public void propertyChange(PropertyChangeEvent evt) {
        if(evt.getPropertyName().equals("Image File"))
        {
            setImageFile((File)evt.getNewValue());
        }
        else if(evt.getPropertyName().equals("Zoom In"))
        {
            double additive = ((Number)evt.getNewValue()).doubleValue();

            zoomMultiplier += additive;

            this.revalidate();
            this.repaint();
        }
        else if(evt.getPropertyName().equals("Zoom Out"))
        {
            double subtractive = ((Number)evt.getNewValue()).doubleValue();

            zoomMultiplier -= subtractive;

            this.revalidate();
            this.repaint();
        }
        else if(evt.getPropertyName().equals("Zoom Reset"))
        {
            zoomMultiplier = 1;

            this.revalidate();
            this.repaint();
        }
    }
}

Main Class

package com.zephyr.graphics;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeSupport;
import java.io.File;

import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;

public class Main {

    public static void main(String[] args)
    {
        PropertyChangeSupport pcs = new PropertyChangeSupport(Main.class);

        JFileChooser chooser = new JFileChooser(System.getProperty("user.home"));
        chooser.setDialogTitle("Select an image");
        chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);

        File file = null;

        if(chooser.showOpenDialog(null) == JFileChooser.APPROVE_OPTION)
        {
            file = chooser.getSelectedFile();
        }
        else
        {
            System.err.println("NO FILE WAS SELECTED OR THE WINDOW WAS CLOSED");
            return;
        }

        JFrame frame = new JFrame("ImagePanel Tester");
        frame.setLocationRelativeTo(null);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        ImagePanel iPanel = new ImagePanel(file);
        pcs.addPropertyChangeListener(iPanel);

        JScrollPane iScroll = new JScrollPane(iPanel, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
        iScroll.setPreferredSize(new Dimension(1024, 768));

        JPanel controlPanel = new JPanel();
        controlPanel.setLayout(new FlowLayout(FlowLayout.CENTER));

        JButton zoomIn = new JButton("Zoom In");
        zoomIn.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent arg0) {
                 pcs.firePropertyChange("Zoom In", null, new Double(.25));
                 frame.revalidate();
                 frame.repaint();
            }

        });
        controlPanel.add(zoomIn);

        JButton zoomOut = new JButton("Zoom Out");
        zoomOut.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                pcs.firePropertyChange("Zoom Out", null, new Double(.25));
                frame.revalidate();
                frame.repaint();
            }

        });
        controlPanel.add(zoomOut);

        JButton zoomReset = new JButton("Zoom Reset");
        zoomReset.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                pcs.firePropertyChange("Zoom Reset", null, null);
                frame.revalidate();
                frame.repaint();
            }

        });
        controlPanel.add(zoomReset);

        JButton changeImage = new JButton("Change Image");
        changeImage.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                if(chooser.showOpenDialog(frame) == JFileChooser.APPROVE_OPTION)
                {
                    File tempFile = chooser.getSelectedFile();
                    pcs.firePropertyChange("Image File", null, tempFile);
                    frame.revalidate();
                    frame.repaint();
                }
            }

        });
        controlPanel.add(changeImage);

        frame.add(iScroll, BorderLayout.CENTER);
        frame.add(controlPanel, BorderLayout.SOUTH);
        frame.pack();
        frame.setVisible(true);
        frame.revalidate(); //Shouldn't really be necessary, but seems to be helping

    }
}
brad95411
  • 141
  • 1
  • 1
  • 12
  • 1
    `this.setPreferredSize(new Dimension(this.getParent().getWidth(), this.getParent().getHeight()));` That is not something that should be called from within a paint method! Override the `getPreferredSize()` method to return the correct value. Also, the preferred size should not depend on that size of the parent, but the size of the content in the component. – Andrew Thompson Sep 13 '16 at 13:50
  • The missing `PropertyChangeSupport` is one of a variety of reasons that code will not compile for others. 1) For better help sooner, post a [MCVE] or [Short, Self Contained, Correct Example](http://www.sscce.org/). Once you have what you think is an MCVE / SSCCE, paste it into a new project in your IDE and check it compiles & runs without modification. 2) One way to get image(s) for an example is to hot link to images seen in [this Q&A](http://stackoverflow.com/q/19209650/418556). – Andrew Thompson Sep 13 '16 at 13:57
  • @AndrewThompson Thompson While your first comment makes sense, your second does not, `PropertyChangeSupport` is created in the very first line of the main method and is a part of the java.beans package. This code compiles fine, on multiple machines. The line of code you mentioned in the first comment is just there to make sure that the preferred size of the ImagePanel matches the real size of the parent component, what will normally be a JScrollPane fitted to a certain area of the window – brad95411 Sep 13 '16 at 14:36
  • Oh, my bad on the `PropertyChangeSupport` call. For some reason my IDE was not picking it up! In any case, a MCVE should be a single copy/paste, so transfer the `main(..)` into the other class. Then hot link to an image. But implement those suggestions in the first comment before making any edits to the question. – Andrew Thompson Sep 13 '16 at 23:38

1 Answers1

0

After some heavy digging, the Scrollable interface saved me. Implementing this and it's respective methods following this example as well as removing the preferredSize references from paintComponent fixes the issues that I was having.

brad95411
  • 141
  • 1
  • 1
  • 12