I'm just starting to learn about programming in Java and even more recently with graphics, and I've run into some problems.
I want to create a game like dwarf fortress in look, with colored text instead of images.
This is what I have so far:
public void paint(Graphics g) {
super.paint(g);
g.setColor(Color.red);
for (int i = 0; i < 50; i++) {
g.drawString("[Game goes here]", 100, 150);
g.dispose();
System.out.println(i);
}
g.dispose();
}
public GTest() {
setSize(Toolkit.getDefaultToolkit().getScreenSize().width / 3, Toolkit
.getDefaultToolkit().getScreenSize().width / 3 + 50);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setVisible(true);
}
public static void main(String[] args) {
GTest myWindow = new GTest();
}
I want it to update the graphics on a timer, but I'm not really sure how to do this.
I know that this is a pretty broad question and I would be happy to clarify anything you might want to know.
EDIT:
So I added this bit:
String[] letters = new String[10];
float fontsize = Toolkit.getDefaultToolkit().getScreenSize().width / 30;
public void paint(Graphics g) {
g.setColor(textColor);
setFont(getFont().deriveFont(fontsize));
for(int i = 0; i < 10; i++){
if((int) (Math.random() * 100) > 97){
letters[i] = "w";
textColor = new Color(0, 0, 100);
}else{
letters[i] = "l";
textColor = new Color(0, 100, 0);
}
g.drawString(letters[i], i * 3, 10);
}
But now it doesn't display at all. I added a sysout after the g.drawString and it performed it so I'm not sure what the problem is.
When doing anything based on a timer in Swing, the javax.swing.Timer class is your best friend.
It's JavaDocs explains its usage pretty well, so here's an example.
public class GTest extends JFrame implements ActionListener {
private Color textColor = Color.BLACK;
private Random random = new Random();
#Override
public void paint(Graphics g) {
super.paint(g);
g.setColor(textColor);
g.drawString("[Game goes here]", 100, 150);
}
#Override
public void actionPerformed(ActionEvent e) {
textColor = new Color(random.nextInt(0x00ffffff));
repaint();
}
public GTest() {
setSize(Toolkit.getDefaultToolkit().getScreenSize().width / 3,
Toolkit.getDefaultToolkit().getScreenSize().width / 3 + 50);
setDefaultCloseOperation(EXIT_ON_CLOSE);
Timer timer = new Timer(500, this);
timer.setInitialDelay(0);
timer.start();
setVisible(true);
}
public static void main(String[] args) {
GTest myWindow = new GTest();
}
}
It's your GTest class once again, but this time the text color changes automatically every 0.5 seconds.
Notice two main changes:
The constructor now sets up a new instance of Timer with no initial delay, period of 500 ms and its listener set up to this. That means that this constructed instance will listen to that timer's ticks.
To do that, we needed to implement the ActionListener interface. This forces us to write the actionPerformed() method which gets called every time the Timer ticks. In this method, we change the textcolor to a random one and call repaint() which subsequently calls our paint() method and gets the text written.
P.S. You don't have to dispose() your Graphics object every time. It's actually said in its JavaDoc, too (emphasize mine):
Graphics objects which are provided as arguments to the paint() and
update() methods of components are automatically released by the system
when those methods return. For efficiency, programmers should call
dispose() when finished using a Graphics object only if it was created
directly from a component or another Graphics object.
First of all, if you don't create a copy of the Graphics context, you shouldn't be disposing of it. This could actually prevent other parts of your application form been painted ;)
Second of all. I would avoid extending from a top level container, like JFrame, apart from allowing you to draw under the frame/border decoration of the window, they aren't double buffered.
Instead, I'd use something like a JPanel instead and override its paintComponent method. This will provide you with automatic double buffering.
Because Swing is a single threaded framework, it is expected that all updates to the UI be executed within the context of the Event Diapatching Thread.
This makes live a little difficult when it comes to try to do things like waiting and synchronizing paint updates.
Luckly, Swing provides a javax.swing.Timer which allows you schedule an event for some time in the future. This can also be setup to repeat and regular intervals.
Timer timer = new Timer(40, new ActionListener() {
public void actionPerformed(ActionEvent e) {
// perform your required actions here
}
});
Now, beware, the actionPerformed method is being executed with the context of the EDT, this means, that any long running/time consuming processing you do here may cause you UI to stop painting
Take a look at
Performing Custom Painting
Intial Threads
Concurrency in Swing
For more details...
Related
I am trying to make a custom loading screen in Java (not using the JProgressBar). Right now I have just tried making a loading bar that slowly fills the screen left to right with a black rectangle. To test this, I made a simple program that factors random numbers. I run the factoring code on a separate thread and I have the graphics get the progress of the factoring once every 100 milliseconds. However, sometimes the application becomes not responsive for like 10 seconds, then continues to run. How can I properly implement a custom loading screen in Java or what is wrong with the code I am using now?
Here is the code:
public class Factorer implements Runnable, Loadable {
private static Random r = new Random();
private volatile int numOn;
private volatile int numsTotal;
public static void main(String[] args){
Factorer fact = new Factorer(25);
LoadingFrame f = new LoadingFrame(fact);
fact.run();
}
public Factorer(int nNums){
numsTotal = nNums;
}
public void factorRandomNumber(){
int randomNumber = r.nextInt(Integer.MAX_VALUE);
int n = randomNumber;
List<Integer> factors = new ArrayList<Integer>();
for (int i = 2; i <= n; i++) {
while (n % i == 0) {
factors.add(i);
n /= i;
}
}
}
public void factorRandomNumbers(){
for(numOn = 0; numOn < numsTotal; numOn++){
factorRandomNumber();
}
}
public float getProgress(){
return ((float) numOn) / ((float) numsTotal);
}
#Override
public void run() {
factorRandomNumbers();
}
}
Here is the loadable interface:
public interface Loadable {
public float getProgress();
}
Here is the graphics class:
public class LoadingFrame extends JFrame {
private static final long serialVersionUID = 8290713730328501888L;
private static final int WIDTH = 400;
private static final int HEIGHT = 400;
private Loadable loadable;
public LoadingFrame(Loadable l){
super("Loading Test");
this.loadable = l;
this.setSize(WIDTH, HEIGHT);
this.setResizable(false);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setVisible(true);
Timer t = new Timer(100, new ActionListener(){
#Override
public void actionPerformed(ActionEvent e) {
repaint();
}
});
t.start();
}
#Override
public void paint(Graphics g){
g.setColor(Color.BLACK);
g.fillRect(0, 0, (int) (loadable.getProgress() * (float) WIDTH), HEIGHT);
}
}
I run the factoring code on a separate thread
Well, yes and no. Your Factorer class implements runnable, but you never treat the class as a separate Thread.
Instead you just use:
fact.run();
If you are trying to use this class as a reusable separate Thread then somewhere in your code you would have:
new Thread( new Factorer() ).start();
to actually start the Thread.
However, because the JVM starts in a separate Thread from the Event Dispatch Thread (EDT) you code is running on a separate Thread but more by chance than design.
Also the logic in your Factorer class won't work because when you invoke the getProgress() method you will always get the same value returned. That is the factoroRandomNumbers() number is executed once at the start of the programs and the numOn and numsTotal will then be initialized to their final values.
public void paint(Graphics g){
You should NOT be overriding paint(). Custom painting is done by overriding paintComponent(...) on a JPanel and then you add the panel to the frame.
Your Timer logic is incorrect. All you do is invoke repaint(), but as I stated earlier the returned value will never change. What you really need to do is invoke a method like getNextValue(). This method would get a value from your List containing the random numbers.
To better understand how Swing works you need to read the Swing Tutorial. You may want to start with section on:
Concurrency in Swing
Performing Custom Painting
It would probably be easier to use a SwingWorker (as discussed in the Concurrency in Swing section). This will create a separate Thread for you. Then you have looping code that generates a random value and "publishes" this value. When this value is published you then repaint the frame with the value.
I have a class that creates a JFrame on which a simple game of Tetris will be played, I also have a class DrawSquare, which does exactly what you think it does, however when I initialise a new instance of the DrawSquare class and then try to draw that one and all the others to my JFrame things start to go wrong, the code is intended for one square to be drawn in the top left hand corner and then drop down a line at a time until it reaches the bottom of the frame (it does this), then a new square should be drawn in the second column at the top of the frame, as well as our first square in the bottom left hand corner, however once it starts dropping down the second column I get a series of squares drawn in a diagonal towards the top right hand corner. At the moment all I plan for the code to do is have a square drop from the top row of each column and stop when it reaches the bottom of the frame, am I storing the instance of the class at the wrong point in the code? Edit: In fact I'm pretty sure it's that, I'd want to store that instance when it reaches the bottom. Does every instance of the class need its own timer?
public class Tetris extends JFrame {
public static final int height = 20; //height of a square
public static final int width = 20; //width of a square
public int xPos = 0; //column number of the square
public int yPos = 0; //row number of the square
public static void main(String[] args){
Tetris tet = new Tetris();
}
public Tetris() {
DrawSquare square = new DrawSquare(xPos, yPos, width, height, false);
add(square);
DrawSquare.squares.add(square);
setSize(220,440);
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setVisible(true);
}
}
public class DrawSquare extends JPanel {
public static List<DrawSquare> squares = new ArrayList<>();
protected int xPos;
protected int yPos;
protected int width;
protected int height;
protected Timer timer = new Timer(200, new TimerListener());
protected boolean endFall = false;
public DrawSquare(int xPos, int yPos, int width, int height, boolean endFall) {
this.xPos = xPos;
this.yPos = yPos;
this.width = width;
this.height = height;
this.endFall = endFall;
this.timer.start();
}
class TimerListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
yPos++;
if (yPos > 19) {
yPos = 19;
endFall = true;
}
if (endFall == true) {
timer.stop();
if (xPos > 8) {
xPos = 8;
}
xPos++;
endFall = false;
yPos = 0;
DrawSquare newSqr = new DrawSquare(xPos, yPos, width, height, true);
squares.add(newSqr);
add(newSqr);
}
timer.start();
repaint();
}
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Iterator<DrawSquare> it = squares.iterator();
while (it.hasNext()) {
DrawSquare square = it.next();
g.fillRect(square.xPos * square.width, square.yPos * square.height, square.width, square.height);
}
}
}
You are giving a great example of the fundamental misunderstanding beginners have of how the swing (and many other graphics toolkits) render stuff to the screen. I will give an overview of that, as it pertains to you, then answer your immediate questions and explain how to fix your code.
It took me a (very long) while to figure out how this stuff works my self, so please bear with me. I hope that reading through this answer will help you in a much more general way than answering this one question.
Asynchronous Drawing
Swing draws windows in a totally different sequence (the event dispatching thread) than the ones that modifies the state of your program (the main thread, as well as timer and other threads). You can modify the coordinates of things you want to draw as many times as you like in the main thread, but the changes will not show up until you request them to by calling JComponent.repaint() on one of your components. This will generally trigger a nearly-immediate repaint of the component, displaying your latest state.
If you change the coordinates of a widget like a JPanel in your main thread, it will likely show up immediately. This is because the methods you use to set the position will trigger repaint requests internally.
A repaint request gets queued and eventually processed by the event dispatching thread. This is where the paintComponent method gets called. The paintComponent method should therefore only draw. It should not do any other logic. If it needs to know how to draw some specialized stuff, the information for that should be stashed somewhere accessible by one of the other threads.
In short, you make calculations and update state as you need in the main thread or the timer. Then you access that state in the event dispatching thread via the paintComponent method.
Timers
There are a bunch of ways you can use timers to run your GUI, but you only really need one for the current application. In your case, the timer only needs to do two things:
Check if a block has fallen all the way down and doesn't need to move any more.
Trigger a repaint of your panel.
You do not need to compute the updated position of the blocks in the timer if the block's position is a simple equation with respect to time. If you know the time at which a block appears on the screen and the current time, you know how far the block has moved, so you can paint it in the correct spot based purely on the elapsed time.
If you had a more complicated system with paths that you could not predict purely on the time, I would recommend sticking the movement logic into the timer events as well. In that case, you might consider having multiple timers, or switching to java.util.timer. But again, this does not apply to your current case (even with multiple blocks).
Model and View
The model of your program is the thing that holds the abstract state. In this case, the positions and other meta-data about all your blocks. The view is the part that does the rendering. It is usually a good idea to separate these two things. There is often a third component to GUIs, called the controller, which connects the model and view to the user. We will ignore it here since you are not asking about controlling the blocks yet.
In your current code, you have attempted to represent your blocks with an extension to JPanel and a static list of existing blocks. While a JPanel may be a convenient way to display rectangular blocks with some custom graphics in them (like icons), I would recommend that you start by drawing the blocks directly using the Graphics object passed to paintComponent. At least initially, it will help you to think of the drawing code and the game logic as separate entities.
Final Rant Before Code Dump
I have made rewrites to your code to encapsulate all the ranting I did before into code. Here are some additional minor points about what I did that may help explain my reasoning:
When you call JFrame.add(...) to add a component to a JFrame, you are really calling JFrame.getContentPane().add(...). The content pane is where 90% of normal swing components go in a window. Therefore, we can either set the JPanel that will do the rendering as your content pane or we can add it to the current content pane. I have chosen to do the latter so that you can add other widgets, like a score board, at a later time.
Class names should generally be nouns, while methods are often verbs. This is not an absolute rule (nothing really is), but naming things this way will often help you visualize the interactions between objects in a more meaningful way. I have renamed DrawSquare to GamePiece for this reason.
There is no longer any reason for GamePiece to be a JPanel. It just needs to know its own width, height, and time of appearance.
The other problem with trying to have DrawSquare draw itself is that a component can only really draw within its own bounding box. So you really want to override the paintComponent of whatever holds the rectangles.
The rendering class maintains a reference to two lists of GamePieces. One is for the moving objects and one is for the ones that have fallen. The logic for moving them between the lists is in the timer. This is better than say adding a flag to GamePiece because it facilitates incremental repaint. I will only partially illustrate this here, but there is a version of repaint that only requests a small region to be painted. This would be useful to speed up the movement.
Code
public class Tetris extends JFrame
{
public static final int height = 20; //height of a square
public static final int width = 20; //width of a square
public static final int x = 0;
private GamePanel gamePanel;
public static void main(String[] args)
{
Tetris tet = new Tetris();
// Normally you would tie this to a button or some other user-triggered action.
tet.gamePanel.start();
tet.gamePanel.addPiece(new GamePiece(width, height, x));
}
public Tetris()
{
getContentPane().setLayout(new BorderLayout());
gamePanel = GamePanel();
add(gamePanel, BorderLayout.CENTER);
setSize(220,440);
setLocationRelativeTo(null);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setVisible(true);
}
}
public class GamePanel extends JPanel
{
private List<GamePiece> moving;
private List<GamePiece> still;
private Timer timer;
public GamePanel()
{
moving = new ArrayList<>();
still = new ArrayList<>();
timer = new Timer(100, new TimerListener());
}
public addPiece(int width, int height, int x)
{
moving.add(new GamePiece(width, height, x));
}
public void start()
{
timer.start();
}
#Override
public void paintComponent(Graphics g)
{
Rectangle clip = g.getClipBounds(null);
Rectangle rectToDraw = new Rectangle();
// I prefer this, but you can make the call every
// time you call `GamePiece.getY()`
long time = System.currentTimeMillis();
for(GamePiece piece : this.moving) {
rectToDraw.setSize(piece.width, piece.height)
rectToDraw.setLocation(piece.x, piece.getY(time))
if(rectangleToDraw.intersects(clip))
((Graphics2D)g).fill(rectToDraw)
}
for(GamePiece piece : this.still) {
rectToDraw.setSize(piece.width, piece.height)
rectToDraw.setLocation(piece.x, piece.getY(time))
if(rectangleToDraw.intersects(clip))
((Graphics2D)g).fill(rectToDraw)
}
}
private class TimerListener implements ActionListener
{
#Override
public void actionPerformed(ActionEvent e)
{
long time = System.currentTimeMillis();
// Using non-iterator loop to move the pieces that
// stopped safely. Iterator would crash on in-loop move.
for(int i = 0; i < moving.size(); i++) {
piece = moving.get(i);
if(piece.getY(time) > 440 - piece.height) {
moving.remove(i);
still.add(piece);
i--;
}
}
repaint();
}
}
}
public class GamePiece
{
public final int width;
public final int height;
public final long startTime;
public int x;
public GamePiece(int width, int height, int x)
{
this.width = width;
this.height = height;
this.startTime = System.currentTimeMillis();
this.x = x;
}
public int getY(long time)
{
// This hard-codes a velocity of 10px/sec. You could
// implement a more complex relationship with time here.
return (int)((time - this.startTime) / 100.0);
}
}
Your main problem in a nutshell: you need to separate the JPanel component class from the square logical class. Right now, they are one and the same, and every time you create a new DrawSqaure, you're creating a new JPanel, starting a new Swing Timer, and thus calling code that doesn't need to be called. This is also forcing you to make the List static else you'd have a stack overflow error. Solution: separate the two out, make your List non-static, and use only one Swing Timer.
I am writing the Sugarscape simulation in Java and need a working GUI. Sugarscape is a spatial landscape consisting of tiles (of sugar), and agents moving and consuming sugar. For simplicity, I have only one agent and no sugar- I just want to see the agent moving.
For the past 2 weeks I have read into painting in java, concurrency in java, concurrency in swing, I have read filthy rich clients and countless StackOverflow threads, but I must resort to asking a question here.
I need my model separate from the GUI. This presents a problem since 99% of tutorials suggest to call for repaint within other methods. My idea was to run one "tick" of the simulation: all agents move, and then send an Event (my GUI class extends Observer) which then triggers a repaint(); request and update the GUI. However the problem (the misunderstanding) lies with the SwingUtilities.InvokeLater method. My code is:
public void setupGUI()
{
SwingUtilities.invokeLater(new Runnable()
{
public void run() {
System.out.println("GUI is being setup, on EDT now? " + SwingUtilities.isEventDispatchThread());
SugarFrame frame = new SugarFrame(simulation.getWorld());
frame.setVisible(true);
}
});
}
For understanding what is happening I have inserted println's everywhere. The order of events is what confuses me:
Console output:
1.Agent created. Starting Position: X= 19 Y= 46 // This is in the Agent constructor
2.Simulation start. Experiment number: 0
GUI is being setup, on EDT now? true // As you see above, this is WITHIN the SwingUtilities.InvokeLater section. But then the EDT pauses and the real model continues:
Tick number 0
Invoke Agent Actions, fire TickStart Event
TickStartEvent created
Invoke Agent Actions, for-loop starting now
Agent number 0 moving now:
Consuming Sugar now.
Moving now.
Sleeping now.
The Sugarframe has been created and Grid added. All on EDT? true // And there it is back again. The paint component follows and the window with the Agent visible appears.
paintComponent called, on EDT? true
Now, I have read that by putting the main thread to sleep, you give the EDT time to run the repaint. However, this only happens once. Repaint is never called again, and I only ever see one iteration of the model.
I simply do not understand what piece of information I am missing to work with the EDT properly. Swingworker and Swingtimer are suggested regularly, but for every suggestion there is a notion that they are not needed for a model such as mine. Either paintComponent is not called at all, or queued up until the end (and then still not repainting, even if I use thread.sleep).
I'd appreciate any help. Apologies for the long post.
//Edit: as per request some more code.
The entire main method:
public class SimulationController {
static Simulation simulation;
public static final int NUM_EXPERIMENTS = 1;
public SimulationController()
{
Random prng = new Random();
SimulationController.simulation = new Simulation(prng);
}
public void run() {
setupGUI();
for(int i=0; i<NUM_EXPERIMENTS; i++) {
System.out.println("Simulation start. Experiment number: " + i);
simulation.getWorld().addObserver(simulation);
simulation.addObserver(simulation.getWorld());
simulation.run();
}
}
public void setupGUI()
{
SwingUtilities.invokeLater(new Runnable()
{
public void run() {
System.out.println("GUI is being setup, on EDT now? " + SwingUtilities.isEventDispatchThread());
SugarFrame frame = new SugarFrame(simulation.getWorld());
frame.setVisible(true);
}
});
}
public static void main(String[] args) {
SimulationController controller = new SimulationController();
controller.run();
}
}
The paint override in my JPanel class:
#Override
public void paintComponent(Graphics g) {
System.out.println(">>>>>>>>paintComponent called, on EDT? " + SwingUtilities.isEventDispatchThread()+"<<<<<<<<<<");
super.paintComponent(g);
//g.clearRect(0, 0, getWidth(), getHeight());
rectWidth = getWidth() / world.getSizeX();
rectHeight = getHeight() / world.getSizeY();
for (int i = 0; i < world.getSizeX(); i++)
{
for (int j = 0; j < world.getSizeY(); j++)
{
// Upper left corner of this terrain rect
x = i * rectWidth;
y = j * rectHeight;
Tile tile = world.getTile(new Position(i, j));
if (tile.hasAgent())
{
g.setColor(Color.red);
} else
{
g.setColor(Color.black);
}
g.fillRect(x, y, rectWidth, rectHeight);
}
}
}
JPanel class again, update methods:
public void update(Observable o, Object arg)
{
if (arg instanceof TickEnd)
{
TickEvent tickEndevent = new TickEvent();
this.addTickEvent(tickEndevent);
}
}
}
private final BlockingQueue<TickEvent> TICK_EVENTS = new LinkedBlockingQueue<TickEvent>();
/**Runnable object that updates the GUI (I think)**/
private final Runnable processEventsRunnable = new Runnable()
{
public void run()
{
TickEvent event = new TickEvent();
while ((event = TICK_EVENTS.poll()) != null)
{
System.out.println("This is within processEventsRunnable, inside the While loop. Repaint is called now.");
repaint();
}
}
};
/**Add Event to the processing-Events-queue**/
public void addTickEvent(TickEvent event)
{
//System.out.println("This is in the Add TickEvent method, but before the adding. "+TICK_EVENTS.toString());
TICK_EVENTS.add(event);
System.out.println("TickEvent has been added! "+TICK_EVENTS.toString() + "On EDT?" + SwingUtilities.isEventDispatchThread());
if (TICK_EVENTS.size() >= 1)
{
SwingUtilities.invokeLater(processEventsRunnable);
}
}
And last but not least, the JFrame constructor:
/** Sugarframe Constructor**/
public SugarFrame(World world)
{
super("Sugarscape"); // creates frame, the constructor uses a string argument for the frame title
grid = new Grid(world); // variable is declared in the class
add(grid);
setDefaultCloseOperation(EXIT_ON_CLOSE); // specifies what happens when user closes the frame. exit_on_close means the program will stop
this.setContentPane(grid);
this.getContentPane().setPreferredSize(new Dimension(500, 500));
this.pack(); // resizes frame to its content sizes (rather than fixed height/width)
System.out.println("The Sugarframe has been created and Grid added. All on EDT? "+ SwingUtilities.isEventDispatchThread());
this.setVisible(true); // makes the Frame appear on screen
}
The sentences,
I need my model separate from the GUI. This presents a problem since 99% of tutorials suggest to call for repaint within other methods.
and
Now, I have read that by putting the main thread to sleep, you give the EDT time to run the repaint.
don't sound quite right to me, so I'll try to clear things up a bit and maybe If you reevaluate the fundamental ideas you had behind those statements you can find the piece of information that you were missing.
First of all, always keep in mind this scheduling model that we were talking about. You can not say "EDT do this for me now!". It is always "EDT here's one more task you need to do, do it when you are done with whatever you are doing". So the EDT has a queue of "tasks" to do and goes through it consuming one by one.
These tasks are usually created by events: pressing a button gives the EDT a task to do, when the state of a component of the GUI changes some listeners may be notified and enqueue some work in the EDT. However, you can also straight up say "EDT execute this piece of code, later". This is what you do with invokeLater, you schedule a work to do in the EDT whenever it's free. Even if you call invokeLater from the EDT the task is scheduled, not executed at the moment.
The same happens with invokeAndWait yes, the code is executed sequentially as if it was executed at the moment, but it is still an scheduled work. So repaint() is no exception to this. repaint() doesn't repaint the GUI, but rather schedules the repainting of the GUI.
However repaint() is exceptional in the sense that it can be called from outside the EDT! This is not surprising now that we know that the only thing that does is scheduling a certain work, it does not actually mess with the GUI so you can call it wherever you want.
This means that the line
SwingUtilities.invokeLater(processEventsRunnable);
where processEventsRunnable basically executes a repaint() is meaningless and the whole tick system overly complex and unnecesary. You just have to call repaint() when you change something on the GUI or on the data that the GUI feeds on so the changes are reflected on the screen.
Furthermore, if you wanted to do something that needs to be executed in the EDT (like changing the text of a Label with the score) you can just put that code in an invokeLater block in your main thread. That will queue and execute the task properly, you don't need to do your own event queue system.
Keeping all this in mind the following makes no sense:
I have read that by putting the main thread to sleep, you give the EDT time to run the repaint
The GUI will be updated on its own shortly after you call repaint(). The main doing a lot of things and calling a lot of repaints does not prevent the GUI from being updated. However, if you want to "sleep" the main so the pace of the changes is slow so the user can appreciate it on the screen, you should use a timer.
So, as long as your main is not accessing GUI values and methods, feel free to call repaint whenever you are done changing the data, periodically or not.
Edit: Also it sounds a little bit weird that you have a main thread doing things. As you read in the concurrency chapter, usually you just create the GUI in the EDT and then the application is mostly event-driven when buttons are pressed and such. If you need to do changes periodically use a timer. You can use auxiliar threads to do specific non-GUI related heavy work, like reading a file. But you don't usually have an auxiliar thread permanently active as part of the design.
The following is a very simple program that moves an square periodically. I just use a timer to change the data and call repaint(). Note that I'm using a SwingTimer (it is executed in the EDT) since I wanted to check the panel width. Otherwise I could run the code of the timer in any thread.
In your case you probably have your "map" stored independently of the GUI, so you just need to check that data to properly move the coordinates of the agent whenever you want (on keyboard press, periodically...).
It looks like this:
Full code:
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class MovingSquareTest
{
int x, y, size, step;
MyPanel panel;
Timer timer;
public static final void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable() {
public void run()
{
MovingSquareTest app = new MovingSquareTest();
app.createAndShowGUI();
app.timer.start();
}
});
}
public MovingSquareTest()
{
x = 0;
y = 150;
size = 50;
step = 50;
timer = new Timer(500, new ActionListener()
{
#Override
public void actionPerformed(ActionEvent e)
{
x += step;
if (x < 0) x = 0;
if (x + size > panel.getWidth()) x = panel.getWidth() - size;
if (x == 0 || x + size == panel.getWidth()) step *= -1;
panel.repaint();
}
});
}
public void createAndShowGUI()
{
JFrame frame = new JFrame("Dance, my square!");
panel = new MyPanel();
frame.add(panel);
frame.setSize(600, 400);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
private class MyPanel extends JPanel
{
#Override
protected void paintComponent(Graphics g)
{
super.paintComponent(g);
g.drawRect(x, y, size, size);
}
}
}
I have a quite simple animation, a text in a big font moving continuously (pixel by pixel) to the left. The text is first converted to an image, then a timer task is started which repeatedly (every 10-20 ms) decrements the x coordinate of the image by 1, and does a repaint().
This program shows a strange behavior on some systems. On my PC with a nVidia card it runs smoothly. On my Vaio notebook, on a BeagleBoneBlack and on a friend's Mac it stutters heavily. It appears to pause for a while, then jump to the left about 10 pixels, pause again and so on.
What stumps me is the fact that on these systems the animation only stutters if you don't touch the mouse. As long as you move the mouse cursor within the window, no matter how slowly, or drag the window itself around, the animation runs perfectly smooth!
Can anybody explain this? Here is the program:
import java.awt.*;
import java.awt.image.*;
import java.awt.event.*;
import javax.swing.*;
import java.io.*;
import java.util.*;
class Textimg extends JComponent
{
String str;
Font font;
int x = 0;
final int ytext = 136;
Image img;
public Textimg(String s)
{
str = s;
font = new Font("Noserif", Font.PLAIN, 96);
setLayout(null);
}
protected void paintComponent(Graphics g)
{
if (img == null)
{
img = createImage(4800, 272);
Graphics gr = img.getGraphics();
gr.setFont(font);
gr.setColor(Color.BLACK);
gr.fillRect(0, 0, 4800, 272);
gr.setColor(new Color(135, 175, 0));
gr.drawString(str, 0, ytext);
gr.dispose();
}
g.drawImage(img, x, 0, this);
}
public void addX(int dif)
{
if (isVisible())
{
x = x + dif;
Graphics g = getGraphics();
if (g != null) paintComponent(g);
}
}
}
public class Banner extends JFrame
{
StringBuffer buf;
int sleeptime = 10;
Banner(String path) throws IOException
{
setSize(new Dimension(480, 272));
setTitle("Java Test");
setDefaultCloseOperation(EXIT_ON_CLOSE);
setLayout(null);
BufferedReader reader = new BufferedReader(
new InputStreamReader(new FileInputStream(path), "UTF-8"));
buf = new StringBuffer();
while (true)
{
String line = reader.readLine();
if (line == null) break;
buf.append(line);
}
final Textimg textimg = new Textimg(buf.toString());
add(textimg);
textimg.setBounds(0, 0, 480, 272);
final javax.swing.Timer timer = new javax.swing.Timer(200, new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
textimg.addX(-1);
}
});
timer.setDelay(sleeptime);
timer.start();
}
//----------------------------------------------------------------------
public static void main(String[] args) throws Exception
{
new Banner(args[0]).setVisible(true);
}
}
Try calling this method when you are done drawing:
Toolkit.getDefaultToolkit().sync();
This flushs the graphics buffer which some systems like Linux use. See the Javadoc: http://docs.oracle.com/javase/7/docs/api/java/awt/Toolkit.html#sync()
Don't, EVER, use getGraphics and you should NEVER call paintComponent yourself, this not how custom painting is done in Swing. Instead, update the state and call repaint.
Don't rely on magic numbers, use the information you have at hand (getWidth and getHeight)
Swing components are doubled buffered, so it's unlikely you would need to create you own buffered strategy. This simple act could be slowing down your painting
You must call super.paintComponent. This is even more important with JComponent, as it is not opaque and failing to do so could result in some nasty paint artefacts.
You should override JComponent#getPreferredSize so it can work with layout managers for efficiently.
You may find a shorter delay produces a better illusion, say 40 milliseconds (roughly 25fps) for example
Take a look at Swing animation running extremely slow, which through some object management and optimisation, was able to increase from 500 animated objects up to 4500.
Also take a look at Performing Custom Painting and Painting in AWT and Swing in particular
Profiling shows that you are saturating the shared thread used by javax.swing.Timer. One mitigation strategy is to use a longer period and/or a larger increment/decrement, as shown here.
Addendum: In addition, you are laboriously re-rendering the entire image in each call to paintComponent(). Instead, render it once using TextLayout, seen here, and draw() only the newly visible portion each time.
Problem solved!
To answer my own question: After realizing that any continuous input (mouse or keyboard) makes the animation run smoothly, I remembered that inputs can be generated by the program itself, using an object of the class java.awt.Robot. That lead to a simple workaround:
Create a Robot and let it press a key or a mouse move in each animation cycle.
final Robot robot = new Robot();
javax.swing.Timer timer = new javax.swing.Timer(initialDelay, new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
// update your image...
robot.keyPress(62);
}
});
This is a kludge, but works perfectly.
I was wondering how I would thread the following code, or just a method in general:
public void run (){
public void paint(Graphics g) {
g.fillRect(20, 20, 20, 20);
for (int i = 20; i < 1000; i++) {
g.fillRect(20, i, 20, 20);
Thread.sleep(10);
}
}
}
I find that I am unable to make a thread of this code because I get an illegal start of expression error, which is fair but I do not see a way around it.
Its hard to tell what you are doing,
but seems like you are trying to override paint() of a Runnable from within its run() method.
This can surely not be done.
The logic is
Take a component
Override its paint method to draw what we need
Call method to update co-ordinates of rectangle (or in this case timer will do that)
Than call repaint() on the component so paint method may be called again and redraw the rectangle with its new co-ordinates (Timer would also take care of repainting after changing co-ordinates of Rectangle)
repeat last 2 steps as many times as needed/wanted
(when I say component I actually mean JPanel, paint method refers to overridden paintComponent(..) of JPanel as this is best practice.)
Some suggestions:
1) Dont override paint rather use JPanel and override paintComponent.
2) Dont forget to honor the paint chain and call super.XXX implementation of overridden paintComponent(Graphics g) (or any overridden method for that fact) unless purposefully leaving it out. i.e
class MyPanel extends JPanel {
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
//do drawings here
}
}
3) If drawing in paintComponent it is usually needed to override getPreferredSize() and return Dimensions which fit the contents/drawings of JPanel, i.e:
class MyPanel extends JPanel {
#Override
public Dimension getPreferredSize() {
return new Dimension(300,300);
}
}
3) Look at Swing Timer instead of Thread.sleep(..) as sleep will block GUI thread and make it seem to be frozen. i.e
Timer t = new Timer(10, new AbstractAction() {
int count = 20;
#Override
public void actionPerformed(ActionEvent ae) {
if (count < 1000) {
//increment rectangles y position
//now repaint container so we can see changes in co-ordinates (unless you have a timer which repaints for you too)
count++;
} else {//counter is at 1000 stop the timer
((Timer) ae.getSource()).stop();
}
}
});
t.start();
4) An alternative (because I see for now you are only moving a Rectangle which is not a Swing component) to Swing timer is TimerTask, and this can be used as long as no Swing components will be created/manipulated from within its run() method (as TimerTask does not run on EDT like Swing Timer). Note revalidate() and repaint() are Thread-safe so it can be used within TimerTask.
The advantage of the above is unnecessary code is kept of EDT (i.e moving AWT rectangle by changing co-ords) i.e
final TimerTask tt = new TimerTask() {
#Override
public void run() {
if (count < 1000) {
//increment rectangles y position
//now repaint container so we can see changes in co-ordinates (unless you have a timer which repaints for you too)
count++;
} else {//counter is at 1000 stop the timer
cancel();
}
}
};
new Timer().scheduleAtFixedRate(tt, 0, 10);//start in 0milis and call run every 10 milis