7

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.

Mario Carneiro
  • 1,548
  • 1
  • 18
  • 32
  • d) why would you want to print this? JScrollPane helps you see this big thing *on the screen* by scrolling. Can you define different view for your custom table's data that makes it Printing-friendly? – Atreys Aug 11 '11 at 13:50
  • @Mario C on the right side I see some related post, there are links/tutorials how to print 2D Graphics and Text JComponents too, if I understood correctly your problem is calculate paper's DPI versus pixels (always) from visible Container (JScrollPane with unknow contents) – mKorbel Aug 11 '11 at 13:55
  • @mKorbel I don't have any problems with incomplete information - I can get the size of my components just fine. The problem is with _painting_ - I can't get any undisplayable components to paint (unsurprisingly). – Mario Carneiro Aug 11 '11 at 14:07
  • @Atreys the idea is that I can just take the form itself and print it, rather than having to create another version of the form that's printer-friendly. In any case, replacing the JScrollPane with its contents directly into the panel won't work either, for the same reasons. After replacement, the content pane will be huge, and it will be squashed down to size when it is made displayable in a JFrame (even if nothing gives in the layout). – Mario Carneiro Aug 11 '11 at 14:11

4 Answers4

8

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.

Community
  • 1
  • 1
trashgod
  • 203,806
  • 29
  • 246
  • 1,045
  • The getSize() method doesn't work unless the content has been made displayable, and once it is, the frame's internal size limit stops it before it reaches full size. I'm not sure if I could get paint to work if I manually set the sizes of all the child components (without the benefit of my layout managers), but that's a frightening prospect. – Mario Carneiro Aug 12 '11 at 00:18
  • Right, I should have used `getPreferredSize()` and scheduled the scaling _after_ `setVisible()`; I've expanded the example above. – trashgod Aug 12 '11 at 03:14
  • Yeah, this looks like it would work. The part I was missing is that you have to image a component connected to the main frame through a `JScrollPane`. If you try to use any component which is a direct descendent of the `JFrame` (like its content pane, like I was doing before), it will not be allowed to become arbitrarily large. – Mario Carneiro Aug 12 '11 at 06:09
  • Yes, the frame's size is limited to the memory available to the host's heavyweight peer, possibly in video RAM; in contrast, the size of a lightweight component is substantially greater. Depending on your content, you might tinker with the interpolation parameter. – trashgod Aug 12 '11 at 07:34
1

you have to print the component without scrollpane. the scrollpane will always only prints the visible content.

if you havent access to the createPanel() Method, and the panel from this method only contains the scrollpane, you can get the component, which the scrollpane contains, in the following way:

JPanel panel = createPanel();
print(((JScrollPane)panel.getComponents()[0]).getViewport());

but if your data is format as a table, you can also about using the JTable. the JTable has a own powerful print() Method. more infos at http://download.oracle.com/javase/tutorial/uiswing/misc/printtable.html

Dragon8
  • 1,775
  • 12
  • 8
  • The dialog created by createPanel() actually contains many other fields and boxes, but there is a scroll pane in the center taking up most of the space. I thought about extracting the viewport of the scroll pane, and printing that, but then I lose the rest of my dialog. But (testing) it _does_ actually print all 3000 pixels height of the JPanel in the viewpot, which gives me another idea! – Mario Carneiro Aug 12 '11 at 00:07
1

Since a component in a JScrollPane can have arbitrary size, even after it is made displayable, My solution is to try this:

JPanel c = createPanel();
JFrame f = new JFrame();
f.getContentPane().add(new JScrollPane(c));
f.pack();
print(c);

so that I can validate the JPanel without it being size-limited to the maximum size of a JFrame. It also has the "unlimited resolution" look on the fonts and things that you get from printing the components directly, without double-buffering like trashgod suggested.

Mario Carneiro
  • 1,548
  • 1
  • 18
  • 32
  • 2
    You don't need the `getContentPane()`; `add()` is forwarded by the [`JFrame`](http://download.oracle.com/javase/6/docs/api/javax/swing/JFrame.html). – trashgod Aug 12 '11 at 03:19
  • Granted, but I prefer not to use `JFrame`'s own `add()` method as a matter of convention, because it can be confusing to those who don't realize that they are synonyms (this shows the actual behavior a little more clearly). – Mario Carneiro Aug 12 '11 at 06:12
  • The print() method requires Graphics as an input. How are you able to pass it a JPanel? – jacen.garriss Aug 16 '13 at 13:57
  • That is my own `print(Component)` method, given in the OP. – Mario Carneiro Aug 16 '13 at 17:31
0

You mentioned in your question, that maybe you would need to print an invisible component. How this works is shown here by Java42, I just rearranged it a bit:

public class PrintInvisible  {
    static class JPanelPrintable extends JPanel implements Printable
    {
        public int print(Graphics g, PageFormat pf, int page) throws PrinterException
        {
            if (page > 0) return Printable.NO_SUCH_PAGE;
            printAll(g);
            return Printable.PAGE_EXISTS;
        }

        public void print() throws PrinterException
        {
            PrinterJob job = PrinterJob.getPrinterJob();
            job.setPrintable(this);
            if (job.printDialog()) job.print();
        }

    };

    public static void main(String args[]) throws PrinterException {
        final JFrame f = new JFrame();
        f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        f.setSize(400,400);
        final JPanelPrintable j = new JPanelPrintable();
        j.setLayout(new BorderLayout());
        j.add(new JButton("1111"),BorderLayout.NORTH);
        j.add(new JButton("2222"),BorderLayout.SOUTH);
        f.add(j);f.repaint();f.pack();
        //f.setVisible(true);
        j.print();
    }
}

If you yet need to print multiple pages, you can combine this solution with my answer on how to print a Component to multiple pages

ArchLinuxTux
  • 840
  • 1
  • 11
  • 28