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.
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!
When I pass JPanel as the component then it only prints the print button!
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;
}
}
}
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...
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.