Make a loop iteration wait for the repaint() method to complete - java

After searching for an answer lots of threads about similar issues, I still don't find a solution that fits my needs.
Basically, I have a while loop and I'd like to wait for the repaint() method to complete before another iteration of that loop starts.
In more detail, I have some segments that are drawn in the paintComponent method of a MapPanel class that extends JComponent.
Then, when the user clicks on a button, an algorithm starts searching for intersections beginning with the "upper" segments (using endpoints as event points).
This algorithm is basically the while loop I spoke about, it calls another algorithm in this while loop sending one event point at each iteration (those event points are sent in order, from top to bottom).
What I'd like to do is to show the current "position" of the algorithm by drawing a line at the current event point.
As the algorithm finds new intersections, I'd like to show them too.
I did draw what was needed in the paintComponent() method.
The problem is that when I call the repaint() method for the MapPanel inside the loop, it waits for the whole loop to end before the UI is updated(I've found a lot about why it does that but not about how to fix this in my particular case).
Okay, I'm done with the explanations, here's the actual algorithm :
private void findIntersection(ArrayList<Segment> segments){
QTree Q=new QTree();
for (Segment s : segments){
Q.initialise(s);
}
TreeT T=new TreeT();
while (Q.getRoot()!=null){
Point p = Q.maxEventPoint(Q.getRoot());
Q.delete(p.getX(), p.getY());
mapPanel.setSweeplinePosition(p.getY());
handleEventPoint(p, T, Q);
mapPanel.repaint()
//should wait here for repaint() to complete
}
System.out.println("all intersections found, found " + mapPanel.getIntersections().size());
}

The problem is that your long-running code is likely being called on the Swing event thread an action which prevents the event thread from doing its actions including drawing the GUI.
The solution is likely similar to what you've found in your search, but we don't know why you cannot use it with your program, and that is to do the while loop in a SwingWorker background thread, to publish the interim results using the SwingWorker's publish/process method pair, and then to do your drawings with the interim data.
For more on this, please read Concurrency in Swing
For example
private void findIntersection(final ArrayList<Segment> segments) {
final SwingWorker<Void, Double> myWorker = new SwingWorker<Void, Double>() {
#Override
protected Void doInBackground() throws Exception {
QTree Q = new QTree();
for (Segment s : segments) {
Q.initialise(s);
}
TreeT T = new TreeT();
while (Q.getRoot() != null) {
Point p = Q.maxEventPoint(Q.getRoot());
Q.delete(p.getX(), p.getY());
publish(p.getY()); // push data to process method
// take care that this method below does not
// make Swing calls
handleEventPoint(p, T, Q);
}
return null;
}
#Override
protected void process(List<Double> chunks) {
for (Double yValue : chunks) {
// this is called on the EDT (Swing event dispatch thread)
mapPanel.setSweeplinePosition(yValue);
mapPanel.repaint();
}
}
};
myWorker.addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
// get notified when Worker is done
if (evt.getNewValue() == SwingWorker.StateValue.DONE) {
// TODO: any clean up code for when worker is done goes here
try {
myWorker.get(); // must call to trap exceptions that occur inside worker
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
});
myWorker.execute(); // execute worker
}

Did you try with the method Thread.sleep(milliseconds)???
For example, I'm used to do something like this in my code:
btnNewButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
new Thread(new Runnable() {
#Override
public void run() {
// TODO Auto-generated method stub
pca.setIcon(new ImageIcon("icon.png"));
for(int i=0; i<4; i++) {
try{Thread.sleep(700);} catch(InterruptedException er){}
x+=20;
p.repaint();
}}
}).start();
}
});

Related

Starting "asyncExec" in Mouse Down Event results in blocking behavior

