Java - Sleeping thread to simulate changing image - java

So I've done a lot of searching and I can't quite find a solution to the problem I'm having. Designing GUIs with Java's JFrame seems fairly straight-forward, but updating them visually when it's more than just a setText call seems much more tricky, especially when you want to simulate some wait time for visual recognition in between. I'm not familiar with multi-threading and all of the solutions I found, I couldn't seem to get to work how I needed them to.
What I'm trying to do is to simulate dice rolling before landing on a particular side for a simple dice game. To do this, I'm just switching between different sides of dice images a set number of times (10, for example) every fraction of a second (150 ms in this case).
The interesting thing is that this code at its current state works EXACTLY how I want it to on start-up (which does a full 10 roll cycle once before accepting any user-input, but every component of the GUI is visible and working as expected). The problem is that when re-rolling, the GUI goes blank until all the thread sleeping is completed.
With my lack of knowledge about how this sort of thing, I don't understand why it would work the way I want the first time, but not any time after. Hopefully I can get some assistance about the right way to accomplish this or a fix for what I'm currently doing. Thanks!
private void rollDice()
{
for(int i = 0; i < 10; i++)
{
dice1 = (int) (Math.random() * 6) + 1;
dice2 = (int) (Math.random() * 6) + 1;
img1 = new ImageIcon(dice1 + ".png");
img2 = new ImageIcon(dice2 + ".png");
try
{
Thread.sleep(150);
}
catch(Exception e)
{
}
lbl1.setIcon(img1);
lbl2.setIcon(img2);
}
}

Try this I found here.
new Thread(new Runnable
{
public void run()
{
for(int count = 0; count < PREROLLS; count++)
{
//Add your for loop logic here!
SwingUtilities.invokeLater(new Runnable() {
public void run()
{
//then update you icon here. I would start with one die if I were you.
imgDice1 = new ImageIcon(path + dice1 + ".png");
}
});
try { Thread.sleep(100); } catch(Exception e) {}
}
}
}).start();

Related

How to NOT use thread.sleep for an image slide show in java

