Adding items to ListView in JavaFx... threading? - java

I'm trying to add a string to ListView in JavaFX whilst processing, but it keeps freezing my GUI.
I've tried the following threading - but can't seem to get it to work for a ListView.
Does anybody know how/have an example of how I can update a ListView in JavaFX whilst processing data?
new Thread(new Runnable() {
#Override public void run() {
for (int i=1; i<=1000000; i++) {
final int counter = i;
Platform.runLater(new Runnable() {
#Override public void run() {
recentList.getItems().add(Integer.toString(counter));
}
});
}
}}).start();

Using Platform.runLater() is the correct way to go.
You could, also, store the String result from Integer.toString(counter) in the background thread (not the UI one). By the way, you should use String.valueOf (there is a thread on StackOverflow which talks about it).
I assume your UI is freezing because of the execution speed of the (very simple) loop.
You should also have a look at Concurrency in JavaFX

You can do the animation / ui update after adding those strings in the list or use Platform.runLater only once (not advisable):
Platform.runLater(new Runnable() {
for (int i=1; i<=1000000; i++) {
final int counter = i;
#Override public void run() {
recentList.getItems().add(Integer.toString(counter));
}
}
});

Your GUI hangs because you are blocking the JavaFX application thread by calling Platform.runLater() continuously in your Thread.
You can perform a quick fix by adding a sleep statement inside your for-loop to avoid it.
for (int i=1; i<=1000000; i++) {
final int counter = i;
Platform.runLater(new Runnable() {
#Override public void run() {
recentList.getItems().add(Integer.toString(counter));
}
});
// Add Sleep Time
Thread.sleep(some milli seconds);
}
To use a more proper and advisable way, use an AnimationTimer, as shown in
JavaFX - Concurrency and updating label

Related

Periodically updated text is not being displayed

I have a for loop in which I would like to call the setText method. This works without any errors except for the fact that the text does not change until the script is done running. What I want it to do is change the code every time I call the setText method. I attached my code below, it should be pretty strait forward.
code:
package com.example.zoe.andone;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.MenuItem;
import android.view.View;
import android.widget.TextView;
import java.util.concurrent.TimeUnit;
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void startLoop(View view) {
String string = getString(R.string.hello);
((TextView)findViewById(R.id.hello)).setText("starting...");
for (int i = 0; i < 10; i++) {
String updated = Integer.toString(i);
((TextView)findViewById(R.id.hello)).setText(updated);
try {
Thread.sleep(250);
} catch(InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
((TextView)findViewById(R.id.hello)).setText("done!");
}
}
That's what is called "dropping frames".
It's time to learn!
Before going to the answer, let's explain what's going on here.
You probably already know that android has a frame-rate of 60fps (60 frames per sec), which means that every 1/60 sec the screen is rendered and displayed to the user (in order to provide the best definition). It does that by running some rendering code on your app (and everywhere else where there's something to be rendered) every 1/60 sec to compute how the displayed screen should look like at this particular ms (it's is called a frame).
However, the code doing that is running on the UI thread, and if you're doing anything when the rendering tick kicks in, the android framework simply drops that frame (it won't compute it).
In your case Thread.sleep(250); is what causes your frames to be dropped cause it's holding the UI thread for 250 ms in every iteration of your for loop (so it's 250 ms * (10 - 1), that's many frames).
Dropping a frame means seeing no update on the UI.
Solution
Instead of using Thread.sleep(250); and dropping frames, you should schedule your update tasks to run every 250 ms. That's it.
private int i = 0; // Does the trick!
public void startLoop(View view) {
String string = getString(R.string.hello);
((TextView)findViewById(R.id.hello)).setText("starting...");
final Handler handler = new Handler();
handler.postDelayed(new Runnable() {
#Override
public void run() {
if (i < 10) {
String updated = Integer.toString(i);
i++;
updateHelloOnUiThread(updated);
} else {
updateHelloOnUiThread("done!");
handler.removeCallbacksAndMessages(null);
i = 0; // For future usages
}
}
},250);
}
private void updateHelloOnUiThread(final String text) {
MainActivity.this.runOnUiThread(new Runnable() {
#Override
public void run() {
((TextView)findViewById(R.id.hello)).setText(text);
}
});
}
Further reading
Android UI : Fixing skipped frames
High Performance Android Apps, Chapter 4. Screen and UI Performance
this.runOnUiThread(new Runnable() {
#Override
public void run() {
int globalTimer = 0;
int limitTimer = 10;
int i = 0;
// create Timer
Timer timer = new Timer();
timer.schedule(new TimerTask() {
#Override
public void run() {
globalTimer++;
//run your code
((TextView)findViewById(R.id.hello)).setText(i+"");
i++;
//check if globalTimer is equal to limitTimer
if (globalTimer == limitTimer) {
timer.cancel(); // cancel the Timer
}
}
}, 0, 250);
}
});
The problem is, you're calling Thread.sleep on the UI thread.
If the thread is sleeping, it can't update the UI(what's displayed on the screen).

