Thread writting status update text on any components under mouse - java

This is a rather odd one. I am using a Swing button to launch a scan of a list of files. Because I want it to display updates on the status bar, I am using a Thread. Since Swing won't let anything draw until the button's code has finished, I am also using a Tread to allow me to change the 'Start Scan' button to a 'Stop Scan' button.
The problem is that if the wait cursor is placed over any other components, during the scan, the status messages are also being written onto those components, such as buttons (see sample button below code), check boxes, etc; which messes up the interface. Is this a major bug or is it not a good idea to do what I am doing? Is there a way around it?
private void jButton47ActionPerformed(java.awt.event.ActionEvent evt)
{
// Scan folders button.
//
this.getFrame().setCursor(new Cursor(Cursor.WAIT_CURSOR));
// If button is in stop mode then...
if (collection.isScanContinue())
{
collection.setScanContinue(false);
jButton47.setText(" Scan Folders For Files ");
jButton47.setBackground(view.getDefaultButtonCol());
}
else // in scan mode...
{
Thread t = new Thread(new Runnable()
{
#Override
public void run()
{
// Setup the stop scan process button (changes the scan button to a stop button).
//
collection.setScanContinue(true);
jButton47.setText(" Stop Scanning Folders ");
jButton47.setBackground(collection.getPrefs().getDeleteCol());
collection.scanSourceAndTargetFolders();
if(collection.isScanContinue())
{
// do scan
}
// Reset the stop scan button and flag.
//
collection.setScanContinue(false);
jButton47.setText(" Scan Folders For Files ");
jButton47.setToolTipText("Scans Source and, if required, Target folders.");
jButton47.setBackground(view.getDefaultButtonCol());
view.getFrame().setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
}
});
t.start();
}
}
It cleans up fine if I re-validate the main frame, but it looks terrible during the file scan.

Any action involving swing, such as changing button text etc, should be performed on event dispatch thread using SwingUtilities.invokeLater. Otherwise, you'll run into concurrency issues like you see here. See this question for more details about how event thread works: Java Event-Dispatching Thread explanation
Also, for doing background tasks like this, Swing provides a handy utility called SwingWorker: http://docs.oracle.com/javase/tutorial/uiswing/concurrency/worker.html

Related

Using a JavaFX Alert dialog freezes a JavaFX task [duplicate]

I am trying to set up a background service that would perform bulk loading of transaction data from a csv file. This background service would be initiated from a menu item action mapped to a method in the controller/presenter class.
Ever so often, some data turns up in the csv file for which no master data can be found in the database, this would normally cause the upload to choke and fail.
On such occasions, I would like to be able to have the background service pause its processing and invoke a dialog from a presenter class to take in user input. The user input would be used to add a master row in the database, after which the background service should resume from where it had left off (not from the beginning of the csv file, but from the row which caused the error).
Is this possible to achieve in JavaFX, perhaps with the javafx.concurrent API? How would I go about doing this?
Solution
When your background process encounters a situation where it requires a user to be prompted for input, use FutureTask executed in Platform.runLater to showAndWait the dialog prompt on the JavaFX application thread. In the background process use futureTask.get to pause the background process until the user has input the necessary values which will allow the process to continue.
Sample Code Snippet
Here is the essence of code for this approach which can be placed inside the call method of your background process:
String nextText = readLineFromSource();
if ("MISSING".equals(nextText)) {
updateMessage("Prompting for missing text");
FutureTask<String> futureTask = new FutureTask(
new MissingTextPrompt()
);
Platform.runLater(futureTask);
nextText = futureTask.get();
}
...
class MissingTextPrompt implements Callable<String> {
private TextField textField;
#Override public String call() throws Exception {
final Stage dialog = new Stage();
dialog.setScene(createDialogScene());
dialog.showAndWait();
return textField.getText();
}
...
}
Sample Application
I created a small, complete sample application to demonstrate this approach.
The output of the sample application is:
Sample Output Explanation
Lines read without missing values are just plain brown.
Lines with a prompt value entered have a pale green background.
Fourteen lines have been read, the background task has already paused once at the 6th line which was missing a value. The user was prompted for the missing value (to which the user entered xyzzy), then the process continued until line 14 which is also missing and the background task is again paused and another prompt dialog is being displayed.

In Eclipse RAP how to push/force current UI state to the client?

