3

I have an extended JPanel class called GridPanel. It lets you drag and drop images into it from a JList. The GridPanel lets you drag the images around with the mouse and rearrange them as you want. What I'm interested in is making a thumbnail view of the GridPanel component.

If I understand correctly, setting a JScrollPane's view to be GridPanel makes GridPanel a child of a JViewPort, which becomes a child of the JScrollPane. Currently GridPanel is already set to be the view of a JScrollPane and I'm pretty sure GridPanel can't have two parents. So I can't have two components share the same view, but I really only need the thumbnail view to paint a scaled visual copy of GridPanel.

This leads to my question. Is it possible to copy what GridPanel paints, but paint it on a completely separate component?

This is an example of what I've tried, in case I'm not being understood.

import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;

public class TestMain {
public static void main(String[] a) {
    Color[] colors = new Color[]{Color.green, Color.red, Color.blue, Color.yellow};


    final JFrame frame = new JFrame();
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setLocationByPlatform(true);
    JPanel content = new JPanel();
    frame.setContentPane(content);
    content.setLayout(new FlowLayout());
    //using variable names to try and help relate to my question
    final JPanel gridPanel = new JPanel();
    gridPanel.setPreferredSize(new Dimension(200,200));
    gridPanel.setLayout(new GridLayout(2, 2));
    JLabel label;
    for (Color c: colors){
        label = new JLabel();
        label.setOpaque(true);
        label.setBackground(c);
        gridPanel.add(label);
    }

    JScrollPane gridScroll = new JScrollPane(gridPanel);

    final JScrollPane thumbnailScroll = new JScrollPane();
    thumbnailScroll.setPreferredSize(new Dimension(200,200));

    JButton tryThumbnailView = new JButton("Activate Thumbnail");
    tryThumbnailView.addActionListener(new ActionListener(){
        @Override
        public void actionPerformed(ActionEvent arg0) {
            thumbnailScroll.setViewportView(gridPanel);
            frame.repaint();

        }
    });
    content.add(tryThumbnailView);

    content.add(gridScroll);
    content.add(thumbnailScroll);

    frame.pack();
    frame.setVisible(true);
    }
}

What I would like to have happen is for both components to show the same set of colored JLabels, without duplicating those JLabels.

Eng.Fouad
  • 115,165
  • 71
  • 313
  • 417
Lucas
  • 1,149
  • 1
  • 9
  • 23

2 Answers2

2

All you have to do is "get rendering result" of one component and reuse it. That takes:

  1. intercept rendering process - override paint(Graphics) method
  2. create own image to render to
  3. render your image to original graphics
  4. render your image to other component - override paint(Graphics) method

First three steps can be done like this:

@Override
public void paint(Graphics originalGraphics) {
  GraphicsEnvironment e = GraphicsEnvironment.getLocalGraphicsEnvironment();
  GraphicsConfiguration c = e.getDefaultScreenDevice().getDefaultConfiguration();
  //create own image to paint to
  BufferedImage image = c.createCompatibleImage(getWidth(), getHeight());
  Graphics2D reusableGraphics = image.createGraphics();
  //let it paint into our graphics
  super.paint(reusableGraphics);
  // draw image on this component
  originalGraphics.drawImage(image, 0, 0, null);
  // draw image on other component
  otherComponent.setMirrorImage(image);
}

In otherComponent you have to save image and paint it when required:

@Override
public void paint(Graphics g) {
  if (mirroredImage == null) {
    super.paintAll(g);
  } else {
    g.drawImage(mirroredImage, 0, 0, getWidth() * 3 / 4, getHeight() * 3 / 4, null);
  }
}
public void setMirrorImage(BufferedImage mirroredImage) {
  this.mirroredImage = mirroredImage;
  repaint();
}

You can take a look here for full example

Piro
  • 1,367
  • 2
  • 19
  • 40
  • This looks more complicated than the other answer, but both answers interest me. Would you be willing to discuss any pro or con to using either method over the other? – Lucas Aug 03 '13 at 16:54
  • 1
    Sure I can. I would certainly use simpler method when possible. My solution has at least two different characteristics to @tbodt solution. First you can keep stored image and manipulate it without rendering original component again. Also since painting is separate and I do not call painting directly (repaint is only suggestion to swing to paint), it is safer if you manipulate graphics incorrectly (forget to scale back for example). tbodt solution can save you memory if you render very large components. – Piro Aug 03 '13 at 18:22
  • I'm sorry to ask another question here this way. In your code, the line `BufferedImage image = c.createCompatibleImage(getWidth(), getHeight());`...is this building the `BufferedImage` with image data? That seems like an extremely handy method to understand, but the java doc is a bit above my current comprehension. – Lucas Aug 04 '13 at 04:09
  • What you mean by "with image data"? Image created has some default background color and you should paint to it, but it can be drawn as is. You can create `BufferedImage` with its constructor, but for performance reasons it is better to use `createCompatibleImage`, because you do not know underlying representation of image data. – Piro Aug 04 '13 at 10:06
0

You can override paintComponent like this:

protected void paintComponent(Graphics g) {
    super.paintComponent(g);
    Graphics2D g2d = (Graphics2D) g;
    g2d.scale(scaleFactor, scaleFactor);
    gridPanel.paintComponenet(g);
}

This assumes that gridPanel either overrides paintComponent as public or this class has to be in the same package as GridPanel.

tbodt
  • 16,609
  • 6
  • 58
  • 83