Java-Repositioning of object when timer increments

I have a JFrame with two buttons. One of the buttons when clicked moves (btnMove) the other button(shape) from the present position to another.I am using a thread as a timer to count in seconds but each time the counter increments, the button moves back to its original position.
public class FrameTh extends JFrame {
class count extends Thread {
public int p = 0;
public void run() {
for (int i = 1; i < 100; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println(e);
}
lblCounter.setText("Seconds: " + i);
}
}
}
count t1 = new count();
private void formWindowActivated(java.awt.event.WindowEvent evt) {
t1.start();
}
private void btnMoveActionPerformed(java.awt.event.ActionEvent evt) {
shape.setLocation(23, 44);
}
The core problem is you're fighting the layout management API, which when you call setText is causing the container to be invalidated and relayed out
You might consider using something like JLayeredPane, but remember, you become entirely responsible for the size and position of the component
The other problem you have is you're violating the single threaded nature of Swing, Swing is not thread safe, meaning you shouldn't update the ui from out of the Event Dispatching Thread.
To solve that particular problem you should use a Swing Timer instead of a thread, see How to use Swing Timers for more details

Update JavaFX ProgressBar without using a Task?

So I have a thread running and inside that thread, it updates my textArea and it should update my progressBar. I'd like to be able to update the progressBar by calling progressBar.setProgress(progress/totalProgress); without having to bind a task to it. Is this even possible?
Some sample/pseudocode because I can't actually upload the code that's being run...
private int progress;
private ProgressBar progressBar;
private int totalProgress;
public void init() {
progress = 0;
totalProgress = 10; // some max progress number
progressBar.setProgress(progress/totalProgress);
new Thread(new Runnable() {
public void run() {
try {
startThread();
} catch (Exception ex) {
System.out.println(ex);
}
}
}).start();
}
public void startThread() {
for(int i = 0; i < someSize; i++) {
textArea.append("some new message);
progress++;
progressBar.setProgress(progress/totalProgress);
}
}
When I print progress and progressBar.getProgress inside the for loop, I can see it incrementing and actually "updating" but I can't get the progressBar UI to update.
Similar to how Swing has a JProgressBar and you can update it via progressBar.setValue(counter); inside a thread that's already doing other things, I'd like to be able to update the ProgressBar for FX inside a thread that's also running other things.

4 threads adds to one jlist

In my program there are 4 threads that addElemets to a model of a jlist at the same time. This causes jlist to blink, throw excpetions or even crash, because of the too many updates or repaints.
I tried to put some delay that fixed my problem but I was losing to much precious time.
What can I do?
this code simulates my problem:
new Thread(new Runnable() {
#Override
public void run() {
for(int i=0; i<4; i++){
new Thread(new Runnable() {
#Override
public void run() {
for(int o=0; o<2000; o++){
model.addElement("add");
try {
Thread.sleep(100);
} catch (InterruptedException ex) {
Logger.getLogger(NewJFrame.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
}).start();
}
}
}).start();
The model.addElement("add"); should be something like this:
SwingUtilities.invokeLater(new Runnable() {
public void run() {
listModel.addElement("add");
}
});
This way you make sure that the elements are added under the EDT and not in some random thread.
GUI manipulations should only be performed on the event dispatch thread. Even if you have created many threads, do your non-GUI work on them n fire an actionCommand (or something similar) to manipulate GUI aspects.
You will get to read this warning many times in the Java Docs:
Swing components are not thread safe
To avoid too many refresh/repaint you have to do the model updates in the background threads and to copy once into the swing model in the EDT with the technique previously exposed. I suggest to do not use the DefaultListModel but a custom made to dispose of addAll().
SwingUtilities.invokeLater(new Runnable() {
public void run() {
listModel.addAll( backgroundModel ); // only one fireDataChanged() fired
}
});

How to validate JLabel in Swing while using FTPClient

I want to update a JLabel each time with the name of the file which I am downloading using FTPClient. I tried repaint(), validate(), revalidate(), first invalidate() and immediately validate()/revalidate() but still nothing is working.
My Code goes as follows:
if(ae.getActionCommand()=="Download"){
int[] row_indexes=table.getSelectedRows();
notifylb.setText("Downloading files");
this.validate();
for(int i=0;i<row_indexes.length;i++)
{
String fn=table.getValueAt(row_indexes[i], 0).toString();
notifylb.setText("Downloading: "+fn); // fn contains filename
this.validate();
this.downloadFtpfile(fn);
}
notifylb.setText("SUCCESSFULLY DOWNLOADED FILE(s) !");
this.validate();
}
Suggestions:
Don't use == to compare Strings. Instead use the equals(...) or equalsIgnoreCase(...) methods. The == operator returns true if the two String objects are the same, but this isn't what matters to you, but rather you want to check if both Strings hold the same characters in the same order, and this is what the two methods above check.
You're currently downloading your files on the Swing event dispatch thread or EDT, and this will not only prevent your JLabel from updating but will also cause your GUI to freeze since this thread is responsible for drawing all Swing graphics including its own components, and for Swing interaction with the user.
Calling repaint(), revalidate(), invalidate(), etc... will do nothing to solve this.
To solve this, do the downloading or any long-running process in a background thread. One way to do this is by creating a new Thread, loading it with a Runnable, and calling start, but there's a better way that is tailor made for Swing GUI's and that's to create a SwingWorker object and do your background coding in its doInBackground() method. The SwingWorker tutorials can help you figure out how to do this, and if you get stuck in your attempt, please come on back with your code.
You'll probably not want to mention your urgency as this often has the opposite effect intended. Please remember that we are all volunteers, that your urgency is truly your urgency not ours, and that no one likes to feel rushed or pressured to do something for someone else, especially volunteers.
Best of luck and welcome to stackoverflow.
Edit
Since you've seen an example of using plain Threads, I figured I'd post an example of what doing this with a SwingWorker object could look like:
if (ae.getActionCommand().equalsIgnoreCase("Download")) {
final int[] row_indexes = table.getSelectedRows();
notifylb.setText("Downloading files");
final List<String> fileNames = new ArrayList<String>();
for (int i = 0; i < row_indexes.length; i++) {
fileNames.add(table.getValueAt(row_indexes[i], 0).toString());
}
SwingWorker<String, String> downloadSwingWorker = new SwingWorker<String, String>(){
#Override
protected String doInBackground() throws Exception {
for (String fileName : fileNames) {
publish("Downloading: " + fileName);
downloadFtpfile(fileName);
}
return "SUCCESSFULLY DOWNLOADED FILE(s) !";
}
#Override
protected void process(List<String> chunks) {
for (String text : chunks) {
notifylb.setText(text);
}
}
#Override
protected void done() {
try {
String text = get();
notifylb.setText(text);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
};
downloadSwingWorker.execute();
}
Edit 2: corrected as per kleopatra's suggestion
#Hovercraft-Full-Of-Eels explain very clear, but if you need the code, here it is how to write it.
final JButton finalButton = button; // this is your button will trigger download
final JLabel finalLabel = finalLabel;
final JTable finalTable = table;
if(ae.getActionCommand().equals("Download"))
{
finalButton.setEnabled(false); //disable button, so user can not start it for twice until ftp finished.
Thread thread = new Thread(new Runnable()
{
#Override
public void run()
{
int[] row_indexes = finalTable.getSelectedRows();
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
finalLabel.setText("Downloading files");
}
});
for(int i = 0; i < row_indexes.length; i++)
{
final String fn = finalTable.getValueAt(row_indexes[i], 0).toString();
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
finalLabel.setText("Downloading: " + fn); // fn contains filename
}
});
this.downloadFtpfile(fn);
}
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
finalLabel.setText("SUCCESSFULLY DOWNLOADED FILE(s) !");
finalButton.setEnabled(true); //enable the button
}
});
}
});
thread.start();
};

Categories