java 为什么 repaint() 不总是调用paintComponent 以及为什么在调用它时它的行为并不总是正确的

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/12773749/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-31 10:09:13  来源:igfitidea点击:

Why won't repaint() always call paintComponent and why it doesn't always behave properly when it's called

javarepaintpaintcomponentrepaintmanager

提问by user1726134

I'm working on a Tetris clone in Java, and all seems to work properly until I want a full row to be cleared and everything above to be dropped. Although all my data properly represents the transformation, my paintComponent method seems to only clear the row, but leave everything displayed above as it was before the repaint() call. The new piece will fall through the phantom blocks and land on invisible blocks at the bottom row, where the above pieces would have fallen.

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

Here's my paint component method:

这是我的绘制组件方法:

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());
    }

}

This is the portion of my actionPerformed method in my Timer listener that detects/clears blocks and calls the repaint method.

这是我的 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

By the time the first repaint() method is called, the well array properly shows that the full row is now entirely null. I would like the repaint() method to update the panel to show this empty row, but paintComponent() doesn't seem to be called. This is also the case with the second repaint() method, where I would like it to update the frame to show the blocks in their new positions after clearing a row and dropping them down. Again, paintComponent() isn't called. For the last repaint() call, however, where I just want to update the position of the falling piece regardless of whatever updates it may or may not have needed to make before, repaint() DOES call paintComponent(). So: question number one is, why is paintComponent() only called at this instance of the repaint() call.

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

However, when paintComponent() is called and it reaches the end of the method, I follow it in debug mode to see at what line does the panel reflect the changes. Once it reaches :"Repaintmanager.paintDirtyRegions(Map< Component,Rectangle >)" line:856, it has cleared the row and displays the new falling piece, but has invisible blocks and phantom blocks.

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

So, my second question is, why is paintComponent() behaving in this way. Obviously I need to do a good bit of reading on Repaintmanager and Java painting in general, but I would greatly appreciate it if someone could explain this to me.

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

Here's the main method if important:

如果重要,这是主要方法:

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);
}

}

}

I apologize if this is obnoxiously long.

如果这太长了,我很抱歉。

回答by MadProgrammer

First of all, if you haven't already done so, I'd have a read through Painting in AWT and Swing, which explains the painting scheme used by Swing (and AWT).

首先,如果您还没有这样做,我会通读AWT 和 Swing 中的绘画,其中解释了 Swing(和 AWT)使用的绘画方案。

Secondly, you don't control the repaints, the repaint manager and the OS do, you just provide suggestions.

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

Thirdly, you could take a look at the JComponent.paintImmediatelymethod, it will require to know the area you want to update, but might help

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

From the Java Docs

来自 Java 文档

Paints the specified region in this component and all of its descendants that overlap the region, immediately.

It's rarely necessary to call this method. In most cases it's more efficient to call repaint, which defers the actual painting and can collapse redundant requests into a single paint call. This method is useful if one needs to update the display while the current event is being dispatched.

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

很少需要调用这个方法。在大多数情况下,调用 repaint 更有效,这会推迟实际绘制,并且可以将冗余请求折叠为单个绘制调用。如果需要在调度当前事件时更新显示,则此方法很有用。

I may also be more prudent to render the game state to an off screen buffer and use this in the paintComponentmethod. You could place these on a queue and pop them off during the paint process, allowing to create more or less on demand (keeping a pool of a few and growing, shrinking the pool as you need)...

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

回答by Piotr Müller

repaint()don't call paintComponent()and this is right working scenerio for repaint()method.

repaint()不要打电话paintComponent(),这是repaint()方法的正确工作场景。

It only marks a state that component should be repainted and paintComponent() is called further by Swing itself.

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

Many repaint()calls can cause only single paintComponent()call and this is valid behaviour.

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

回答by Spino Prime

Instead of using repaint()you could try directly calling paint()yourself.

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

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

This way it will paint immediately whenever you want to make a change. Assign this to another function and call it instead when you want to see your changes right away.

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