-1

I am trying to help my son do math, so I made a small program. I have a JPanel with 12 Canvases and a giant print button on the bottom.

Window I want to print

This is the listener I have attached on my print button.

    print.addActionListener(new ActionListener()
    {
        @Override
        public void actionPerformed(ActionEvent e)
        {
            panel.remove(print);
            window.pack();
            printComponent(panel, false);
            panel.add(print);
            window.pack();
        }
    });

I have followed example from here and sadly it doesn't work. When I pass JFrame as the component then it prints the whole window! Window printed! When I pass JPanel as the component then it only prints the print button! Print button only printed!

If it is of any relevance I attach everything to the JFrame and JPanel like so:

panel.setBackground(Color.WHITE);
panel.setLayout(/*layout stuff*/);
window.setContentPane(panel);
for (int i = 0; i < 12; i++)
{
    panel.add(/*canvas class which renders one single question*/);
}
panel.add(print, /*layout constraints*/);

I have even tried following examples from here and here and nothing works. I can't seem to just print jpanel with the canvas showing up, and I sure as heck do not want to print the whole window. Any advice on how to fix this?

Here is small example code with same problem.

package carefree.school;

import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.print.PageFormat;
import java.awt.print.Paper;
import java.awt.print.Printable;
import java.io.File;
import java.io.IOException;

public class TestWindow
{
    public static void main(String[] args)
    {
        JFrame frame = new JFrame();
        JPanel panel = new JPanel();
        Canvas canvas = new Canvas()
        {
            @Override
            public void paint(Graphics g)
            {
                super.paint(g);
                g.drawOval(3, 3, 94, 94);
            }
        };
        canvas.setSize(100, 100);
        JButton print = new JButton("Print");
        frame.setContentPane(panel);
        panel.add(canvas);
        panel.add(print);
        frame.setVisible(true);
        frame.pack();
        print.addActionListener(e -> printComponentToFile(panel, false));
    }

    public static void printComponentToFile(Component comp, boolean fill)
    {
        Paper paper = new Paper();
        paper.setSize(8.3 * 72, 11.7 * 72);
        paper.setImageableArea(18, 18, 559, 783);

        PageFormat pf = new PageFormat();
        pf.setPaper(paper);
        pf.setOrientation(PageFormat.PORTRAIT);

        BufferedImage img = new BufferedImage((int) Math.round(pf.getWidth()), (int) Math.round(pf.getHeight()),
                                              BufferedImage.TYPE_INT_RGB);

        Graphics2D g2d = img.createGraphics();
        g2d.setColor(Color.WHITE);
        g2d.fill(new Rectangle(0, 0, img.getWidth(), img.getHeight()));
        ComponentPrinter cp = new ComponentPrinter(comp, fill);
        try
        {
            cp.print(g2d, pf, 0);
        }
        finally
        {
            g2d.dispose();
        }

        try
        {
            ImageIO.write(img, "png", new File("Page-" + (fill ? "Filled" : "") + ".png"));
        }
        catch (IOException ex)
        {
            ex.printStackTrace();
        }
    }

    public static class ComponentPrinter
            implements Printable
    {
        private Component comp;
        private boolean   fill;

        public ComponentPrinter(Component comp, boolean fill)
        {
            this.comp = comp;
            this.fill = fill;
        }

        @Override
        public int print(Graphics g, PageFormat format, int page_index)
        {
            if (page_index > 0)
            {
                return Printable.NO_SUCH_PAGE;
            }

            Graphics2D g2 = (Graphics2D) g;
            g2.translate(format.getImageableX(), format.getImageableY());

            double width  = (int) Math.floor(format.getImageableWidth());
            double height = (int) Math.floor(format.getImageableHeight());

            if (!fill)
            {

                width = Math.min(width, comp.getPreferredSize().width);
                height = Math.min(height, comp.getPreferredSize().height);

            }

            comp.setBounds(0, 0, (int) Math.floor(width), (int) Math.floor(height));
            if (comp.getParent() == null)
            {
                comp.addNotify();
            }
            comp.validate();
            comp.doLayout();
            comp.printAll(g2);
            if (comp.getParent() != null)
            {
                comp.removeNotify();
            }

            return Printable.PAGE_EXISTS;
        }
    }
}
Quillion
  • 6,346
  • 11
  • 60
  • 97
  • You are asking about code that is not behaving as expected, a common issue. Also, you're not new here, and so should be quite familiar with the [mre] link, why it is recommended in this situation and why it would be quite helpful for you and for us. So, I have to ask, why not post a valid [mre] code post with your question? – Hovercraft Full Of Eels May 17 '23 at 02:59
  • @HovercraftFullOfEels I have tried to do so. But the code to generate the problem in question is quite large. I will try to create a quick window with a canvas and a dot then as an example. Sorry about that! – Quillion May 17 '23 at 03:01
  • As you probably know, this is usually the case, and that is why the [mre] link recommends that you create a new small program, one that isolates the problem from the rest of the code so that the issue is easy to see, easy to analyze and easy to fix. Always best to do this before asking, if at all possible. – Hovercraft Full Of Eels May 17 '23 at 03:03
  • @HovercraftFullOfEels added! Sorry about that! Made it as small as possible! – Quillion May 17 '23 at 03:08
  • 1
    `Canvas` is not the best choice for this, use a `JPanel` instead – MadProgrammer May 17 '23 at 03:21
  • I would, instead, start with a pluggable rendering workflow, which could be used to render directly to any given `Graphics`. I'd then use it to paint on to a component or a `Printable` directly – MadProgrammer May 17 '23 at 03:28
  • Did you write `ComponentPrinter` yourself? – g00se May 17 '23 at 03:34
  • @g00se No, it's mine – MadProgrammer May 17 '23 at 04:04
  • @MadProgrammer yup yup, I linked to your answer. Just had to provide a compilable example, so I included your code. Also thanks for suggesting to use pluggable workflow. – Quillion May 17 '23 at 13:34

1 Answers1

1

First, Don't use Canvas for this, I'm not sure it's "printable", or at the very least, it didn't work for me.

Instead, extend from JPanel instead...

JPanel canvas = new JPanel() {
    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        g.drawOval(3, 3, 94, 94);
    }

    @Override
    public Dimension getPreferredSize() {
        return new Dimension(100, 100);
    }
};

You should also avoid call setBounds and prefer to provide sizing hints instead.

Second, choose the component you want to print carefully, for example, in your runnable code, you're passing panel, which will print both the button and the circle, instead, you should really only need to pass canvas

print.addActionListener(e -> printComponentToFile(canvas, false));

With these couple of fixes, it now outputs...

enter image description here

I also thing that if (comp.getParent() != null) { is a mistake and instead should be if (comp.getParent() == null) {

You may also need to call

comp.revalidate();
comp.repaint();

after you've printed the component, but this is one of the many issues with trying to print a "live" component.

Personally, I create a concept of a "renderable question", which could be passed a Graphics context onto which it should be rendered.

You could then create a "renderable question sheet" which would paint multiple "questions".

This could then be used by both a custom component and Printable independently, but that's me.

MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
  • How would one know which components are not printable? – Quillion May 17 '23 at 13:36
  • Well, since `Printable` is a `Swing` based concept, I would suggest that only Swing based components are likely to support it. I would also, personally, discourage the printing of Swing components directly like this, as there are significant differences between screen rendering and paper rendering – MadProgrammer May 17 '23 at 21:35
  • @Quillion See [this example](https://stackoverflow.com/questions/28427566/save-jpanel-as-image-hd-quality/28492187#28492187) for a demonstration of a "renderable" solution – MadProgrammer May 18 '23 at 00:00