I'm trying to sum the similar threads inside the ExecutorService. I think I should use join() method, but I wasn't able to do it correctly.
My Code:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextArea;
import javax.swing.JTextField;
public class Gui extends JFrame implements ActionListener{
private JTextField txtSeats, txtAgents, txtTime;
public int numFirst, numSecond, numThird;
private JTextArea [] bookSeat;
private JButton btn1, btn2;
private String output, name;
private Random ran;
private JPanel p1, p2;
private String [] agentNumber;
private int [] sum;
private int x, z;
public Gui() {//A constructor for the Gui class.
ran = new Random();//Initializing the Random class.
setLayout(new BorderLayout());//Setting the layout for the JFrame.
setTitle("");//the title of the program.
p1 = new JPanel();//Creating the first JPanel to add JButtons and JTextField on it.
p1.setLayout(new FlowLayout());//Setting the first JPanel's layout.
//Creating 3 JTextField with a title for each, and adding each of them to the first JPanel.
txtSeats = new JTextField("Number of seats");
p1.add(txtSeats);
txtAgents = new JTextField("Number of agents");
p1.add(txtAgents);
txtTime = new JTextField("Max waiting time");
p1.add(txtTime);
//Creating 2 JButton with a title for each, and adding each of them to the first JPanel.
btn1 = new JButton("Create seats");
p1.add(btn1);
btn2 = new JButton("Book");
p1.add(btn2);
//Registering the 2 JButton to the ActionListener so they work with the actionPerformed() method when they get clicked.
btn1.addActionListener(this);
btn2.addActionListener(this);
add(p1, BorderLayout.NORTH);//Adding the first JPanel to the main JFrame at the position NORTH.
//Giving some properties to the JFrame layout.
setExtendedState(JFrame.MAXIMIZED_BOTH);
setVisible(true);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setResizable(false);
}//End of the constructor.
public void createSeat() {//A method for creating a number of empty seats depending on the users wish.
//Storing the value of the first JTextField into an integer variable.
//With removing the spaces in it if there is any using "trim()".
numFirst = Integer.parseInt(txtSeats.getText().trim());
//Creating a JTextArea array for the number of seats the user wants to add.
//The size is entered by the user.
bookSeat = new JTextArea[numFirst];
p2 = new JPanel(); //Creating the second JPanel to add JTextArea on it.
p2.setLayout(new FlowLayout()); //Setting the second JPanel's layout.
//for-loop for adding the new JTextArea array into the second JPanel.
//With setting their title and background color and other properties.
for(int i = 0; i < numFirst; i++) {
bookSeat[i] = new JTextArea("Not booked");
bookSeat[i].setBackground(Color.WHITE);
bookSeat[i].setEditable(false);
p2.add(bookSeat[i]);
add(p2, BorderLayout.CENTER);//Adding the second JPanel to the main JFrame at the position CENTER.
setVisible(true);
}
}//End of createSeat() method.
#Override
public void actionPerformed(ActionEvent e) {//A method Overrode from the ActionListener interface.
if(e.getSource().equals(btn1)) {//First case: The JButton "Create seats" is clicked.
//Calling the createSeat() method; to create an empty seats based on the entered number of seats in the first JTextField.
createSeat();
//This method called on a container once new components are added or old ones removed.
//This call is an instruction to tell the layout manager to reset based on the new component list.
//revalidate() will trigger a call to repaint what the component thinks are 'dirty regions.'
//https://stackoverflow.com/questions/1097366/java-swing-revalidate-vs-repaint
revalidate();
}
else if(e.getSource().equals(btn2)) {//Second case: The JButton "Book" is clicked.
//Storing the values of the second and third JTextField into 2 integer variables.
//With removing the spaces in them if there is any using "trim()".
numSecond = Integer.parseInt(txtAgents.getText().trim());
numThird = Integer.parseInt(txtTime.getText().trim());
//Creating an ExecutorService object with fixed thread pool with maximum number of agents threads.
ExecutorService executor = Executors.newFixedThreadPool(numSecond);
//for-loop for the number of times the ExecutorService (thread pool) should run.
//It will keep creating threads until it reaches the maximum number of seats.
for(int i = 0; i < numFirst; i++) {
int count = i;
//Submitting Runnable task to the executor.
executor.execute(new Runnable() {
#Override
public void run() {//A method Overrode from the Runnable interface.
try {
//Getting the name of the current thread & store it in a String variable.
//Then we will have a long name like: pool-1-thread-n (n is a changing thread number).
//We use split("-"); to split it to 4 parts and we take the 4 part which is in position [3].
name = Thread.currentThread().getName();
agentNumber = name.split("-");
//Setting the new text and background color to the JTextArea array after they get booked.
bookSeat[count].setText("Booked by Agent " + agentNumber[3]);
bookSeat[count].setBackground(Color.RED);
revalidate();
//Generating random number between 0 and the waiting time entered by the user.
//And inserting this value inside the sleep() method. For a waiting time between each thread.
x = ran.nextInt(numThird + 1);
Thread.currentThread().sleep(x);
}
catch (Exception e) {}
}//End of run() method.
});//End of the executor.
}//End of the for-loop.
//Temporary solution for finding the similar elements in the array and return the sum for each
//equal threads. BUT NOT WORKING!!!
z = 0;
for(int i = 0; i < numFirst;i++) {
for(int j = 0; j < numFirst; j++) {
if(bookSeat[i].getName().equals(bookSeat[j].getName())) {
sum[i] = z + 1;
}
}
}
//for-loop for storing the booked seats in a String variable.
output = "";
for(int i = 1; i <= numSecond; i++) {
String allAgents = String.valueOf(i);
output += ("\n" + "Agent " + allAgents + " booked " + sum + " seats.");
}
//Displaying a Message Dialog with the information of each agent's booking.
JOptionPane.showMessageDialog(this,output);
//Shutdown the executor.
executor.shutdown();
//Wait until all tasks are finished.
while(!executor.isTerminated()) {
System.out.println("The booking has finished");
}
}//End of else if condition.
}//End of actionPerformed() method.
public static void main(String [] args) {//Main method.
new Gui(); //Calling the Gui.
}//End of Main method.
}//End of the class.
As you may see, I have tried with doing a nested for loop for checking the elements in the array if they were similar and return the sum of them. But this still didn't work!
z = 0;
for(int i = 0; i < numFirst;i++) {
for(int j = 0; j < numFirst; j++) {
if(bookSeat[i].getName().equals(bookSeat[j].getName())) {
sum[i] = z + 1;
}
}
}
Using Executor you loose direct access to the Thread instance which is needed for waiting for it (e.g. join()).
First create an array of Threads, then start them, then join() them. E.g.:
int numSecond = 3;
Runnable r = new Runnable() {
#Override
public void run()
{
// do your stuff inside thread
}//End of run() method.
};
IntStream.range(1,numSecond + 1).forEach( i -> {
Thread t = new Thread( r, "pool-1-thread-" + i);
t.start();
try
{
t.join();
}
catch ( InterruptedException t1 )
{
System.err.println( "thread interrupted" );
}
});
Above example names the thread as you expect in your code:
"pool-1-thread-" + i
If you are only interested in the numer then just pass in
"" + i
Using CompletableFutures might solve the problem also in an elegant way.
BTW1: Using a sleep(x) at the end of the thread's run method is absolutely senseless. It does not delay the start time of another thread. Why at all you want to do this? If you want to execute the logic in run() in a sequential way, there is no need to create use threads at all.
BTW2: Next time you asking something please boil down your code to a minimum which shows your problem. There is no need to setup a whole GUI with buttons, klick handlers etc.
BTW3: The access to shared (global) variables from inside the threads must be synchronized.
Related
I am trying to simulate a coin flipper using Java and window builder. I have a button called "Flip" when that button is pressed the coin image changes depending on which number the random generator I created generates.
I now am trying to figure out a way to display the number of times the coin lands a heads or a tails in their respective JTextFields. I was thinking of using a counter, but I am struggling with how to update that into the text field, so far it only puts in that I have flipped each coin once.
I am very new to programming so any advice or guidance is much appreciated.
// this button flips the coin
btnFlip = new JButton("Flip");
btnFlip.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
int headCounter = 0;
int tails = 0;
// this implements the random flip of the coin when the checkbox Run Multiple
// flips is unchecked
if (chckbxNewCheckBox.isSelected() == false) {
Random r = new Random();
int flipper = r.nextInt(2);
if (flipper == 1) {
lblImages.setIcon(new ImageIcon(FinalPrep.class.getResource("/finalPrep/heads.png")));
textFieldHeads.setText(String.valueOf(headCounter));
} else {
lblImages.setIcon(new ImageIcon(FinalPrep.class.getResource("/finalPrep/tails.png")));
textFieldTails.setText(String.valueOf(tails));
}
}
}
});
btnFlip.setFont(new Font("Tahoma", Font.PLAIN, 15));
panel_1.add(btnFlip);
You have to create something like 2 layers. One that contains the window itself along with the title (think of it like a receptor) then you have to create another "layer" that contains the buttons and the textfields. Something like this:
import javax.swing.*;
public class MyFrame extends JFrame {
private JPanel panel;
private JTextField textField;
private JButton button;
public MyFrame(){
panel = new JPanel(); //Step 1. Creation of a receptor
tfCount = new JTextField(10); //Step 2.
button = new JButton("Press Me"); //Creation of buttons & textfields.
panel.add(tfCount); //Step 3.
panel.add(button); //Add those graphics to the receptor
this.setContentPane(panel); //Step 4 Adjust the receptor to the object
this.setVisible(true);
this.setSize(400, 400);
this.setTitle("My 1st GUI!");
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
(Sorry for the random example but you did not post any sample of code so I am trying to explain as best as I can)
Then you should put a "flag" depending of the number that was generated for example:
boolean flag = true; //Flag
int counter = 0; //The counter for the heads
int x; //The number you received from the generator
int counter_b; //The counter for the tails
if(x%2 == 0){ //If the number is even
flag = false; // You set down the flag
counter++; //And counter raises its value
}
else
counter_b++;
tfCount.setEditable(true); //This makes your textfield editable
tfCount.setText(String.valueOf(counter)); //And this prints the counter's value
Keep in mind that the counters for the tails and the heads are just examples for you to understand how to use "flags". I hope that I was able to help you because I am new too!! :)
I am working in the Treasure Hunt Game Project for my java class, I got super confused when the professor asked me to do this "Do not use instanceof, or break, except inside switch statements."
currently inside the functions i am using if and for statements.
Can i change it so i can get rid of intanceof?
In the button part I am using if(. . . intanceof. . . )
Can i do it with try and catch? instead of if? if so how?
Treasure Game
import java.awt.*;
import java.util.Random;
import javax.swing.*;
public class TreasureBoardPanel extends JPanel
{
private static final int WIDTH = 10;
private static final int HEIGHT = 10;
// Instance variables
private BoardButton[] button; //used to add the button to the panel
private TreasureGame game; //used to make make the constructor for the TreasureBoardPanel
//Costructor
public TreasureBoardPanel(TreasureGame game)
{
super();
this.game = game;
//setting the layout
setLayout(new GridLayout(WIDTH, HEIGHT));
//adding the buttons
addButtons();
}
//Create and adding buttons to panel
private void addButtons()
{
this.button = new BoardButton[WIDTH * HEIGHT];
//creating the random buttons
Random random = new Random();
for (int i = 0; i < TreasureGame.NUM_TREASURES; i++)
{
int index = random.nextInt(this.button.length);
//Check if index is already a button
while (this.button[index] != null)
index = random.nextInt(this.button.length);
this.button[index] = new TreasureButton();
}
// For all the buttons which are not TreasureButton i.e. null buttons
// create BoardButton
for (int i = 0; i < this.button.length; i++)
{
if (this.button[i] == null)
this.button[i] = new BoardButton();
}
// Add all buttons to the panel and add action listener
for (int i = 0; i < this.button.length; i++)
{
this.button[i].addActionListener(new ButtonListener(game));
add(this.button[i]);
}
}
//Display text from all treasures method
public void displayAllTreasures()
{
for (int i = 0; i < this.button.length; i++)
{
if (this.button[i] instanceof TreasureButton)
this.button[i].setText(this.button[i].getDisplayText());
}
}
}
The point your professor was making wasn't to replace if with switch, but to avoid using instanceof in the first place and use polymorphism instead. Usually you can just add a method to the object's class which either handles the task itself or returns the information needed for the task, and override it in subclasses.
In this case, you could add a method to BoardButton called isTreasure that returns false, and have TreasureButton override it to return true. Then you can just call btn.isTreasure() instead of checking which class it is.
I am trying to make a game and in said game, there are 21 sticks and each person takes turns taking 1-4 sticks until there are no sticks left, if you cant take anymore sticks you lose. I have successfully made this program in eclipse but now I want to add GUI to it so I have to change the code. This code isn't complete but it crashes whenever I press the Go button which is my actionListener. I would type in a number to the text field, press go and it will just crash. How can I fix this?
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class Sticks extends JFrame {
JButton Go;
JTextField tf1, tf2;
static JTextField sttf;
JLabel startTake;
static JLabel errorTake;
JLabel uTake;
JLabel compTake;
public Sticks() {
setLayout(new GridLayout(5, 2, 5, 5));
startTake = new JLabel("How many sticks do you want to take? (1-4)");
add(startTake);
sttf = new JTextField();
add(sttf);
errorTake = new JLabel("Hello");
add(errorTake);
Go = new JButton("Go");
add(Go);
uTake = new JLabel("");
add(uTake);
compTake = new JLabel("");
add(compTake);
// tf1 = new JTextField();
// add(tf1);
// TakeP = new JLabel("One stick taken");
// add(TakeP);
event e = new event();
Go.addActionListener(e);
}
public static class event implements ActionListener {
public void actionPerformed(ActionEvent e) {
int numSticks = 21;
int numToTake = 0;
int randomNum = 0;
while (numSticks > 0) {
try {
int num = (int) (Double.parseDouble(sttf.getText()));
int NumSticks = numSticks - num;
errorTake.setText("There are: " + numSticks + " left");
Robot Rob = new Robot();
numToTake = (int)Math.random() * 4 + 1;
errorTake.setText("There are: " + numSticks + " left");
}
catch (Exception ex) {
ex.printStackTrace();;errorTake.setText("There is a problem");
}
}
}
}
public static void main(String[] args) {
Sticks gui = new Sticks();
gui.setDefaultCloseOperation(EXIT_ON_CLOSE);
gui.setVisible(true);
gui.setSize(600, 200);
gui.setTitle("Nice Game");
}
}
Everytime you click the "Go" button, your actionPerformed fires, and it doesn't wait for user input at all. This is your problem line.
public static class event implements ActionListener {
public void actionPerformed(ActionEvent e) {
//...
int num = (int) (Double.parseDouble(sttf.getText()));
//...
}
}
sttf.getText() always returns "" because sttf is empty, the program doesn't wait for user input unlike Scanner(System.in).
Makes sense that you don't get any Exceptions because it just runs and finishes the game without giving the user enough time to input anything. Are you sure the console doesn't print "Numbers only!", though? Because I've never tried to parse an empty String before.
Okay I've been reading this all wrong, sorry.
Your actionListener generates a new game everytime you click it, because you set your numSticks to 21 at each click. Looking forward, I don't think that's a good idea unless you want to take the same amount of sticks the whole way until the game ends. Same thing stands. If you input a value in sttf, the program won't wait for you to change it because it'd keep using that value until your while loop ends.
My program contains a label with the caption “Choose a coffee” and four check boxes – Americano, Espresso, Double Espresso and Latte. Note: A JCheckBox array is recommended here)
I need to add event handling code to allow the user to purchase one or more items. The bill amount is displayed in a label after the user has made their selections.
The prices are Americano €3.75, Espresso €4.00, Double Espresso €4.50 and Latte €3.50. An array is suitable here also.
As the user makes a choice a label is displayed showing the bill.
I cant figure out how to add the cost when the check box is selected and remove the cost when it is de selected using the arrays.
Any help appreciated.
This is my code so far:
package Lab4EventHandling;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
class Frame3 extends JFrame implements ActionListener {
private Container cPane;
private JLabel tMsg, bMsg;
private JCheckBox americano, espresso, doubleEspresso, latte;
JCheckBox[] boxes = new JCheckBox[]{americano, espresso, doubleEspresso, latte};
private JPanel checkPanel = new JPanel(new GridLayout(0,1));
private Color cl;
private double cost = 0;
private final int WINDOW_WIDTH = 200;
private final int WINDOW_HEIGHT = 200;
private final int x = 550;
private final int y = 400;
public Frame3()
{
cPane = getContentPane();
cl = new Color(150, 150, 250);
cPane.setBackground(cl);
this.setLayout(new BorderLayout(0,1));
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setLocation(x,y);
this.add(checkPanel, BorderLayout.CENTER);
tMsg = new JLabel("Choose a coffee:" ,SwingConstants.CENTER);
tMsg.setBorder(BorderFactory.createEmptyBorder(4,4,4,4));
this.add(tMsg, BorderLayout.PAGE_START);
americano = new JCheckBox("Americano", false);
checkPanel.add(americano);
americano.addActionListener(this);
espresso = new JCheckBox("Espresso", false);
checkPanel.add(espresso);
espresso.addActionListener(this);
doubleEspresso = new JCheckBox("Double Espresso", false);
checkPanel.add(doubleEspresso);
doubleEspresso.addActionListener(this);
latte = new JCheckBox("Latte", false);
checkPanel.add(latte);
latte.addActionListener(this);
bMsg = new JLabel("Bill is ");
bMsg.setHorizontalAlignment(SwingConstants.CENTER);
bMsg.setBorder(BorderFactory.createEmptyBorder(4,4,4,4));
this.add(bMsg, BorderLayout.SOUTH);
this.setSize(WINDOW_WIDTH, WINDOW_HEIGHT);
this.setVisible(true);
}
public void actionPerformed(ActionEvent e) {
Double[] array = {3.75, 4.00, 4.50, 3.50};
for (JCheckBox box : boxes) {
if(americano.isSelected())
{
cost += 3.75;
String r = String.valueOf(cost);
bMsg.setText(r);
}
if(espresso.isSelected())
{
cost += 4.00;
String r = String.valueOf(cost);
bMsg.setText(r);
}
else if(doubleEspresso.isSelected())
{
cost = 4.50;
String r = String.valueOf(cost);
bMsg.setText(r);
}
else if(latte.isSelected())
{
cost = 3.50;
String r = String.valueOf(cost);
bMsg.setText(r);
}
}
}
public class Frame3Test{
public static void main(String [] args)
{
Frame3 f = new Frame3();
f.setVisible(true);
}
}
Your first problem (which you may or may not have noticed) is your array of JCheckBoxes only contains null elements:
// check boxes are null here
private JCheckBox americano, espresso, doubleEspresso, latte;
// array created with only null elements
JCheckBox[] boxes = new JCheckBox[]{americano, espresso, doubleEspresso, latte};
So you need to create the array after instantiating the actual check boxes.
...
americano = new JCheckBox("Americano", false);
checkPanel.add(americano);
americano.addActionListener(this);
espresso = new JCheckBox("Espresso", false);
checkPanel.add(espresso);
espresso.addActionListener(this);
doubleEspresso = new JCheckBox("Double Espresso", false);
checkPanel.add(doubleEspresso);
doubleEspresso.addActionListener(this);
latte = new JCheckBox("Latte", false);
checkPanel.add(latte);
boxes = new JCheckBox[] {
americano, espresso, doubleEspresso, latte
};
Then the assignment is suggesting to use arrays because you can create another parallel array of prices (which you did). But since you need these prices in parallel you cannot use a for each loop. You need the index. Then you need to recalculate the entire cost every time anything is selected or deselected.
final double[] prices = {
3.75, 4.00, 4.50, 3.50
};
...
double total = 0.0;
for(int i = 0; i < boxes.length; i++) {
if(boxes[i].isSelected()) {
total += prices[i];
}
}
There's two other notes that seem to be outside the scope of the assignment:
You should always make a class for this kind of association.
You should never use double for money. Use BigDecimal or something like it.
Using a class makes logic simpler and not using double makes the calculation not incur error for this decimal addition.
class PricePair {
JCheckBox jCheckBox;
BigDecimal price;
}
BigDecimal total = new BigDecimal("0.00");
for(PricePair option : options) {
if(option.jCheckBox.isSelected()) {
total = total.add(option.price);
}
}
First of all, you're not handling all the checkboxes the same way. For the first two choices, you add the price to the cost:
cost += 3.75;
whereas for the last two choices, you replace the cost:
cost = 4.50;
The first way is the correct way.
Second, there's no reason to have the cost as a field. You should recompute the cost, and it should always start with 0, every time a checkbox selection changes. So cost should be a local variable of the actionPerformed() method.
Third, there's no reason to change the label value before you know the final cost. So the lines
String r = String.valueOf(cost);
bMsg.setText(r);
should only be there once, at the end of the actionPerformed() method, when the final cost is known.
Finally, you want to use it us an array instead of handling each checkbos separately. It's quite easy, you just need to loop over the checkboxes:
double prices = {3.75, 4.00, 4.50, 3.50};
double cost = 0.0;
for (int i = 0; i < boxes.length; i++) {
if (boxes[i].isSelected()) {
double price = prices[i];
cost += price;
}
}
// now display the cost in the label
Although both answers posted here are very helpful I'm adding this one because IMHO arrays are the less Object Oriented thing ever. You can achieve a more robust and OO solution following this hints:
Use
putClientProperty()
method inherited from
JComponent
to hold the prices in the check boxes.
Implement an ActionListener to add/substract the check box price to the total depending on the check box state. Use getClientProperty() method to retrieve the value stored in the check box.
See the example below:
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.math.BigDecimal;
import java.math.BigInteger;
import javax.swing.BoxLayout;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class Demo {
BigDecimal total = new BigDecimal(BigInteger.ZERO);
private void createAndShowGUI() {
final JLabel totalLabel = new JLabel("Total: ");
ActionListener actionListener = new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
JCheckBox checkBox = (JCheckBox)e.getSource();
BigDecimal value = (BigDecimal)checkBox.getClientProperty("price");
total = checkBox.isSelected() ? total.add(value) : total.subtract(value);
StringBuilder sb = new StringBuilder("Total: ").append(total);
totalLabel.setText(sb.toString());
}
};
JCheckBox americano = new JCheckBox("Americano");
americano.addActionListener(actionListener);
americano.putClientProperty("price", new BigDecimal("3.75"));
JCheckBox espresso = new JCheckBox("Espresso");
espresso.addActionListener(actionListener);
espresso.putClientProperty("price", new BigDecimal("4.00"));
JCheckBox doubleEspresso = new JCheckBox("Double Espresso");
doubleEspresso.addActionListener(actionListener);
doubleEspresso.putClientProperty("price", new BigDecimal("4.50"));
JCheckBox latte = new JCheckBox("Latte");
latte.addActionListener(actionListener);
latte.putClientProperty("price", new BigDecimal("3.50"));
JPanel content = new JPanel();
content.setLayout(new BoxLayout(content, BoxLayout.PAGE_AXIS));
content.add(americano);
content.add(espresso);
content.add(doubleEspresso);
content.add(latte);
content.add(totalLabel);
JFrame frame = new JFrame("Demo");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.add(content);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new Demo().createAndShowGUI();
}
});
}
}
This way you can forget about mapping every check box with a value using arrays or maps. If you need to add a new sort of coffee you should simply add 4 lines like this:
JCheckBox newCoffee = new JCheckBox("New Coffee");
newCoffee.addActionListener(actionListener);
newCoffee.putClientProperty("price", new BigDecimal("4.00"));
content.add(newCoffee);
*Problem solved - thanks for all your answers, they were very helpful!
I am making a small dice game for a school assignment, and i came across this problem.
I want to simulate the rolling of a die, by quickly cycle trough a number of die-icons.
This by itself is not what is causing the problem though.
If i make the "animation" directly in a JFrame it displays correctly. I have done that in the code below:
public class Example{
private static ImageIcon die1 = new ImageIcon("terning1.jpg");
private static ImageIcon die2 = new ImageIcon("terning2.jpg");
private static ImageIcon die3 = new ImageIcon("terning3.jpg");
private static ImageIcon die4 = new ImageIcon("terning4.jpg");
private static ImageIcon die5 = new ImageIcon("terning5.jpg");
private static ImageIcon die6 = new ImageIcon("terning6.jpg");
private static JLabel die = new JLabel(die1);
private static Random generator = new Random();
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(die);
frame.pack();
frame.setVisible(true);
for (int i = 0; i < 100; i++) {
int x = generator.nextInt(6) + 1;
switch(x){
case 1 : die.setIcon(die1);
break;
case 2 : die.setIcon(die2);
break;
case 3 : die.setIcon(die3);
break;
case 4 : die.setIcon(die4);
break;
case 5 : die.setIcon(die5);
break;
case 6 : die.setIcon(die6);
break;
}
//Make the loop wait for 50 millis
long a, b;
a = System.currentTimeMillis();
do {
b = System.currentTimeMillis();
} while ((b-a) < 50);
}
}
}
Now that works fine, but obviously it only works when i first open the JFrame. So i want to add a button, that makes the die roll.
But if i add a JButton with a actionlistener, and put the for-loop in the actionPerformed method, it stalls the program until the loop has finished, and only shows the last die in the loop.
Example:
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new FlowLayout());
frame.add(button);
frame.add(die);
button.addActionListener(new ButtonListener());
frame.pack();
frame.setVisible(true);
}
private static class ButtonListener implements ActionListener {
public void actionPerformed(ActionEvent event) {
for (int i = 0; i < 100; i++) {
int x = generator.nextInt(6) + 1;
switch (x) {
case 1:
die.setIcon(die1);
break;
case 2:
die.setIcon(die2);
break;
case 3:
die.setIcon(die3);
break;
case 4:
die.setIcon(die4);
break;
case 5:
die.setIcon(die5);
break;
case 6:
die.setIcon(die6);
break;
}
//Make the loop wait for 50 millis
long a, b;
a = System.currentTimeMillis();
do {
b = System.currentTimeMillis();
} while ((b - a) < 50);
}
Any tips on how to solve this?
Thanks beforehand!
What's happening is that the "event dispatch thread", where all the Swing events happen, is having to wait for your code. Don't do long-running stuff on the event dispatch thread. This is a famous anti-pattern.
You should read the lesson from the Java tutorials, that starts at http://docs.oracle.com/javase/tutorial/uiswing/concurrency/index.html, which describes this.
Two small points that are unrelated to your problem:
Your code will be much more manageable if you use an array of ImageIcon variables, instead of six separate variables.
You could use the sleep method of the Thread class instead of the "busy sleep" that you're using.
Your UI is locked because everything is in the same thread so it has to wait until the for loop is done executing.Your going to need to run your logic in a separate thread.
You run your actionPerformed(ActionEvent event) method in EDT because of that you can't update UI. For updating UI from your code try to use SwingWorker, it can update UI while background process running. You can found a lot of examples of it in the internet.
Or you can try to use Executors for background process and updating UI from EDT.
Swing's event handling code runs in EDT(even dispatch thread). The ActionEvent is also no exception. You can't do any long running task inside EDT. Put your dice rolling loop inside a new thread:
new Thread(){
public void run(){
// your dice rolling code
}
}.start();
and then update the GUI using SwingUtilities.invokdeLater().
Note: Instead of conditioning with switch-case you just could use an array of ImageIcon and access it with random generated index.
int x = generator.nextInt(6) + 1;
die.setIcon(imageIconArr[x]); // array of image icon
See this answer for details.