I can't figure this out. After initializing a Canvas with new Canvas(), referencing it results in a NullPointerException. The doc tells me this can occur when the Canvas is not "enabled", but I don't know what it means to be enabled. I tried to debug it by including while(! cvs.isEnabled()); but the program just hung. What are the conditions that could result in a Canvas not being enabled, and how do I fix them?
Exception in thread "main" java.lang.NullPointerException
at matt.io.ConsoleCanvas.<init>(ConsoleCanvas.java:72)
at matt.io.ConsoleCanvas.<init>(ConsoleCanvas.java:51)
at matt.io.ConsoleCanvas.main(ConsoleCanvas.java:32)
public class ConsoleCanvas extends JFrame
{
private static final Font DEFAULT_FONT = new Font("Monospaced", Font.PLAIN, 12);
public static void main(String[] args)
{
ConsoleCanvas me = new ConsoleCanvas(); //ConsoleCanvas.java:32
//Program has crashed by this point, so rest of main removed to be concise
}
protected JTextField in;
private Canvas cvs;
private int row;
private int col;
public ConsoleCanvas()
{
this("Console Pane", 80, 10); //ConsoleCanvas.java:51
}
public ConsoleCanvas(String title, int rows, int cols)
{
in = new JTextField();
in.setEditable(true);
in.setFont(DEFAULT_FONT);
in.setColumns(cols);
cvs = new Canvas();
cvs.setSize(in.getWidth(), in.getHeight() * rows);
cvs.setFont(DEFAULT_FONT);
row = 0;
col = cvs.getGraphics().getFontMetrics().getHeight(); //ConsoleCanvas.java:72
//Program crashes at this line, so I'll leave out the rest for brevity again
//I've isolated the null to the Graphics returned by cvs.getGraphics()
}
}
I didn't work with swing for quite some time, but try adding the canvas to a Panel before calling get graphics (the panel should also have some form of layout).
You can find an interesting tutorial here : http://docs.oracle.com/javase/tutorial/uiswing/
"To appear onscreen, every GUI component must be part of a containment hierarchy. A containment hierarchy is a tree of components that has a top-level container as its root. We'll show you one in a bit." So I think that instantiating the canvas class is not enough. The graphics object is assigned to your canvas object only after you add it to a top-level container.
Until a component is displayable, its graphics will be null. This is a real hassle because there is no easy way to access a FontMetrics
without a Graphics
.
There are several solutions that I know of:
If you just want the line height, Font.getLineMetrics()
is sufficient.
Toolkit.getDefaultToolkit().getFontMetrics(Font)
works, but is deprecated.
A non-deprecated but needlessly roundabout solution is to create a BufferedImage
and get its Graphics
.
Edit:
Actually, as of 1.5 you can just do cvs.getFontMetrics(DEFAULT_FONT)
. I can't believe I never noticed that before!
You probably first shouldn't be using Canvas. This is an old AWT class. If you're using JFrame then you're using Swing. If you want to draw on something: subclass JComponent, override paintComponent, and you can put your drawing code there. My guess is getGraphics() returns null because it's not attached to the parent. You might have to wait until later to get access to the Graphics object like once it's displayed.
Aren't you supposed to add the Canvas to JFrame first?
cvs = new Canvas();
getContentPane().add(cvs);
someObject.getGraphics() is notoriously risky within a constructor. In this case probably since it has not been displayed yet. I'd recommend using lazy instantiation on col. Simply write a getter that checks whether it has been assigned yet:
public Graphics getCol() {
if (col == null) {
col = cvs.getGraphics();
}
return col;
}
That way, you should get Graphics object the moment you require it for the first time. As an added safeguard you can check whether the assignment was successful and throw an unchecked exception if it is null (i.e. fail-fast).