抱歉标题不够清晰,我被这个错误困扰了,不知道如何表达问题.
我正在学习基本的Swing并完成在线书籍《使用Java进行编程入门》中的练习.
我没有严格按照说明操作,而是尝试做到以下几点:
- 显示两个骰子的图像窗口
- 当单击其中一个骰子时,它会“滚动”并显示新值
我的实现方法:
- 一个非常基本的JDie对象,扩展JPanel
- 重写paintComponent方法以绘制骰子表示
- 每次更改值时更改骰子颜色,只是为了视觉提示
- 添加一个侦听器在鼠标按下时“滚动”骰子,然后重绘
该错误非常具体:
- 运行 DieTest 的main方法
- 调整窗口大小以适应两个骰子
- 单击第二个骰子以使其滚动
- 现在单击第一个骰子以使其滚动
- 第二个骰子的值会改回其原始值
- 如果调整窗口大小,则第二个骰子的值也会改回原始值
如果我在调整窗口大小之前点击骰子进行滚动,则不会出现该错误...
我猜测我可能犯了一些基本错误,它以这种奇怪的行为掩盖了自己.
我已经尽可能地缩小了代码,这花费了很长时间才弄清楚错误何时出现何时没有:
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演示了可能更好地说明问题:
当我再次单击第一个骰子时,可以清楚地看到原始值与原始颜色被重新绘制。当我调整窗口大小时,第二个骰子的值会向前跳一个,回到之前的值......我真的无法理解这个...
---------------EDIT 2---------------------
- 在另一台(苹果)电脑上尝试相同的代码,但无法复制出现的问题。
- 在Eclipse之外编译和运行代码,无法复制出现的问题。
- 从命令行运行Eclipse编译的代码,只有一次出现问题,今天无法复制出现的问题。
- 从Eclipse运行代码,我仍然会遇到这个问题,大约每5-10次就会出现一次?如果它在第一个“通过”中没有显示,它根本不会出现。这个gif很好说明了这个问题。
因此,似乎我的计算机设置也与此有关,详细信息如下:
- Windows 7 64位
- Eclipse Kepler Service Release 1
- Java Version 7 Update 51
- Java SE Development Kit 7 Update 3(64位)
这是一个棘手的问题,因为我不再知道是我的代码还是其他程序导致了问题。作为新手,我如何知道任何未来的问题是由于我的糟糕编程还是其他原因...令人沮丧。
----------EDIT 3-----------
为了快速调查并发方面的问题: 我将所有实例字段设置为volatile 我将包括paintComponent在内的所有方法设置为synchronized 我删除了Math.random()调用(尽管我读到另一个线程说这是线程安全的实现),并使用实例Random对象进行了替换
不幸的是,我仍然遇到视觉切换回来的情况。
另一件我注意到的事情是,现在它似乎不那么频繁地出现,大约每10次中只有1次。我一直对它已经被修复抱有希望,但接下来的尝试依旧有bug。在我最初的程序中,它似乎更像是3分之1的概率(我现在完全改变了那个程序,所以手头没有它)。
--------EDIT 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();
}
});
}
}
以下是一些观察:
- 据我所知,面板必须一开始重叠
- 如果我使用流式布局、增加窗口大小或任何使面板最初不重叠的方法,似乎就不会出现这个问题(或者可能发生得更少?)