神秘(并发/组件绘图?)非常简单的Swing骰子程序中的错误

mal*_*rdz 6 java concurrency swing drawing

对于这个糟糕的问题标题感到抱歉,我对这个错误的原因感到困惑,并且不知道该怎么说这个问题.

我正在学习基本的Swing,并从在线书籍Java编程入门中学习这个练习.

我没有按照信中的说明进行操作,而是尝试这样做:

  • 有一个窗口,显示两个骰子的视觉表示
  • 当你点击其中一个骰子时,它'滚动'并显示新值

我的实施:

  • 一个非常基本的JDie对象,它扩展了JPanel
  • 重写paintComponent绘制模具表示的方法
  • 每次改变模具颜色只是为了一个视觉线索
  • 添加了一个监听器,在按下鼠标时"滚动"骰子,然后
    重新绘制

这个bug非常具体:

  1. 运行DieTest主方法
  2. 调整窗口大小以适合两个骰子
  3. 点击第二个模具进行滚动
  4. 现在点击第一个模具进行滚动
  5. 第二个骰子的值会变回其原始值
  6. 如果你调整窗口大小,第二个骰子的值也会改变

如果我点击骰子滚动它们,我调整窗口大小之前,错误将不会出现......

我想我在某个地方犯了一个基本的错误,这个错误只是伪装成这种奇怪的行为.

我试图尽可能地减少代码,只需要花费很长时间就可以解决bug出现时的问题,以及什么时候没有:

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

class JDie extends JPanel {

    private Color color;
    private int value;

    JDie(){

        value = getValue();
        color = Color.BLACK;

        //add listener
        addMouseListener(new MouseAdapter(){
            @Override
            public void mousePressed(MouseEvent arg0) {
                value = getValue(); //'roll' the die
                repaint();
            }
        });
    }

    /*private helper methods */
    private int getValue(){
        int v =(int)(Math.random()*6) + 1;
        //change color just to show that the
        //value has changed
        color = getRandomColor();
        return v;
    }
    private Color getRandomColor(){
        float r = (float)Math.random();
        float g = (float)Math.random();
        float b = (float)Math.random();
        return new Color(r, g, b);
    }

    //draws the pips for the die
    @Override
    public void paintComponent(Graphics g){
        super.paintComponent(g);
        g.setColor(color);


        //draw pips
        //set pip size
        int pip_side = 10;
        switch(value){
        case 1:
            g.fillRect(3*pip_side, 3*pip_side, pip_side, pip_side);
            break;
        case 2:
            g.fillRect(5*pip_side, pip_side, pip_side, pip_side);
            g.fillRect(pip_side, 5*pip_side, pip_side, pip_side);
            break;
        case 3:
            g.fillRect(5*pip_side, pip_side, pip_side, pip_side);
            g.fillRect(pip_side, 5*pip_side, pip_side, pip_side);
            g.fillRect(3*pip_side, 3*pip_side, pip_side, pip_side);
            break;
        case 4:
            g.fillRect(pip_side, pip_side, pip_side, pip_side);
            g.fillRect(5*pip_side, 5*pip_side, pip_side, pip_side);
            g.fillRect(5*pip_side, pip_side, pip_side, pip_side);
            g.fillRect(pip_side, 5*pip_side, pip_side, pip_side);
            break;
        case 5:
            g.fillRect(pip_side, pip_side, pip_side, pip_side);
            g.fillRect(5*pip_side, 5*pip_side, pip_side, pip_side);
            g.fillRect(5*pip_side, pip_side, pip_side, pip_side);
            g.fillRect(pip_side, 5*pip_side, pip_side, pip_side);
            g.fillRect(3*pip_side, 3*pip_side, pip_side, pip_side);
            break;
        case 6:
            g.fillRect(pip_side, pip_side, pip_side, pip_side);
            g.fillRect(5*pip_side, 5*pip_side, pip_side, pip_side);
            g.fillRect(5*pip_side, pip_side, pip_side, pip_side);
            g.fillRect(pip_side, 5*pip_side, pip_side, pip_side);
            g.fillRect(pip_side, 3*pip_side, pip_side, pip_side);
            g.fillRect(5*pip_side, 3*pip_side, pip_side, pip_side);
            break;
        }
    }
}

