为啥在 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 类的扩展)在未呈现的地方我仍然能够单击它们等,但它们不会是 画。

我用普通的JLabelJButton 进行了测试,效果相同(尽管JButton 在鼠标悬停时会呈现)。

所以我的问题是为什么会发生这种情况?

这里有一个 SSCCE 来演示:

paintComponentMainMenuPanel 类中取消对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 会有内存泄漏

为啥Android找不到android.graphics.OpenGLContext?

为啥 Graphics::DrawString 绘制杂项字符?