Multiple swing timers - java

I have (what should be) a simple problem to tackle and I'm open to other ways to solve it. I am open to other solutions.
The problem:
We are using java swing to display the graphics of a turn-based, tile-based game. I'm using jlabels with icons, absolutely positioned.
To animate the movement, I am using a swing timer that updates the location by 4 pixels at a time, slowly moving the sprite right, left, etc.
To achieve this initially, I was running a timer, which works wonderfully. The problem comes in when I try to move down, then move right.
The sprite moves down, never moves right, and if I watch the execution with some console printing, it's clear to see that both timers are running at the same time. I've done a fair amount of digging on the internet and I wasn't able to find a way to tell a swing timer not to execute until the first timer has stopped, and if I try to busy-wait until one timer finishes (yuck) the UI never displays at all (clearly a step in the wrong direction.)
Now I can convert away from timers altogether and either have the sprite teleport to its new location, or use some awful busy-wait movement scheme, but I'm hoping some kind soul has a solution.
In short: I need a way to run a swing timer for a set period of time, stop it, and then start a new timer, so that they do not overlap. Preferably this method would allow each timer to be in its own method, and I could then call the methods one after the other.
Thanks in advance for any advice you might have.
Edit: Expanded example code. If a full scsse is a requirement for your advice then I'm sorry to have wasted your time, because the full code is a beast. This sample code does not work at all as it stands, sorry, but it should illustrate the point.
So. We have two functions, each with a timer that runs an animation cycle, one for moving down and right diagonally, one for moving straight down.
public class TestClass {
static int counter = 0;
static int counter2 = 0;
static Timer timerC;
static Timer timerX;
public static void main(String[] args) {
moveC();
moveX();
}
public static void moveC() {
int delay = 200; // milliseconds
timerC = new Timer(delay, null);
timerC.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
if (counter < 32) {
counter = counter + 4;
System.out.println("*C*");
} else {
timerC.stop();
System.out.println("*C STOP*");
}
}
});
timerC.start();
}
public static void moveX() {
int delay = 200; // milliseconds
timerX = new Timer(delay, null);
timerX.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
if (counter < 32) {
counter = counter + 4;
System.out.println("*X*");
} else {
timerX.stop();
System.out.println("*X STOP*");
}
}
});
timerX.start();
}
}
What I would want to see here eventually would be
*C*
*C*
*C*
*C*
*C STOP*
*X*
*X*
*X*
*X*
*X STOP*
What I actually get is
*C*
*X*
*C*
*X*
*C*
*X*
*C*
*X*
*C STOP*
*X STOP*
The point I'm trying to get at here is running one animation cycle to completion, then the other.
Thanks again.