public class DieTest extends JFrame{

    DieTest(){
        setLayout(new GridLayout());
        add(new JDie());
        add(new JDie());

        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        //if I set the size smaller than the JDie is displaying
        //and resize the window before 'rolling' the dice
        //then the bug appears...?!
        setSize(80, 80);
        //setting the size larger than both JDie
        //and it works fine whether you resize or not
//      setSize(200, 200);

        setVisible(true);

    }

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

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

        });
    }

}
Run Code Online (Sandbox Code Playgroud)

- - - - - - - 编辑 - - - - - - - - -

再次运行代码,我现在已经注意到它不会100%发生,但是bug仍然存在.这是一个我刚刚接受它的gif,可以更好地说明问题:

奇怪的错误

当我再次点击第一个骰子时,您可以清楚地看到原始颜色正在重绘的原始值.当我调整窗口大小时,第二个骰子的值跳回一个,到它以前的值......我真的不明白这个......

---------------编辑2 ---------------------

  • 在另一台计算机(mac)上尝试相同的代码但无法复制问题.
  • 编译并在Eclipse之外的计算机上运行代码,无法复制问题.
  • 运行Eclipse从命令行编译的代码,只遇到一次问题,今天无法复制它.
  • 从Eclipse运行代码,我仍然可以在5到10倍的时间内解决问题吗?如果它没有出现在第一个"通行证"上,它就不会显示出来.gif很好地说明了这一点.

所以看来我的电脑设置也有一些影响,细节是:

  • Windows 7 64位
  • Eclipse Kepler Service Release 1
  • Java版本7更新51
  • Java SE开发工具包7更新3(64位)

这是一个棘手的问题,因为我知道这是我的代码还是其他一些导致问题的程序.作为一个新手我怎么能知道未来的问题是由于我糟糕的编程还是别的......令人沮丧.

----------编辑3 -----------

作为对并发性方面的快速调查:我将所有实例字段设置为volatile我将所有方法(包括paintComponent)设置为synchronized我删除了Math.random()调用(虽然我读了另一个线程说这是线程安全实现)并用实例Random对象替换它

不幸的是,我仍然得到了视觉转换.

我注意到的另一件事是它现在似乎发生的次数要少得多,现在大约十分之一.我一直希望它能够得到修复,然后下一次尝试再次出现问题.在我正在处理的原始程序中,它看起来更像是三分之一(我现在完全改变了程序,所以不要手动).

--------编辑4 ---------我已经提出了一个稍微简化的版本,它不再使用任何随机值,仍然会产生视觉转换.这个代码似乎经常发生:

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class ColorPanelsWindow extends JFrame{

    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
                    //click on panel to rotate color
            addMouseListener(new MouseAdapter(){
                @Override
                public void mousePressed(MouseEvent arg0) {
                    color = rotateColor();
                    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();
        }

    });
}

}
Run Code Online (Sandbox Code Playgroud)

几点意见:

  • 首先,面板必须重叠
  • 如果我使用流布局,增加窗口的大小,或任何阻止面板最初重叠的方式,那么我似乎没有得到问题(或者它可能只是发生得更少?)

Mor*_*hai 2

是的,是的,我完全知道你在说什么。我的申请也受到了同样的影响。

尽管我很难弄清楚原因,但我 - 即使现在 - 也不确定这是否真的是我所想的;这种情况正在发生,(技术性太强,不适合在这里发布)我确实修复它,但我什至不知道它为什么起作用。

因此,不要重新绘制组件本身,而是重新绘制其容器(或其容器的容器 - 如果仍然没有修复)。

getParent().repaint();
Run Code Online (Sandbox Code Playgroud)

希望有帮助。