Having a "next" Button, when I press keyboard enter key with the button selected, the widgetSelected event of the button is being repeatedly called once and once and doing a super fast next. It's exactly the behaviour I want, but only happens with keyboard enter key.
I want to have that behaviour with mouse click when holding the click. When trying to do the same with the mouse click, the behaviour is not the same, it only makes one event call, and when the click is end (UP). How to simulate the same behaviour with the mouse click?
I tried it doing this, but it blocks the UI (can't understand why) and mouseUp is never being called, it blocks forever in the while:
button.addMouseListener(new MouseAdapter() {
boolean mouseDown;
#Override
public void mouseDown(MouseEvent e) {
System.out.println("mouseDown");
mouseDown = true;
Display.getDefault().asyncExec(new Runnable() {
#Override
public void run() {
while (mouseDown) {
System.out.println("Doing next in mouseDown");
next(composite, label_1);
synchronized(this){
try {
wait(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
});
}
#Override
public void mouseUp(MouseEvent e) {
System.out.println("mouseUp");
mouseDown = false;
}
});
The Runnable you give to asyncExec runs in the UI thread. You must never do any sort of wait in the UI thread as that will block the UI until it completes.
So you cannot run a loop like this as it just blocks the UI. Since the loop never returns to the main SWT readAndDispatch loop no UI actions are done.
Instead use the timerExec method of Display to schedule a Runnable to run after a given interval. This runnable should do one step of the action and use timerExec to schedule the next step later.
I remember there was another question a few days ago regarding a long mouse click behaviour but I can't find it anymore. I put this code based on greg-449 solution to use timerExec method, after my failed attempts to use asyncExec in the UI thread. :)
button.addMouseListener(new MouseAdapter() {
boolean mouseDown;
#Override
public void mouseDown(MouseEvent e) {
mouseDown = true;
Display.getCurrent().timerExec(1000, () -> {
if (mouseDown) {
button.notifyListeners(SWT.Selection, new Event());
button.notifyListeners(SWT.MouseDown, new Event());
}
});
}
#Override
public void mouseUp(MouseEvent e) {
mouseDown = false;
}
});
button.addSelectionListener(SelectionListener.widgetSelectedAdapter(
e -> System.out.println("Do next")));

Updating imageViews in onTouchEvent in realtime

In my onTouchEvent method (in a class that extends Activity), I have some imageViews that change one by one, which is done by waiting a little before executing the next change.
//in the onTouchEvent method
//ivList is an arrayList of imageViews
ivList.get(0).setImageResource(R.drawable.drawable1);
synchronized (this) {
try {
wait(500);
} catch (InterruptedException e) {
}
}
ivList.get(1).setImageResource(R.drawable.drawable2);
However, when this is executed, the changes only take place once onTouchEvent is concluded, so the result is a minor delay and then both imageViews change at once, as opposed to one imageView changing, a minor delay, and then the second one changes. Can someone explain how to fix this?
The problem is you should not use wait() because it will block the main thread
ivList.get(0).setImageResource(R.drawable.drawable1); // this is doing in main thread
synchronized (this) {
try {
wait(500); // this blocks the main thread
} catch (InterruptedException e) {
}
}
ivList.get(1).setImageResource(R.drawable.drawable2); // this is also doing in main thread
try following
Runnable r = new Runnable() {
#Override
public void run() {
ivList.get(1).setImageResource(R.drawable.drawable2);
}
};
ivList.get(0).setImageResource(R.drawable.drawable1);
// no synchronized is needed
new Handler().postDelayed(r,500); // call to change the image after 500s
of cause your Runnable and Handler can create else where so that you do not need to create them every time the onTouchEvent is trigeered
okay , if you are going to make it work for 10 images.
// these are local variables
//create a contant int array for the 10 images
int[] imgs = {R.drawable.drawable1 , R.drawable.drawable2 ,....,R.drawable.drawable10};
int count = 0; // counting which image should show
// local variable ends
// initialize
Handler handler = new Handler();
Runnable r = new Runnable() {
#Override
public void run() {
if (count < 9){
count++;
ivList.get(count).setImageResource(imgs[count]);
handler().postDelayed(r,500);
}
}
};
// in your touch event
ivList.get(0).setImageResource(R.drawable.drawable1);
// no synchronized is needed
handler().postDelayed(r,500); // call to change the image after 500s

Java Thread.sleep() within a for loop

public void playPanel() throws IOException{
for(int i = 0; i<listData.size(); i++){
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
ascii.setText(listData.get(i));
}
}
What I'm trying to do is play through the listData ArrayList type, which was copied from the ascii JTextArea. Its supposed to be an animation, so when they hit play the function displays the first slide, waits a second, then the next slide, etc.
When I run this the only thing that happens is a pause with nothing on the screen changing until it displays only the final slide. I'm not sure what's wrong with it
You should never call Thread.sleep(...) on the Swing event thread unless your goal is to put the entire application to sleep, rendering it useless. Instead, get rid of the for loop, and "loop" using a Swing Timer. Something like this should work, or be close to a functioning solution (caveat: code has not been compiled nor tested):
int delay = 1000;
new Timer(delay, new ActionListener() {
private int i = 0;
#Override
public void actionPerformed(ActionEvent e) {
if (i < listData.size()) {
ascii.setText(listData.get(i));
} else {
((Timer) e.getSource()).stop();
}
i++;
}
}).start();

JLayer Player overwrites refesh of SWT widgets when placed in a loop

I am attempting to make an application that retrieves images and .mp3 files and transitions from one image to the next once the audio has finished. The underlying framework of how I transition between these images is a little convoluted, but I have managed to get an action in SWT that successfully enables me to manually transition from one to the next. However, a problem has arisen when I've tried to automate it; when placed into a loop, my playAudio() method begins before all of the calls I make in my displayShow() method have resolved, which results in a blank window, despite the audio still playing.
Here is the run method for the action that I want to start the show:
Action startAction = new Action("Start") {
public void run() {
//do {
displayShow();
playAudio();
//} while(true);
}
};
Here is playAudio(). I am able to PLAY the audio without incident:
public void playAudio() {
final String audio = "Happy_Birthday.mp3";
audioLatch = new CountDownLatch(1);
audioThread = new Thread() {
public void run() {
try {
Player player = new Player
(new BufferedInputStream
(new FileInputStream(audio)));
player.play();
audioLatch.countDown();
} catch (JavaLayerException e) {
} catch (FileNotFoundException e) {
} catch (Exception e) {
}
}
};
audioThread.start();
try {
audioLatch.await();
} catch (InterruptedException e) {
}
}
And here is displayShow():
private void displayShow() {
new Thread() {
public void run() {
Display.getDefault().asyncExec(new Runnable() {
public void run() {
Control[] children = container.getChildren();
for (int i = 0; i < children.length; i++) {
children[i].dispose();
}
show.showSlide(container);
container.layout();
}
});
}
}.start();
}
show.showSlide returns a composite whose parent is container, which is the immediate child of the highest parent composite. Within the newly created composite, an image is added to a label and the label's parent is assigned to composite. I realize whether displayShow() is in a separate thread or not seems to be immaterial; this was just the last thing I tried.
It is not solely the addition of the loop that causes the refresh to not execute. The only way I can get the manual transition to work is if I remove the CountDownLatch from the playAudio() method. Were I to remove this latch, the only way to encase these two methods in a loop would be embedded while loops, which seem to hog a fair amount of the CPU and still does not solve my problem. Am I missing anything?
The audioLatch.await() is blocking the main program thread, this is the thread that all SWT operations run on so the Display.asyncExec runnables are just being queued until the thread is available.
If you really must wait in the playAudio method you could run the display event loop there until the background thread is finished:
while (! background thread finished)
{
if (!display.readAndDispatch())
display.sleep();
}

How do I make this java for loop pause for 1/2 a second between each iteration?

private class MultipleGensListener implements ActionListener
{
public void actionPerformed(ActionEvent e)
{
for(int i = 0; i < 25; i++)
{
game.runSimulationOneGen();
changeGrid();
}
}
}
//this is the loop. The changeGrid method displays a game grid on a GUI but
// only the 25th iteration is visible on screen. I would like each one to be
// visible for about a half a second before the loop continues.
// I have seen some questions answered on here that are very close to what I'm asking,
// but I just don't really understand how to apply it to my program..
// thanks for any help.
If the code performed by the simulation is quick and does not consume too much CPU and time, then consider using a Swing Timer to do your looping and delay. Otherwise, you'll need to use a background thread such as can be done with a SwingWorker object.
For e.g. if using both Swing Timer and SwingWorker:
private class MultipleGensListener implements ActionListener {
protected static final int MAX_INDEX = 25;
public void actionPerformed(ActionEvent e) {
int timerDelay = 500; // ms delay
new Timer(timerDelay, new ActionListener() {
int index = 0;
public void actionPerformed(ActionEvent e) {
if (index < MAX_INDEX) { // loop only MAX_INDEX times
index++;
// create the SwingWorker and execute it
new SwingWorker<Void, Void>() {
#Override
protected Void doInBackground() throws Exception {
game.runSimulationOneGen(); // this is done in background thread.
return null;
}
#Override
protected void done() {
changeGrid(); // this is called on EDT after background thread done.
}
}.execute(); // execute the SwingWorker
} else {
((Timer) e.getSource()).stop(); // stop the timer
}
}
}).start(); // start the Swing timer
}
}
NEVER BLOCK THE GUI EVENT THREAD
you can use a timer for that and have it only fire 25 times
final Timer t = new Timer(500,null);
t.addActionListener(new ActionListener(){
int i=0;
public void actionPerformed(ActionEvent e){
game.runSimulationOneGen();//run 1 iteration per tick
changeGrid();
if(i>25){t.stop();}
i++;
}
});
t.setRepeats(true);
t.start();
btw the reason only the last iteration is shown is that gui updates (redraws) are done in a separate event, but to let another event trigger you need to return from the listener method which you didn't
the Timer I showed is a more elaborate iteration which lets other events run in between iterations allowing the gui to show the changes
check my post that shows both methods java.swing.Timer#setDelay(int)
and
correct usage of Thread.sleep(int)
java wait cursor display problem

Categories

Resources