【问题标题】:How do I redraw things when my JFrame is restored (deiconified)?当我的 JFrame 恢复(去图标化)时,我如何绘制东西?
【发布时间】:2013-06-26 23:13:47
【问题描述】:

我有一个非常基本的小 JFrame,它带有 JToggleButtons 和子类 JPanel,它们知道如何绘制我希望它们绘制的内容。选择一个按钮会导致一个椭圆出现在相应的面板中。取消选择按钮会使绘图消失。

不幸的是,最小化(图标化)然后恢复(去图标化)会导致任何绘制的形状消失。所以我需要手动触发重绘。问题是,如果我先显示一个消息框,我只能完成重绘(也就是说,我只能看到它)。

这是 JFrame 的 deiconify 事件:

private void formWindowDeiconified(java.awt.event.WindowEvent evt)
{                                       
    //having this message makes everything work
    JOptionPane.showMessageDialog(null, "Useless message this is.");
    //but if I skip it, I'm SOL
    //what's going on?
    drawAll();
}

此方法遍历我的所有按钮,并在必要时要求重绘:

public void drawAll()
{
    for (int i=0; i<channels; i++)
    {
        if (buttons[i].isSelected())
        {
            lightboxes[i].drawMe();            
        }
    }
}

这是我的子类 JPanel:

class MyJPanel extends JPanel {

    public void drawMe()
    {
        Graphics myGraphics = this.getGraphics();
        myGraphics.fillOval(0, 0, this.getWidth(), this.getHeight());    
    }

    public void unDraw()
    {
        this.invalidate();
        this.repaint();
    }
}

【问题讨论】:

  • 如需尽快获得更好的帮助,请发帖SSCCE
  • 我听说过这些,但我认为在这种情况下不实用。它最终变得无关紧要 - 重点是我应该在组件的 paintComponent 事件中进行绘制并让 Swing 处理细节。

标签: java swing graphics jframe windowlistener


【解决方案1】:

一旦由RepaintManager 恢复,窗口应该会自动重新绘制。问题是你没有像你应该的那样执行自定义绘画......

这不是自定义绘画的方法...

public void drawMe()
{
    Graphics myGraphics = this.getGraphics();
    myGraphics.fillOval(0, 0, this.getWidth(), this.getHeight());    
}

getGraphics 可以返回null,充其量只是图形状态的快照。

由于许多不同的原因,Swing 中的绘画随时可能发生,其中大部分是您无法控制的(也不应该在意)。

您的工作只是响应这些重绘请求并更新您的组件状态。

Swing 有一个自动调用的详细绘制链,您可以使用它。

您应该覆盖 paintComponent 并在此方法中执行所有绘制

查看Performing Custom PaintingPainting in AWT and Swing 了解更多详情

【讨论】:

  • 谢谢。我在该教程中获得了非常有成效的体验。
【解决方案2】:

首先,为了速度,我会使用双缓冲。最好将图形绘制到屏幕外,并在绘图完成后将它们显示到屏幕上。下面的内容应该可以解决您的问题。

public class MyPanel extends JPanel {
    private BufferedImage buffer;
    private Graphics2D canvas;

    @Override
    public void paintComponent(Graphics g) {
        if(buffer == null) {
            buffer = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB);
            canvas = buffer.createGraphics();
        }
        canvas.fillOval(0, 0, this.getWidth(), this.getHeight());
        g.drawImage(buffer, 0, 0, this);
    }
}

【讨论】:

  • Swing 组件默认是双缓冲的......所以现在是三重缓冲......我也可能是错的,但在某些系统上,不处理图形上下文可能会减慢渲染速度或阻止从头开始画(我认为这是前段时间有人在 Mac 上遇到的问题......)
  • 谢谢!除了 MadProgrammer 指出的教程之外,这非常有效。 (我给你检查是因为你的答案有代码可以帮助我在一两分钟内解决我的问题的基本情况。我没有停在那里,教程有所帮助,但这是一个重要的起点.)
  • 确实 Swing 是双缓冲的,但 AWT 不是。
【解决方案3】:

我只是提供这个答案,以便人们可以看到我最终做了什么。大家指出的主要教训是使用组件的paintComponent。有关您自己可能遇到的问题,请参阅 cmets。

编辑:更新以反映来自 MadProgrammer 的 cmets。

//with help from Slihp and MadProgrammer on StackOverflow
//http://stackoverflow.com/q/17331986/1736461
class MyJPanel extends JPanel {

    private boolean buttonSelected = false;

    @Override
    public void paintComponent(Graphics g) {
        //make sure the background gets painted (wacky results otherwise)        
        super.paintComponent(g);

        //now if the corresponding toggle button is on, plop on the circle
        if (buttonSelected)
        {
            g.fillOval(0, 0, this.getWidth(), this.getHeight());
        }        
    }

    //an action listener for the button calls this
    public void setButtonSelected(boolean buttonStateIn)
    {
        buttonSelected = buttonStateIn;    
    }
}

我也对按钮进行了子类化,因此我可以从事件处理程序中获取它的“ID”:

class MyJToggleButton extends JToggleButton
{       
    private int whoAmI;

    public MyJToggleButton(int whoAmIn)
    {
        //the string given to the constructor becomes the button's label
        //("1", "2", "3", etc..)
        super(Integer.toString(whoAmIn + 1));
        whoAmI = whoAmIn;      
    }

    public int getWho()
    {
        return whoAmI;
    }
}

制作按钮的 JFrame 代码:

private void makeButtons(int howMany)
    {
        buttons = new MyJToggleButton[howMany];
        for (int i=0; i<howMany; i++)
        {
            buttons[i] = new MyJToggleButton(i);
            buttons[i].addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent evt) {   
                    //find out which button this is
                    MyJToggleButton button = (MyJToggleButton) evt.getSource();
                    int which = button.getWho();
                    //send the button state to the corresponding lightbox
                    lightboxes[which].setButtonSelected(button.isSelected());
                    //trigger its redrawing
                    lightboxes[which].invalidate();
                    lightboxes[which].repaint();
                }
            });
            this.add(buttons[i]);
        }
    }

这是我唯一需要做的手动重绘——调整大小和重新显示以及所有其他有趣的事情最终都会影响到paintComponent,它只需要知道它的按钮是否被按下就知道要做什么。超级干净,正是我想要的。

【讨论】:

  • 别忘了,Swing 组件已经是双缓冲的。在某些操作系统不处理图形上下文的情况下,实际上可能会减慢渲染过程
  • 您是否建议我不应该使用 BufferedImage?我将在哪里处理图形上下文?
  • 你不需要缓冲的图像,Swing 组件已经是双缓冲的。如果您要使用这样的东西,那么您应该 1- 在绘制方法结束时处理(缓冲图像的)图形上下文。这意味着您需要在开始时创建它; 2-监视组件大小的变化并相应地创建新的缓冲图像。永远不要丢弃不是您创建的图形上下文
  • 谢谢。我已将我的“解决方案”更新为不使用 BufferedImage 的解决方案。
猜你喜欢
  • 2015-04-11
  • 1970-01-01
  • 1970-01-01
  • 2011-06-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-18
相关资源
最近更新 更多