java label setText and setBounds clashing?

2019-01-28 10:05发布

问题:

I would like to have a JLabel changint color to a random one, while jumping to a random position, and while changing its text.

but the setText and setBounds seem to clash and i don't know why. if you comment out the setText then the setBounds will work, but they won't work together.

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

public class test2 extends JFrame { 

private static JLabel label = new JLabel("0");
private static Random gen = new Random();

public test2() {
    JPanel panel = new JPanel();
    panel.add(label);
    this.add(panel);
}

public static void move() {
    for (int i = 0; i < 10; i++) {
        int n = gen.nextInt(254)+1;
        int nn = gen.nextInt(254)+1;
        int nnn = gen.nextInt(254)+1;
        label.setText(""+i);
        //the setBounds command will not work with the setText command. why?
        label.setBounds(n*2, nn*2, 20, 20);
        label.setForeground(new Color(n, nn, nnn));
        try {
            Thread.sleep(200);
        } catch (Exception e) {}
    }
}

public static void main(String[] args) {
    test2 frame = new test2();
    frame.setVisible(true);
    frame.setSize(600, 600);
    frame.setResizable(true);
    frame.setLocationRelativeTo(null);
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);   
    move();
}
}

回答1:

setBounds shouldn't work with a non-null layout, and your problem is happening because changing the text in the JLabel stimulates a re-layout of the JPanel, and since JPanels by default use FlowLayout, the JLabel stays at the top in the middle. So if you really need this functionality, then consider setting the layout of the JPanel to null, or else use a JLayeredPane.

  public test2() {
     JPanel panel = new JPanel(null);
     panel.add(label);
     this.add(panel);
  }

Also you shouldn't be calling Thread.sleep(...) on the EDT, the Swing thread. Instead consider using a Swing Timer.

Since I disagreed with the other example, I will post an example of what I was recommending in its place, one that uses a Swing Timer:

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

@SuppressWarnings("serial")
public class Test3 extends JPanel {
    private static final int SIDE = 600;
    public static final int MAX_COUNTER = 20;
    private static final int TIMER_DELAY = 400;
    private static final int MAX_RAND = 220;
    private int counter = 0;
    private JLabel label = new JLabel(String.valueOf(counter));
    private Random gen = new Random();
    private int[] randNumb = new int[3];

    public Test3() {
        setPreferredSize(new Dimension(SIDE, SIDE));
        setLayout(null);
        add(label);

        new Timer(TIMER_DELAY, new TimerListener()).start();
    }

    private void myMove() {
        for (int i = 0; i < randNumb.length; i++) {
            randNumb[i] = gen.nextInt(MAX_RAND);
        }
        label.setText(String.valueOf(counter));
        label.setSize(label.getPreferredSize());
        label.setLocation((SIDE * randNumb[0])/MAX_RAND, (SIDE*randNumb[1])/MAX_RAND);
        label.setForeground(new Color(randNumb[0], randNumb[1], randNumb[2]));
        repaint();
    }

    private class TimerListener implements ActionListener {

        public void actionPerformed(ActionEvent e) {
            if (counter < MAX_COUNTER) {
                myMove();
                counter++;
            } else {
                ((Timer)e.getSource()).stop();
            }
        }
    }

    private static void createAndShowUI() {
        JFrame frame = new JFrame("Test3");
        frame.getContentPane().add(new Test3());
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        java.awt.EventQueue.invokeLater(new Runnable() {
            public void run() {
                createAndShowUI();
            }
        });
    }
}


回答2:

Don't know why it's happening, but here's an ugly work-around:

package com.me;

import java.awt.Color;
import java.util.Random;

import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;

public class test2 extends JFrame {

private static int i;

private static JLabel label = new JLabel("0"){
@Override
public String getText(){
    return i+"";
}
};
private static Random gen = new Random();

public test2() {
    JPanel panel = new JPanel();
    panel.add(label);
    this.add(panel);
}

public static void move() {
    for (i = 0; i < 10; i++) {
        int n = gen.nextInt(254)+1;
        int nn = gen.nextInt(254)+1;
        int nnn = gen.nextInt(254)+1;
        //the setBounds command will not work with the setText command. why?
        label.setBounds(n*2, nn*2, 20, 20);
        label.setForeground(new Color(n, nn, nnn));
        try {
            Thread.sleep(200);
        } catch (Exception e) {}
    }
}

public static void main(String[] args) {
    test2 frame = new test2();
    frame.setVisible(true);
    frame.setSize(600, 600);
    frame.setResizable(true);
    frame.setLocationRelativeTo(null);
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    move();

}

}