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

时间:2014-03-10 17:06:50

标签: java swing concurrency drawing

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

我正在学习基本的Swing并从在线书籍Java编程入门中学习this exercise

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

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

我的实施:

  • 一个非常基本的JDie对象,它扩展了JPanel
  • 重写paintComponent方法以绘制模具表示
  • 每次改变模具颜色只是为了一个视觉线索
  • 添加了一个听众,当按下鼠标然后再按“掷”骰子 重绘

该错误非常具体:

  1. 运行DieTest主方法
  2. 调整窗口大小以适应两个模具
  3. 点击第二个模具滚动
  4. 现在点击第一个掷骰子
  5. 第二个模具的值会变回其原始值
  6. 如果你调整窗口大小,第二个骰子的值也会改变回来
  7. 如果我点击骰子滚动它们,之前我调整窗口大小,则不会出现错误......

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

    我试图尽可能地减少代码,但是当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();
                }
    
            });
        }
    
    }
    

    -------------- EDIT -----------------

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

    Strange bug

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

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

    • 在另一台计算机(mac)上尝试了相同的代码,但无法复制 问题。
    • 编译并在Eclipse之外的计算机上运行代码,但不能 复制问题。
    • 运行Eclipse从命令行编译的代码,只得到了 问题曾经,今天无法复制。
    • 从Eclipse运行代码,我仍然遇到大约1英寸的问题 5-10次?如果它没有出现在第一个“通行证”上,那就是它 根本没有显示出来。 gif很好地说明了这一点。

    所以看来我的电脑设置也对此有所影响,详情如下:

    • Windows 7 64位
    • Eclipse Kepler Service Release 1
    • Java Version 7 Update 51
    • Java SE Development Kit 7 Update 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();
            }
    
        });
    }
    
    }
    

    有几点意见:

    • 表示面板必须首先重叠
    • 如果我使用流布局,增加窗口的大小,或任何方式 最初阻止面板重叠,然后我似乎没有 要解决问题(或者可能只是经常发生这种情况?)

2 个答案:

答案 0 :(得分:4)

与Múna一样,我无法重现您的发现,但我有一些观察结果:

  • 覆盖getPreferredSize()以定义初始几何体。

  • 定义强制几何关系的常量。

  • 使用符合首选尺寸的layout,例如FlowLayout要查看效果。

  • 使用Color.getHSBColor()获取各种饱和色调。

  • 根据需要使用Random的单个实例。

附录:问题的间歇性和平台可变性强烈暗示不正确的同步。在原始程序中,两个骰子共享由Random拥有的Math的单个静态实例;在下面的例子中,每个骰子都有自己的实例。另请注意,间接调整帧的大小会调用paintComponent()

image

经测试:

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

public class DieTest extends JFrame {

    DieTest() {
        setLayout(new FlowLayout());
        add(new JDie());
        add(new JDie());
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        pack();
        setVisible(true);
    }

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

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

    private static class JDie extends JPanel {

        private static final int SIDE = 32;
        private static final Random r = new Random();
        private Color color;
        private int value = getValue();
        private final Timer t = new Timer(500, null);

        JDie() {
            setBorder(BorderFactory.createEtchedBorder(color, color.darker()));
            value = getValue();
            addMouseListener(new MouseAdapter() {
                @Override
                public void mousePressed(MouseEvent arg0) {
                    value = getValue();
                    repaint();
                }
            });
            t.addActionListener(new ActionListener() {

                @Override
                public void actionPerformed(ActionEvent e) {
                    value = getValue();
                    repaint();
                }
            });
            t.start();
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(SIDE * 7, SIDE * 7);
        }

        private int getValue() {
            color = Color.getHSBColor(r.nextFloat(), 1, 1);
            return r.nextInt(6) + 1;
        }

        @Override
        public void paintComponent(Graphics g) {
            super.paintComponent(g);
            g.setColor(color);
            switch (value) {
                case 1:
                    g.fillRect(3 * SIDE, 3 * SIDE, SIDE, SIDE);
                    break;
                case 2:
                    g.fillRect(5 * SIDE, SIDE, SIDE, SIDE);
                    g.fillRect(SIDE, 5 * SIDE, SIDE, SIDE);
                    break;
                case 3:
                    g.fillRect(5 * SIDE, SIDE, SIDE, SIDE);
                    g.fillRect(SIDE, 5 * SIDE, SIDE, SIDE);
                    g.fillRect(3 * SIDE, 3 * SIDE, SIDE, SIDE);
                    break;
                case 4:
                    g.fillRect(SIDE, SIDE, SIDE, SIDE);
                    g.fillRect(5 * SIDE, 5 * SIDE, SIDE, SIDE);
                    g.fillRect(5 * SIDE, SIDE, SIDE, SIDE);
                    g.fillRect(SIDE, 5 * SIDE, SIDE, SIDE);
                    break;
                case 5:
                    g.fillRect(SIDE, SIDE, SIDE, SIDE);
                    g.fillRect(5 * SIDE, 5 * SIDE, SIDE, SIDE);
                    g.fillRect(5 * SIDE, SIDE, SIDE, SIDE);
                    g.fillRect(SIDE, 5 * SIDE, SIDE, SIDE);
                    g.fillRect(3 * SIDE, 3 * SIDE, SIDE, SIDE);
                    break;
                case 6:
                    g.fillRect(SIDE, SIDE, SIDE, SIDE);
                    g.fillRect(5 * SIDE, 5 * SIDE, SIDE, SIDE);
                    g.fillRect(5 * SIDE, SIDE, SIDE, SIDE);
                    g.fillRect(SIDE, 5 * SIDE, SIDE, SIDE);
                    g.fillRect(SIDE, 3 * SIDE, SIDE, SIDE);
                    g.fillRect(5 * SIDE, 3 * SIDE, SIDE, SIDE);
                    break;
            }
        }
    }
}

答案 1 :(得分:2)

是的,是的,我确切地知道你在谈论什么。我的申请受到了同样的影响。

虽然我很难弄清楚原因,但我 - 现在 - 不确定这是不是我的想法;那发生了, (太技术性,不适合在这里发布所有内容)我做了修复它,我甚至不知道它为什么会起作用。

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

getParent().repaint();

希望有所帮助。