为什么使用repaint()会导致Swing bug,而使用getParent().repaint()不会导致?

3

这个问题是基于我之前使用简单的Swing骰子程序遇到的问题。我发布的原始问题在这里,有一个被接受的答案,但我想知道究竟发生了什么、为什么会出现这个问题以及为什么解决方法有效。

我已经将原始代码缩减到核心部分,现在它看起来非常不同:

  • 我有两个ColorPanel,每个面板上都画了一个有颜色的正方形。
  • 当你点击一个面板时,该方块会按照以下顺序更改颜色:从黑色开始,然后变成红色、绿色、蓝色、红色、绿色、蓝色等。
  • 一旦一个方块改变了颜色,它就永远不应该再变成黑色。

但是,当我只在MouseListener中调用repaint()时,程序行为非常奇怪:

  • 我点击一个面板,正方形的颜色会改变。
  • 然后我点击另一个面板,它的正方形颜色也会改变,但是第一个正方形也会变成黑色。
  • 你可以在下面的GIF中看到这种行为:

buggy program

如果使用getParent().repaint(),此问题将消失,程序将按预期运行:

enter image description here

  • 该问题似乎仅在面板/正方形开始“重叠”时发生。
  • 如果使用阻止此类问题的布局或不设置小尺寸,则不会出现问题。
  • 最初我认为涉及并发问题,但问题并非每次都会出现。
  • 我在原始问题中遇到的代码似乎并未对所有人造成问题,因此我的IDE、jdk等可能也相关:Windows 7,Eclipse Kepler,jdk1.7.0_03。

除导入等内容以外的代码如下:

public class ColorPanelsWindow extends JFrame{

    static class ColorPanel extends JPanel {

        //color starts off black
        //once it is changed should never be 
        //black again
        private Color color = Color.BLACK;

        ColorPanel(){
            //add listener
            addMouseListener(new MouseAdapter(){
                @Override
                public void mousePressed(MouseEvent arg0) {
                    color = rotateColor();
                    repaint();
                    //using getParent().repaint() instead of repaint() solves the problem
                    //getParent().repaint();
                }
            });
        }
        //rotates the color black/blue > red > green > blue
        private Color rotateColor(){
            if (color==Color.BLACK || color == Color.BLUE)
                return Color.RED;
            if (color==Color.RED)
                return Color.GREEN;
            else return Color.BLUE;
        }

        @Override
        public void paintComponent(Graphics g){
            g.setColor(color);
            g.fillRect(0, 0, 100, 100);
        }
    }

    ColorPanelsWindow(){
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        setLayout(new GridLayout(1,0));
        add(new ColorPanel());
        add(new ColorPanel());
        //the size must be set so that the window is too small
        // and the two ColorPanels are overlapping
        setSize(40, 40);
//      setSize(300, 200);

        setVisible(true);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable(){

            @Override
            public void run() {
                new ColorPanelsWindow();
            }

        });
    }
}

所以我的问题是,这到底是怎么回事?

只有在调整大小时才会发生这种情况。 - Mordechai
2个回答

4

我不确定你问题的原因,因为我无法复现你的代码出现的错误,但是你的paintComponent方法重写没有调用父类的paintComponent方法。加上这个调用看看会发生什么。

    @Override  // method should be protected, not public
    protected void paintComponent(Graphics g) { 

        // ******* add
        super.paintComponent(g);

        g.setColor(color);
        g.fillRect(0, 0, 100, 100);
    }

请注意,如果这是我的程序,并且这是我想要的唯一绘画行为,我将通过不覆盖paintComponent而仅仅调用setBackground(color);来简化我的程序。 我还将创建一个颜色数组或列表并对其进行迭代:
public static final Color[] COLORS = {Color.RED, Color.Blue, Color.Green};
private int colorIndex = 0;

// .....

@Override
public void mousePressed(MouseEvent mEvt) {
   colorIndex++;
   colorIndex %= COLORS.length;
   color = COLORS[colorIndex];
   setBackground(color);
}

我会重写getPreferredSize方法。
例如:
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

import javax.swing.*;

@SuppressWarnings("serial")
public class MyColorsPanelDemo extends JPanel {
   private static final int GAP = 20;

   public MyColorsPanelDemo() {
      setLayout(new GridLayout(1, 0, GAP, GAP));
      setBorder(BorderFactory.createEmptyBorder(GAP, GAP, GAP, GAP));
      add(new ColorPanel());
      add(new ColorPanel());
   }

   private static class ColorPanel extends JPanel {
      public static final Color[] COLORS = {Color.red, Color.green, Color.blue};
      private static final int PREF_W = 100;
      private static final int PREF_H = PREF_W;
      private static final Color INIT_BACKGROUND = Color.black;
      private int colorIndex = 0;

      public ColorPanel() {
         setBackground(INIT_BACKGROUND);
         addMouseListener(new MouseAdapter() {
            @Override
            public void mousePressed(MouseEvent mEvt) {
               setBackground(COLORS[colorIndex]);
               colorIndex++;
               colorIndex %= COLORS.length;
            }
         });
      }

      @Override
      public Dimension getPreferredSize() {
         return new Dimension(PREF_W, PREF_H);
      }
   }

   private static void createAndShowGui() {
      MyColorsPanelDemo mainPanel = new MyColorsPanelDemo();

      JFrame frame = new JFrame("MyColorsPanelDemo");
      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      frame.getContentPane().add(mainPanel);
      frame.pack();
      frame.setLocationByPlatform(true);
      frame.setVisible(true);
   }

   public static void main(String[] args) {
      SwingUtilities.invokeLater(new Runnable() {
         public void run() {
            createAndShowGui();
         }
      });
   }
}

在编程中,正确的绘制技巧非常重要(覆盖getPreferredSize()并调用super.paintComponent())。 - camickr
谢谢您的尝试和建议。这段代码不是我用过的东西,而只是我拥有的最小的能够以显而易见的方式出现错误的代码。使用合理的大小、getPreferredSize()和流式布局等可以防止错误出现。super.paintComponent(g)不幸地没有改变任何东西。 - mallardz

4
我希望知道发生了什么事情,我在使用Windows 7上的JDK7u60时遇到了相同的问题。这对我来说肯定是一个错误。我的最佳猜测是双缓冲存在问题。我在paintComponent()方法中添加了一些调试代码。
1)当您单击右侧组件时,只会调用其paintComponent()方法,并且面板将以正确的颜色绘制。
2)当您单击左侧组件时,只会调用其paintComponent()方法,并且面板将以正确的颜色绘制,但是右侧面板会重新变为黑色,而不会在右侧面板上调用paintComonent()方法。这使我相信某种旧缓冲正在使用(这就是错误所在,我不知道如何修复它)。
getParent().repaint()之所以有效,是因为这强制重绘两个组件,无论单击哪个面板都会起作用。

这很有道理。在我的原始代码中,我有一些随机事件、骰子和其他东西,曾经认为可能是并发问题,但缓冲区错误更好地解释了它。我会等一两天看看是否有人能够提出确切的解决方案,否则我会将其标记为已接受。 - mallardz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接