I'm trying to create something like an info screen that just scrolls through images (and maybe one day pdfs) that are uploaded with a small managment app I wrote.
After some reading I think I understand why I should NOT have Thread.sleep in my for loop. I read some posts on stackoverflow and other pages that teached me not to do it.
Apparently I'm supposed to use ExecutorService for something like this, but I can't wrap my head around it.
So after preparing my GUI and everything else I finally call my showImages and stay in this while loop.
After each loop I reload the records from the file systems, so any changes are represented on the screen.
I created a metafile containing the preferred delay for each image and just sleep for the amount of time.
private void showImages() {
//noinspection InfiniteLoopStatement
while (true) {
loadRecords();
for (ImageRecord record : records) {
System.out.println("Showing: " + record.getFilename());
imageLabel.setIcon(record.getIconForInfoScreen(screenWidth, screenHeight));
int delay = record.getDurationAsInt();
System.out.println("Waiting for " + delay + " milliseconds");
try {
Thread.sleep(delay);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
So if I understand correctly I could use something like
ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
exec.scheduleAtFixedRate(showImage(),0, delay, TimeUnit.MILLISECONDS);
But this would trigger the showImage() every "delay" millisecond and I still need to increment some counter, to get to the next image in my records.
Could someone push me in the right direction? I'm kinda lost at the moment.
All the comments on my question can be considered good ideas an and one of them also lead me to my final result. But it in the end, the matter was, that I did not understand how to implement a timer into my loop.
Or in other words, the loops had to be gone and instead use a counter and set the delay for the follow up record inside the timer loop.
private void loopImages() {
loadRecords();
recordSize = records.size();
int delay = records.get(1).getDurationAsInt();
timer = new Timer(delay, e -> showImages());
timer.start();
}
private void showImages() {
if (recordIndex == recordSize) {
loadRecords();
recordSize = records.size();
recordIndex = 0;
}
ImageRecord record = records.get(recordIndex);
imageLabel.setIcon(record.getIconForInfoScreen(screenWidth, screenHeight));
recordIndex++;
if (recordIndex < recordSize) {
record = records.get(recordIndex);
int delay = record.getDurationAsInt();
timer.setDelay(delay);
}
}

How can i go to next iteration when i click a buttom in a while loop?

I am making a quiz. I store my question in an array. So I use a while loop to display the question one by one. When I click the button, the counter +1 and go to next iteration which is next question. However it seems like that the it does not work out.
final int[] k = {0};
while (k[0] <2){
ql.setText(qa[k[0]][0]);
ql.setVisible(true);
for (int l = 0; l < 4; l++) {
ch[l].setVisible(true);
ch[l].setText(String.valueOf((int) (50 * Math.random())));
}
rc = (int) (4 * Math.random());
ch[rc].setText(qa[k[0]][1]);
nx.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
k[0]++;
}
});
}
What is the problem here?
Thanks!!
You have to invert your loop to work in an event-driven style. So what I'm thinking is, you should have the button click drive the "loop" in that it will only activate the questions provided your counter has not run down, otherwise it will just ignore any more presses.
Something like:
private void showQuestion(int question) {
ql.setText(qa[question][0]);
ql.setVisible(true);
for (int l = 0; l < 4; l++) {
ch[l].setVisible(true);
ch[l].setText(String.valueOf((int) (50 * Math.random())));
}
int rc = (int) (4 * Math.random());
ch[rc].setText(qa[question][1]);
}
...
// In some method...
nx.addActionListener(new ActionListener() {
int nextQuestion = 1;
#Override
public void actionPerformed(ActionEvent e) {
if (k < 1) {
showQuestion(nextQuestion++);
}
}
});
showQuestion(0); // Shows the first question
There are a number of issues with your code but the most fundamental (focusing on your question) is that you are misunderstanding the difference between a loop in your code (e.g. for) and the GUI event loop. Loops in your code execute and then finish. GUI event loops wait until the user performs some action and then respond to it. It is not desirable to have a code loop waiting for some user action before continuing: that's just not how GUIs work.
I suggest you read the section in the swing tutorial on writing event listeners: here. It explains how to get your code correctly responding to events triggered by users.

How can I make a progress complete show in a JTextArea while running code that's figuring out what to display in that JTextArea?

I don't know if this is possible. I'm making a lottery application, and I'm trying to use as few GUI components as possible. So I have a JTextArea that is supposed to show the following message (for example):
"Calculating...55.4%"
When I print it to console, it shows just fine, but it won't print it to the JTextArea. I tried to use SwingUtilities.invokeLater but that's not working either.
for (int x = 0; x < daysBetween; x++)
{
completion = "Calculating..." + df.format((100 * (x + 1)) / daysBetween) + "%";
if (!textArea.getText().equals(completion))
{
textArea.setText(completion);
}
/*
Here I have a lot of irrelevant code that compares your tickets to the winning tickets and counts your winnings and other statistics...
*/
}
ticketReport += "Computation time: " + getElapsedTime(start, System.nanoTime());
ticketReport += "\nEnd date: " + (cal.get(Calendar.MONTH) + 1) + "/" + cal.get(Calendar.DAY_OF_MONTH) + "/" + cal.get(Calendar.YEAR);
ticketReport += "\nTotal # of tickets purchased: " + numOfTicketsPurchased;
/*
etc. with filling out the ticket report
*/
textArea.setText(ticketReport);
As you can guess, I want the JTextArea to update as I set the text of the textArea in the for loop above. It does not update the JTextArea until the end of the method, which is the very bottom when I set the text area to show the ticket report.
MY END GOAL: I want to eventually turn this into an Android phone application, so that's why I don't want to use any pop-ups or anything.
My original SwingWorker approach made it where it showed the completion percentage status in the JTextArea. I added an option to cancel the operation, and when I attempted to cancel, the "thread" (SwingWorker) would not cancel. When I cancel and restart, it would stack constantly. Each successive starting while the other was going would cause the calculations to go slower and slower, and then give incorrect information.
The way I did it this second time solved the original problem and this new problem. I created a new private class that extends Thread, overran its run() method, and inserted the code to be executed in the thread in that method. I noticed any method to stop the thread was deprecated because of potential issues, so I created a boolean to be set when the user requests to stop the thread, and inside the thread it periodically reads the value of the boolean, then exits when it equals to true.
The way I start the thread is by re-initializing the object BackgroundCalc and calling its start() method.
private class BackgroundCalc extends Thread
{
public void run()
{
/*
initializing stuff here
*/
for (int x = 0; x < daysBetween; x++)
{
progress = (100 * (x + 1)) / daysBetween;
if (destroyCalcThread) return;
/*
background calculations here
*/
}
/*
finish up thread here
*/
}
}
I'm a little inexperienced with threads, so hopefully my struggle here and the way I explained it will help someone else who's inexperienced with the concept.
/*
EDIT: This temporarily solved the problem, but I ran into another problem. Now when I attempted to cancel the SwingWorker by calling its cancel method with parameter true, it would not cancel. Please see my other answer for the way I solved this.
*/
I figured it out. The method above I put inside a SwingWorker, as follows:
SwingWorker<Void, Void> sw = new SwingWorker<Void, Void>()
{
#Override
protected Void doInBackground() throws Exception
{
//method here
}
}
Inside that method, I called setProgress method with parameter "(100 * (x + 1)) / daysBetween" to show the correct % completion. Then after that, I added this:
sw.execute();
sw.addPropertyChangeListener(new PropertyChangeListener()
{
#Override
public void propertyChange(PropertyChangeEvent arg0)
{
textArea.setText("Calculating..." + sw.getProgress() + "%");
if (sw.getProgress() == 100)
textArea.setText(ticketReport);
}
});
It shows percentages as whole numbers rather than the #.## I originally wanted, but I can change very easily if I wanted to.

Getting a position in a JTextArea and using a timer

I'm working on a school project and I'm hoping if you guys could help me.
I have this:
JTextArea processTxt = new JTextArea();
And when I press this button,
JButton sortBtn = new JButton("SORT!");
This would display this on processTxt.
public void sort(String type, String order, int size) {
int tempInt, int1, int2;
processTxt.append("Pass 0:\n");
for (int i=0; i<size; i++) {
if (type.equals("int")) {
processTxt.append(intList.get(i)+" ");
}
}
processTxt.append("\n");
for (int i=0; i<size-1; i++) {
int x=0;
processTxt.append("Pass "+(i+1)+": \n");
while (x<size-1) {
int y = x + 1;
if (type.equals("int")) {
int1 = intList.get(x);
int2 = intList.get(y);
if (order.equals("asc")) {
if (int1 >= int2) {
tempInt = int1;
intList.set(x, int2);
intList.set(y, int1);
}
}
else if (order.equals("desc")) {
if (int1 <= int2) {
tempInt = int1;
intList.set(x, int2);
intList.set(y, int1);
}
}
}
for (int z=0; z<size; z++) {
if (type.equals("int")) {
processTxt.append(intList.get(z)+" ");
}
}
processTxt.append("\n");
x++;
}
}
processTxt.append("Sorted:\n");
for (int i=0; i<size; i++) {
if (type.equals("int")) {
processTxt.append(intList.get(i)+" ");
}
}
}
I was thinking:
How could I get the position of int1 on the text area?
Is there any way that I could use a timer to delay the output on the text area? I have used thread.sleep() but it won't work.
1) how could i get int1's position on the textarea;
Apart from physically searching the text, you could maintain reference to the location that it would have been inserted, before you inserted.
You could use the JTextArea's caret position if you are inserting text at the current cursor position, otherwise, you will need to determine the index point from within the Document you are updating.
2) is there any way that i could use a timer to delay the output on the textarea? i have used thread.sleep() but it wont work.
Yes, but understand, this becomes increasingly more complex.
You could use a Swing Timer which will would trigger a callback at a regular interval, allowing you to update state of the sort and update the UI. This is useful as it triggers the callback within the context of the Event Dispatching Thread, making it safe to update the UI from within.
It does, how ever, increase the complexity, as the Timer acts as a kind of pseudo loop.
You could use a SwingWorker instead. This would allow you to take your current code and run it within the doInBackground method, which is executed outside of the Event Dispatching Thread, allowing you to use Thread.sleep to inject a delay where you want them without affecting the UI.
The problem is, you will need to use the SwingWokers publish/process methods to push updates to the UI, this disconnects the time you make a change to the model to the time it appears in the UI, which may present issues with determining the location that a given update should occur (within the context of your first question).
Take a look at:
Concurrency in Swing
How to use Swing Timers
Worker Threads and SwingWorker
for more details

