I'm trying to learn how to properly use multi-threading and for some reason my code keeps freezing after one iteration through updating my progressbar. I've got the progressbar setup correctly because I've tested that on the side. I thought I had my multi-threading correct but the two classes dont seem to be communicating right. Can anyone with a trained eye spot my error here? Thank you
import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JProgressBar;
import javax.swing.border.Border;
public class ProgressBar {
static int j = 0;
static int n = 0;
synchronized public static void main(String args[]) {
double percent = .01;
n = Integer.parseInt(JOptionPane.showInputDialog("pick a really big number for no reason"));
//this block is just the progress bar
JFrame f = new JFrame("Progress Bar");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Container content = f.getContentPane();
JProgressBar progressBar = new JProgressBar();
progressBar.setValue(0);
progressBar.setStringPainted(true);
Border border = BorderFactory.createTitledBorder("Reading...");
progressBar.setBorder(border);
content.add(progressBar, BorderLayout.NORTH);
f.setSize(300, 100);
f.setVisible(true);
//this block is just the progress bar
Computation c = new Computation();
c.start();
while(percent <= 1.0){
if( j >= (n*percent) ){
progressBar.setValue((int) (percent*100));
progressBar.repaint();
percent += .01;
}
else{
continue;
}
}
}
public static class Computation extends Thread{
synchronized public void run(){
while( j < n ){
j++;
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
You need to synchronize the access to the shared data. The synchronization need to be done using the same object. Here is one solution, maybe not the best but at least it works. Also note the loop surrounding the wait. This is typical, to check whether you have met the condition to carry on.
import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JProgressBar;
import javax.swing.border.Border;
import java.awt.*;
import java.util.concurrent.locks.Condition;
public class ProgressBar {
static final Object lock = new Object();
static int j = 0;
static int n = 0;
public static void main(String args[]) throws InterruptedException {
double percent = .01;
n = Integer.parseInt(JOptionPane.showInputDialog("pick a really big number for no reason"));
//this block is just the progress bar
JFrame f = new JFrame("Progress Bar");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Container content = f.getContentPane();
JProgressBar progressBar = new JProgressBar();
progressBar.setValue(0);
progressBar.setStringPainted(true);
Border border = BorderFactory.createTitledBorder("Reading...");
progressBar.setBorder(border);
content.add(progressBar, BorderLayout.NORTH);
f.setSize(300, 100);
f.setVisible(true);
//this block is just the progress bar
Computation c = new Computation();
c.start();
while (percent <= 1.0) {
synchronized (lock) {
while (j < n * percent) {
lock.wait();
}
}
progressBar.setValue((int) (percent * 100));
progressBar.repaint();
percent += .01;
}
}
public static class Computation extends Thread {
public void run(){
while(true) {
synchronized (lock) {
if (j >= n) break;
j++;
lock.notify();
}
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
Related
We are using foxtrot package for stop freeze the swing application.
But in this below code it make a deadlock.
import java.awt.Container;
import java.awt.Dimension;
import java.awt.GridBagLayout;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
import foxtrot.Task;
import foxtrot.Worker;
public class FoxtrotExample extends JFrame {
public static void main(String[] args) {
FoxtrotExample example = new FoxtrotExample();
example.setVisible(true);
}
boolean st = true;
public FoxtrotExample() {
super("Foxtrot Example");
final JButton button = new JButton("Take a nap !");
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
System.out.println("Start..");
button.setText("Sleeping...");
String text = null;
try {
text = (String) Worker.post(new Task() {
public Object run() throws Exception {
System.out.println("Inside Worker 1");
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
try {
System.out.println("Inside invokeLater");
Worker.post(new Task() {
#Override
public Object run()
throws Exception {
System.out.println("Inside Worker 2");
st = false;
return null;
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
});
while (st) {
System.out.println("Inside the loop..");
try {
Thread.sleep(1000);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
}
return "Slept !";
}
});
} catch (Exception x) {
}
button.setText(text);
System.out.println("Finished.....");
}
});
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
Container c = getContentPane();
c.setLayout(new GridBagLayout());
c.add(button);
setSize(300, 200);
Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
Dimension size = getSize();
int x = (screen.width - size.width) >> 1;
int y = (screen.height - size.height) >> 1;
setLocation(x, y);
}
}
If use ConcurrentWorker this will work fine.Can any one explane this.
I am bit confuse how EDT behave here ?
This is the result of my program.
Start 1st worker
In the loop
Start invoke later
In the loop
In the loop
In the loop
In the loop
......
It start the 1st worker.Then part of the code is in invokeLater.So request is enqued in the event queue and start the loop.Later execute the invokeLater but not execute the 2nd worker because first worker still doing some work.Since worker are ruining one after another and it runs on a single worker queue 2nd worker cannot execute and deadlock comes.
Thanks to MadProgrammer i understood this.Hope this is correct.
I am gonna re-post this questione again trying to be more precise and hoping I will get some help because this is driving me crazy. I am developing a board game with up to 6 player, each one with a different colored pawn. I have the following image that is loaded in BufferedImage arrays treating it as a sprite:
and this is the relative code, putting each face of each colored die in a position in the BufferedImage[]:
private BufferedImage[] initAnimationBuffer() {
BufferedImage[] result = new BufferedImage[36];
for (int i = 0; i < 6; i++) {
for (int j = i; j < 6 + i; j++)
result[i + j] = DieSprite.getSprite(j, i, 0);
}
return result;
}
Then each player, according to his color, wil have also the following matrix containing the faces of his color according to the obtained die value/position. In other words this matrix contains "a line" of the image and it is indexed by value:
private BufferedImage[][] initExactDieFaces() {
BufferedImage[][] result = new BufferedImage[6][1];
int row = -1;
String myColor = this.coreGame.getMyPartecipant().getColor();
if (myColor.equals(Constants.COLOR[0])) {
row = 0;
} else if (myColor.equals(Constants.COLOR[1])) {
row = 2;
} else if (myColor.equals(Constants.COLOR[2])) {
row = 4;
} else if (myColor.equals(Constants.COLOR[3])) {
row = 1;
} else if (myColor.equals(Constants.COLOR[4])) {
row = 5;
} else if (myColor.equals(Constants.COLOR[5])) {
row = 3;
}
int offset = 0;
for (int i = 0; i < 6; i++) {
result[i][0] = DieSprite.getSprite(row, i, offset);
offset += 2;
}
return result;
}
What I want is the following:
-when the "flip die" button is pressed, I want that (for example) 20 random die faces are shown (they should be taken from the first array, AnimationBuffer) in a specific JLabel inside a JPanel
-as soon as the previous animation has finished, I want that the obtained result of the launch of the die is shown (according to the color pawn, taken from ExcatDieFaces).
To get this I know that I need Swing Timer but I am not able to put it all together; here's some code of the startAnimationDie method which is called when the "flip die" button is pressed:
private void startAnimationDie(final JPanel dieContainer) {
final BufferedImage[] animationBuffer = initAnimationBuffer();
final BufferedImage[][] exactDieFaces = initExactDieFaces();
final AnimationSprite animation = new AnimationSprite(
animationBuffer, Constants.DIE_ANIMATION_SPEED);
/* getting launch value fromt the core Game */
int launchResult = coreGame.launchDie();
coreGame.getMyPartecipant().setLastLaunch(launchResult);
final Timer timer = new Timer(250, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
dieContainer.removeAll();
dieContainer.updateUI();
animation.start();
JLabel resultDie = new JLabel();
resultDie.setBounds(60, 265, Constants.DIE_SIZE,Constants.DIE_SIZE);
resultDie.setIcon(new ImageIcon(animationBuffer[new Random().nextInt(36)]));
dieContainer.add(resultDie);
dieContainer.updateUI();
updateUI();
repaint();
}
});
/* animation begins, rolling faces are shown each time the Timer ends*/
for(int i = 0; i<20; i++)
timer.start()
/* showing the final face according to the pawn color and the obtained result from the launch */
dieContainer.removeAll();
dieContainer.updateUI();
AnimationSprite resultAnimation = new AnimationSprite(exactDieFaces[launchResult - 1], 6);
resultAnimation.start();
resultAnimation.update();
resultDie.setIcon(new ImageIcon(exactDieFaces[launchResult - 1][0]));
resultDie.setBounds(60, 265, Constants.DIE_SIZE, Constants.DIE_SIZE);
dieContainer.add(resultDie);
dieContainer.updateUI();
dieContainer.repaint();
}
How can I make it work? I think I am supposed to use Swing.invokeAndWait but I cannot put together all the pieces...Can you help please?
Don't call updateUI, unless you're dealing with installing a look and feel, it's not doing what you think it is (and it's very inefficient)
Don't rebuild the UI each time, this is time consuming work, which is going to make the animation look stilled and staggered and probably flash a lot. Instead, simply update the icon property of the label
Use a single Timer, allow it to increment a counter, so you know how many times it's been called and update the die roll and counter on each tick.
Think of the Timer as a kind of loop, where on each iteration (tick), you need to do something (like increment the counter)
(Note- When it looks like the die has "stalled", it's because the image is been displayed more then once in sequence. You could over come this by placing all the images into a List and using Collections.shuffle. Do this three times, adding the result to another List should give you 24, no-repeating sequence (ok, it "might" repeat on the boundaries, but it's better then using Math.random ;))
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private BufferedImage[] dice = new BufferedImage[6];
private JLabel die;
public TestPane() {
try {
BufferedImage img = ImageIO.read(new File("/Users/swhitehead/Documents/Die.png"));
int width = 377 / 6;
for (int index = 0; index < 6; index++) {
dice[index] = img.getSubimage(width * index, 0, width, width);
}
} catch (IOException ex) {
ex.printStackTrace();
}
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridwidth = GridBagConstraints.REMAINDER;
die = new JLabel(new ImageIcon(dice[0]));
add(die, gbc);
JButton roll = new JButton("Roll");
add(roll, gbc);
roll.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
roll.setEnabled(false);
Timer timer = new Timer(250, new ActionListener() {
private int counter;
private int lastRoll;
#Override
public void actionPerformed(ActionEvent e) {
if (counter < 20) {
counter++;
lastRoll = (int)(Math.random() * 6);
System.out.println(counter + "/" + lastRoll);
die.setIcon(new ImageIcon(dice[lastRoll]));
} else {
lastDieRollWas(lastRoll);
((Timer)e.getSource()).stop();
roll.setEnabled(true);
}
}
});
timer.start();
}
});
}
protected void lastDieRollWas(int roll) {
System.out.println("You rolled " + (roll + 1));
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
}
}
I'm trying to make a program that generates random numbers if the user presses a button. It should stop generating them, when the user presses the button the second time, and then it should print all the random numbers added together and the average random number, but I don't know how I should do this. When I'm in the loop i can't press the button. I'll be thankful for any help. My Code:
package Test;
import java.awt.Dimension;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
public class Average
{
static int sizeX = 200;
static int sizeY = 200;
static int maxNum = 100;
static int minNum = 1;
static boolean running = true;
static JButton b1 = new JButton("Click me");
static void JFrame()
{
Toolkit tk = Toolkit.getDefaultToolkit();
Dimension dim = tk.getScreenSize();
JFrame f = new JFrame("Test");
f.setSize(sizeX, sizeY);
f.setVisible(true);
f.setLocation((dim.width - sizeX) / 2, (dim.height - sizeY) / 2);
f.setResizable(false);
f.setAutoRequestFocus(true);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(b1);
}
static void ActionListener()
{
b1.addActionListener(new ActionListener()//ActionListener
{
public void actionPerformed(ActionEvent e)//Execute when button is pressed
{
int numsGenerated = 0;
double ave = 0;
if (running == true)
{
while (true)
{
double r = Math.random() * (maxNum - minNum) + minNum; //(maxNum - minNum) + minNum
numsGenerated++;
ave = ave + r;
System.out.println("Random: " + r);
if (!running)
{
break;
}
}
running = false;
}
else
{
System.out.println("");
System.out.println("All: " + ave);
System.out.println("Average: " + ave / numsGenerated);
running = true;
}
}
}); //ActionListenerEnd
}
public static void main(String[] args)//Main
{
JFrame();
ActionListener();
}
}
As Ordous told in a comment, you should make a different thread for this work. But you may need to synchronize on "running" variable to make sure it works always.
import java.awt.Dimension;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
public class Average
{
static Object lock=new Object();
static int sizeX = 200;
static int sizeY = 200;
static int maxNum = 100;
static int minNum = 1;
static boolean running = false;
static JButton b1 = new JButton("Click me");
static void JFrame()
{
Toolkit tk = Toolkit.getDefaultToolkit();
Dimension dim = tk.getScreenSize();
JFrame f = new JFrame("Test");
f.setSize(sizeX, sizeY);
f.setVisible(true);
f.setLocation((dim.width - sizeX) / 2, (dim.height - sizeY) / 2);
f.setResizable(false);
f.setAutoRequestFocus(true);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(b1);
}
public class Thr extends Thread
{
#Override
public void run()
{
int numsGenerated = 0;
double ave = 0;
synchronized(lock)
{
running=!running;
}
if (running == true)
{
while (true)
{
double r = Math.random() * (maxNum - minNum) + minNum; //(maxNum - minNum) + minNum
numsGenerated++;
ave = ave + r;
System.out.println("Random: " + r);
synchronized(lock)
{
if (!running)
{
break;
}
}
}
synchronized(lock)
{
running = false;
}
}
System.out.println("");
System.out.println("All: " + ave);
System.out.println("Average: " + ave / numsGenerated);
running = true;
}
}
static Thr thread=null;
static void ActionListener()
{
b1.addActionListener(new ActionListener()//ActionListener
{
public void actionPerformed(ActionEvent e)//Execute when button is pressed
{
Average av=new Average();
if(thread==null)
{
thread=av.new Thr(); // should add in an executor
thread.start();
}
else
{
synchronized(lock)
{
running=false;
}
}
}
}); //ActionListenerEnd
}
public static void main(String[] args)//Main
{
JFrame();
ActionListener();
}
}
To have a simple thread, "Thread" is inherited and run() methoud must be overridden to use it. (you can use a "Runnable" instead of this too)
public class Thr extends Thread
{
#Override
public void run()
{
}
}
You start it like
threadName.Start()
or
ExecutorObject.submit(threadName);
where executor object has a pool for threads to run efficiently and is created by executor service.
Do not start threads with
threadname.run()
directly.
Also inter-thread communication is important. One thread leaves a message to somewhere, another thread reads. If there is no synchronization, they can race for the variable's visibility and wrong output can happen.
You can have an object to lock on.
synchronized(lock)
{
}
so this block can be entered by only single thread until it exits from this block. You should leave a message for other threads, to say "its ready" and wake them up(iff they are waiting) with
synchronized(lock)
{
lock.notifyAll();
}
then make the message-leaving thread (current thread) go on a waiting state so other threads can wake it up and let it exit the block.
synchronized(lock)
{
lock.notifyAll();
lock.wait();
}
but this alone is not enough because current thread accidentally exit the waiting state so you may force it to keep waiting:
synchronized(lock)
{
lock.notifyAll();
while(condition)
lock.wait();
}
if you make sure only a single thread can access a variable at a time, you can use it to communicate with other threads.
You can use some other ways to have barriers to synchronize all threads on a point so no threads can continue until all hit the same barrier. More communication can decrease overall performance.
Main program is a thread too, so it can wait, notify and synchronize.
Just want the color of the letters to change with a little pauses (pause may vary as per the time given for a word and length of the word).
The following code works fine for me.But I think I have created a mess with my logic.I can understand, but it should be easy for my team mates to understand.
Code:
import java.awt.Color;
import java.lang.reflect.InvocationTargetException;
import java.awt.Toolkit;
import javax.swing.JPanel;
import javax.swing.JTextPane;
import javax.swing.SwingUtilities;
import javax.swing.text.DefaultStyledDocument;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyledDocument;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.Timer;
public class Reminder
{
static JFrame frame;
Toolkit toolkit;
Timer timer;
int point=0,temp=0,hil=0,point2=0;long time1=0,time2=0;
static StyledDocument doc;
static JTextPane textpane;
String[] arr={"Tes"," hiiii"," what"," happpn"};
int i=0;
int[] a=new int[5];
public Reminder()
{
a[0]=1000;
a[1]=900;
a[2]=300;
a[3]=1500;
a[4]=1700;
ActionListener actionListener = new ActionListener()
{
public void actionPerformed(ActionEvent actionEvent)
{
point =arr[i].length();
temp=point+1;
time1=System.currentTimeMillis();
new Thread(new t1()).start();
}
};
timer = new Timer(a[i], actionListener);
timer.setInitialDelay(0);
timer.start();
}
public class t1 implements Runnable
{ /* true idea to use current time is beacuse i want to check and make
sure that the time taken since the timer started, and the present time should
not exceed the time given in the array in any case*/
public void run()
{
try
{
time2=System.currentTimeMillis();
while(time2-time1<=a[i]-200){Thread.sleep((long) (a[i] / (arr[i].length() * 4)));
if(hil<=temp-1)
{
doc.setCharacterAttributes(point2,hil, textpane.getStyle("Red"), true);}
hil++;
time2=System.currentTimeMillis();
}
doc.setCharacterAttributes(point2,point+1, textpane.getStyle("Red"), true);
point2+=point;hil=0;i++;
timer.setDelay(a[i]);
}
catch(Exception e)
{
e.printStackTrace();
}
}
}
public static void newcompo()
{
JPanel panel = new JPanel();
doc = (StyledDocument) new DefaultStyledDocument();
textpane = new JTextPane(doc);
textpane.setText("Test hiiii what happpn");
javax.swing.text.Style style = textpane.addStyle("Red", null);
StyleConstants.setForeground(style, Color.RED);
panel.add(textpane);
frame.add(panel);
frame.pack();
}
public static void main(String args[]) throws InterruptedException
, InvocationTargetException
{
SwingUtilities.invokeAndWait(new Runnable()
{
#Override
public void run()
{
frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
newcompo();
Reminder aa= new Reminder();
}
});
}
}
Any suggestions?How can I simplify?
UPDATE FOR ERROR
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import javax.swing.AbstractAction;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JTextPane;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.text.Style;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyledDocument;
public class KaraokeTest {
private int[] timingsArray = {1000, 900, 300, 1500};//word/letters timings
private String[] individualWordsToHighlight = {"Tes", " hiiii", " what", " happpn"};//each individual word/letters to highlight
private int count = 0;
private final JTextPane jtp = new JTextPane();
private final JButton startButton = new JButton("Start");
private final JFrame frame = new JFrame();
public KaraokeTest() {
initComponents();
}
private void initComponents() {
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setResizable(false);
for (String s : individualWordsToHighlight) {
String tmp = jtp.getText();
jtp.setText(tmp + s);
}
jtp.setEditable(false);
startButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent ae) {
startButton.setEnabled(false);
count = 0;
//create Arrays of individual letters and their timings
final ArrayList<String> chars = new ArrayList<>();
final ArrayList<Integer> charsTiming = new ArrayList<>();
for (String s : individualWordsToHighlight) {
for (int i = 0; i < s.length(); i++) {
chars.add(String.valueOf(s.charAt(i)));
System.out.println(String.valueOf(s.charAt(i)));
}
}
for (int x = 0; x < timingsArray.length; x++) {
for (int i = 0; i < individualWordsToHighlight[x].length(); i++) {
charsTiming.add(timingsArray[x] / individualWordsToHighlight[x].length());
System.out.println(timingsArray[x] / individualWordsToHighlight[x].length());
}
}
new Timer(1, new AbstractAction() {
#Override
public void actionPerformed(ActionEvent ae) {
if (count < charsTiming.size()) {
highlightNextWord();
//restart timer with new timings
((Timer) ae.getSource()).setInitialDelay(charsTiming.get(count));
((Timer) ae.getSource()).restart();
} else {//we are at the end of the array
reset();
((Timer) ae.getSource()).stop();//stop the timer
}
count++;//increment counter
}
}).start();
}
});
frame.add(jtp, BorderLayout.CENTER);
frame.add(startButton, BorderLayout.SOUTH);
frame.pack();
frame.setVisible(true);
}
private void reset() {
startButton.setEnabled(true);
jtp.setText("");
for (String s : individualWordsToHighlight) {
String tmp = jtp.getText();
jtp.setText(tmp + s);
}
JOptionPane.showMessageDialog(frame, "Done");
}
private void highlightNextWord() {
//we still have words to highlight
int sp = 0;
for (int i = 0; i < count + 1; i++) {//get count for number of letters in words (we add 1 because counter is only incrementd after this method is called)
sp += 1;
}
//highlight words
Style style = jtp.addStyle("RED", null);
StyleConstants.setForeground(style, Color.RED);
((StyledDocument) jtp.getDocument()).setCharacterAttributes(0, sp, style, true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new KaraokeTest();
}
});
}
}
Gives me Exception:
Exception in thread "AWT-EventQueue-0" java.lang.RuntimeException: Uncompilable source code - illegal start of type
at KaraokeTest$1.actionPerformed(KaraokeTest.java:47)
at javax.swing.AbstractButton.fireActionPerformed(AbstractButton.java:1995)
at javax.swing.AbstractButton$Handler.actionPerformed(AbstractButton.java:2318)
at javax.swing.DefaultButtonModel.fireActionPerformed(DefaultButtonModel.java:387)
at javax.swing.DefaultButtonModel.setPressed(DefaultButtonModel.java:242)
at javax.swing.plaf.basic.BasicButtonListener.mouseReleased(BasicButtonListener.java:236)
at java.awt.Component.processMouseEvent(Component.java:6263)
at javax.swing.JComponent.processMouseEvent(JComponent.java:3267)
at java.awt.Component.processEvent(Component.java:6028)
at java.awt.Container.processEvent(Container.java:2041)
at java.awt.Component.dispatchEventImpl(Component.java:4630)
at java.awt.Container.dispatchEventImpl(Container.java:2099)
at java.awt.Component.dispatchEvent(Component.java:4460)
at java.awt.LightweightDispatcher.retargetMouseEvent(Container.java:4574)
at java.awt.LightweightDispatcher.processMouseEvent(Container.java:4238)
at java.awt.LightweightDispatcher.dispatchEvent(Container.java:4168)
at java.awt.Container.dispatchEventImpl(Container.java:2085)
at java.awt.Window.dispatchEventImpl(Window.java:2475)
at java.awt.Component.dispatchEvent(Component.java:4460)
at java.awt.EventQueue.dispatchEvent(EventQueue.java:599)
at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:269)
at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:184)
at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:174)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:169)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:161)
at java.awt.EventDispatchThread.run(EventDispatchThread.java:122)
OK, here is a cleaned up version of your code which should approximatively perform the same thing:
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextPane;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.text.BadLocationException;
import javax.swing.text.DefaultStyledDocument;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyledDocument;
public class Reminder {
private static final String TEXT = "Test hiiii what happpn";
private static final String[] WORDS = TEXT.split(" ");
private JFrame frame;
private Timer timer;
private StyledDocument doc;
private JTextPane textpane;
private List<Integer> times = Arrays.asList(1000, 900, 300, 1500);
private int stringIndex = 0;
private int index = 0;
public void startColoring() {
ActionListener actionListener = new ActionListener() {
#Override
public void actionPerformed(ActionEvent actionEvent) {
doc.setCharacterAttributes(stringIndex, 1, textpane.getStyle("Red"), true);
stringIndex++;
try {
if (stringIndex >= doc.getLength() || doc.getText(stringIndex, 1).equals(" ")) {
index++;
}
if (index < times.size()) {
double delay = times.get(index).doubleValue();
timer.setDelay((int) (delay / WORDS[index].length()));
} else {
timer.stop();
System.err.println("Timer stopped");
}
} catch (BadLocationException e) {
e.printStackTrace();
}
}
};
timer = new Timer(times.get(index), actionListener);
timer.setInitialDelay(0);
timer.start();
}
public void initUI() {
frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel panel = new JPanel();
doc = new DefaultStyledDocument();
textpane = new JTextPane(doc);
textpane.setText(TEXT);
javax.swing.text.Style style = textpane.addStyle("Red", null);
StyleConstants.setForeground(style, Color.RED);
panel.add(textpane);
frame.add(panel);
frame.pack();
frame.setVisible(true);
}
public static void main(String args[]) throws InterruptedException, InvocationTargetException {
SwingUtilities.invokeAndWait(new Runnable() {
#Override
public void run() {
Reminder reminder = new Reminder();
reminder.initUI();
reminder.startColoring();
}
});
}
}
A few tricks to help others read and understand your code:
Use coherent and appropriate indentation (I personnally try to stick to default Sun Java conventions)
Follow the Java coding conventions (constants are in upper-case, class name starts with an upper-case, variables and methods start with a lower case, use camel case)
Use meaningful variable and method names
class member should be declared one by one (don't use int i, j, k; )
Use a single instruction per line (avoid stuffs like if(something) doSomething(); else {doSomethingElse1(); doSomethingElse2();} on a single line)
Avoid unnecessary usage of the static keyword (to the exception of constants)
Try to avoid coupling your code so much (try to make the minimum assumptions on how the rest of the code performs)
Add javadoc and comments in your code, this is always a good practice and it is of great help for you and others.
Your main cause for concern is that you not doing the updates related to JTextPane on the Event Dispatch Thread.
For a situation like this, when you really wanted to update a certain thingy from another Thread, always use EventQueue.invokeLater(...) or EvenQueue.invokeAndWait(), which can asynchronously (former)/synchronously (latter) update your request on the EDT, though care must be taken, as invokeAndWait() might can lead to deadlocks/run conditions if not used in the right sense.
Here is your updated code, that might work for your expectations. Hope you can modify the code, as per your liking.
import javax.swing.*;
import javax.swing.text.DefaultStyledDocument;
import javax.swing.text.Style;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyledDocument;
import java.awt.*;
import java.lang.reflect.InvocationTargetException;
/**
* Created with IntelliJ IDEA.
* User: Gagandeep Bali
* Date: 1/12/13
* Time: 5:55 PM
* To change this template use File | Settings | File Templates.
*/
public class ColouringText
{
private StyledDocument document;
private JTextPane textPane;
private String message;
private String[] parts;
private Timer timer;
private int counter;
private int start, end;
private Thread thread = new Thread()
{
#Override
public void run()
{
while (counter < parts.length)
{
final int len = parts[counter++].length();
try
{
EventQueue.invokeAndWait(new Runnable()
{
#Override
public void run()
{
document.setCharacterAttributes(
start, len, textPane.getStyle("RED"), true);
}
});
Thread.sleep(len * 1000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
catch (InvocationTargetException e)
{
e.printStackTrace();
}
start += (len + 1);
}
}
};
public ColouringText()
{
document = (StyledDocument) new DefaultStyledDocument();
message = "Hello there... Joey Rohan. Had you ever thought about putting indentations before pasting your code.";
parts = message.split(" ");
counter = 0;
start = 0;
end = 6;
System.out.println("Message Length : " + message.length());
}
private void displayGUI()
{
JFrame frame = new JFrame("Colouring Text Example");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
JPanel contentPane = new JPanel();
textPane = new JTextPane(document);
textPane.setText(message);
Style style = textPane.addStyle("RED", null);
StyleConstants.setForeground(style, Color.RED);
contentPane.add(textPane);
frame.setContentPane(contentPane);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
thread.start();
}
public static void main(String... args)
{
EventQueue.invokeLater(new Runnable()
{
#Override
public void run()
{
new ColouringText().displayGUI();
}
});
}
}
Having been the one who answered your 2 previous questions on a similar task:
highlighting words in java
highlighting the word in java
and others not agreeing on my close vote, I decided to work on this a bit.
I have taken my latest example and edited to simplify where possible etc to make things more readable for you to understand, which in turn should help you understand how to go about simplifying your own.
I dont understand why you start a new thread to make words highlight slowly. Simply use my other examples and supply single letters rather than multiple letters and their timings to highlight, thus they will be highlighted individually. Or just make a method to do this work for you and iterate through those arrays as the letters and timings.
The below example uses an array of integers which hold the timings for words to be highlighted. It also contains an array for each individual word/letters we would like to highlight:
private int[] timingsArray = {1000, 900, 300, 1500};//word/letters timings
private String[] individualWordsToHighlight = {"Tes", " hiiii", " what", " happpn"};//each individual word/letters to highlight
In our start button we have a method to convert the above to single timings/letters for each individual letter for a letter by letter highlight:
//create Arrays of individual letters and their timings
final ArrayList<String> chars = new ArrayList<>();
final ArrayList<Integer> charsTiming = new ArrayList<>();
for (String s : individualWordsToHighlight) {
for (int i = 0; i < s.length(); i++) {
chars.add(String.valueOf(s.charAt(i)));
System.out.println(String.valueOf(s.charAt(i)));
}
}
for (int x = 0; x < timingsArray.length; x++) {
for (int i = 0; i < individualWordsToHighlight[x].length(); i++) {
charsTiming.add(timingsArray[x] / individualWordsToHighlight[x].length());
System.out.println(timingsArray[x] / individualWordsToHighlight[x].length());
}
}
Next there is a single Timer which will be started when start button is pressed which will highlight words/letters and restart itself with a new delay each time until all words/letters have been highlighted:
new Timer(1, new AbstractAction() {
#Override
public void actionPerformed(ActionEvent ae) {
if (count < charsTiming.size()) {
highlightNextWord();
//restart timer with new timings
((Timer) ae.getSource()).setInitialDelay(charsTiming.get(count));
((Timer) ae.getSource()).restart();
} else {//we are at the end of the array
reset();
((Timer) ae.getSource()).stop();//stop the timer
}
count++;//increment counter
}
}).start();
Hope this helps.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import javax.swing.AbstractAction;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JTextPane;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.text.Style;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyledDocument;
public class KaraokeTest {
private int[] timingsArray = {1000, 900, 300, 1500};//word/letters timings
private String[] individualWordsToHighlight = {"Tes", " hiiii", " what", " happpn"};//each individual word/letters to highlight
private int count = 0;
private final JTextPane jtp = new JTextPane();
private final JButton startButton = new JButton("Start");
private final JFrame frame = new JFrame();
public KaraokeTest() {
initComponents();
}
private void initComponents() {
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setResizable(false);
for (String s : individualWordsToHighlight) {
String tmp = jtp.getText();
jtp.setText(tmp + s);
}
jtp.setEditable(false);
startButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent ae) {
startButton.setEnabled(false);
count = 0;
//create Arrays of individual letters and their timings
final ArrayList<String> chars = new ArrayList<>();
final ArrayList<Integer> charsTiming = new ArrayList<>();
for (String s : individualWordsToHighlight) {
for (int i = 0; i < s.length(); i++) {
chars.add(String.valueOf(s.charAt(i)));
System.out.println(String.valueOf(s.charAt(i)));
}
}
for (int x = 0; x < timingsArray.length; x++) {
for (int i = 0; i < individualWordsToHighlight[x].length(); i++) {
charsTiming.add(timingsArray[x] / individualWordsToHighlight[x].length());
System.out.println(timingsArray[x] / individualWordsToHighlight[x].length());
}
}
new Timer(1, new AbstractAction() {
#Override
public void actionPerformed(ActionEvent ae) {
if (count < charsTiming.size()) {
highlightNextWord();
//restart timer with new timings
((Timer) ae.getSource()).setInitialDelay(charsTiming.get(count));
((Timer) ae.getSource()).restart();
} else {//we are at the end of the array
reset();
((Timer) ae.getSource()).stop();//stop the timer
}
count++;//increment counter
}
}).start();
}
});
frame.add(jtp, BorderLayout.CENTER);
frame.add(startButton, BorderLayout.SOUTH);
frame.pack();
frame.setVisible(true);
}
private void reset() {
startButton.setEnabled(true);
jtp.setText("");
for (String s : individualWordsToHighlight) {
String tmp = jtp.getText();
jtp.setText(tmp + s);
}
JOptionPane.showMessageDialog(frame, "Done");
}
private void highlightNextWord() {
//we still have words to highlight
int sp = 0;
for (int i = 0; i < count + 1; i++) {//get count for number of letters in words (we add 1 because counter is only incrementd after this method is called)
sp += 1;
}
//highlight words
Style style = jtp.addStyle("RED", null);
StyleConstants.setForeground(style, Color.RED);
((StyledDocument) jtp.getDocument()).setCharacterAttributes(0, sp, style, true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new KaraokeTest();
}
});
}
}
UPDATE
In response to your edited question.
Again the code complies fine for me, which brings me to the conclusion our java Runtimes differ and might be causing a problem. Mine is:
java version "1.7.0_10" Java(TM) SE Runtime Environment (build
1.7.0_10-b18) Java HotSpot(TM) 64-Bit Server VM (build 23.6-b04, mixed mode)
UPDATE 2:
As per your comment on your java version:
JDK 6, NetBeans 6.5.1
Have you tried:
final ArrayList<String> chars = new ArrayList<String>();
final ArrayList<Integer> charsTiming = new ArrayList<Integer>();
note i dont use <> anymore as Java 6 does not support the Diamond operator it would have to have the data type included i.e <Integer>.
I have a simple puzzle game. There is an image consisting of 16 tiles (randomly placed). Images are stored in an array and when game is launched they're added to main JPanel.
Game works in this way : Each image has atributes 'place' and 'number'. 'Place' is the current place on grid (either correct or not) and 'number' is the desired place for the image. When a user clicks image their 'place' and 'number' attributes are checked. If they match nothing happens. If not game checks if any image is currently in memory. If there is none, then this image's 'place' and 'number' are stored. If there is some image in memory, then the currently clicked image's 'plac'e is checked with stored image's 'number'. When they match - their places are exchanged. This part works properly. But now, I'm calling addComponent method on my JPanel with updated images and simply nothing happens. Shouldn't the new images be added to JPanel replacing the old ones ?
package Bonus;
import javax.swing.*;
import java.util.Random;
import java.awt.event.*;
import java.awt.*;
class Puzzle extends JPanel implements ActionListener {
private int selected_nr=-1;
private int selected_pl=-1;
private boolean memory=false;
private static Img[] images;
public Puzzle(){
JFrame f = new JFrame("Smile");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(this);
f.setSize(252,252);
f.setVisible(true);
setLayout(new GridLayout(4, 4));
images = new Img[16];
int[] buttons = new int[16];
for(int i=0; i<16; i++){
buttons[i] = i;
}
int rand;
int temp;
Random random;
random = new Random(System.currentTimeMillis());
for (int i = 0; i < buttons.length; i++) {
rand = (random.nextInt() & 0x7FFFFFFF) % buttons.length;
temp = buttons[i];
buttons[i] = buttons[rand];
buttons[rand] = temp;
}
for (int i = 0; i < 16; i++) {
images[i] = new Img(i, buttons[i]);
}
addComponents(images);
}
public void addComponents(Img[] im){
this.removeAll();
for(int i=0; i<16; i++){
im[i].addActionListener(this);
im[i].setPreferredSize(new Dimension(53,53));
add(im[i]);
}
this.validate();
}
public void actionPerformed(ActionEvent e) {
Img b = (Img)(e.getSource());
int num = b.getNumber();
int pl = b.getPlace();
if(!(b.rightPlace())){
if(memory){
if(pl == selected_nr){
images[pl].setPlace(selected_pl);
images[selected_pl].setPlace(selected_nr);
selected_nr = -1;
selected_pl = -1;
memory = false;
addComponents(images);
}
else{
System.out.println("Try other image");
}
}
else{
memory = true;
selected_nr = num;
selected_pl = pl;
}
}
else{
System.out.println("OK !");
}
}
public static void main(String args[]) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
new Puzzle();
}
});
}
}
class Img extends JButton {
int number;
int place;
ImageIcon img;
public Img(int p, int n){
number = n;
place = p;
img = new ImageIcon("u"+number+".jpg", BorderLayout.CENTER);
setIcon(img);
}
public boolean rightPlace(){
boolean correct=false;
if(number == place){
correct = true;
}
return correct;
}
public void setPlace(int i){
place = i;
}
public int getNumber(){
return number;
}
public int getPlace(){
return place;
}
}
EDIT: Changed the code to use the answers, but still no luck. addComponents() gets updated images[] but doesn't revalidate them.
Rather than relying on precut image files, here's an example of slicing an existing image and shuffling the resulting pieces. It combines the helpful (+1) suggestions of both #Frederick and #akf.
import java.awt.EventQueue;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.Timer;
public class ImageLabelPanel extends JPanel implements ActionListener {
private static final int N = 4;
private final List<JLabel> list = new ArrayList<JLabel>();
private final Timer timer = new Timer(1000, this);
ImageLabelPanel() {
this.setLayout(new GridLayout(N, N));
BufferedImage bi = null;
try {
bi = ImageIO.read(new File("image.jpg"));
} catch (IOException e) {
e.printStackTrace();
}
for (int r = 0; r < N; r++) {
for (int c = 0; c < N; c++) {
int w = bi.getWidth() / N;
int h = bi.getHeight() / N;
BufferedImage b = bi.getSubimage(c * w, r * h, w, h);
list.add(new JLabel(new ImageIcon(b)));
}
}
createPane();
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(this);
f.pack();
f.setVisible(true);
timer.start();
}
private void createPane() {
this.removeAll();
for (JLabel label : list) add(label);
this.validate();
}
#Override
public void actionPerformed(ActionEvent e) {
Collections.shuffle(list);
createPane();
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new ImageLabelPanel();
}
});
}
}
You are adding all of your components again to your JPanel without actually removing any of them. In your addComponents() method, I would first call removeAll(). You might want to rename that method to highlight the side-effects, as it no longer would only be adding components. Perhaps, resetComponents() would be better.
After changing the components, you need to 'refresh' the Swing component by calling invalidate() or revalidate().