Reputation: 2545
I'm trying to paint component inside paint(Graphics)
method of JPanel
.
The following code snippet works just fine, a JButton
is painted nicely in my JPanel
:
@Override
public void paint(Graphics g) {
super.paint(g);
JButton btn = new JButton("hello");
Dimension dim = btn.getPreferredSize();
btn.setSize(dim.width, dim.height);
btn.paint(g); // paint the button
}
The code snippet works perfectly also for other components (JLabel
, JTree
, ...) except JPanel
.
The following code will cause very strange NullPointerException at java.awt.Window.access$700(Window.java:132)
.
@Override
public void paint(Graphics g) {
super.paint(g);
JPanel panel = new JPanel();
panel.setSize(10, 10);
panel.paint(g); // paint the panel
}
Here the full stacktrace:
Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException
at java.awt.Window.access$700(Window.java:132)
at java.awt.Window$1.isOpaque(Window.java:3458)
at javax.swing.RepaintManager.getVolatileOffscreenBuffer(RepaintManager.java:983)
at javax.swing.RepaintManager$PaintManager.paint(RepaintManager.java:1395)
at javax.swing.BufferStrategyPaintManager.paint(BufferStrategyPaintManager.java:294)
at javax.swing.RepaintManager.paint(RepaintManager.java:1224)
at javax.swing.JComponent.paint(JComponent.java:1015)
at test.paintcontainer.TestPaintContainerMain$TestContentPane.paint(TestPaintContainerMain.java:48)
at javax.swing.JComponent.paintChildren(JComponent.java:862)
at javax.swing.JComponent.paint(JComponent.java:1038)
at javax.swing.JLayeredPane.paint(JLayeredPane.java:567)
at javax.swing.JComponent.paintChildren(JComponent.java:862)
at javax.swing.JComponent.paintToOffscreen(JComponent.java:5131)
at javax.swing.BufferStrategyPaintManager.paint(BufferStrategyPaintManager.java:278)
at javax.swing.RepaintManager.paint(RepaintManager.java:1224)
at javax.swing.JComponent.paint(JComponent.java:1015)
at java.awt.GraphicsCallback$PaintCallback.run(GraphicsCallback.java:21)
at sun.awt.SunGraphicsCallback.runOneComponent(SunGraphicsCallback.java:60)
at sun.awt.SunGraphicsCallback.runComponents(SunGraphicsCallback.java:97)
at java.awt.Container.paint(Container.java:1780)
at java.awt.Window.paint(Window.java:3375)
at javax.swing.RepaintManager.paintDirtyRegions(RepaintManager.java:796)
at javax.swing.RepaintManager.paintDirtyRegions(RepaintManager.java:713)
at javax.swing.RepaintManager.seqPaintDirtyRegions(RepaintManager.java:693)
at javax.swing.SystemEventQueueUtilities$ComponentWorkRequest.run(SystemEventQueueUtilities.java:125)
at java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:209)
at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:641)
at java.awt.EventQueue.access$000(EventQueue.java:84)
at java.awt.EventQueue$1.run(EventQueue.java:602)
at java.awt.EventQueue$1.run(EventQueue.java:600)
at java.security.AccessController.doPrivileged(Native Method)
at java.security.AccessControlContext$1.doIntersectionPrivilege(AccessControlContext.java:87)
at java.awt.EventQueue.dispatchEvent(EventQueue.java:611)
at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:269)
at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:184)
at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:174)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:169)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:161)
at java.awt.EventDispatchThread.run(EventDispatchThread.java:122)
Any idea how to solve this problem? I need to paint JPanel
inside paint(Graphics)
method.
I wrote a simple test application which you can copy-paste to reproduce the aforementioned exception:
package test.paintcontainer;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
public class TestPaintContainerMain extends JFrame {
public static void main(String[] args) {
TestPaintContainerMain test = new TestPaintContainerMain();
test.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
test.setBounds(0, 0, 300, 200);
test.setContentPane(new TestContentPane());
test.setVisible(true);
}
static class TestContentPane extends JPanel {
JRadioButton paintButtonCheck;
JRadioButton paintPanelCheck;
public TestContentPane() {
paintButtonCheck = createRadioButton("paint button", true);
paintPanelCheck = createRadioButton("paint panel", false);
ButtonGroup buttonGroup = new ButtonGroup();
buttonGroup.add(paintButtonCheck);
buttonGroup.add(paintPanelCheck);
add(paintButtonCheck);
add(paintPanelCheck);
}
@Override
public void paint(Graphics g) {
super.paint(g);
g.translate(100, 100);
if (paintButtonCheck.isSelected()) {
createButton().paint(g);
} else {
createPanel().paint(g);
}
}
private JButton createButton() {
JButton button = new JButton("button");
button.setSize(button.getPreferredSize().width, button.getPreferredSize().height);
return button;
}
private JPanel createPanel() {
JPanel panel = new JPanel();
panel.setBackground(Color.GREEN);
panel.add(createButton());
panel.setSize(panel.getPreferredSize().width, panel.getPreferredSize().height);
return panel;
}
private JRadioButton createRadioButton(String title, boolean selected) {
JRadioButton radio = new JRadioButton(title, selected);
radio.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
TestContentPane.this.repaint();
}
});
return radio;
}
}
}
Upvotes: 1
Views: 2448
Reputation: 1148
This is most likely not a bug in Swing, but more of a problem because you are trying to paint a component which has not yet been realized, meaning it has no active graphic context. You can realize a component by adding it to already realized component like your JFrame - which itself gets realized by setVisible(true)
.
Also one should probably never call JComponent.paint(Graphics)
manually, because this is the job of Swing (more precisely the Event Dispatcher Thread) - it even says so in the documentation of the paint
method:
Applications should not invoke paint directly, but should instead use the repaint method to schedule the component for redrawing.
What you can call is the method printAll(Graphics g)
, which paints the component and all its subcomponents. Also in Swing one should also not override paint
but paintComponent
.
So here is a test code:
JButton button = createButton();
JPanel panel = createPanel();
public TestContentPane() {
paintButtonCheck = createRadioButton("paint button", true);
paintPanelCheck = createRadioButton("paint panel", false);
ButtonGroup buttonGroup = new ButtonGroup();
buttonGroup.add(paintButtonCheck);
buttonGroup.add(paintPanelCheck);
add(paintButtonCheck);
add(paintPanelCheck);
//Hack, just prove something (realize both components)
add(panel);
add(button);
}
...
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.translate(100, 100);
if (paintButtonCheck.isSelected()) {
button.paintAll(g);
} else {
panel.paintAll(g);
}
g.translate(-100, -100);
}
This should work (although you will obviously have two components on the screen you don't want). Also note "reset" the graphics object, because it will still be used afterwards by Swing.
So this is the theory, but it's not yet an actual solution.
My solution to your problem is: "Don't do it like this"!
Components are not like images, in the sense that they don't look the same everywhere. The output of the paintAll
call will be different, depending on how (or where) the components were realized.
So one suggestion is to show actual components. Create your tooltip box, add your panel and your button and let them draw themselves. You can even subclass these components and override their paintComponent()
methods, add transparency and all. It will require some work, but Swing was never known to be easy.
Upvotes: 2
Reputation: 2545
I just found a solution.
The only modification of a sample code from my question is that called panel.setDoubleBuffered(false)
on JPanel
I was trying to paint.
However, I would still consider the exeption to be a Swing bug. If double buffering should be turned off by design you shouldn't get NullPointerException
but some other, more meaningful exception which explains the condition.
Here is a fixed sample application:
package test.paintcontainer;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
public class TestPaintContainerMain extends JFrame {
public static void main(String[] args) {
TestPaintContainerMain test = new TestPaintContainerMain();
test.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
test.setBounds(0, 0, 300, 200);
test.setContentPane(new TestContentPane());
test.setVisible(true);
}
static class TestContentPane extends JPanel {
JRadioButton paintButtonCheck;
JRadioButton paintPanelCheck;
public TestContentPane() {
paintButtonCheck = createRadioButton("paint button", false);
paintPanelCheck = createRadioButton("paint panel", true);
ButtonGroup buttonGroup = new ButtonGroup();
buttonGroup.add(paintButtonCheck);
buttonGroup.add(paintPanelCheck);
add(paintButtonCheck);
add(paintPanelCheck);
}
@Override
public void paint(Graphics g) {
super.paint(g);
g.translate(100, 100);
if (paintButtonCheck.isSelected()) {
createButton().paint(g);
} else {
createPanel().paint(g);
}
}
private JButton createButton() {
JButton button = new JButton("button");
button.setSize(button.getPreferredSize().width, button.getPreferredSize().height);
return button;
}
private JPanel createPanel() {
JPanel panel = new JPanel();
panel.setBackground(Color.GREEN);
panel.add(createButton());
// --------------------------------
panel.setDoubleBuffered(false); // <-- TURN OFF DOUBLE BUFFERING
// --------------------------------
panel.setSize(panel.getPreferredSize().width, panel.getPreferredSize().height);
return panel;
}
private JRadioButton createRadioButton(String title, boolean selected) {
JRadioButton radio = new JRadioButton(title, selected);
radio.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
TestContentPane.this.repaint();
}
});
return radio;
}
}
}
Upvotes: 1