Don't use multiple Timers, but rather only one Timer that deals with each direction as it's needed. You need some type of queue to hold the direction information, either a formal queue or a collection that you use as a queue (first in, first out), and then have your Timer extract the direction from this queue as it's running. For example, here I use my JList's model as my queue by removing and using the Direction that was added first (at the top of the JList):
import java.awt.GridLayout;
import java.awt.event.*;
import javax.swing.*;
public class TimerPlay extends JPanel {
private DefaultListModel directionJListModel = new DefaultListModel();
private JList directionJList = new JList(directionJListModel);
JButton startTimerButton = new JButton(
new StartTimerBtnAction("Start Timer"));
public TimerPlay() {
ActionListener directionBtnListener = new ActionListener() {
#Override
public void actionPerformed(ActionEvent actEvt) {
String actionCommand = actEvt.getActionCommand();
Direction dir = Direction.valueOf(actionCommand);
if (dir != null) {
directionJListModel.addElement(dir);
}
}
};
JPanel directionBtnPanel = new JPanel(new GridLayout(0, 1, 0, 10));
for (Direction dir : Direction.values()) {
JButton dirBtn = new JButton(dir.toString());
dirBtn.addActionListener(directionBtnListener);
directionBtnPanel.add(dirBtn);
}
add(directionBtnPanel);
add(new JScrollPane(directionJList));
add(startTimerButton);
}
private class StartTimerBtnAction extends AbstractAction {
protected static final int MAX_COUNT = 20;
public StartTimerBtnAction(String title) {
super(title);
}
#Override
public void actionPerformed(ActionEvent arg0) {
startTimerButton.setEnabled(false);
int delay = 100;
new Timer(delay, new ActionListener() {
private int count = 0;
private Direction dir = null;
#Override
public void actionPerformed(ActionEvent e) {
if (count == MAX_COUNT) {
count = 0; // restart
return;
} else if (count == 0) {
if (directionJListModel.size() == 0) {
((Timer)e.getSource()).stop();
startTimerButton.setEnabled(true);
return;
}
// extract from "queue"
dir = (Direction) directionJListModel.remove(0);
}
System.out.println(dir); // do movement here
count++;
}
}).start();
}
}
private static void createAndShowGui() {
TimerPlay mainPanel = new TimerPlay();
JFrame frame = new JFrame("TimerPlay");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
enum Direction {
UP, DOWN, LEFT, RIGHT;
}

For reference, this example manages four instances of Timer, two of which run (interleaved) while hovering in any corner. You might compare it to your approach. This related answer discusses animation in a similar tile-based game.

put all Icons in some form of array
create a single Swing Timer with a short delay
in Swing ActionListener, take each `Icon from the array, getBounds from screen, move Icon one step
repeat until target reached.

Related

Java - Why does my print statement not run

So this is my code:
System.out.println("System Exiting...");
long current = System.currentTimeMillis();
long disired = current + 4000;
boolean done = false;
while (!done)
{
current = System.currentTimeMillis();
if (current == disired)
{
done = true;
System.exit(0);
}
}
My problem is that the print statement doesn't run, well it does run, but it runs at the same time as the exit statement, so you don't see it
[EDIT] Ok, so i just ran this code in its own file(with nothing else), and it works as i want, it prints "system Exiting..." it waits 4 seconds, and the code exits.
so it has to be something to do with the fact that i have this code inside an event listener
Your if condition is much too restrictive since your code will almost never get the times to be exactly equal, but the change needed is very simple:
Change
// hitting this exactly is like finding the proverbial needle
// in the haystack -- almost impossible to do.
if (current == disired)
to
// this is guaranteed to work.
// note if this is in English, you'll want to change disired to desired
if (current >= disired)
Having said this, your while (true) loop is not a good thing to do as it will needlessly tie up the CPU with empty cycles. Instead use some type of event notification or call-back system like ChangeListener or a PropertyChangeListener or a Timer.
You state:
yes it is within a swing GUI
You're calling a long while (true) block of code on the Swing event thread, rendering this thread ineffective. Since the event thread is responsible for all Swing graphics and user interactions, this effectively freezes your GUI until the while loop completes. The solution is obvious: 1) use a Swing Timer for your delay, not a while true loop (this is the callback mechanism that I mentioned in my original answer), and 2) in the future, please give us this important relevant information with the original question since it changes the entire nature of the question.
e.g.
// caveat: code not tested
System.out.println("System Exiting...");
int delay = 4 * 1000;
new Timer(delay, new ActionListener(){
#Override
public void actionPerformed(ActionEvent e) {
System.out. println("Exited");
System.exit(0);
}
}).start();
e.g.,
import java.awt.Component;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import javax.swing.*;
#SuppressWarnings("serial")
public class TestDelayedExit extends JPanel {
private static final int GAP = 100;
public TestDelayedExit() {
add(new JButton(new DisposeAction("Exit", KeyEvent.VK_X)));
setBorder(BorderFactory.createEmptyBorder(GAP, GAP, GAP, GAP));
}
private static void createAndShowGui() {
JFrame frame = new JFrame("TestDelayedExit");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(new TestDelayedExit());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
private class DisposeAction extends AbstractAction {
private int count = 4;
private Timer timer;
public DisposeAction(String name, int mnemonic) {
super(name);
putValue(MNEMONIC_KEY, mnemonic); // for alt-key combo
}
#Override
public void actionPerformed(ActionEvent e) {
if (timer != null && timer.isRunning()) {
return;
}
final Component c = (Component) e.getSource();
int timerDelay = 1000;
putValue(NAME, String.valueOf(count));
timer = new Timer(timerDelay, new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (count == 0) {
((Timer) e.getSource()).stop();
// this will not work for JMenuItems, and for that
// you would need to get the pop up window's parent component
Window win = SwingUtilities.getWindowAncestor(c);
if (win != null) {
win.dispose();
}
} else {
count--;
putValue(NAME, String.valueOf(count));
}
}
});
timer.start();
}
}
}

I'm probably doing this incorrectly. Is there a better way?

So usually when I make mock-up programs (like this one) I look for things I can improve on in case the situation happens again.
Today I thought I'd brush up on basic OOP (I understand the concept of OOP, just haven't messed around with it for a bit and wanted to freshen my memory). So I decided to make a little game that just creates 3 monsters on a 10x10 plane and 1 player (you), you are able to move your player in any x/y direction. My program works but I can't help but feel that I'm doing something incorrectly.
So the basic layout of my program was to have 5 classes. A GUI class that shows the game and gives you directional buttons for movement control, a class that creates the monsters, a class that creates the players, a class that creates the 10x10 board and keeps track of monster/player locations, and of course a main class that creates all the objects and has the main game loop and whatnot.
I was having a bit of a hard time interacting with my main class and my GUI class. What I ended up doing was doing a while loop in my main class and waiting until the player presses the start button, and once the player presses it (via action listener) the GUI class sets a public variable (running) from false to true, and I am able to act accordingly once the variable is changed.
HERE is where I feel like I am doing something wrong: At first my while loop would not terminate unless I printed out to the console. I Googled the issue and apparently people have said that it's some sort of issue with threading or "active polling", which I did not understand. I went to my program and added a small 10ms thread sleep in my while loops and everything started working great.
My question to you guys is, what is active polling? Why is it bad? How/why/where was this going on in my program? And finally if there's a better way of interacting with a GUI class and a main class. Sorry for the giant wall of text but I like to be thorough when explaining a situation!
TL;DR: Am I interacting correctly with my GUI class and my main class? If not what is the proper way to do it?
My main class:
public class MainGame {
public static void main(String[] args) throws InterruptedException{
ShowGUI gui = new ShowGUI();
while(!gui.running){
Thread.sleep(10);
}
Board gameBoard = new Board();
gui.setLabelText(gameBoard.getBoard());
//Add Player
Player playerOne = new Player(1000, "Player 1");
//Add monsters
Monster monstMatt = new Monster(1000, "Matt");
Monster monstJon = new Monster(1000, "Jon");
Monster monstAbaad = new Monster(1000, "Abaad");
while(gui.running){
Thread.sleep(10);
int x, y;
x = playerOne.getX();
y = playerOne.getY();
if(gui.buttonPress != -1){
if(gui.buttonPress == 1){
playerOne.move(x, --y);
}else if(gui.buttonPress == 2){
playerOne.move(x, ++y);
}else if(gui.buttonPress == 3){
playerOne.move(--x, y);
}else if(gui.buttonPress == 4){
playerOne.move(++x, y);
}
gui.buttonPress = -1;
gui.setLabelText(gameBoard.getBoard());
}
}
}
}
My GUI Class:
public class ShowGUI{
private JTextArea board;
private JButton moveUp;
private JButton moveDown;
private JButton moveLeft;
private JButton moveRight;
public boolean running = false;
public int buttonPress = -1;
public ShowGUI(){
System.out.println("GUI Successfully Loaded");
createAndShow();
}
private void createAndShow(){
JFrame mainFrame = new JFrame("Bad Game");
addComponents(mainFrame.getContentPane());
mainFrame.setSize(500, 400);
mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
mainFrame.setLocationRelativeTo(null);
mainFrame.setResizable(false);
mainFrame.setVisible(true);
}
private void addComponents(Container pane){
pane.setLayout(null);
board = new JTextArea(1, JLabel.CENTER);
moveUp = new JButton("Up");
moveDown = new JButton("Down");
moveLeft = new JButton("Left");
moveRight = new JButton("Right");
moveUp.setBounds(185, 225, 130, 35);
moveLeft.setBounds(115, 280, 130, 35);
moveRight.setBounds(255, 280, 130, 35);
moveDown.setBounds(185, 335, 130, 35);
board.setEditable(false);
board.setBounds(115, 30, 270, 145);
board.setFont(new Font("Consolas", Font.BOLD, 12));
addActionListeners();
pane.add(board);
pane.add(moveUp);
pane.add(moveRight);
pane.add(moveLeft);
pane.add(moveDown);
}
private void addActionListeners(){
moveUp.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
running = true;
buttonPress = 1;
}
});
moveDown.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
buttonPress = 2;
}
});
moveLeft.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
buttonPress = 3;
}
});
moveRight.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
buttonPress = 4;
}
});
}
public void setLabelText(char[][] boardToShow){
board.setText(" ");
for(int i = 0; i < boardToShow.length; i++){
for(int j = 0; j < boardToShow[i].length; j++){
board.append(boardToShow[i][j] + " ");
}
board.append("\n ");
}
}
}
If you require my Board/Monster/Player classes I can post them, but I don't think the problem is with those classes.
Active polling (a.k.a. busy waiting) is when a program checks over and over whether some condition is true, and since we're talking about computers, this is usually done many times a second. It's bad because it means the process is constantly using up CPU to check for this condition, and, even worse, because it uses up so much of the CPU, it can prevent the condition from being able to become true in the first place (this is what is happening in your case).
I'll try to explain this with a metaphor, where your Main class (specifically your while(gui.running) loop) is the boss, and the GUI class is the employee. Imagine the boss comes to his employee every second and asks "Have you done what I asked you to do?". By doing so every second, not only is the boss wasting his time, but is actually preventing his employee from being able to do what he was asked to do.
That is exactly what is happening between your Main and GUI class. The value of buttonPress can not change when the while loop keeps running, and that's why sleeping (and printing to console, because that requires an IO operation which also blocks the thread for a while) makes it work, because the while loop stops executing for a short amount of time, giving your program the chance to change the value of buttonPress.
The solution
In order to solve this, let's go back to the boss/employee metaphor. Instead of checking every second, the boss should say, "When you are done doing this, come and tell me". That way he doesn't need to keep checking on the employee and the employee has time to do the task. Similarly, your GUI class should call a method in your Main class when the actionListeners are fired. Ultimately, as other people pointed out, your structure needs quite a bit of work and clean up, I would recommend you look into using the Model-View-Controller pattern.
However I will propose a solution which will work for the setup you currently have. Your Main class should look something like:
public class Main {
private Player playerOne;
private Monster monstMatt;
private Monster monstJon;
private Monster monstAbaad;
private ShowGUI gui;
private Board gameBoard;
public Main() {
//Add Player
playerOne = new Player(1000, "Player 1");
//Add monsters
monstMatt = new Monster(1000, "Matt");
monstJon = new Monster(1000, "Jon");
monstAbaad = new Monster(1000, "Abaad");
gui = new ShowGUI(this);
gameBoard = new Board();
gui.setLabelText(gameBoard.getBoard());
}
public movePlayerUp() {
movePlayer(0, -1);
}
public movePlayerDown() {
movePlayer(0, 1);
}
public movePlayerLeft() {
movePlayer(-1, 0);
}
public movePlayerRight() {
movePlayer(1, 0);
}
private movePlayer(x, y) {
playerOne.move(playerOne.getX() + x, playerOne.getY() + y);
}
}
And the GUI class:
public class ShowGUI {
private Main main;
public ShowGui(Main main) {
this.main = main;
createAndShow();
System.out.println("GUI Successfully Loaded");
}
private void addActionListeners(){
moveUp.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
main.movePlayerUp();
}
});
moveDown.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
main.movePlayerDown();
}
});
moveLeft.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
main.movePlayerLeft();
}
});
moveRight.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
main.movePlayerRight();
}
});
}
/* All the other methods you have */
...
}
So instead of using flags (buttonPress=2), use methods which will be called when a certain action occurs. Again, this is not a perfect long-term solution, but it has the right gist and is the sort of pattern you should follow.

Making an updating JLabel

I am new to programming (I'm 11 and hoping for java coding to be my career, but its just a hobby right now :)) and I just made a countdown program, here is the class:
package me.NoahCagle.JAVA;
import javax.swing.JFrame;
public class Main extends JFrame implements Runnable {
private static final long serialVersionUID = 1L;
public static int width = 600;
public static int height = 500;
public static String title = "Countdown!";
public static boolean running = false;
public int number = 11;
public Thread thread;
Dimension size = new Dimension(width, height);
public Main() {
super(title);
setSize(size);
setResizable(false);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLocationRelativeTo(null);
setVisible(true);
}
public static void main(String[] args) {
Main m = new Main();
m.start();
}
public void start() {
if (running) {
return;
}
running = true;
Thread thread = new Thread(this);
thread.start();
}
#SuppressWarnings("static-access")
public void run() {
while (running) {
number--;
if (number == -1) {
System.out.println("Done!");
System.exit(0);
}
try {
thread.sleep(1000);
}catch (Exception e) {
e.printStackTrace();
System.exit(0);
}
System.out.println("" + number);
}
}
public void stop() {
if (!running) {
return;
}
running = false;
try {
thread.join();
}catch (Exception e) {
e.printStackTrace();
System.exit(0);
}
}
}
That may not have been necessary, but whatever. Well like I was saying, if you read the code, you will notice that it prints the value to the console. Well, if I could get that to display on a JLabel, while updating at the same time. I have tried just doing setText("" + number) thinking that because I have a thread going, it would repaint. But that didn't happen. It was just stuck at 11. Can someone please help me? Thanks
First, you may want to take a read through Concurrency in Swing. There are some very important constraints when it comes to dealing with multiple threads and Swing.
For your problem, you really should be using a javax.swing.Timer, and with examples...
Java Label Timer and Saving
Adding a timer and displaying label text
How could I add a simple delay in a Java Swing application?
As 11yrs old you have done good job here. But where did you add any panel to the frame on which u want to show the number? Once you do it and put some label to add the number you will need to call the repaint method. Also to use threads with swings, there are many libraries you can use like Timer.
Happy Coding!

Best way of Changing the background of JButtons

Right now i change the background color of a button by using
button.setBackground(Color.WHITE);
That being an example.
But when i have a massive grid out of jbuttons (1000+), just running a for loop to change every buttons background is very, very slow. You can see the grid slowly turning white, box by box. I really don't want this
Is there a better way of changing every JButton on the grid to the same color at the same time?
This is how i am making the grid, the numbers used are only for example...
grid = new JPanel(new GridLayout(64, 64, 0, 0));
That's 4096 buttons, takes about 30+ seconds to change every button to the same color.
Edit 1: I need the buttons to be clickable, like when i click a button it turns blue for example. when all of the buttons are clicked, change the color of every button to white. Right now i have that working fine, but it is just slow to change the color of every button.
Edit 2: this is how i am changing the buttons:
new javax.swing.Timer(300, new ActionListener() {
int counter = 0;
public void actionPerformed(ActionEvent e) {
if (counter >= counterMax) {
((Timer) e.getSource()).stop();
}
Color bckgrndColor = (counter % 2 == 0) ? flashColor : Color.white;
for (JButton button : gridButton) {
button.setBackground(bckgrndColor);
}
counter++;
}
}).start();
The fact that you see the boxes being repainted individually indicates that either double buffering is turned off, or that the paint code in the button UI makes use of paintImmediately().
I tested your setup with 64x64 JButtons, an made sure that all UI operations were executed in the EDT (Event Dispatch Thread). I can confirm the effect you saw, changing the background of all buttons took about 1200 ms, with every box repainted immediately.
You can bypass the immediate repaints by setting the grid to non-visible before, and to visible after you changed the backgrounds:
grid.setVisible(false);
for (Component comp : grid.getComponents()) {
comp.setBackground(color);
}
grid.setVisible(true);
This caused the grid to do only one repaint, and reduced the time to ~300ms (factor 4).
This is still too slow for frequent updates, so you're better off with a custom component which draws the grid, or a flyweight container (what trashgod suggested in the comment to your question) if you want allow the grid cells to be arbitrary components.
You can get a considerable benefit if only visible buttons need to be repainted. In the MVC approach shown below, each button listens to a model that defines it's current state. Updating the model is quite fast compared to repainting. Although startup takes a few seconds, I see updates taking < 10 ms. in the steady-state. It's not as scalable as the flyweight pattern used by JTable, illustrated here, but it may serve.
import java.awt.*;
import java.awt.event.*;
import java.util.Observable;
import java.util.Observer;
import java.util.Random;
import javax.swing.*;
/** #see https://stackoverflow.com/questions/6117908 */
public class UpdateTest {
private static final int ROW = 64;
private static final int COL = 64;
private static final int MAX = COL * ROW;
private final DataModel model = new DataModel(MAX);
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new UpdateTest().create();
}
});
}
void create() {
JPanel panel = new JPanel();
panel.setLayout(new GridLayout(ROW, COL));
for (int i = 0; i < MAX; i++) {
panel.add(new ViewPanel(model, i));
}
Timer timer = new Timer(1000, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
long start = System.nanoTime();
model.update();
System.out.println(
(System.nanoTime() - start) / (1000 * 1000));
}
});
JFrame f = new JFrame("JTextTest");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(new JScrollPane(panel), BorderLayout.CENTER);
f.setPreferredSize(new Dimension(800, 600));
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
timer.start();
}
private static class ViewPanel extends JPanel implements Observer {
private final JButton item = new JButton();
private DataModel model;
private int index;
public ViewPanel(DataModel model, int i) {
this.model = model;
this.index = i;
this.add(item);
item.setText(String.valueOf(i));
item.setOpaque(true);
item.setBackground(new Color(model.get(index)));
model.addObserver(this);
}
#Override
public void update(Observable o, Object arg) {
int value = model.get(index);
item.setBackground(new Color(value));
}
}
private static class DataModel extends Observable {
private final Random rnd = new Random();
private final int[] data;
public DataModel(int n) {
data = new int[n];
fillData();
}
public void update() {
fillData();
this.setChanged();
this.notifyObservers();
}
public int get(int i) {
return data[i];
}
private void fillData() {
for (int i = 0; i < data.length; i++) {
data[i] = rnd.nextInt();
}
}
}
}

Showing text in JTextArea while calculating

An application I am writing consists, among others, a JButton and a JTextArea. A click on the button leads to a long calculation, resulting in a text shown in the JTextArea. Even though the calculation is long, I can have middle-results on the go (think, for example, of an application which approximates pi up to 100 digits - every few seconds I could write another digit). The problem is, that even if I write (being in the ActionListener class because the button invoked the calculation) to set the text of the JTextArea to something, it isn't shown while the calculation is done, and I can only see the end result, after the calculation is over.
Why is it so, and how can I fix it?
Thank you in advance.
Your problem is that you're doing a long calculation in the main Swing thread, the EDT, and this will freeze your entire GUI until the process has completed itself. A solution is to use a background thread for your calculation, and an easy way to do this it to use a SwingWorker to create a thread background to the main Swing thread, the EDT, and publish/process the interim results into the JTextArea. For more on SwingWorkers and the EDT, please look here: Concurrency in Swing
Also, if you provide a decent sscce we can probably give you a more detailed response perhaps even with sample code.
An example SSCCE:
import java.awt.event.*;
import java.text.DecimalFormat;
import java.util.List;
import javax.swing.*;
public class InterimCalc {
private JPanel mainPanel = new JPanel();
private JTextField resultField = new JTextField(10);
private JButton doItBtn = new JButton("Do It!");
private DecimalFormat dblFormat = new DecimalFormat("0.0000000000");
private SwingWorker<Void, Double> mySwingWorker = null;
public InterimCalc() {
mainPanel.add(doItBtn);
mainPanel.add(resultField);
displayResult(0.0);
doItBtn.addActionListener(new DoItListener());
}
public void displayResult(double result) {
resultField.setText(dblFormat.format(result));
}
public JPanel getMainPanel() {
return mainPanel;
}
private class DoItListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
if (mySwingWorker != null && !mySwingWorker.isDone()) {
mySwingWorker.cancel(true);
}
displayResult(0.0);
mySwingWorker = new MySwingWorker();
mySwingWorker.execute();
}
}
private class MySwingWorker extends SwingWorker<Void, Double> {
private static final int INTERIM_LENGTH = 10000; // how many loops to do before displaying
#Override
protected Void doInBackground() throws Exception {
boolean keepGoing = true;
long index = 1L;
double value = 0.0;
while (keepGoing) {
for (int i = 0; i < INTERIM_LENGTH; i++) {
int multiplier = (index % 2 == 0) ? -1 : 1;
value += (double)multiplier / (index);
index++;
}
publish(value);
}
return null;
}
#Override
protected void process(List<Double> chunks) {
for (Double dbl : chunks) {
displayResult(dbl);
}
}
}
private static void createAndShowUI() {
JFrame frame = new JFrame("Decay Const");
frame.getContentPane().add(new InterimCalc().getMainPanel());
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();
}
});
}
}
you may also want to display some sort of spinning gif or "progress bar" to show that the answer is being calculated; feedback to the user is good.
(once you are using a swingworker, then the gui won't freeze and the gui can do its own thing while the calculation is taking place)

Categories