13

I am trying to display a large image inside a JFrame's contentpane. I would like to make the image or contentpane scrollable as the image is large. I tried to do it using Jscrollpane and add it into the contentpane but it didn't work. Did some searching for solution but end up failed to find one. Can someone guide me? My code are below

FinalEnvironment.java

package environment;

import java.awt.*;
import java.net.URL;

import javax.swing.*;

public class FinalEnvironment{

public FinalEnvironment(){

    Image Eastlake;
    URL EastlakeURL = null;

    EastlakeURL = FinalEnvironment.class.getResource("/image1/eastlake_night.png");
    Eastlake = Toolkit.getDefaultToolkit().getImage(EastlakeURL);

    JFrame frame = new JFrame("UniCat World");
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setSize(800, 600);
    frame.setResizable(false);
    frame.setLocationRelativeTo(null);

    JMenuBar yellowMenuBar = new JMenuBar();
    Map map = new Map(800, 550, Eastlake);
    yellowMenuBar.setOpaque(true);
    yellowMenuBar.setBackground(Color.YELLOW);
    yellowMenuBar.setPreferredSize(new Dimension(800, 50));
    frame.setJMenuBar(yellowMenuBar);
    JScrollPane scroller = new JScrollPane(map);
    scroller.setAutoscrolls(true);
    scroller.setPreferredSize(new Dimension(800, 550));
    frame.getContentPane().add(scroller, BorderLayout.CENTER);


    frame.setSize(800, 600);
    frame.setVisible(true);
}

public static void main(String[] args){
    FinalEnvironment fe = new FinalEnvironment();
}
}

Here is my map.java

package environment;

import java.awt.*;

import javax.swing.*;


public class Map extends JPanel{

    private int width;
    private int height;
    private Image img;

    public Map(int width, int height, Image img){

        this.width = width;
        this.height = height;
        this.img = img;
    }

    protected void  paintComponent(Graphics g)
    {
        super.paintComponents(g);
        Graphics2D g2d = (Graphics2D) g;
        g2d.drawImage(img,0,0,2624,1696,null);
    }

}

Lastly, I would like to place Jbuttons on top of this image. Should I call a Rectangle and place it on top the image in the contentpane which then I use Point to position my buttons or should I straight away use the image or the component itself to do it? I need the button to be able to synchronize with the image when it is scrolled instead of static in the contentpane.

Thanks

Bill the Lizard
  • 398,270
  • 210
  • 566
  • 880
Noble
  • 191
  • 1
  • 3
  • 12

4 Answers4

16

What I would do here:

1.Have a panel (canvas) which only responsibility is to paint a given image independent of the real image size in overridden method paintComponent()

    super.paintComponent(g);
    g.drawImage(image, 0, 0, null);

2.Make sure the canvas preferred size equals to image real size.

3.Have a second panel which will serve as content pane of a frame.

4.In it you will set a JScrollPane as its centre.

5.In the scroll pane viewport will be the component from step 1.

6.Add your button to canvas panel from step 1. It will be scrolled together with the image.

7.Add the content pane, the panel from step 3, to a frame, and run the application.

EDIT: Code sample with button added to canvas, which stays always in its place, independent of scroll position or frame size.

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URL;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;

public class ScrollImageTest extends JPanel {
    private static final long serialVersionUID = 1L;
    private BufferedImage image;
    private JPanel canvas;