New thread freezing application

I have an application which is quite resource intensive, it is using large images as input and some of the operations on these images can take a while. I am therefore looking to make some parts run in their own threads. To do this I have used the following code just to test out first:
Thread t1 = new Thread(new Runnable() {
public void run()
{
inputChooser.setFileFilter(filter);
inputChooser.addChoosableFileFilter(filter);
int img = inputChooser.showOpenDialog(this);
if (img == JFileChooser.APPROVE_OPTION) {
File file = inputChooser.getSelectedFile();
String filename = file.getName();
if (filename.contains("B10")) {
greenBand = 1;
}
if (filename.contains("B20")) {
greenBand = 2;
}
if (filename.contains("B30")) {
greenBand = 3;
}
if (filename.contains("B40")) {
greenBand = 4;
}
if (filename.contains("B50")) {
greenBand = 5;
}
if (filename.contains("B60")) {
greenBand = 6;
}
if (filename.contains("B70")) {
greenBand = 7;
}
try {
greenImage = ImageIO.read(file);
ImageIO.write(greenImage, "JPEG", new File("img2_tmp.jpeg"));
greenImage = ImageIO.read(new File("img2_tmp.jpeg"));
if (greenImage.getWidth() > 8000 | greenImage.getHeight() > 7000) {
greenImage = greenImage.getSubimage(1450, 1400, (greenImage.getWidth()-3200), (greenImage.getHeight()-3000));
}
update(greenImage, greenIcon, greenLabel);
loadingBar.setIndeterminate(false);
checkInput();
} catch (IOException e) {
JOptionPane.showMessageDialog(null, "Input Image Error", "Input Error", WARNING_MESSAGE);
}
}
}});
t1.start();
When I run the application it freezes when this code is called. However, I have managed to get it to work once, I am not sure how but it ran perfectly (not the first time, it froze a few times first and then randomly worked one time). I haven't changed any of the code just some of the indents and such to get it to fit with the rest of the code and ever since it just continues to freeze. A button action press calls this method where the above code is, as soon as the buttons pressed it freezes.
Is there a reason as to why this is happening?
Thanks in advance
You are calling a non-thread-safe code (swing (is NOT thread safe)) from both threads (newly created and main thread) at the same time.
Make sure that you have decoupled the logic before creating new threads.
For this specific use case, I'd suggest that you use SwingWorkers in stead of threads, they are easy to use, and work well within the limitations of swing.
More about SwingWorkers at http://docs.oracle.com/javase/tutorial/uiswing/concurrency/worker.html
Hope this helps.
Good luck.
It's difficult to say exactly, but I notice that the variable greenImage and greenBand are not declared anywhere. That makes me think they are global variables. If something else has access to them, it's possible that they're causing some manipulation that sends your code into an infinite loop or does other unexpected Bad Things.

Categories