I'm using Eclipse RAP and have the following use case:
enter search text in a Text field and start search
change a Label text to "searching...."
do actual search (async) and display result in a Table
The problem, even though the label should change to "searching...." before the actual search is started, it is changed to "searching...." after the search is done.
What I'm looking for is a way to push/force/update the current UI state to the client after the label changed, prior to searching:
enter search text in a Text field and start search
change a Label text to "searching...."
push current UI state to client
do actual search (async) and display result in a Table
Here some sample code:
Label statusLabel = new Label(parent, SWT.NONE);
Text searchText = new Text(parent, SWT.SEARCH);
searchText.addSelectionListener(new SelectionAdapter() {
#Override
public void widgetDefaultSelected(SelectionEvent e) {
// change label
statusLabel.setText("searching...");
// HERE force client update
// start searching
Display.getCurrent().asyncExec(new Runnable() {
#Override
public void run() {
// do actual search
}
});
}
});
asyncExec still runs the code in the UI thread it just delays it slightly until the next call to Display.readAndDispatch. So your search code will still block and isn't allowing the label to update.
You need to actually run the search in a separate thread.
asyncExec is intended to be used in a background thread to run a small amount of code in the UI thread when possible. (You need to use Display.getDefault() rather than Display.getCurrent() in a background thread).
So in your background thread you do something like:
while (more to do)
{
.... do a step of the search
Display.getDefault().asyncExec(.... UI update code ....);
}

JavaFX: Stage close handler

I want to save a file before closing my JavaFX application.
This is how I'm setting up the handler in Main::start:
primaryStage.setOnCloseRequest(event -> {
System.out.println("Stage is closing");
// Save file
});
And the controller calling Stage::close when a button is pressed:
#FXML
public void exitApplication(ActionEvent event) {
((Stage)rootPane.getScene().getWindow()).close();
}
If I close the window clicking the red X on the window border (the normal way) then I get the output message "Stage is closing", which is the desired behavior.
However, when calling Controller::exitApplication the application closes without invoking the handler (there's no output).
How can I make the controller use the handler I've added to primaryStage?
If you have a look at the life-cycle of the Application class:
The JavaFX runtime does the following, in order, whenever an
application is launched:
Constructs an instance of the specified Application class
Calls the init() method
Calls the start(javafx.stage.Stage) method
Waits for the application to finish, which happens when either of the following occur:
the application calls Platform.exit()
the last window has been closed and the implicitExit attribute on Platform is true
Calls the stop() method
This means you can call Platform.exit() on your controller:
#FXML
public void exitApplication(ActionEvent event) {
Platform.exit();
}
as long as you override the stop() method on the main class to save the file.
#Override
public void stop(){
System.out.println("Stage is closing");
// Save file
}
As you can see, by using stop() you don't need to listen to close requests to save the file anymore (though you can do it if you want to prevent window closing).
Suppose you want to ask the user if he want to exit the application without saving the work. If the user choose no, you cannot avoid the application to close within the stop method. In this case you should add an EventFilter to your window for an WINDOW_CLOSE_REQUEST event.
In your start method add this code to detect the event:
(Note that calling Platform.exit(); doesn't fire the WindowEvent.WINDOW_CLOSE_REQUEST event, see below to know how to fire the event manually from a custom button)
// *** Only for Java >= 8 ****
// ==== This code detects when an user want to close the application either with
// ==== the default OS close button or with a custom close button ====
primaryStage.getScene().getWindow().addEventFilter(WindowEvent.WINDOW_CLOSE_REQUEST, this::closeWindowEvent);
Then add your custom logic. In my example i use an Alert popup to ask the user if he/she want to close the application without saving.
private void closeWindowEvent(WindowEvent event) {
System.out.println("Window close request ...");
if(storageModel.dataSetChanged()) { // if the dataset has changed, alert the user with a popup
Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.getButtonTypes().remove(ButtonType.OK);
alert.getButtonTypes().add(ButtonType.CANCEL);
alert.getButtonTypes().add(ButtonType.YES);
alert.setTitle("Quit application");
alert.setContentText(String.format("Close without saving?"));
alert.initOwner(primaryStage.getOwner());
Optional<ButtonType> res = alert.showAndWait();
if(res.isPresent()) {
if(res.get().equals(ButtonType.CANCEL))
event.consume();
}
}
}
The event.consume() method prevents the application from closing. Obviously you should add at least a button that permit the user to close the application to avoid the force close application by the user, that in some cases can corrupt data.
Lastly, if you have to fire the event from a custom close button, you can use this :
Window window = Main.getPrimaryStage() // Get the primary stage from your Application class
.getScene()
.getWindow();
window.fireEvent(new WindowEvent(window, WindowEvent.WINDOW_CLOSE_REQUEST));
Ahh this is a known bug in JavaFX where the Stage will not close if a modal dialog is present at the time of closing. I will link you to the bug report which I just saw today. I think it is fixed in the latest release.
Here you go:
https://bugs.openjdk.java.net/browse/JDK-8093147?jql=text%20~%20%22javafx%20re-entrant%22
resolved in 8.4 it says. I think this what you are describing.
public Stage getParentStage() {
return (Stage) getFxmlNode().getScene().getWindow();
}
btnCancel.setOnAction(e -> {
getParentStage().close();
});

Unable to change button text to icon immediate after click button in java swing

I have written following code.I have not shown full source but psudo code.
class UI extends JFrame
{
//created UI with one Button
onButtonclick()
{
//did some operation before set icon to button
//say opened fileopen dialog and get file
button.setText("");
ImageIcon progressbar = new
ImageIcon(DatasetExporterUI.class.getResource("/progreassbar.gif"));
buttonExport.setIcon(progressbar);
// did some database operations
//again removed icon from button
button.setIcon(null);
button.setText("click");
}
}
When I click on button It opens file open dialog and and button text get set to empty.
But It doesn't set Icon to button.When all Database operation are done which are performed after Icon set to button that time Icon is appeared on button.
Why this behavior is?
How to set Icon to button and do some Database operations and again remove it?
Thank you. :)
The GUI system can only do one thing at a time, like most code (except for code that uses threads). Calling your listener is a thing. The GUI system cannot do anything else while your listener is running.
Your database operation needs to run on another thread (which you can create) and then update the GUI when it's done. Something like this:
void onButtonPressed() {
// The code to open the file dialog goes here
button.setText("");
ImageIcon progressbar = new
ImageIcon(DatasetExporterUI.class.getResource("/progreassbar.gif"));
buttonExport.setIcon(progressbar);
new Thread() {
#Override
public void run() {
// do some database operations here
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
//again remove icon from button
button.setIcon(null);
button.setText("click");
}
});
}
}.start();
}
Code in different threads runs at the same time. This is convenient but dangerous. Be extremely careful when accessing data from the new thread - if one thread changes a field and the other thread reads it, the results might not be what you expect. The simplest thing to do is to make sure the main thread doesn't change any variables used by the new thread while it's running.
When your database operations are finished, you can't set the button back to normal by just calling setText. Only the main thread is allowed to affect the GUI - what if the main thread was drawing the button on the screen at the same time the database operation thread was changing the text? The button might be drawn incorrectly. So you need to call EventQueue.invokeLater which tells the GUI system to run your code in the near future when it's not busy. The code inside new Runnable() {} is like the code in the button listener - no other GUI-related code will run while it does.
This should work:
Image progressbar= ImageIO.read(DatasetExporterUI.class.getResource("/progreassbar.gif"));
buttonExport.setIcon(new ImageIcon(progressbar));