    public ScrollImageTest() {
        try {
            this.image = ImageIO.read(new URL("http://interviewpenguin.com/wp-content/uploads/2011/06/java-programmers-brain.jpg"));
        }catch(IOException ex) {
            Logger.getLogger(ScrollImageTest.class.getName()).log(Level.SEVERE, null, ex);
        }

        this.canvas = new JPanel() {
            private static final long serialVersionUID = 1L;
            @Override
            protected void paintComponent(Graphics g) {
                super.paintComponent(g);
                g.drawImage(image, 0, 0, null);
            }
        };
        canvas.add(new JButton("Currently I do nothing"));
        canvas.setPreferredSize(new Dimension(image.getWidth(), image.getHeight()));
        JScrollPane sp = new JScrollPane(canvas);
        setLayout(new BorderLayout());
        add(sp, BorderLayout.CENTER);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                JPanel p = new ScrollImageTest();
                JFrame f = new JFrame();
                f.setContentPane(p);
                f.setSize(400, 300);
                f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                f.setVisible(true);
            }
        });
    }
}
Boro
  • 7,913
  • 4
  • 43
  • 85
  • I able to make the image scrollable now. Thanks! Still working on the button though. Will post comment here or in this topic if I face problem. Thanks! – Noble Sep 04 '11 at 12:23
  • @Noble happy to help. Try just adding the button to canvas, in your case `map`, e.g. `map.add(new JButton("OK"));` and it should work as you want it, if I understand synchronized with image correctly. I think moving as it moves, right?. – Boro Sep 04 '11 at 12:27
  • 2
    @Noble: if you face a new problem with your program and need our help, then your best bet is to ask a new question on SO. You can link to this question if desired, but creating a new question for a new problem will increase your chances of getting timely help. – Hovercraft Full Of Eels Sep 04 '11 at 12:36
  • No, I just want certain buttons to place at certain positions based on image itself. I tried last time and I only able to get the button to be positioned based on the frame size instead and not the image. To simplify what I trying to say, lets say I placed a button at the bottom right of the image, I would like it to stay at that position even when I scrolled the image to the top or to the left. Another small confusion, if I would like to repaint the image or the component itself based on an interval which based on my code, the whole map.java itself, where should I code it? – Noble Sep 04 '11 at 12:40
  • @Hovercraft Full Of Eels: Ok, but I try not to create a new question if the problem is not too big as sometimes, I only get confused a bit with the method concepts in Java instead of totally not having the idea. – Noble Sep 04 '11 at 12:42
  • 1
    @Noble: please check my edit with code sample where I add a button. It stays always in the position it was set in the beginning. – Boro Sep 04 '11 at 12:55
6

What if you use your dimensions to set the Map's preferred size. For instance, give Map this method:

// method in the Map class
@Override
public Dimension getPreferredSize() {
  return new Dimension(width, height);
}

This way the Map JPanel will take up the necessary room to show the entire image. Also, why does your drawImage method in the paintComponent method have the large magic numbers? Why not use the width and height there as well? Edit 1: or don't even specify the image size as Boro suggests in his answer (1+ to him).

Hovercraft Full Of Eels
  • 283,665
  • 25
  • 256
  • 373
  • Hi @Hovercraft Full Of Eels I think what you are suggesting should do it. Or in constructor he could use `setPreferredSize(...)` instead of overriding it. Anyway after rereading the code, once more, the real problem of the OPs was that the `setPreferredSize()` method was not applied to the panel that goes inside the `ScrollPane`. Instead I see it set for the `ScrollPane` which is unnecessary in this case since it is being set as centre of border layout, and it ignores both width and height of the component. – Boro Sep 04 '11 at 12:17
  • Ya, I changed my code to not specify the image size like Boro suggested. The large magic numbers was actually hard coded by me for testing. – Noble Sep 04 '11 at 12:22
6

Why is everybody reinventing the wheel??? There is no need for a custom panel to paint the image!!!

All you need to do is create a JLabel and add an ImageIcon to the label and you won't have a problem. The label will:

  1. paint the image at (0, 0) at its original size (which is exactly what the custom code is doing).

  2. determine the preferred size of the image based on the image size. Now scrolling will happen automatically.

Also there is rarely any reason to use the setPreferredSize() method since all components have a default preferred size. So you should not set the default size of the menu bar. The only time I set a preferred size would be on the JScrollPane. This will allow the frame to be packed at a reasonable size and then scrollbars will appear automatically based on the size of the image in the label.

camickr
  • 321,443
  • 19
  • 166
  • 288
  • 1
    Good points. In a custom component, is there a reason to prefer overriding `getPreferredSize()` versus invoking `getPreferredSize()`? – trashgod Sep 04 '11 at 15:36
  • 1
    In a custom component you should always override getPreferredSize() because the size may change dynamically as the properties of the component change. – camickr Sep 04 '11 at 16:31
  • ImageIcon seems to have a rather low dimension restriction, seems to be even dependent on the platform. Under Win 10 I can show 32768x400 RGB images as ImageIcon embedded in a JLabel, under Ubuntu 16.10 maximum width seems to be between 16384 and 32768. Cool stuff is that this throws no error and you start searching..... – user1050755 Jan 18 '17 at 20:50
3

In addition to other helpful answers, you might like studying this example that uses mouse gestures to scroll arbitrary content.

Community
  • 1
  • 1
trashgod
  • 203,806
  • 29
  • 246
  • 1,045
  • +1 Interesting. At first I was thinking it is something mimicking smart phone scrolling (i.e. on drag). BTW do you know of such API that mimics phone behaviour? – Boro Sep 04 '11 at 12:49