JavaFX action event statement execution order - java

public class MainController {
#FXML
public Button browse_report;
#FXML
public Button browse_directory;
#FXML
public Button export;
#FXML
public Button close;
#FXML
public Label report;
#FXML
public Label directory;
#FXML
public Label processing;
#FXML
public TextField report_text;
#FXML
public TextField directory_text;
#FXML
public ProgressBar pg = new ProgressBar();
public void closeButton(ActionEvent e) {
System.exit(0);
}
public void getReport(ActionEvent e) {
FileChooser fc = new FileChooser();
File file=fc.showOpenDialog(null);
report_text.setText(file.getAbsolutePath());
}
public void getDirectory(ActionEvent e) {
DirectoryChooser dc = new DirectoryChooser();
File file =dc.showDialog(null);
directory_text.setText(file.getAbsolutePath());
}
public void Export(ActionEvent e) {
pg.setProgress(-1);
foo();
Alert alert = new Alert(AlertType.INFORMATION, "Spreadsheet Generated", ButtonType.CLOSE);
alert.showAndWait();
pg.setProgress(1);
}
The above Export() method, when executed on button click,appears to run the method statements out of order. The progress bar animation does not display until after the alert window pops up. How can I correct this?

Assuming your foo() method takes a long time to run, what is happening is that you are blocking the FX Application Thread, which prevents it from performing its usual duties, such as rendering the UI. So while the statements are (of course) executed in the order you write them, i.e. the progressProperty of pg is set to -1, then foo() is executed, then the Alert is shown, you won't actually see the results of those in the UI until the whole process completes (or, actually, in this case until you relinquish control of the FX Application Thread by calling showAndWait()).
The basic way to solve this is to run foo() in a background thread. If there is UI-related code you want to perform when that completes, the best way to do that is to use a Task and use its onSucceeded handler to perform the UI code at the end. So in general you do something like this:
public void Export(ActionEvent e) {
pg.setProgress(-1);
Task<Void> task = new Task<Void>() {
#Override
protected Void call() throws Exception {
foo();
return null ;
}
};
task.setOnSucceeded(evt -> {
Alert alert = new Alert(AlertType.INFORMATION, "Spreadsheet Generated", ButtonType.CLOSE);
alert.showAndWait();
pg.setProgress(1);
});
new Thread(task).start();
}

Related

Refactor JFoenix JFXDialogLayout alert notification code to a simpler form so that it can be reused for other classes

how can I refactor the following code so that only the code in deleteButton.setOnAction(deleteEvent -> {//only this code varies} changes. Everything else will stay the same but the block of code in the lambda expression varies from time to time when I call the class from another class. The block of code that goes through the lambda expression is supposed to be a void method.
public class A {
public void test() {
// ensure that user can't close the alert
Stage primaryStage = (Stage) RootLayoutController.getRootLayout().getScene().getWindow();
JFXAlert<javafx.scene.control.ButtonType> alert = new JFXAlert<>(primaryStage);
alert.initModality(Modality.APPLICATION_MODAL);
alert.setOverlayClose(false);
//create font awesome icon
String ICON = "\uf071";
Label labelIcon = new Label(ICON);
labelIcon.setStyle("-fx-font-family: 'FontAwesome'; -fx-font-size: 60px; -fx-text-fill: #D34336;");
labelIcon.setPadding(new Insets(0,5,0,0));
// Create the content of the JFXAlert with JFXDialogLayout
JFXDialogLayout layout = new JFXDialogLayout();
Label labelHeading = new Label("Alert Notification");
Label labelBody = new Label("Are you sure you want to delete this?");
layout.setHeading(labelHeading);
layout.setBody(new VBox(new HBox(labelIcon, labelBody)));
// Buttons get added into the actions section of the layout.
JFXButton deleteButton = new JFXButton("Delete");
deleteButton.setDefaultButton(true);
deleteButton.setOnAction(deleteEvent -> {
//only this block of code changes
alert.hideWithAnimation();
});
JFXButton cancelButton = new JFXButton("Cancel");
cancelButton.setCancelButton(true);
cancelButton.setOnAction(closeEvent -> alert.hideWithAnimation());
layout.setActions(deleteButton, cancelButton);
alert.setContent(layout);
alert.showAndWait();
}
}
It is not entirely clear from your question what you are trying to accomplish, but I will take a wild stab at it.
If you are looking to be able to pass a code block to the deleteButton.setOnAction() method, you could use an Interface and pass implementations of that interface to the A class. Then just pass that reference to an internal method for the onAction lambda.
Here is a very quick example of how you could do something like this:
Main.java:
public class Main extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
// Action button
Button btnDoSomething = new Button("Do something...");
btnDoSomething.setOnAction(e -> doTheThings(new ImplDoSomething()));
Button btnDoSomethingElse = new Button("Do something else...");
btnDoSomethingElse.setOnAction(e -> doTheThings(new ImplDoSomethingElse()));
VBox mainPane = new VBox(5);
mainPane.setAlignment(Pos.CENTER);
mainPane.setPadding(new Insets(10));
mainPane.getChildren().addAll(btnDoSomething, btnDoSomethingElse);
primaryStage.setScene(new Scene(mainPane));
primaryStage.show();
}
private void doTheThings(IParameterMethod parameterMethod) {
parameterMethod.call();
}
}
The IParameterMethod.java Interface:
public interface IParameterMethod {
void call();
}
Then you can create as many classes as you like that implement that interface, each with their own call() method, allowing you to execute different code.
ImplDoSomething.java
public class ImplDoSomething implements IParameterMethod {
#Override
public void call() {
System.out.println("Doing something!");
}
}
ImplDoSomethingElse.java:
public class ImplDoSomethingElse implements IParameterMethod {
#Override
public void call() {
System.out.println("Doing something else!");
}
}
This should be easily adapted for your project.

Thread for controlling components in JFXDrawer

I'm using a JFXDrawer, SalesDrawer containing a VBox and 2 JFXButtons inside it. The contents of the JFXDrawer have a separate controller, SalesDrawerController.java. The controller file, SalesController.java that contains the JFXDrawer consists of 2 anchor panes that I want to set visible on click of the buttons in the JFXDrawer. Till now I was using a set of static boolean variables and making sure on click of a button in the JFXDrawer one of the variables is set to true. Then in the SalesController.java, I used a TimerTask object to check which of these variables were true and set the needed anchor pane to visible. Is there a better way of doing this?
SalesDrawerController.java
public class SalesDrawerController implements Initializable {
#Override
public void initialize(URL url, ResourceBundle rb) {
// TODO
}
#FXML
private void button1Hit(MouseEvent event) {
SalesController.SD[0]=true;
}
#FXML
private void button2Hit(MouseEvent event) {
SalesController.SD[1]=true;
}
}
SalesController.java
public class SalesController implements Initializable {
public static boolean SD[]= {false,false};
static boolean tock=true;
#FXML
private AnchorPane eq_newpane;
#FXML
private AnchorPane eq_delpane;
#FXML
private JFXHamburger SalesHam;
#FXML
private JFXDrawer SalesDraw;
public void initialize(URL url, ResourceBundle rb) {
eq_newpane.setVisible(true);
eq_delpane.setVisible(false);
eq_newpane.setDisable(false);
eq_delpane.setDisable(true);
VBox box = FXMLLoader.load(getClass().getResource("/fxml/SalesDrawer.fxml"));
SalesDraw.setSidePane(box);
HamburgerBackArrowBasicTransition transition = new HamburgerBackArrowBasicTransition(SalesHam);
transition.setRate(-1);
SalesHam.addEventHandler(MouseEvent.MOUSE_PRESSED,(e)->{
transition.setRate(transition.getRate()*-1);
transition.play();
if(SalesDraw.isShown()){
SalesDraw.close();
SalesDraw.toBack();
}
else{
SalesDraw.toFront();
SalesDraw.open();
}
});
threadtock();
}
public void threadtock() {
final java.util.Timer timer = new java.util.Timer();
final TimerTask delayedThreadStartTask;
delayedThreadStartTask = new TimerTask() {
public void run() {
try
{
if(tock){
if(SD[0])
{
eq_newpane.setVisible(true);
eq_delpane.setVisible(false);
eq_newpane.setDisable(false);
eq_delpane.setDisable(true);
}
else if(SD[1]){
eq_delpane.setVisible(true);
eq_newpane.setVisible(false);
eq_newpane.setDisable(true);
eq_delpane.setDisable(false);
}
if(tock)
{
threadtock();
}
}
}
catch(Exception e)
{
e.printStackTrace();
}
}
};
timer.schedule(delayedThreadStartTask, 500);
};
}
Use synchronization mechanisms so you do not inherit deadlocks in your code from 2 or more threads attempting to operate on the same variable at once.
Figuring out how to properly synchronize your threads can be a pain, and it is very hard to explain, let alone debug. So, my best advice is to use google for "Java Concurrency" and "Java Thread Synchronization".
Otherwise, you should start a thread/threads from your main application to preform your intended operations

JavaFX and ProgressIndicator with fxml

I wonder from morning how to solve the problem. I have login aplication. When user waiting for login i want use processindicator. I used the second thread but it does not work
Main loader fxml
MainController
#FXML public StackPane MainStackPane;
public void initialize(URL arg0, ResourceBundle arg1) {
FXMLLoader loader = new FXMLLoader(this.getClass().getResource("/LoginForm/Login.fxml"));
Pane pane = null;
try {
pane = loader.load();
} catch (IOException e) {System.out.println(e.getMessage());}
LoginController login = loader.getController();
login.setMainController(this);
setScreen(pane, true);
}
public void setScreen(Pane pane, boolean clean){
MainStackPane.getChildren().addAll(pane);
}
LoginForm:
private MainController mainController;
private void Zaloguj() throws IOException {
String cryptUser = null, cryptPass = null;
Test test = new Test(this.mainController);
test.start();
try {
Thread.sleep(5000);
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
LoginSQL sql = new LoginSQL();`
Byte LoginResult = sql.SQLselect(cryptUser, cryptPass);
...}
Class Test
public class test extends Service<Void>{
private MainController mainController;
public test(MainController mainController) {
this.mainController = mainController;
}
protected Task<Void> createTask() {
return new Task<Void>() {
#Override
protected Void call() throws Exception {
System.out.println("Service: START");
ProgressIndicator p = new ProgressIndicator();
Platform.runLater(new Runnable() {
#Override public void run() {
mainController.MainStackPane.getChildren().addAll(p);
}});
if (isCancelled()) {
mainController.MainStackPane.getChildren().remove(p);
}
return null;
};};}}
ProgressIndicator appears only in the next window after login. How to do it ?
JavaFX rendering happens in the main thread only. If you add the ProgressIndicator and then use Thread.sleep(), JavaFX won't render the indicator until Thread.sleep() is done. Also, if the login request hangs, JavaFX will also wait until the login request is complete before rendering.
What you have to do is to make sure to never interrupt/hang the main thread. Remove Thread.sleep, and also move your login request to a child thread. When the request is complete, notify the main thread so that it can remove the ProgressIndicator.

JLabel disappears after a setText()

I'm setting up a mock program in an MVC fashion with Swing. I'm having troubles calling setText on a JLabel from an external Thread. I know Swing has its own threading model (setText is thread-safe though), so I've also tried forcing the setText call to be carried out by the EDT via SwingUtilities functions, with no results.
This is what the classes look like - I've omitted the irrelevant chunks, suppose they have references to each other and the GUI's layout is properly set up:
public class View extends JFrame
{
private final JLabel label = new JLabel("idle");
private final JButton button = new JButton("press me");
public View(final Controller controller)
{
this.controller = controller;
button.addListener(e -> controller.input());
}
public void refresh(final boolean value)
{
label.setText(value ? "YEP" : "NOPE");
}
}
public class Controller
{
private final ExecutorService service =
Executors.newSingleThreadExecutor();
public void input()
{
service.submit((Runnable) () ->
{
try
{
view.refresh(true);
Thread.sleep(1000);
}
catch(final InterruptedException exception)
{
exception.printStackTrace();
}
finally
{
view.refresh(false);
}
});
}
}
Expected behavior:
Button is pressed
input() is called on controller
A new thread is created, which first sets label's text to "YEP"
The newly created thread simulates some delay with Thread.sleep()
It awakens and sets label's text to "NOPE"
What I get is that after the call at point 3 the label disappears, but is drawn again at 5, showing the text "NOPE".
What am I doing wrong?

How to return result from event handler in javafx?

How to return result from event handler in javafx? I have bellow code, and how to return data from event to function showPrompt? Is it possible to recover the data for the function of the event?
public static String showPrompt(String title, String defValue){
final Stage dlgStage = new Stage();
TextField txtPromptValue = new TextField(defValue);
Button btnOk = new Button("Ok");
Button btnCancel = new Button("Cancel");
btnOk.setOnAction(new EventHandler<ActionEvent>(){
#Override
public void handle(ActionEvent arg0) {
//How to return data from event to function?
dlgStage.close();
}
});
btnCancel.setOnAction(new EventHandler<ActionEvent>(){
#Override
public void handle(ActionEvent arg0) {
//How to return data from event to function?
dlgStage.close();
}
});
//
Label lblTitle = new Label(title);
lblTitle.setFont(Font.font("Amble CN", FontWeight.NORMAL, 14));
//
VBox vbox = new VBox(lblTitle,txtPromptValue,btnOk,btnCancel);
vbox.setAlignment(Pos.CENTER);
vbox.setMinSize(300, 200);
//
Scene dlgScene = new Scene(vbox);
//
dlgStage.setScene(dlgScene);
dlgStage.initStyle(StageStyle.UNDECORATED);
dlgStage.initModality(Modality.APPLICATION_MODAL);
dlgStage.setMinWidth(300);
dlgStage.setMinHeight(200);
dlgStage.show();
}
The short answer is you can't return a value.
Why ?
This code bellow is called a callback.
new EventHandler<ActionEvent>(){
#Override
public void handle(ActionEvent arg0) {
dlgStage.close();
}
}
Callbacks have no return type, as you can see in the example above, it is void.
Callbacks are methods that you pass as an argument to another method. The other method will call you callback method when it wants. This means that callbacks are asynchronous. In your example, it calls the callback when you press the button.
In conclusion, you can't return from it using return.
What to do ?
You can call a method from your callback and sent your return value to it as an argument.
Example:
btnCancel.setOnAction(
new EventHandler<ActionEvent>(){
#Override
public void handle(ActionEvent arg0) {
YourClass.setReturnValue("This is button Cancel");
dlgStage.close();
}
}
});
Where setReturnValue is a method belonging to YourClass or an instance of it so it will retail your returned value.
Another way better approach would be to create a class that extends Stage maybe. Also in your showPrompt method you will have to block execution using showAndWait() or similar.
In conclusion, you can't create your entire Prompt from just one method.
You can't, because by the time you've opened and closed the prompt stage, the main thread will have already passed the showPrompt method.
As Andrei said, what you need to do is create your own custom PromptStage with a showPrompt API that blocks the main thread until the prompt stage is closed.
public static String showPrompt(final String title, final String defValue)
{
// This line will block the main thread
// See the "showAndWait()" API from JavaFX
final boolean result = PromptStage.showPrompt("My Prompt Stage", " ");
// And when the stage is closed, it will carry on to this piece of code
if (result)
{
return "This is button OK";
}
else
{
return "This is button CANCEL";
}
}
Or you could even create instances of your PromptDialog if you like
public static String showPrompt(final String title, final String defValue)
{
final PromptStage pStage = new PromptStage();
// This line will block the main thread
// See the "showAndWait()" API from JavaFX
pStage.showAndWait();
return pStage.getResultAsString();
}
There are very many approaches here. To be honest, I won't bother writing the whole class for you. However, do comment if you're stuck.
Another option is to pass the showPrompt(...) method a StringProperty, and update the property in your OK button's handler. The caller of showPrompt can then create the StringProperty, register a listener with it, and observe it. Something like:
public String showPrompt(String title, String defValue, final StringProperty result){
// ...
final TextField txtPromptValue = new TextField(defValue);
// ...
btnOk.setOnAction(new EventHandler<ActionEvent>(){
#Override
public void handle(ActionEvent event) {
result.set(txtPromptValue.getText());
dlgStage.close();
}
});
// ...
}
Then you call the dialog with something like:
StringProperty dialogResult = new SimpleStringProperty();
dialogResult.addListener(new ChangeListener<String>() {
public void changed(ObservableValue<? extends String> obs, String oldValue, String newValue) {
// process newValue, the value from the dialog...
}
});
showPrompt("Dialog Title", "Default value", dialogResult);

Categories

Resources