Firstly, apologies but it would be really hard to reconstruct a SSCCE for this, though I think I can explain the situation considerably well and get my question across,
My situation is as thus: I have a JLabel that serves as a status indicator (e.g. that will display "Loading..." or "Ready") and I call the setText method in a MouseAdapter before another method is called to do the actual action. However, the JLabel text never changes, unless I do something like call JOptionPane.showMessageDialog() in which case the text does update.
So, does anybody have any suggestions as to how I can resolve this situation without doing something like displaying a message box for (what would be) no reason whatsoever?
Thanks in advance
Make sure you don't run your task (your "Loading..." procedure) on the EDT (Event Dispatch Thread); if you do so, your GUI won't get updated.
You have to run your application code (unless it's very fast, say less than 100ms, no network access, no DB access, etc) on a separate thread. The SwingWorker (see javadocs) class might come handy for this purpose.
The EDT (e.g. code blocks inside user interface listeners) should only contain code for updating the GUI, operating on Swing components, etc. Everything else you should run on its own Runnable object.
--
EDIT: reponse to Andy's comment. Here's a raw example (written on the fly, it might have typos and such and might not run as-is) of how you can use the SwingWorker class
Put this in your mouse listener event or whatever makes your task start
//--- code up to this point runs on the EDT
SwingWorker<Boolean, Void> sw = new SwingWorker<Boolean, Void>()
{
#Override
protected Boolean doInBackground()//This is called when you .execute() the SwingWorker instance
{//Runs on its own thread, thus not "freezing" the interface
//let's assume that doMyLongComputation() returns true if OK, false if not OK.
//(I used Boolean, but doInBackground can return whatever you need, an int, a
//string, whatever)
if(doMyLongComputation())
{
doSomeExtraStuff();
return true;
}
else
{
doSomeExtraAlternativeStuff();
return false;
}
}
#Override
protected void done()//this is called after doInBackground() has finished
{//Runs on the EDT
//Update your swing components here
if(this.get())//here we take the return value from doInBackground()
yourLabel.setText("Done loading!");
else
yourLabel.setText("Nuclear meltdown in 1 minute...");
//progressBar.setIndeterminate(false);//decomment if you need it
//progressBar.setVisible(false);//decomment if you need it
myButton.setEnabled(true);
}
};
//---code under this point runs on the EDT
yourLabel.setText("Loading...");
//progressBar.setIndeterminate(true);//decomment if you need it
//progressBar.setVisible(true);//decomment if you need it
myButton.setEnabled(false);//Prevent the user from clicking again before task is finished
sw.execute();
//---Anything you write under here runs on the EDT concurrently to you task, which has now been launched
Do you call the setText() method from the EDT?
Related
I'm trying to get into JavaFX for making first attempts in making GUIs with Java. Therefore I made a simple neural network which learns the XOR and displays the output in JavaFX. My question is - how can I update the GUI regularly while processing the data?
Everything I achieved so far is a single update in the GUI when the network finished learning. Even if I started the networking in a thread.
I expect that the right handed side of the GUI updates (circle change the colors in dependence of the output) regularly for each n epoch and not only once. The attached image shows the GUI before the network started.
I appreciate any help in advance.
JavaFX has an "Event Thread", which is responsible for handling button clicks, updating labels, and any other GUI-related tasks. When you call button.setOnAction(e -> doSomething());, when your button is pressed, doSomething() happens on the JavaFX thread. During the time that this is running, no other GUI events can occur. This means your interface will completely freeze, which leads to a bad user experience.
Also, you cannot perform GUI operations on any thread other than the JavaFX thread, or you will get an IllegalStateException. (Try calling Executors.newSingleThreadExecutor().execute(() -> label.setText("hello")); to see this in action)
Luckily, JavaFX provides methods to get around this.
First, and easiest, is to call your long-running method inside a new thread (perhaps with ExecutorServices as above), and when you need to modify the interface, wrap those calls in a call to Platform.runLater(() -> updateInterface());. This will post updateInterface() to the GUI thread, and will allow it to run.
However, this can be messy, so the preferred method is to use a Service.
Assume your long running calculation returns an Double, you create a class extending Service<Double>, override its createTask() method, and perform the calculation there, as such:
public class CalculationService extends Service<Double> {
#Override
protected Task<Double> createTask() {
return new Task<Double>() {
#Override
protected Double call() throws Exception {
return doCalculation();
}
};
}
}
Then, in your controller, declare a private final CalculationService service = new CalculationService();
In your controller's initialize() method, you can then bind the output of this service to anything you want. For example:
calculationDisplayLabel.textProperty().bind(Bindings.createStringBinding(service.valueProperty()));
// continuously updates the label whenever the service calculates a new value
Then, whenever you decide you want to start calculating again, you call service.restart() to interrupt the process if it is running, and start again from the beginning.
If you want to call code when the value changes, add a listener to the value of the service. For example, if you want it to recalculate as soon as it has finished, call:
service.valueProperty().addListener((obs, old, newValue) -> service.restart());
If in doubt, refer to https://docs.oracle.com/javase/8/javafx/api/javafx/concurrent/Service.html
I just try to show a loading-animation while I access an ERP in my code like this:
protected void submit()
{
messageField.getStyleClass().add("smallLoading");
submitImpl();
messageField.getStyleClass().remove("smallLoading");
}
Sadly the animation is never shown... Just the result as before. I tried using Platform.runLater, which yielded the same result. I also transfered the last 2 lines in a Thread, which worked (the animation was shown), but lead to the error "Not on FX application thread", when the Submitter tried to write to my message-field. When I passed the Thread to Platform.runLater it did not show the animation... I googled a little bit, but could not find a solution. Maybe I'm missing something important...
I appreciate any help. Thank you!
It seems like you don't really fully understand how the UI thread works.
The code you've posted is single-threaded. It all operates on the UI thread. You add a style class, do some work, then remove it. The problem is that this sequence of operations is effectively "atomic": the UI doesn't actually update anything until its all done. This is why you don't see the loading symbol change.
When you put all of this within runLater the result is the same. It's still all on the UI thread. The only difference here is that rather than running the code now, you're deferring it until some point "later" (probably actually very soon).
When you try to put the last two lines in a separate thread, the issue is that you're trying to make UI changes on a non-UI thread. That's not allowed.
What you want to do is run everything on a non-UI thread, and push back the UI operations to the UI thread with runLater. Something like this:
new Thread(() -> {
Platform.runLater(()-> messageField.getStyleClass().add("smallLoading"));
submitImpl();
Platform.runLater(()-> messageField.getStyleClass().remove("smallLoading"));
}).start();
I have a basic implementation of a Kafka Consumer. I have it working properly when it is calling the poll() method on button click (let's call this V1).
But as soon as I place the poll() method inside a while loop (let's call this V2), I stop receiving the messages.
The weird thing is that V1 is still able to pick up the new messages but V2 still receives an empty ConsumerRecords object.
I have made sure that V1 and V2 are in different ConsumerGroups.
Here, V2 contains the code as shown below.
And V1 has the while loop removed. Otherwise there is no other point of difference.
Can someone tell me what is happening and why?
Code Reproduced below:
btnButton.addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent arg0){
while(true) {
ConsumerRecords<String, String> records = consumerGroupObj.consumerObj.poll(100);
// Trying to insert into DefaultTableModel here for an existing jTable
// Some other processing code
}
}
}
Update:
I noticed something here. It might not be the poll() method that is causing this. But the enclosing eventHandler. Please check the code provided above for updates.
What I've noticed is that, until the control exits this mouseClicked() method, the changes made by the code inside are not visible in the GUI.
So, my new question: How do I make a Kafka consumer run in an infinite loop that is started by a button click event?
Empty results may caused by short timeout parameter, you could try to increase it.
timeout - The time, in milliseconds, spent waiting in poll if data is
not available in the buffer.
I found out (through a very convoluted search) that you cannot have a loop inside an event listener, for reasons that seem very obvious now.
That being said, I did find out that I can use a class that extends the SwingWorker class.
So basically, what I did was remove all the code from the mouseClicked() method and put it in the doInBackground() method of my SwingWorker class. Obviously, this created a lot of errors.
To troubleshoot those errors, I needed to pass all the information I needed from the GUI components to the SwingWorker class. I did this by using a parameterised constructor.
After that, I just instantiated the SwingWorker class with the proper values and executed the object. Et Voila!
I have a simple JAVA program with gui that just increments int variable and displays its value in JLabel.
I create new thread for proper(thread-safe) updating JLabel by calling inside it EventQueue.invokeLater() with Runnable class which run method simply does
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
label.setText("" + number);
}
});
When i run program, as expected label's number starts to grow rapidly from 1 to about 5000 but then it starts to slow down and i'm starting to see such label's updates like 100255, 173735, 235678 and big pauses between them with blocked GUI.
But when i compile without using EventQueue.invokeLater(), just calling directly label.setText("" + number); everything works fine and perfect and i can see how each number of my label is changing extremely fast. But of course i realize in that case my method isn't thread-safe.
What's the problem? It seems to me that EventQueue works slow or something.
Probably, the event queue is being choked up. You may want to look at coalescing the events to remove redundant entries when you are queuing event faster than they can be dequeued and actioned.
Every time an event is added to the queue, the existing events are queried to see if they merge the new event with themselves. As the queue backs up, more and more events have to be so queried, and the system gets progressively further behind. This is useful for mouse events, but in a simple (and artificial) case like this it can be detrimental.
Having said that, I vaguely recall that the GUI code is optimized to not attempt coalescing events that don't override the appropriate method, so your problem may be just a simple backlog.
Instead of calling setText directly, you could create a custom event for setting text on a component, implement coalescing for it and use that instead so that at any given time only the most recent text is pending. If you do this and you want to set the text based on what was previously set it's better to retain the value and always set the GUI widget from that rather than recalling the GUI widget's current value with getText. Otherwise merging is much more difficult.
As part of a larger application, I am writing a settings class, which collects and stores user-defined settings. This class is a singleton, and is instantiated during application startup.
In order to accept user input, two different GUI frames are insantiated from within ConfigSettings.java, from a public static method, selectSettings(). Both are subclasses of JFrame. Here is the code for the instantiation of the file selection dialog:
private void selectFile() {
SelectFileGUI fileSelector = new SelectFileGUI();
fileSelector.setVisible(true);
synchronized(this) {
try {
wait();
} catch(Exception e) {
e.printStackTrace();
}
}
fileSelector.dispose();
}
This works fine when the application is initially run. However, at a later point the user may alter their selected settings, including selecting a new source file. This is done by calling selectSettings() again.
The issue I'm having is that any subsequent attempt to instantiate and display these GUI components again results in a new JFrame being displayed, but with a grey background, and no buttons or other components shown. While debugging I was also failing to create new instances of SelectFileGUI directly.
What could be causing this sort of behaviour?
I would check to see if the second time you call it you are using the GUI thread or calling from one of your own threads.
At the top of that method you can test for it (The AWT thread is pretty easily identifiable by name) and have it throw an exception so developers know not to call it on the wrong thread--or you can block their thread and do it in a worker thread.
I don't know what is causing this behavior but in your code the following simply cannot possibly be the right way to manage dialogs (more below):
fileSelector.setVisible(true);
synchronized(this) {
try {
wait();
} catch(Exception e) {
e.printStackTrace();
}
}
fileSelector.dispose();
Do you want your dialogs to be modal or not?
If you want them to be modal, then you simply make a blocking call like when you're invoking JColorChooser.showDialog(...) method and your return "value" is your color/file/whatever.
If you want them non-modal, then you use a callback to get your color/file. In the JColorChooser dialog example, you'd call the createDialog(...) method and use the ok/cancel listeners as callbacks.
I suggest you take a look at sun's tutorial, for example the one on color chooser, to see how to correctly display a modal (or non-modal) dialog:
http://java.sun.com/docs/books/tutorial/uiswing/components/colorchooser.html
Once again, that synchronized(this) { try { wait() ... } to manage something as simple as a file selector/dialog frame just cannot be correct.
Agree with BillK: sounds like you're calling it from outside the EDT first time around (so your call to wait() doesn't block the EDT), then from the EDT the second time around. See SwingUtilities.invokeAndWait() and/or Dialog.setModal().
The consensus here is that you are breaking the rules governing the use of the AWT painting thread (the Event Dispatch Thread).
A couple things to note:
If your code attempts to paint your GUI components outside this painting thread, the gray dialog could be the result of a deadlock between the EDT and the thread your application is using to paint.
If you do get into this situation, you will experience the inability to create new dialogs as described.
However, as you mention that you are debugging while experiencing this problem, it might be that you have paused the EDT through your IDE.
Take a look at this tutorial for some guidelines on how use threads in a client application.
To fully appreciate the issue, it would be nice to see some more code - pertinent parts of selectSettings(), for example.