【问题标题】:Why won't repaint() always call paintComponent and why it doesn't always behave properly when it's called为什么 repaint() 不总是调用 paintComponent 以及为什么它在调用时并不总是正常运行
【发布时间】:2012-09-28 05:35:28
【问题描述】:

我正在用 Java 编写俄罗斯方块克隆,在我想要清除一整行并删除上面的所有内容之前,一切似乎都可以正常工作。尽管我的所有数据都正确地表示了转换,但我的 paintComponent 方法似乎只清除了该行,但上面显示的所有内容都与 repaint() 调用之前一样。新碎片将穿过幻影块并落在底行的隐形块上,上面的碎片会掉下来。

这是我的绘制组件方法:

public void paintComponent(Graphics page)
{
    super.paintComponent(page);
    Graphics2D page2D = (Graphics2D) page;
    for (int c = 0; c < 10; c++) 
    {
        for (int r = 0; r < 18; r++)
        {
            if (well[c][r] != null) //Well is a 2D array of Block objects that have Rectangle object, coordinates and color
            {
                page2D.setColor(well[c][r].getColor());
                page2D.fill(well[c][r].getSquare());
                page2D.setColor(Color.gray);
                page2D.draw(well[c][r].getSquare());
            }       

        }
    }
    for (int i = 0; i < 4; i++) //tetro = the player's tetris piece
    {
        page2D.setColor(tetro.getColor());
        page2D.fill(tetro.getBlock(i).getSquare());
        page2D.setColor(Color.GRAY);
        page2D.draw(tetro.getBlock(i).getSquare());
    }

}

这是我的 Timer 侦听器中的 actionPerformed 方法的一部分,它检测/清除块并调用重绘方法。

        int count = 0;  //Number of occupied cells in well
        int clears = 0; //number of rows to be clear
        int lowestClear = -1; //Lowest row that was cleared, -1 if none
        for (int row = 0; row < 18; row++)
        {
            for (int col = 0; col < 10; col++)
            {
                if (well[col][row] != null)
                {
                    count++;
                }
            }
            if (count == 10)
            {
                clears++;
                if (lowestClear < 0)
                {
                    lowestClear = row;
                }
                for (int col = 0; col < 10; col++)
                {
                    well[col][row] = null;
                }
            }
            count = 0;
        }
        if (clears > 0)
        {
            repaint(); //Doesn't call paintComponent()
            for (int i = 1; i <= clears; i++)
            {
                for (int r = 16; r >= 0; r--)
                {
                    if (r > lowestClear)
                    {
                        break;
                    }
                    for (int c = 0; c < 10; c++)
                    {
                        if (well[c][r] != null)
                        {
                            well[c][r+1] = well[c][r];
                            well[c][r] = null;
                        }           
                    }
                }
            }
            repaint(); //Does not call paintComponent()
        }       
        tetro.fall();
        repaint(); //DOES call paint component

在调用第一个 repaint() 方法时,well 数组正确地显示整行现在完全为空。我希望 repaint() 方法更新面板以显示此空行,但似乎没有调用 paintComponent()。第二个 repaint() 方法也是这种情况,我希望它更新框架以在清除一行并将它们放下后将块显示在新位置。同样,paintComponent() 没有被调用。 然而,对于最后一次 repaint() 调用,我只想更新下落块的位置,而不管它之前可能需要或不需要进行的任何更新,repaint() 确实调用了 paintComponent()。所以:第一个问题是,为什么 paintComponent() 只在 repaint() 调用的这个实例中被调用。

但是,当调用 paintComponent() 并到达方法的末尾时,我会在调试模式下跟踪它,以查看面板在哪一行反映了更改。一旦到达:"Repaintmanager.paintDirtyRegions(Map)" line:856,它已经清除了行并显示了新的下落块,但有不可见块和幻象块。

所以,我的第二个问题是,为什么paintComponent() 会以这种方式运行。显然,我需要大量阅读 Repaintmanager 和 Java 绘画,但如果有人可以向我解释这一点,我将不胜感激。

如果重要的话,这里是主要方法:

import javax.swing.JFrame;

public class TetrisDriver 
{


public static void main(String[] args) 
{
    JFrame frame = new JFrame("Tetris");
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

    frame.getContentPane().add(new Matrix()); //Matrix = jPanel class
    frame.pack();
    frame.setVisible(true);
}

}

如果这篇文章太长了,我深表歉意。

【问题讨论】:

    标签: java repaint paintcomponent repaintmanager


    【解决方案1】:

    首先,如果您还没有这样做,我会阅读Painting in AWT and Swing,它解释了 Swing(和 AWT)使用的绘画方案。

    其次,您不控制重绘,重绘管理器和操作系统可以控制,您只需提供建议。

    第三,你可以看看JComponent.paintImmediately方法,它需要知道你要更新的区域,但可能会有所帮助

    来自 Java 文档

    绘制此组件中的指定区域及其所有区域 立即与区域重叠的后代。

    很少需要调用此方法。在大多数情况下,它更多 调用 repaint 是有效的,它推迟了实际的绘制并且可以 将多余的请求折叠到单个绘制调用中。这种方法是 如果需要在当前事件发生时更新显示,这很有用 正在发送。

    我也可能更谨慎地将游戏状态渲染到屏幕外缓冲区并在paintComponent 方法中使用它。您可以将它们放在一个队列中并在绘制过程中将它们弹出,允许根据需要或多或少地创建(保持几个池并不断增长,根据需要缩小池)...

    【讨论】:

    • 我将再次阅读这篇文章,但据我了解,问题一定与重绘管理器折叠多个请求有关,但我不知道为什么在执行 paintComponent 时它会绘制行被清除的简短状态,而不是清除行上方的所有内容都向下移动时的实际状态。老实说,我不确定如何在不首先了解正在发生的事情的情况下实现屏幕外缓冲区或paintImmediately 以防止发生任何事情。您对我的代码中的重绘管理器在做什么有任何想法吗?
    • 您将如何制作 GUI 队列,或者您要制作什么队列?它会像ArrayList 还是我误读了您写的内容?
    • @JohnnyCoder 这取决于你想要做什么
    • @user1726134 请记住,绘画可以在任何时间、出于任何原因发生,很多时候您都不知情或无法控制。可能是您在状态更新之间遇到了重绘点,但是如果没有某种可运行的示例就很难判断,其他一切都是猜测工作
    【解决方案2】:

    repaint() 不要调用paintComponent(),这是repaint() 方法的正确工作场景。

    它只标记了组件应该被重新绘制的状态,而paintComponent() 会被 Swing 本身进一步调用。

    许多repaint() 调用只能导致单个paintComponent() 调用,这是有效的行为。

    【讨论】:

      【解决方案3】:

      您可以尝试自己直接调用paint(),而不是使用repaint()

      Graphics g;
      g = getGraphics();
      paint(g);
      

      这样,只要您想进行更改,它就会立即绘制。将此分配给另一个函数,并在您想立即查看更改时调用它。

      【讨论】:

      • @sdasdadas 为什么调用paint() 不是一个好主意?我听说过,但不完全理解。
      • Swing 使用被动渲染算法,这意味着绘画可以在任何时间以任何原因发生,大多数情况下无需您的知识或干预。尝试以您建议的方式绘画可能会导致NullPointerException 或在您与绘画子系统作斗争时导致其他绘画伪影。您应该在 API 内工作,而不是反对它。 getGraphics 绝不是个好主意
      最近更新 更多