为啥在 Graphics 对象上调用 dispose() 会导致 JPanel 不呈现任何组件
Posted
技术标签:
【中文标题】为啥在 Graphics 对象上调用 dispose() 会导致 JPanel 不呈现任何组件【英文标题】:Why does calling dispose() on Graphics object cause JPanel to not render any components为什么在 Graphics 对象上调用 dispose() 会导致 JPanel 不呈现任何组件 【发布时间】:2012-12-04 09:44:57 【问题描述】:在得知dispose()
应该在使用后在Graphics
/Graphics2D
对象上调用后,我开始改变我的游戏以包含它。
当我在 JPanel
的覆盖 paintComponent(Graphics g)
中添加 g2d.dispose()
时,我添加的组件(JLabel
类的扩展)在未呈现的地方我仍然能够单击它们等,但它们不会是 画。
我用普通的JLabel
和JButton
进行了测试,效果相同(尽管JButton
在鼠标悬停时会呈现)。
所以我的问题是为什么会发生这种情况?
这里有一个 SSCCE 来演示:
在paintComponent
的MainMenuPanel
类中取消对dispose()
的调用后:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Image;
import java.awt.RenderingHints;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.net.URL;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class Test
public Test()
try
initComponents();
catch (Exception ex)
Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex);
public static void main(String[] args)
SwingUtilities.invokeLater(new Runnable()
@Override
public void run()
new Test();
);
private void initComponents() throws Exception
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setResizable(false);
MainMenuPanel mmp = new MainMenuPanel();
frame.add(mmp);
frame.pack();
frame.setVisible(true);
class MainMenuPanel extends JPanel
//create labels for Main Menu
private PopUpJLabel versusesModeLabel;
private PopUpJLabel singlePlayerModeLabel;
private PopUpJLabel optionsLabel;
private PopUpJLabel helpLabel;
private PopUpJLabel aboutLabel;
//create variable to hold background
private Image background;
private Dimension preferredDimensions;
public static String gameType;
public static final String SINGLE_PLAYER = "Single Player", VERSUS_MODE = "VS Mode";
/**
* Default constructor to initialize double buffered JPanel with
* GridBagLayout
*/
public MainMenuPanel()
super(new GridBagLayout(), true);
try
initComponents();
catch (Exception ex)
JOptionPane.showMessageDialog(null, "Could not load main menu background!", "Main Menu Error: 0x004", JOptionPane.ERROR_MESSAGE);
System.exit(4);
/*
* Create JPanel and its components
*/
private void initComponents() throws Exception
//set prefered size of JPanel
preferredDimensions = new Dimension(800, 600);
background = scaleImage(800, 600, ImageIO.read(new URL("http://photos.appleinsider.com/12.08.30-Java.jpg")));
//create label instances
singlePlayerModeLabel = new PopUpJLabel("Single Player Mode");
singlePlayerModeLabel.setEnabled(false);
versusesModeLabel = new PopUpJLabel("Versus Mode");
optionsLabel = new PopUpJLabel("Options");
helpLabel = new PopUpJLabel("Help");
aboutLabel = new PopUpJLabel("About");
//create new constraints for gridbag
GridBagConstraints gc = new GridBagConstraints();
gc.fill = GridBagConstraints.HORIZONTAL;
gc.ipady = 50;//vertical spacing
//add newGameLabel to panel with constraints
gc.gridx = 0;
gc.gridy = 0;
add(singlePlayerModeLabel, gc);
gc.gridy = 1;
add(versusesModeLabel, gc);
//add optionsLabel to panel with constraints (x is the same)
gc.gridy = 2;
add(optionsLabel, gc);
//add helpLabel to panel with constraints (x is the same)
gc.gridy = 3;
add(helpLabel, gc);
//add aboutLabel to panel with constraints (x is the same)
gc.gridy = 4;
add(aboutLabel, gc);
public static BufferedImage scaleImage(int w, int h, BufferedImage img) throws Exception
BufferedImage bi;
//bi = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
bi = new BufferedImage(w, h, img.getType());
Graphics2D g2d = (Graphics2D) bi.createGraphics();
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.addRenderingHints(new RenderingHints(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY));
g2d.drawImage(img, 0, 0, w, h, null);
g2d.dispose();
return bi;
/*
* Will return the preffered size of JPanel
*/
@Override
public Dimension getPreferredSize()
return preferredDimensions;
/*
* Will draw the background to JPanel with anti-aliasing on and quality rendering
*/
@Override
protected void paintComponent(Graphics grphcs)
super.paintComponent(grphcs);
//convert graphics object to graphics2d object
Graphics2D g2d = (Graphics2D) grphcs;
//set anti-aliasing on and rendering etc
//GamePanel.applyRenderHints(g2d);
//draw the image as the background
g2d.drawImage(background, 0, 0, null);
//g2d.dispose();//if I uncomment this no LAbels will be shown
class PopUpJLabel extends JLabel
public final static Font defaultFont = new Font("Arial", Font.PLAIN, 50);
public final static Font hoverFont = new Font("Arial", Font.BOLD, 70);
PopUpJLabel(String text)
super(text);
setHorizontalAlignment(JLabel.CENTER);
setForeground(Color.ORANGE);
setFont(defaultFont);
//allow component to be focusable
setFocusable(true);
//add focus adapter to change fints when focus is gained or lost (used for transversing labels with keys)
addFocusListener(new FocusAdapter()
@Override
public void focusGained(FocusEvent fe)
super.focusGained(fe);
if (isEnabled())
setFont(getHoverFont());
@Override
public void focusLost(FocusEvent fe)
super.focusLost(fe);
setFont(getDefaultFont());
);
addMouseListener(new MouseAdapter()
@Override
public void mouseEntered(MouseEvent me)
super.mouseEntered(me);
if (isEnabled())
setFont(getHoverFont());
//call for focus mouse is over this component
requestFocusInWindow();
);
Font getDefaultFont()
return defaultFont;
Font getHoverFont()
return hoverFont;
【问题讨论】:
"在得知使用后应该在 Graphics/Graphics2D 对象上调用 dispose() 之后"你从哪里了解到的? 对“哪里”也很感兴趣... @kleopatra 这是对我的答案的评论,它与通过图形缩放图像有关,所以它是正确的,尽管我认为它是正确的,但无论何时使用图形对象都必须这样做 感谢您的澄清 - 吸取的教训:不要相信每一条评论 :-) 【参考方案1】:问题是您在paintComponent
中使用的Graphics
上下文是由调用者(框架)创建和提供的,它也负责处理它。
您只需要在您自己实际创建Graphics
时处理它(例如通过调用Component.getGraphics()
)。在你的情况下,你不是在创建它,你只是在投射它,所以在这种情况下不要调用 dispose。
【讨论】:
+1 谢谢,我明白了我的错误,虽然每次我们使用paintComponent 等对象时都应该调用它,但我会在可以接受的时候接受 我不明白您为什么说 您只需要在自己实际创建 Graphics 时处理它(例如通过调用 Component.getGraphics())。 当您打电话给Component.getGraphics()
你会创建一个新的Graphics
吗?我的理解是Graphics.createGraphics()
可以,但不是简单的getGraphics()
。无论如何,我认为打电话给getGraphics()
通常是个坏主意,应该避免。
您说得对,应该避免使用 getGraphics
,但在文档中明确指出,这是获取 Graphics
上下文的一种方式。另外,getGraphics
文档说:创建一个图形上下文...,所以我假设每次都会创建一个新上下文。当然BufferedImage.createGraphics
也是一种创建Graphics
上下文的方式。【参考方案2】:
因为.dispose()
释放了你的资源。是的,一切。
【讨论】:
以上是关于为啥在 Graphics 对象上调用 dispose() 会导致 JPanel 不呈现任何组件的主要内容,如果未能解决你的问题,请参考以下文章
错误 - 尝试在空对象引用上调用虚拟方法“int android.graphics.Bitmap.getHeight()”
由 java.lang.NullPointerException 引起:尝试在空对象引用上调用虚拟方法“int android.graphics.Bitmap.getWidth()”
打开失败:EACCES(权限被拒绝)并尝试在空对象引用上调用虚拟方法“int android.graphics.Bitmap.getWidth()”
RxSwift 为啥我们没有调用 dispose 会有内存泄漏