javaswinggraphicsjscrollpanejviewport

Is it possible to paint a JComponent and all its children to another component?


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.


Solution

  • 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