Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Printing a large Swing component

I have a Swing form with a custom table inside a JScrollPane (it's just a JPanel, not a JTable subclass), and I am trying to get it to print. If I just send the whole frame to the printer, the scroll pane cuts off, and if I resize the frame to the size of the contents of the scroll pane, some sort of internal barrier stops the JFrame becoming more than about 1100 pixels tall.

Another alternative would be to create the content pane of the dialog without attaching it to the root JFrame, because the JPanel's size is not limited in that case. But to get the components to lay themselves out and resize to their proper sizes, I seem to need to make the panel displayable, which means at the very least adding it to a JFrame and calling JFrame.pack(), but again, the 1100 pixel limit comes back.

Here's my code for printing the component:

public static void print(final Component comp) {
    final float SCALE = .5f;
    PrinterJob job = PrinterJob.getPrinterJob();
    job.setPrintable(new Printable() {
        public int print(Graphics g, PageFormat pf, int page)
            throws PrinterException
        {
            if (page * pf.getImageableHeight() >= SCALE * comp.getHeight())
                return NO_SUCH_PAGE;
            ((Graphics2D)g).translate(pf.getImageableX(), pf.getImageableY()
               - page * pf.getImageableHeight());
            ((Graphics2D)g).scale(SCALE, SCALE);
            comp.printAll(g);
            return PAGE_EXISTS;
        }
    });
    if (job.printDialog())
        try { job.print(); }
        catch (PrinterException ex) {}
}

If I do this, the component has zero size:

JPanel c = createPanel(); // This JPanel has a JScrollPane in it with its
                          // preferredSize equal to that of its viewport component
                          // (which is not what I do to show the dialog normally)
print(c);

If I do this, the component has the right size, but prints as solid gray because the sub-components have not been laid out:

JPanel c = createPanel();
c.setSize(c.getPeferredSize());
print(c);

These don't seem to make a difference:

JPanel c = createPanel();
c.validate();
c.revalidate();
c.repaint();
print(c);

This makes the panel larger, but it stops at about a page and a half large (1100px):

JPanel c = createPanel();
JFrame f = new JFrame();
f.setContentPane(c);
f.pack();
print(c);

I'm running out of permutations here. Does anyone know either (a) how to change the OS maximum frame size, (b) how to layout and paint an off-screen component, or (c) how to print a Swing component directly, without having to paint it (?). Help is appreciated.

like image 959
Mario Carneiro Avatar asked Aug 11 '11 13:08

Mario Carneiro


1 Answers

Using your custom panel's paint() method, render the content into a BufferedImage.

Addendum: Here's a more complete example of the approach, which simply scales the component by half. You'll want to preserve the aspect ratio in your actual application.

enter image description here

import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.geom.AffineTransform;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;

/** @see https://stackoverflow.com/questions/7026822 */
public class PanelPaint extends JPanel {

    private static final double SCALE = 0.5;

    public PanelPaint() {
        super(new GridLayout(0, 1));
        final MyPanel panel = new MyPanel();
        JScrollPane scroll = new JScrollPane(panel);
        scroll.getViewport().setPreferredSize(new Dimension(320, 240));
        this.add(scroll);
        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                add(new JLabel(new ImageIcon(createImage(panel))));
            }
        });
    }

    private BufferedImage createImage(MyPanel panel) {
        Dimension size = panel.getPreferredSize();
        BufferedImage image = new BufferedImage(
            size.width, size.height, BufferedImage.TYPE_INT_ARGB);
        Graphics2D g2d = image.createGraphics();
        panel.paint(g2d);
        g2d.dispose();
        AffineTransform at = new AffineTransform();
        at.scale(SCALE, SCALE);
        AffineTransformOp scaleOp =
            new AffineTransformOp(at, AffineTransformOp.TYPE_BILINEAR);
        return scaleOp.filter(image, null);
    }

    private static class MyPanel extends JPanel {

        private static final int N = 16;

        public MyPanel() {
            super(true);
            this.setLayout(new GridLayout(N, N));
            for (int i = 0; i < N * N; i++) {
                this.add(new JLabel(String.valueOf(i) + " "));
            }
        }
    }

    private void display() {
        JFrame f = new JFrame("PanelPaint");
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.add(this);
        f.pack();
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                new PanelPaint().display();
            }
        });
    }
}

As shown here, you can scale the rendering to fit the destination's MediaPrintableArea, or use getSubimage() to divide the content into pages as desired.

like image 135
trashgod Avatar answered Sep 20 '22 00:09

trashgod