JLabel.setText()仅为循环中的最后一个元素设置文本

时间:2014-05-08 09:36:25

标签: java swing user-interface awt event-dispatch-thread

首先,对此啰嗦多久道歉。

我正在尝试制作一个简单的轮盘赌游戏,允许用户添加玩家,为这些玩家下注,并旋转轮盘赌轮,它表示为一个简单的JLabel,用它传递的每个数字更新它的文本。

但是,我遇到了一个我遇到很多麻烦的错误:JLabel只更新循环中 last 元素的文本。

基本上,我的解决方案是这样的:

当用户按下标有“Spin”的按钮时(假设用户已添加到游戏中),我从名为SpinWheelService的类中调用一个方法,该类是一个Observable单例,后者又调用{ {1}}方法:

notifyObservers()

这是我的public void actionPerformed(ActionEvent e) { String cmd = e.getActionCommand(); String description = null; if (ADD_PLAYER.equals(cmd)) { addDialog(); } else if (PLACE_BET.equals(cmd)) { betDialog(); } else if (SPIN.equals(cmd)) { SpinWheelService.sws.setSpinWheelService(); } else if (DISPLAY.equals(cmd)) { System.out.println("Display selected!"); } } 课程:

SpinWheelService

package model; import java.util.*; public class SpinWheelService extends Observable { public static SpinWheelService sws = new SpinWheelService(); public void setSpinWheelService() { setChanged(); notifyObservers(); } } 注册的唯一监听器是这个类,其中GameEngine是我处理内部游戏逻辑的游戏引擎,WheelCallbackImpl是一个更新View的类:

SpinWheelService

这里要注意的主要问题是我的class SpinWheelObserver implements Observer { GameEngine gameEngine; ArrayList<SimplePlayer> players; WheelCallbackImpl wheelCall; int n; public SpinWheelObserver(GameEngine engine, WheelCallbackImpl wheel, ArrayList<SimplePlayer> playerList) { players = playerList; gameEngine = engine; wheelCall = wheel; } public void update(Observable sender, Object arg) { // check if any players are present if (players.size() == 0) { System.out.println("Empty player array!"); return; } do { gameEngine.spin(40, 1, 300, 30, wheelCall); n = wheelCall.playback(); } while (n== 0); } } 方法,即:

gameEngine.spin()

方法callback.nextNumber(curNo,this):

public class GameEngineImpl implements GameEngine {

private List<Player> playerList = new ArrayList<Player>();

// method handles the slowing down of the roulette wheel, printing numbers at an incremental delay
public void delay(int millis) {
    try {
        Thread.sleep(millis);
    } catch (InterruptedException e) {
        System.out.println("Sleep method failed.");
    }
}

public void spin(int wheelSize, int initialDelay, int finalDelay,
    int delayIncrement, WheelCallback callback) {

    Random rand = new Random();
    int curNo = rand.nextInt(wheelSize) + 1;
    int finalNo = 0;

    assert (curNo >= 1);

    // while loop handles how long the wheel will spin for
    while (initialDelay <= finalDelay) {
        delay(initialDelay);
        initialDelay += delayIncrement;

        // handles rotating nature of the wheel, ensures that if it reaches wheel size, reverts to 1
        if (curNo > wheelSize) {
            curNo = 1;
            callback.nextNumber(curNo, this);
            curNo++;
        }

        assert (curNo <= wheelSize);
        callback.nextNumber(curNo, this);
        curNo++;

        finalNo = curNo - 1;
    }

    calculateResult(finalNo);

    callback.result(finalNo, this);

}

在哪里,wcWheel是我的View的单例实例,其中包含方法setCounter():

public void nextNumber(int nextNumber, GameEngine engine) {

    String strNo = Integer.toString(nextNumber);

    assert (nextNumber >= 1);

    System.out.println(nextNumber);

    wcWheel.setCounter(strNo);
}

对不起我的解释是多么复杂,但基本上归结为 public void setCounter(String value) { label.setText(value); } 肯定被调用,但似乎只在最终数字<上调用setCounter()方法/ em>的。所以我留下的是一个空标签,在整个轮盘完成旋转之前不会显示数字。

我已经确定setText()在事件调度线程上运行,我怀疑这是一个并发问题,但我不知道如何纠正它。

我试图包含所有相关代码,但如果我遗漏了任何内容,请提及它,我也会将其发布。

我的斗智尽头,所以如果有人能提供帮助,那就太棒了。

谢谢!

3 个答案:

答案 0 :(得分:4)

while循环Thread.sleep()将阻止并重新创建或更改用户界面,直到循环结束。

相反,您希望为延迟实施javax.swing.Timer,并保留计数器以确定停止数量。您可以在How to Use Swing Timers

查看更多信息

基本构造是

Timer ( int delayInMillis, ActionListener listener )

其中delayInMillis是点燃ActionEvent之间的毫秒延迟。 listener会听取此事件。因此,每次触发事件时,都会调用侦听器的actionPerfomed。所以你可能会这样做:

Timer timer = new Timer(delay, new ActionListener()(
    @Override
    public void actionPerformed(ActionEvent e) {
        if (count == 0) {
            ((Timer)e.getSource()).stop();
        } else {
            //make a change to your label
            count--;
        }
    }
));

您可以致电timer.start()启动计时器。每隔delay毫秒,标签将更改为您需要的值,直到某个任意计数达到0,然后计时器停止。然后,您可以将计数变量设置为您需要的任何值,如果您想要随机,则可以根据车轮旋转的难度来确定:D

答案 1 :(得分:2)

我认为您没有发布确切知道问题所需的所有相关代码。

但很可能问题是由于您在EDT(事件调度线程)中运行循环和JLabel.setText()

请注意,更新UI组件(例如JLabel的文本)也会在EDT中发生,因此当您的循环在EDT中运行时,文本将不会更新,只有在您的循环结束后才会更新从您的事件监听器返回。然后,由于您修改了JLabel的文本,它将被刷新/重新绘制,您将看到您设置的最后一个值。

演示此内容的示例。在以下示例中,事件侦听器中的循环从0到9循环并设置标签的文本,但您只能看到最后的9个设置:< / p>

JPanel p = new JPanel();
final JLabel l = new JLabel("-1");
p.add(l);
JButton b = new JButton("Loop");
p.add(b);

b.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        for ( int i = 0; i < 10; i++ ) {
            l.setText( "" + i );
            try { Thread.sleep( 200 ); } catch ( InterruptedException e1 ) {}
        }
    }
} );

建议的解决方案:使用javax.swing.Timer执行循环工作。 Swing的计时器在EDT中调用其监听器,因此可以安全地更新其中的swing组件,一旦监听器返回,组件UI更新可以立即发生:

JPanel p = new JPanel();
final JLabel l = new JLabel("-1");
p.add(l);
JButton b = new JButton("Loop");
p.add(b);

b.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        new Timer(200, new ActionListener() {
            int i = 0;
            @Override
            public void actionPerformed(ActionEvent e2) {
                l.setText("" + i);
                if ( ++i == 10 )
                    ((Timer)e2.getSource()).stop();
            }
        }).start();
    }
} );

在此解决方案中,您将看到标签的文字从0到9很好地计数。

答案 2 :(得分:1)

在我看来,你的整个游戏必须在动作处理程序中阻塞,直到while循环结束?因此标签的文本将会更新,但只有在AWT线程再次运行后才能看到最后一次更新。