How to force redraw of an ex-invisible group's content in SWT?

I'm a total noob in SWT, just getting started, but I have previously worked with GUI frameworks such as Swing.
I have a Composite which contains a Group and a Button. The Group is initially set to invisible (using group.setVisible(false)), and is set to visible when clicking the button. This starts a thread which perform some calculations, updating a label inside the group with the progress (kind of a manual progress bar. This is what the customer wants :) ).
Anyway, for some reason, the group only appears after the thread has finished running, and I can't seem to make it appear, no matter what I've used (tried calling this.pack(), this.layout(), this.getShell().layout(), redraw() on a variety of controls in the path - nothing).
Here's how I create the group:
statusGroup = new Group(this, SWT.SHADOW_NONE);
statusGroup.setLayout(null);
statusGroup.setVisible(false);
percentCompleteLabel = new Label(statusGroup, SWT.NONE);
percentCompleteLabel.setText("0% complete");
Here's how I'm updating it from the Button's SelectionListener:
this.statusGroup.setVisible(true);
this.statusGroup.pack(true);
this.statusGroup.layout();
this.getShell().layout();
myThreadStartupCode(); // psuedo
while (!workIsDone) // psuedo
{
final int progress = myProgressCalcMethod(); // psuedo
percentCompleteLabel.setText(progress + "% complete");
percentCompleteLabel.pack(true);
this.layout();
this.redraw();
Thread.sleep(100);
}
Any clue would be appreciated.
Apparently, the solution is to use Display.getCurrent().update();

Categories