I'm making a game client/server and I'm having a new thread update some info on the client GUI (which is using Swing). I'm trying to use SwingUtilities but it's not working. Also, I heard SwingUtilities creates a new thread everytime you use it so I'm looking for a new way as well (I have 10 or so JTextFields to be updated). Is there a way to do it without using SwingUtilities? Here is what I have right now.
SwingUtilities.invokeLater(
new Runnable() {
public void run()
{
Client.status.setText("status = "+status);
}
});
Interestingly, just recently I encountered a similar issue, so In order to get over this I used SwingUtilities.invokeAndWait(Runnable runnable), that's what made the SSCCE I was creating to work as expected, though if I change all calls to invokeAndWait() with invokeLater(), one can clearly see the difference between the two things.
A quote from Java Doc says :
Causes doRun.run() to be executed synchronously on the AWT event dispatching thread.
This call blocks until all pending AWT events have been processed and
(then) doRun.run() returns. This method should be used when an application thread
needs to update the GUI.
This is a small program I had made as an SSCCE to represent a Bubble Sort Animation :
import javax.swing.*;
public class BubbleSortFrame extends JFrame
{
private BubbleSortView contentPane;
private void displayGUI()
{
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
contentPane = new BubbleSortView();
setContentPane(contentPane);
pack();
setLocationByPlatform(true);
setVisible(true);
}
public static void main(String... args)
{
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
new BubbleSortFrame().displayGUI();
}
});
}
}
import java.awt.*;
import java.awt.event.*;
import java.lang.reflect.InvocationTargetException;
import javax.swing.*;
public class BubbleSortView extends JPanel
{
private JLabel sizeLabel;
private JTextField sizeField;
private JTextField[] vField;
private JLabel[] vLabel;
private JButton startButton, createButton;
private int size;
private JPanel createPanel, animationPanel;
private BubbleSort bubbleSort;
public BubbleSortView()
{
size = 5;
displayAndCreateGUI();
}
private void displayAndCreateGUI()
{
setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
setOpaque(true);
setBackground(Color.WHITE);
JPanel basePanel = new JPanel();
basePanel.setLayout(new GridLayout(2, 1, 5, 5));
basePanel.setOpaque(true);
basePanel.setBackground(Color.WHITE);
JPanel topPanel = new JPanel();
topPanel.setOpaque(true);
topPanel.setBackground(Color.WHITE);
topPanel.setBorder(
BorderFactory.createTitledBorder("Input : "));
topPanel.setLayout(new GridLayout(2, 1, 5, 5));
/*
* This will act as the area
* for taking the input for
* number of elements in an Array.
*/
JPanel sizePanel = new JPanel();
sizePanel.setOpaque(true);
sizePanel.setBackground(Color.WHITE);
sizeLabel = new JLabel("Enter Number of Elements : ");
sizeField = new JTextField(10);
createButton = new JButton("CREATE");
/*
* This will act as the area
* where we will specify the values
* for each index in an Array.
*/
createPanel = new JPanel();
createPanel.setOpaque(true);
createPanel.setBackground(Color.WHITE);
createPanel.setBorder(
BorderFactory.createTitledBorder("Please Enter values for an Array : "));
createPanel.setVisible(false);
animationPanel = new JPanel();
animationPanel.setOpaque(true);
animationPanel.setBackground(Color.WHITE);
animationPanel.setBorder(
BorderFactory.createTitledBorder("Animation : "));
animationPanel.setVisible(false);
createButton.addActionListener(new ActionListener()
{
#Override
public void actionPerformed(ActionEvent ae)
{
if (sizeField.getDocument().getLength() > 0)
{
size = Integer.parseInt(sizeField.getText());
vField = new JTextField[size];
createPanel.setVisible(true);
for (int i = 0; i < size; i++)
{
vField[i] = new JTextField(5);
/*
* Adding the Listener to the
* last JTextField on the Right
* Side.
*/
if (i == (size - 1))
{
vField[i].addActionListener(new ActionListener()
{
#Override
public void actionPerformed(ActionEvent ae)
{
animationPanel.setLayout(
new GridLayout(1, size, 2, 2));
animationPanel.setVisible(true);
vLabel = new JLabel[size];
for (int i = 0; i < size; i++)
{
vLabel[i] = new JLabel(
vField[i].getText(), JLabel.CENTER);
vLabel[i].setOpaque(true);
vLabel[i].setBackground(Color.YELLOW);
vLabel[i].setForeground(Color.RED);
animationPanel.add(vLabel[i]);
}
animationPanel.revalidate();
animationPanel.repaint();
bubbleSort = new BubbleSort(vLabel, size);
Thread t = new Thread(bubbleSort);
t.start();
}
});
}
createPanel.add(vField[i]);
}
createPanel.revalidate();
createPanel.repaint();
createButton.setEnabled(false);
}
else
size = 5;
}
});
sizePanel.add(sizeLabel);
sizePanel.add(sizeField);
sizePanel.add(createButton);
/*
* Initializing JTextField Array
* so that it can be first presented
* to the USER to take input for
* 5 values.
*/
//for (int i = 0; i < size; i++)
// vField[i] = new JTextField(5);
topPanel.add(sizePanel);
topPanel.add(createPanel);
basePanel.add(topPanel);
basePanel.add(animationPanel);
add(basePanel);
}
private class BubbleSort implements Runnable
{
private int[] arr;
private JLabel[] vLabel;
private int size;
private int pass;
public BubbleSort(JLabel[] label, int size)
{
vLabel = label;
this.size = size;
pass = 1;
for (int i = 0; i < size; i++)
System.out.print("" + vLabel[i].getText() + "\t");
System.out.println("");
}
#Override
public void run()
{
try
{
bubbleSorting();
}
catch (InvocationTargetException ite)
{
ite.printStackTrace();
}
catch(InterruptedException ie)
{
ie.printStackTrace();
}
}
private void bubbleSorting()
throws InterruptedException, InvocationTargetException
{
while (pass < size)
{
for (int i = 0; i < (size - pass); i++)
{
final int j = i;
SwingUtilities.invokeAndWait(new Runnable()
{
#Override
public void run()
{
vLabel[j].setBackground(Color.RED);
vLabel[j].setForeground(Color.WHITE);
vLabel[j + 1].setBackground(Color.RED);
vLabel[j + 1].setForeground(Color.WHITE);
}
});
try
{
Thread.sleep(1500);
}
catch(InterruptedException ie)
{
ie.printStackTrace();
}
int left = Integer.parseInt(vLabel[i].getText());
int right = Integer.parseInt(vLabel[i + 1].getText());
if (left > right)
{
String temp = vLabel[i].getText();
vLabel[i].setText(vLabel[i + 1].getText());
vLabel[i + 1].setText(temp);
}
SwingUtilities.invokeAndWait(new Runnable()
{
#Override
public void run()
{
vLabel[j].setBackground(Color.YELLOW);
vLabel[j].setForeground(Color.RED);
vLabel[j + 1].setBackground(Color.YELLOW);
vLabel[j + 1].setForeground(Color.RED);
}
});
}
System.out.println("Pass : " + pass + "\tSize : " + size);
SwingUtilities.invokeAndWait(new Runnable()
{
#Override
public void run()
{
vLabel[size - pass].setBackground(Color.GREEN);
vLabel[size - pass].setForeground(Color.BLUE);
}
});
pass++;
}
SwingUtilities.invokeAndWait(new Runnable()
{
#Override
public void run()
{
vLabel[0].setBackground(Color.GREEN);
vLabel[0].setForeground(Color.BLUE);
}
});
}
}
}
I don't know where you heard about "SwingUtilities creates a new thread" from, but I think you've either misunderstood or being informed incorrectly. SwingUtilities.invokeLater places the Runnable onto the end of the Event Dispatcher's queue. The queue then processes this event within the it's own thread context (in time), calling Run, there is no "new" thread created for this process.
As to your question.
You may need to call validate() (and possibly repaint()) on the fields parent container to encourage it update ;)
And no, there isn't any other way to sync the UI across threads, that's the reason for SwingUtilities
Related
My project uses Java Swing as a GUI. I am making a Towers of Hanoi game. I've just about got the GUI all working, but my solve command wont work properly.
Without threading calls, it immediately solves the towers as expected. I added a couple Thread.waits expected it to solve it step by step so the user can see how it does but instead, it waits some time, then solves the entire puzzle at once. I'm thinking it might not be repainting, but I'm not sure why. Does anyone know what is going on?
Heres the code for the solve:
public class Solver {
public Solver() {
// nothing
}
public void solve(
int numberBlocks,
int startPin,
int auxiliaryPin,
int endPin) {
if (numberBlocks == 1) {
movePin(startPin, endPin);
try {
Thread.sleep(200);
}
catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
else {
solve(numberBlocks - 1, startPin, endPin, auxiliaryPin);
movePin(startPin, endPin);
try {
Thread.sleep(200);
}
catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
solve(numberBlocks - 1, auxiliaryPin, startPin, endPin);
}
}
private void movePin(int startPin, int endPin) {
TowersOfHanoiGame.moveTopBlock(startPin, endPin);
}
Here is the code from the GUI that does the work:
I know its terribly written, this is my first time writing with Java Swing, Im learning it as I go. If anyone has any pointers on how to better structure this, I'd love to hear about that also.
I'm pasting the whole class, but the important methods are initListeners, and moveTopBlock, and the methods they call.
import java.awt.BorderLayout;
import java.awt.Button;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
public class TowersOfHanoiGame {
private static JFrame mainWindow;
private static JPanel mainContentPanel;
private static JPanel content;
private static ArrayList<Block> pegOneBlocks = new ArrayList<Block>();
private static ArrayList<Block> pegTwoBlocks = new ArrayList<Block>();
private static ArrayList<Block> pegThreeBlocks = new ArrayList<Block>();
private Color[] randomColors = new Color[8];
private Dimension menuSize = new Dimension(100, 100);
private static final int DISCSTEXTSIZE = 20;
private static final int MOVESTEXTSIZE = 30;
private ActionListener downButtonListener;
private ActionListener upButtonListener;
private ActionListener solveButtonListener;
private JLabel discs;
private JLabel moves;
private int discsNumber = 3;
private int movesNumber = 0;
private Solver solver = new Solver();
public TowersOfHanoiGame() {
// do nothing
initRandomColors();
initBlocks(3);
}
/**
* Initialize and display the game
*/
public void display() {
initListeners();
initWindow();
mainWindow.setVisible(true);
}
private void initListeners() {
downButtonListener = new ActionListener() {
#Override
public void actionPerformed(ActionEvent arg0) {
if (discsNumber > 3) {
discsNumber--;
updateLabels();
// clearContentFrame();
clearBlockArrays();
initBlocks(discsNumber);
reDrawContentFrame();
}
}
};
upButtonListener = new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
if (discsNumber < 8) {
discsNumber++;
updateLabels();
// clearContentFrame();
clearBlockArrays();
initBlocks(discsNumber);
reDrawContentFrame();
}
}
};
solveButtonListener = new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
solver.solve(discsNumber, 0, 1, 2);
}
};
}
private void updateLabels() {
discs.setText("DISCS: " + discsNumber);
moves.setText("MOVES: " + movesNumber);
}
/**
* Init the main window
*/
private void initWindow() {
mainWindow = new JFrame("Towers Of Hanoi");
initContentPanel();
mainWindow.setContentPane(mainContentPanel);
mainWindow.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
mainWindow.setSize(1000, 1000);
mainWindow.setResizable(false);
mainWindow.getContentPane().setBackground(Color.WHITE);
}
/**
* Init the main content panel
*/
private void initContentPanel() {
mainContentPanel = new JPanel(new BorderLayout(50, 50));
JPanel menu = initMenuFrame();
content = initContentFrame();
mainContentPanel.add(menu, BorderLayout.PAGE_START);
mainContentPanel.add(content, BorderLayout.CENTER);
}
private static JPanel initContentFrame() {
JPanel ret = new JPanel(new BorderLayout());
JPanel pegs = new JPanel(new BorderLayout());
pegs.setBackground(Color.WHITE);
ret.setBackground(Color.WHITE);
Peg peg1 = new Peg(25, 500, 1.2);
Peg peg2 = new Peg(50, 500, 1.2);
Peg peg3 = new Peg(0, 500, 1.2);
peg1.addBlocks(pegOneBlocks);
peg2.addBlocks(pegTwoBlocks);
peg3.addBlocks(pegThreeBlocks);
pegs.add(peg1, BorderLayout.LINE_START);
pegs.add(peg2, BorderLayout.CENTER);
pegs.add(peg3, BorderLayout.LINE_END);
ret.add(pegs, BorderLayout.CENTER);
return ret;
}
private Color randomColor() {
int R = (int)(Math.random() * 256);
int G = (int)(Math.random() * 256);
int B = (int)(Math.random() * 256);
Color color = new Color(R, G, B); // random color, but can be bright or
// dull
// to get rainbow, pastel colors
Random random = new Random();
final float hue = random.nextFloat();
final float saturation = 0.9f;// 1.0 for brilliant, 0.0 for dull
final float luminance = 1.0f; // 1.0 for brighter, 0.0 for black
color = Color.getHSBColor(hue, saturation, luminance);
return color;
}
private void initRandomColors() {
for (int i = 0; i < 8; i++) {
randomColors[i] = randomColor();
}
}
private void initBlocks(int numBlocks) {
int startWidth = Block.LONGESTWIDTH;
for (int i = 0; i < numBlocks; i++) {
Block b = new Block((startWidth - (i * 15)), randomColors[i]);
pegOneBlocks.add(b);
}
}
private static void clearContentFrame() {
mainContentPanel.remove(content);
mainContentPanel.repaint();
}
private void clearBlockArrays() {
pegOneBlocks.clear();
pegTwoBlocks.clear();
pegThreeBlocks.clear();
}
public static void reDrawContentFrame() {
content = initContentFrame();
mainContentPanel.add(content, BorderLayout.CENTER);
mainContentPanel.repaint();
}
public static void moveTopBlock(int startPin, int destinationPin) {
Block b = null;
if (startPin == 0) {
b = pegOneBlocks.get(pegOneBlocks.size() - 1);
pegOneBlocks.remove(pegOneBlocks.size() - 1);
}
else if (startPin == 1) {
b = pegTwoBlocks.get(pegTwoBlocks.size() - 1);
pegTwoBlocks.remove(pegTwoBlocks.size() - 1);
}
else if (startPin == 2) {
b = pegThreeBlocks.get(pegThreeBlocks.size() - 1);
pegThreeBlocks.remove(pegThreeBlocks.size() - 1);
}
if (destinationPin == 0) {
pegOneBlocks.add(b);
}
else if (destinationPin == 1) {
pegTwoBlocks.add(b);
}
else if (destinationPin == 2) {
pegThreeBlocks.add(b);
}
reDrawContentFrame();
content.validate();
mainContentPanel.validate();
mainWindow.validate();
}
/**
* Build the menu panel
*
* #return menu panel
*/
private JPanel initMenuFrame() {
JPanel ret = new JPanel(new BorderLayout());
ret.setPreferredSize(menuSize);
// left
JPanel left = new JPanel(new FlowLayout());
left.setPreferredSize(menuSize);
JLabel label = new JLabel("DISCS: 3");
discs = label;
label.setFont(new Font("Serif", Font.BOLD, DISCSTEXTSIZE));
Button down = new Button("Decrease");
down.addActionListener(downButtonListener);
Button up = new Button("Increase");
up.addActionListener(upButtonListener);
left.add(label);
left.add(up);
left.add(down);
// mid
moves = new JLabel("MOVES: 0");
moves.setHorizontalAlignment(JLabel.CENTER);
moves.setFont(new Font("Serif", Font.BOLD, MOVESTEXTSIZE));
// right
JPanel right = new JPanel(new FlowLayout());
Button solve = new Button("Solve");
solve.addActionListener(solveButtonListener);
Button reset = new Button("Reset");
right.add(solve);
right.add(reset);
// sync
JPanel menu = new JPanel(new BorderLayout());
menu.add(left, BorderLayout.LINE_START);
menu.add(moves, BorderLayout.CENTER);
menu.add(right, BorderLayout.LINE_END);
ret.add(menu, BorderLayout.CENTER);
return ret;
}
}
solveButtonListener = new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
solver.solve(discsNumber, 0, 1, 2);
}
};
The problem is that code invoked for any listener is executed on the Event Dispatch Thread (EDT). The EDT is responsible for responding to event and repaint the GUI. The Thread.sleep() method causes the EDT to sleep and as a result the GUI can't repaint itself until all the code has finished executing.
What you need to do is start a separate Thread when you invoke the solver.solve(...) method.
Read the section from the Swing tutorial on Concurrency for more information.
Note, the above suggestion to use a separate Thread is still not a proper solution. Swing was designed to be single Thread, which means that all updates to the state of your GUI and the repainting of the GUI should be done on the EDT. So this would mean you should also be using SwingUtilities.invokeLater() to add code to the EDT for processing. I have never tried doing this when using recursion, so I'm not sure the best way to do this.
I am printing simple value to append JTextArea using simple for loop, and when I run it, it's properly Run if I print value in console output...
But if I append JTextArea and print value in the text area, they are appended all after whole program run.
public class SwingThread {
private JFrame frame;
/**
* Launch the application.
*/
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
SwingThread window = new SwingThread();
window.frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
/**
* Create the application.
*/
public SwingThread() {
initialize();
}
/**
* Initialize the contents of the frame.
*/
private void initialize() {
frame = new JFrame();
frame.setBounds(100, 100, 450, 300);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JScrollPane scrollPane = new JScrollPane();
frame.getContentPane().add(scrollPane, BorderLayout.CENTER);
JTextArea textArea = new JTextArea();
scrollPane.setViewportView(textArea);
JButton btnNewButton = new JButton("New button");
scrollPane.setColumnHeaderView(btnNewButton);
btnNewButton.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent arg0)
{
try
{
for(int i = 0 ; i <= 5 ; i++)
{
textArea.append("Value "+i+"\n");
System.out.println("Value is" + i);
Thread.sleep(1000);
}
}
catch(Exception e)
{
System.out.println("Error : "+e);
}
}
});
}
}
I want to append one by one, but it was appended after the whole program runs.
Your problem is with your use of Thread.sleep, since when you call this on the Swing event thread (or EDT for Event Dispatch Thread) as you are doing, it will put the entire Swing event thread to sleep. When this happens the actions of this thread cannot be performed, including painting the GUI (updating it) and interacting with the user, and this will completely freeze your GUI -- not good. The solution in this current situation is to use a Swing Timer as a pseudo-loop. The Timer creates a loop within a background thread and guarantees that all code within its actionPerformed method will be called on the Swing event thread, a necessity here since we don't want to append to the JTextArea off of this thread.
Also as others have noted, if all you want to do is to perform a repeated action with delay in Swing, then yes, use this Swing Timer. If on the other hand you wish to run a long-running bit of code in Swing, then again this code will block the EDT and will freeze your program. For this situation use a background thread such as one supplied by a SwingWorker. Please check out Lesson: Concurrency in Swing for more on this.
e.g.,
btnNewButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
// delay between timer ticks: 1000
int timerDelay = 1000;
new Timer(timerDelay, new ActionListener() {
private int counter = 0;
#Override
public void actionPerformed(ActionEvent e) {
// timer's stopping condition
if (counter >= MAX_VALUE) { // MAX_VALUE is a constant int = 5
((Timer) e.getSource()).stop();
} else {
textArea.append("Value " + counter + "\n");
}
counter++; // increment timer's counter variable
}
}).start();
}
});
The whole thing:
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.event.*;
import javax.swing.*;
public class SwingThread2 {
protected static final int MAX_VALUE = 5; // our constant
private JFrame frame;
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
SwingThread2 window = new SwingThread2();
window.frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
public SwingThread2() {
initialize();
}
private void initialize() {
frame = new JFrame();
// frame.setBounds(100, 100, 450, 300); // avoid this
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JScrollPane scrollPane = new JScrollPane();
frame.getContentPane().add(scrollPane, BorderLayout.CENTER);
JTextArea textArea = new JTextArea(15, 40);
scrollPane.setViewportView(textArea);
JButton btnNewButton = new JButton("New button");
scrollPane.setColumnHeaderView(btnNewButton);
btnNewButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
// delay between timer ticks: 1000
int timerDelay = 1000;
new Timer(timerDelay, new ActionListener() {
private int counter = 0;
#Override
public void actionPerformed(ActionEvent e) {
// timer's stopping condition
if (counter >= MAX_VALUE) { // MAX_VALUE is a constant int = 5
((Timer) e.getSource()).stop();
} else {
textArea.append("Value " + counter + "\n");
}
counter++; // increment timer's counter variable
}
}).start();
}
});
// better to avoid setting sizes but instead to
// let the components size themselves vis pack
frame.pack();
frame.setLocationRelativeTo(null);
}
}
Just for further information, here is an example of the same program above that uses a SwingWorker to perform a long running action, and then update a JProgressBar with this action. The worker is quite simple, and simply uses a while loop to advance a counter variable by a bounded random amount. It then transmits uses this value to update its own progress property (a value that can only be from 0 to 100, and so in other situations, the value will need to be normalized to comply with this). I attach a PropertyChangeListener to the worker, and this is notified on the Swing event thread whenever the worker's progress value changes and also whenever the SwingWorker changes state, such as when it is done operating. In the latter situation, the worker's StateValue becomes StateValue.DONE. The listener then updates the GUI accordingly. Please ask if any questions.
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.event.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Random;
import java.util.concurrent.ExecutionException;
import javax.swing.*;
public class SwingThread2 {
protected static final int MAX_VALUE = 5; // our constant
private JFrame frame;
private JProgressBar progressBar = new JProgressBar();
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
SwingThread2 window = new SwingThread2();
window.frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
public SwingThread2() {
initialize();
}
private void initialize() {
frame = new JFrame();
// frame.setBounds(100, 100, 450, 300); // avoid this
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JScrollPane scrollPane = new JScrollPane();
frame.getContentPane().add(scrollPane, BorderLayout.CENTER);
JTextArea textArea = new JTextArea(15, 40);
scrollPane.setViewportView(textArea);
JButton btnNewButton = new JButton("New button");
scrollPane.setColumnHeaderView(btnNewButton);
btnNewButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
// delay between timer ticks: 1000
int timerDelay = 1000;
new Timer(timerDelay, new ActionListener() {
private int counter = 0;
#Override
public void actionPerformed(ActionEvent e) {
// timer's stopping condition
if (counter >= MAX_VALUE) { // MAX_VALUE is a constant
// int = 5
((Timer) e.getSource()).stop();
} else {
textArea.append("Value " + counter + "\n");
}
counter++; // increment timer's counter variable
}
}).start();
}
});
progressBar.setStringPainted(true);
JPanel bottomPanel = new JPanel();
bottomPanel.setLayout(new BoxLayout(bottomPanel, BoxLayout.LINE_AXIS));
bottomPanel.add(new JButton(new MyAction("Press Me")));
bottomPanel.add(progressBar);
frame.getContentPane().add(bottomPanel, BorderLayout.PAGE_END);
// better to avoid setting sizes but instead to
// let the components size themselves vis pack
frame.pack();
frame.setLocationRelativeTo(null);
}
private class MyAction extends AbstractAction {
public MyAction(String name) {
super(name);
int mnemonic = (int) name.charAt(0);
putValue(MNEMONIC_KEY, mnemonic);
}
public void actionPerformed(ActionEvent e) {
progressBar.setValue(0);
setEnabled(false);
MyWorker myWorker = new MyWorker();
myWorker.addPropertyChangeListener(new WorkerListener(this));
myWorker.execute();
}
}
private class WorkerListener implements PropertyChangeListener {
private Action action;
public WorkerListener(Action myAction) {
this.action = myAction;
}
#Override
public void propertyChange(PropertyChangeEvent evt) {
if ("progress".equals(evt.getPropertyName())) {
int progress = (int) evt.getNewValue();
progressBar.setValue(progress);
} else if ("state".equals(evt.getPropertyName())) {
if (evt.getNewValue() == SwingWorker.StateValue.DONE) {
action.setEnabled(true);
#SuppressWarnings("rawtypes")
SwingWorker worker = (SwingWorker) evt.getSource();
try {
// always want to call get to trap and act on
// any exceptions that the worker might cause
// do this even though get returns nothing
worker.get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
}
}
private class MyWorker extends SwingWorker<Void, Void> {
private static final int MULTIPLIER = 80;
private int counter = 0;
private Random random = new Random();
#Override
protected Void doInBackground() throws Exception {
while (counter < 100) {
int increment = random.nextInt(10);
Thread.sleep(increment * MULTIPLIER);
counter += increment;
counter = Math.min(counter, 100);
setProgress(counter);
}
return null;
}
}
}
I am creating a program to take in sets of binary digits and convert them into hammingcodes (Effectively take in 8 digits, turn into 12, print out) but i am having trouble. Currently, i am using a JTextField for the user to enter their number, then they press a JButton to enter the data. I then do funky shit with that number to put it into a list and confirm that this is the last of the numbers they wish to enter. If they click a JButton called yes (New text in button, but same button) if goes on to do what i need. But if they click the other JButton called no, it goes back and repeats the same process. My problem is after clicking no once, the program stops allowing you to press no at the step to check if you want to add another list of numbers. IT appears to skip the check all together and assume they pressed yes as it does the rest of the working out thats done after all entry is finished.
My code is a tad messy due to messing with it for a few hours.
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.*;
import javax.swing.*;
public class MainProgram extends JFrame
{
public MainProgram()
{
}
public static void main(String[] args)
{
MainProgram mp = new MainProgram();
mp.run();
}
private void run()
{
java.util.List<Integer> streamSplit = new ArrayList<>();
java.util.List<Integer> tempEight = new ArrayList<>();
java.util.List<Integer> finalStream = new ArrayList<>();
yes.addActionListener(new ActionListener()
{
#Override
public void actionPerformed(ActionEvent e)
{
checkYes = true;
}
});
no.addActionListener(new ActionListener()
{
#Override
public void actionPerformed(ActionEvent e)
{
checkNo = true;
}
});
init();
yesChange("Enter");
boolean confirm = false;
int aCheck = 0;
while (aCheck == 0)
{
confirm = false;
while (!confirm)
{
setTopText("<html>Please enter your next 8 bits. Do not enter more than 8 bits.<br> Press Enter when done</html>");
yesChange("Enter");
confirm = checkYes();
}
confirm = false;
setTopText("Digits Successfully added.");
int stream = checkInput();
do
{
streamSplit.add(stream % 10);
stream /= 10;
} while (stream != 0);
setYesNo();
setTopText("<html>Are you finished entering streams?</html>");
yesChange("YES");
noChange("NO");
aCheck = 2;
checkYes();
checkNo();
while (aCheck == 2)
{
if ( checkNo())
{
aCheck = 0;
System.out.println("CrapNo");
}
else if (checkYes())
{
aCheck = 1;
System.out.println("CrapYes");
}
}
}
int arrayLength = streamSplit.size();
int bufferLength = 8 - arrayLength % 8;
int numberOfStreams = 0;
if (bufferLength != 8)
{
numberOfStreams = arrayLength / 8 + 1;
} else
{
numberOfStreams = arrayLength / 8;
}
int tempStreams = numberOfStreams;
System.out.println(numberOfStreams + "<Streams Buffer>" + bufferLength);
while (bufferLength > 0 && bufferLength != 8)
{
streamSplit.add(0);
bufferLength--;
}
while (tempStreams > 0)
{
for (int i = 0; i < 8; i++)
{
tempEight.add(streamSplit.get(i));
}
if ((tempEight.get(0) + tempEight.get(1) + tempEight.get(3) + tempEight.get(4) + tempEight.get(6)) % 2 == 0)
{
tempEight.add(0, 0);
} else
{
tempEight.add(0, 1);
}
if ((tempEight.get(1) + tempEight.get(3) + tempEight.get(5) + tempEight.get(6) + tempEight.get(7)) % 2 == 0)
{
tempEight.add(1, 0);
} else
{
tempEight.add(1, 1);
}
if ((tempEight.get(3) + tempEight.get(4) + tempEight.get(5) + tempEight.get(8) + tempEight.get(9)) % 2 == 0)
{
tempEight.add(3, 0);
} else
{
tempEight.add(3, 1);
}
if ((tempEight.get(7) + tempEight.get(8) + tempEight.get(9) + tempEight.get(10)) % 2 == 0)
{
tempEight.add(7, 0);
} else
{
tempEight.add(7, 1);
}
tempStreams--;
for (int i = 0; i < 12; i++)
{
finalStream.add(tempEight.get(0));
tempEight.remove(0);
}
}
Collections.reverse(streamSplit);
System.out.print("Your original bit-stream was: ");
for (int i = 0; i < numberOfStreams * 2; i++)
{
for (int j = 0; j < 4; j++)
{
System.out.print(streamSplit.get(j + (i * 4)));
}
System.out.print(" ");
}
System.out.println();
System.out.print("Your new HammingCode bit-stream is: ");
for (int i = 0; i < numberOfStreams * 3; i++)
{
for (int j = 0; j < 4; j++)
{
System.out.print(finalStream.get(j + (i * 4)));
}
System.out.print(" ");
}
System.out.println();
}
public Boolean checkYes = false;
public Boolean checkNo = false;
private JFrame frame = new JFrame("Absolute Layout Example");
private JPanel contentPane = new JPanel();
private JLabel topText = new JLabel("Welcome to my Hamming Code Generator", JLabel.CENTER);
private JTextField inputText = new JTextField();
private JButton yes = new JButton("YES");
private JButton no = new JButton("NO");
public void init()
{
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
contentPane.setOpaque(true);
contentPane.setBackground(Color.WHITE);
contentPane.setLayout(null);
topText.setLocation(0, 0);
topText.setSize(400, 50);
topText.setBorder(BorderFactory.createLineBorder(Color.black));
inputText.setLocation(0,50);
inputText.setSize(400,75);
inputText.setBorder(BorderFactory.createLineBorder(Color.black));
yes.setSize(80, 40);
yes.setLocation(60, 135);
no.setSize(80, 40);
no.setLocation(260, 135);
contentPane.add(topText);
contentPane.add(inputText);
contentPane.add(yes);
contentPane.add(no);
frame.setContentPane(contentPane);
frame.setSize(400, 225);
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public void setTopText(String s)
{
topText.setText(s);
}
public void setYesNo()
{
checkYes = false;
checkNo = false;
}
public Boolean checkYes() {return checkYes;}
public Boolean checkNo() {return checkNo;}
public int checkInput()
{
String temp1 = inputText.getText();
int temp = Integer.parseInt(temp1);
return temp;
}
public void yesChange(String s)
{
yes.setText(s);
}
public void noChange(String s)
{
no.setText(s);
}
}
I find it tough to answer this question not fully knowing what your code is doing, especially the part where you "... do funky #### with that number..."
But I do know that you have significant issues with your program structure, especially within your lengthy run() method where you have numerous nested while (...) loops and do-while loops, code constructs that might seem at home within a linear processing console program but which seems out of place within an event-driven Swing GUI.
What I suggest that you do is try to use some state-dependent coding. For instance, you could give your class the boolean variables, enteringData and dataValidYet, to represent two key states: whether the user is now entering data into the JTextField, and whether that data has yet been validated yet. And then within your JButton ActionListeners, use if and if/else blocks to decide what to do on button push depending on the state of these boolean fields, and likely other key fields of the class.
For a code "skeleton" example, one that doesn't yet do anything, but hopefully will show you the structure I'm talking about:
import java.awt.BorderLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
#SuppressWarnings("serial")
public class StateMachine extends JPanel {
private static final String INITIAL_TITLE = "Please enter your next 8 bits. "
+ "Do not enter more than 8 bits.\n"
+ "Press Enter when done";
private static final String ARE_YOU_FINISHED = "Are you finished entering streams?";
private static final String YES = "Yes";
private static final String ENTER = "Enter";
private static final String NO = "No";
private static int GAP = 8;
private static final int COLUMNS = 30;
// this is a JTextArea built to look like a JLabel
private JTextArea topTextArea = new JTextArea(2, COLUMNS);
private JTextField dataEntryField = new JTextField(COLUMNS);
private JButton yesEnterButton = new JButton(ENTER);
private JButton noButton = new JButton(NO);
private boolean enteringData = true;
private boolean dataValidYet = false;
public StateMachine() {
yesEnterButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
yesEnterButtonActionPerfromed(e);
}
});
noButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
noButtonActionPerfromed(e);
}
});
topTextArea.setWrapStyleWord(true);
topTextArea.setLineWrap(true);
topTextArea.setFocusable(false);
topTextArea.setEditable(false);
topTextArea.setOpaque(false);
topTextArea.setText(INITIAL_TITLE);
JPanel innerButtonPanel = new JPanel(new GridLayout(1, 0, GAP, 0));
innerButtonPanel.add(yesEnterButton);
innerButtonPanel.add(noButton);
JPanel outerButtonPanel = new JPanel();
outerButtonPanel.add(innerButtonPanel);
setBorder(BorderFactory.createEmptyBorder(GAP, GAP, GAP, GAP));
setLayout(new BorderLayout(GAP, GAP));
add(topTextArea, BorderLayout.PAGE_START);
add(dataEntryField, BorderLayout.CENTER);
add(outerButtonPanel, BorderLayout.PAGE_END);
}
protected void noButtonActionPerfromed(ActionEvent e) {
// TODO depending on state of enteringData and dataValidYet booleans
// change text in buttons, do things with JTextField data
// set state of enteringData and dataValidYet booleans
if (enteringData) {
// a no press is meaningless if entering data
return;
}
// .... more
}
private void yesEnterButtonActionPerfromed(ActionEvent e) {
// TODO depending on state of enteringData and dataValidYet booleans
// change text in buttons, do things with JTextField data
// set state of enteringData and dataValidYet booleans
if (enteringData) {
topTextArea.setText(ARE_YOU_FINISHED);
yesEnterButton.setText(YES);
yesEnterButton.setActionCommand(YES);
enteringData = false;
return;
}
// .... more
}
private static void createAndShowGui() {
StateMachine mainPanel = new StateMachine();
JFrame frame = new JFrame("State Machine");
frame.setDefaultCloseOperation(JFrame.DISPOSE_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();
}
});
}
}
Also, as a "side" recommendation, one unrelated to your main problem, understand that null layouts and setBounds() might seem to Swing newbies like the easiest and best way to create complex GUI's, the more Swing GUI'S you create the more serious difficulties you will run into when using them. They won't resize your components when the GUI resizes, they are a royal witch to enhance or maintain, they fail completely when placed in scrollpanes, they look gawd-awful when viewed on all platforms or screen resolutions that are different from the original one.
Note that if this were my program, I would use more indirection including creating separate classes to separate out the GUI portion of the program from the logic portion.
i have a fun project where i need to change the content of a text area inside a iteration.
Its a character, a "projectile", moving trought a string. The string is updated and sent to the textArea inside the iteration, and the iteration stops when the character reaches a wall.
But my textArea only updates (visually) when i leave the iteration. While im inside it, textArea freezes, as if its waiting for the iteration, even with Thread.sleep() inside it.
I made an MVCE exemplifing the problem bellow, notice the text only shows after the iteration, i want it to apper in every step of the while.
public class GUIProblem extends JFrame{
public GUIProblem() {
setSize(640, 480);
JPanel panel = new JPanel();
getContentPane().add(panel, BorderLayout.CENTER);
final JTextArea textArea = new JTextArea();
textArea.setRows(10);
textArea.addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent arg0) {
int i = 0;
while(i < 10){
textArea.setText("this text only appears after the iteration, i want it to appear in each step of the iteration!");
System.out.println("iterating..." + i++);
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
textArea.setColumns(30);
panel.add(textArea);
}
/**
*
*/
private static final long serialVersionUID = 1L;
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
GUIProblem gui = new GUIProblem( );
gui.setVisible(true);
}
});
JOptionPane.showMessageDialog(null, "Click the textArea!");
}
}
You've a classic Swing threading issue where you stop the Swing event thread in its tracks with your iteration and its Thread.sleep() calls. The solution is the same as for similar questions: use a Swing Timer or background thread such as a SwingWorker. In your case, use the Timer.
For example, since you posted an MCVE
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class GUIProblem extends JFrame {
public GUIProblem() {
// setSize(640, 480);
JPanel panel = new JPanel();
getContentPane().add(panel, BorderLayout.CENTER);
final JTextArea textArea = new JTextArea(20, 50);
textArea.addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent mEvt) {
int i = 0;
int timerDelay = 200;
new Timer(timerDelay, new ActionListener() {
int count = 0;
private final int MAX_COUNT = 10;
#Override
public void actionPerformed(ActionEvent e) {
if (count >= MAX_COUNT) {
((Timer) e.getSource()).stop(); // stop the timer
return;
}
textArea.append("Count is: " + count + "\n");
count++;
}
}).start();
}
});
panel.add(new JScrollPane(textArea));
}
/**
*
*/
private static final long serialVersionUID = 1L;
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
GUIProblem gui = new GUIProblem();
gui.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
gui.pack();
gui.setLocationRelativeTo(null);
gui.setVisible(true);
}
});
JOptionPane.showMessageDialog(null, "Click the textArea!");
}
}
I'm trying to learn SwingWorker and I have this simple code running and working, but I can't figure out how to prompt a JOptionPane.showMessageDialog when the task is complete. I've to tried to put
if (isDone())
JOptionPane.showMessageDialog(null, "Task Complete");
in different locations, but can't get anything to work. I've read that I may have to put it in the invokeLater() for it to run in the EDT, I'm not really sure how to accomplish that.
I tried to have my SwingWorker as a class member of my panel, but I can't instantiate it because it gets intantiaated in a listener. So I get a null pointer trying to put if (task.isDone()) in my invokeLater().
What is the proper way to accomplish this task?
I have an SSCCE here (all you do is enter a number and it prints my name that many times to a text area).
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.List;
import javax.swing.*;
public class RepeatNameSwingWorker extends JPanel {
private JProgressBar jpb = new JProgressBar(0, 100);
private JTextArea jtaNames = new JTextArea(20, 50);
private final JTextField jtfTimes = new JTextField(8);
private final JButton jbtExecute = new JButton("Execute");
private final JLabel jlblNumTimes = new JLabel("Enter number of times: ");
public RepeatNameSwingWorker(){
jpb.setStringPainted(true);
jtaNames.setLineWrap(true);
jtaNames.setWrapStyleWord(true);
JPanel panel = new JPanel();
panel.add(jlblNumTimes);
panel.add(jtfTimes);
panel.add(jbtExecute);
setLayout(new BorderLayout());
add(jpb, BorderLayout.NORTH);
add(new JScrollPane(jtaNames), BorderLayout.CENTER);
add(panel, BorderLayout.SOUTH);
jbtExecute.addActionListener(new ActionListener(){
#Override
public void actionPerformed(ActionEvent e) {
RepeatNames task = new RepeatNames(
Integer.parseInt(jtfTimes.getText()), jtaNames);
task.addPropertyChangeListener(new PropertyChangeListener(){
#Override
public void propertyChange(PropertyChangeEvent e) {
if ("progress".equals(e.getPropertyName())) {
jpb.setValue((Integer)e.getNewValue());
}
}
});
task.execute();
if (task.isDone())
JOptionPane.showMessageDialog(null, "Task Complete");
}
});
}
static class RepeatNames extends SwingWorker<String, String>{
int times;
JTextArea result;
public RepeatNames(int times, JTextArea result) {
this.times = times;
this.result = result;
}
#Override
protected String doInBackground(){
publishNames(times);
return null;
}
#Override
protected void done() {
try {
result.append(get().toString()); // Display in text field
} catch (Exception ex) {
result.append(ex.getMessage());
}
}
#Override
protected void process(List<String> list) {
for (int i = 0; i < list.size(); i++) {
result.append(list.get(i) + " ");
}
}
private void publishNames(int n) {
int count = 0;
int number = 2;
while (count <= n) {
if (isPrime(number)) {
count++;
setProgress(100 * count / n);
publish("Paul");
}
number++;
}
}
private static boolean isPrime(int number) {
for (int divisor = 2; divisor <= number / 2; divisor++) {
if (number % divisor == 0) {
return false;
}
}
return true;
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
frame.add(new RepeatNameSwingWorker());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationByPlatform(true);
frame.pack();
frame.setVisible(true);
}
});
}
}
, but I can't figure out how to prompt a JOptionPane.showMessageDialog when the task is complete
Show the JOptionPane in the done() method.