I am trying to create my very first application in JavaFX and I have a problem with Button that calls a method (for example to open another window) - I always have to click it twice in order to trigger action.
Here's my code from the Controller:
import javafx.fxml.FXML;
import javafx.scene.control.Button;
public class ControllerSignIn {
#FXML
private Button forgot;
#FXML
private Button back;
#FXML
private Button signin;
public void forgetPasswordClicked() {
forgot.setOnAction(e -> ForgotPassword.setUpWindow()); //works on 2nd click
}
public void backClicked() {
back.setOnAction(e -> ForgotPassword.closeWindow()); //works on 2nd click
}
public void signInClicked() {
System.out.println("Sign In CLICKED"); //works on first click
}
}
My methods are implemented here:
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Modality;
import javafx.stage.Stage;
import java.io.IOException;
public class ForgotPassword {
static Stage window;
static Scene scene;
static Parent root;
private static void loadFXML() {
try {
root = FXMLLoader.load(ForgotPassword.class.getResource("ForgotPassword.fxml"));
} catch (IOException e) {
e.printStackTrace();
}
}
public static void setUpWindow() {
loadFXML();
scene = new Scene(root);
scene.getStylesheets().add("signin/SignIn.css");
window = new Stage();
window.initModality(Modality.APPLICATION_MODAL);
window.setTitle("Forgot Password?");
window.setScene(scene);
window.showAndWait();
}
public static void closeWindow() {
window.close();
}
}
Most likely you have the following in your FXML:
<Button fx:id="forgot" onAction="#forgetPasswordClicked" />
This makes your button forgot call your method forgetPasswordClicked(). But instead of defining your logic to be executed when your button is clicked, the first time you say: "When this button is clicked, place an action event on my button which will call setUpWindow()"
forgot.setOnAction(e -> ForgotPassword.setUpWindow());
Therefore, your first click "sets up" the logic of your button. The second click, actually executes it. To solve this, either immediately use your logic as such:
public void forgetPasswordClicked() {
ForgotPassword.setUpWindow();
}
or don't define the method to be called in your fxml, and move the initialization of your button (setting the action listener) to your initialization as following:
public class ControllerSignIn implements Initializable {
#FXML
private Button forgot;
#FXML
private Button back;
#Override
public void initialize(URL location, ResourceBundle resources) {
forgot.setOnAction(e -> ForgotPassword.setUpWindow());
back.setOnAction(e -> ForgotPassword.closeWindow());
}
}
This is also why your signInClicked() method works from the first click, because it actually executes the logic instead of setting up the handler first.
Related
package PostLogIn;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.AnchorPane;
public class PostLogInController {
#FXML Button team;
#FXML Button matches;
#FXML Button trainings;
#FXML Button photoGallery;
#FXML Button videoGallery;
#FXML Button results;
#FXML AnchorPane aPane;
public void resize(MouseEvent event) {
matches.setOnMouseEntered(e->{
matches.setScaleX(2);
matches.setTranslateX(50);
});
matches.setOnMouseExited(e1->{
matches.setScaleX(1);
matches.setTranslateX(0);
});
}
}
Instead of array, I would rather recommend to go with List. It makes the code simple to handle further.
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.AnchorPane;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class PostLogInController {
#FXML
Button team;
#FXML
Button matches;
#FXML
Button trainings;
#FXML
Button photoGallery;
#FXML
Button videoGallery;
#FXML
Button results;
#FXML
AnchorPane aPane;
private List<Button> buttons;
#FXML
public void initialize() {
buttons = Stream.of(team, matches, trainings, photoGallery, videoGallery, results).collect(Collectors.toList());
}
public void resize(MouseEvent event) {
buttons.forEach(b->{
b.setOnMouseEntered(e->{
b.setScaleX(2);
b.setTranslateX(50);
});
b.setOnMouseExited(e->{
b.setScaleX(1);
b.setTranslateX(0);
});
});
}
}
This would be much easier in CSS. Just do the following in an external CSS file:
.button:hover {
-fx-scale-x: 2 ;
-fx-translate-x: 50 ;
}
You can declare the CSS file directly in the FXML with a styleSheets attribute on the root element.
You could put them all in an array, and use a for-each loop to add the listener, but it would make it harder to refer to each individually:
...
public class PostLogInController {
Button[] buttons = new Button[6];
AnchorPane aPane;
public void resize(MouseEvent event) {
for(Button b : buttons) {
b.setOnMouseEntered(e->{
b.setScaleX(2);
b.setTranslateX(50);
});
b.setOnMouseExited(e1->{
b.setScaleX(1);
b.setTranslateX(0);
});
}
}
}
ok after the answer from #Jaden and taking a look at the Array of Button in JavaFX using Scene Builder I came out with a solution that worked out perfectly :
#FXML Button team;
#FXML Button matches;
#FXML Button trainings;
#FXML Button photoGallery;
#FXML Button videoGallery;
#FXML Button results;
#FXML AnchorPane aPane;
public Button[] arrayButton;
#FXML
public void initialize(){
initializeButtonArray();
}
private void initializeButtonArray() {
arrayButton = new Button[6];
arrayButton[0] = matches;
arrayButton[1] = team;
arrayButton[2] = trainings;
arrayButton[3] = photoGallery;
arrayButton[4] = videoGallery;
arrayButton[5] = results;
}
public void resize(MouseEvent event) {
for(Button b : arrayButton) {
b.setOnMouseEntered(e->{
b.setScaleX(2);
b.setTranslateX(50);
});
b.setOnMouseExited(e1->{
b.setScaleX(1);
b.setTranslateX(0);
});
}
}
}
Based on this document from Oracle on handling events in JavaFX, I don't know why you won't create a Mouse Event handler class that handles the specific action, and then pass the same instance to all your buttons:
In the document, they show the following example:
final Circle circle = new Circle(radius, Color.RED);
circle.setOnMouseEntered(new EventHandler<MouseEvent>() {
public void handle(MouseEvent me) {
System.out.println("Mouse entered");
}
});
For you, this means that, instead of creating an anonymous EventHandler, you create your own class that implements that interface (or extend some EventHandler adapter class) and your resizing code there (inside the handle method).
I have a class tasks, which handles multiple tasks using a menu layout, class tasks check the application flow by setting up the stage, with the menu scene to list all individual task. I want to run some task from the available list using there own classes, something like this:
Tasks.java:
package tasks;
import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.layout.VBox;
import javafx.scene.control.Button;
public class Tasks extends Application
{
private Stage window;
private Scene menuScene;
private Task1 task1;
public Tasks()
{
this.window=null;
this.menuScene=null;
this.task1=null;
}
private void setMenu()
{
VBox menu=new VBox();
Button newTask1Button=new Button("New Task 1");
newTask1Button.setOnAction(clickEvent -> this.startNewTask1());
menu.getChildren().add(newTask1Button);
//More buttons
this.menuScene=new Scene(menu,400,600);
this.window.setScene(this.menuScene);
}
private void startNewTask1()
{
this.task1=new Task1(this.window);
this.launchTask1();
}
private void launchTask1()
{
if(this.task1!=null)
{
int task1State=1;
//while(task1State==1) //To re-run for pause state
//{
task1State=this.task1.runTask1();
System.out.println("Task1 is in state "+task1State); //In no way part of program, just for debugging. Always give state=-1
//If 1-Paused, then display pause Menu for task1, by calling this.task1.paused(); and then again based on user input re-run runTask1
//If 0-Exit, then change the scene back to menuScene and quit the function
//}
}
}
#Override
public void start(Stage primaryStage)
{
this.window=primaryStage;
this.window.setTitle("Tasks");
this.setMenu();
this.window.show();
}
public static void main(String[] args)
{
Application.launch(args);
}
}
Task1.java:
package tasks;
import javafx.stage.Stage;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.layout.HBox;
import javafx.scene.control.Button;
class Task1
{
private Stage window;
private Scene task1Scene;
private boolean intialised;
private int state;
public Task1()
{
}
public Task1(Stage _window)
{
this.window=_window;
this.task1Scene=null; //Will be set later
this.intialised=false;
this.state=-1;
}
private Scene createScene()
{
//Creates some GUI to interact
//Buttons in End, to control exit
HBox menu=new HBox();
Button pauseButton=new Button("Pause");
pauseButton.setOnAction(clickEvent -> this.state=1);
menu.getChildren().add(pauseButton);
Button exitButton=new Button("Exit");
exitButton.setOnAction(clickEvent -> this.state=0);
menu.getChildren().add(exitButton);
Scene scene=new Scene(menu,400,600);
return scene;
}
private void setupControls()
{
//To assign event handlers to interact with GUI
}
public int runTask1()
{
if(!this.intialised)
this.task1Scene=this.createScene();
this.window.setScene(this.task1Scene);
this.setupControls();
//while(this.state==-1);
return this.state;
}
}
The problem with this I face is, function runTask1() is always instantly returning, even though operation assigned using event handlers for Task1 are still running and no event for exit has been generated.
I tried to solve this by setting an instance variable named state and setting it to -1, and putting a while loop till this state variable is not modified. But that completely stops the GUI.
I realised its reason later by Googling, but couldn't determine which way to solve this.
At places, it was suggested to use Threads (not sure how, I don't want multiple processes running in the program) and at places, it was also suggested to set another Event Handler (but, they were running the different process in the start() function (inherited from Application) itself and it was more of transferring the flow rather than returning backwards).
How should I code to keep running only runTask1() till it is not finished, and return to launchTask1() sequentially?
The infinite while loop in method runTask1() in class Task1 is freezing the JavaFX application thread. Just remove it.
Basically your Task1 class is another Scene so when you click on button newTask1Button in class Tasks you simply want to set a new Scene.
Here is class Task1 with the required change.
package tasks;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.layout.HBox;
import javafx.scene.control.Button;
public class Task1 {
private Stage window;
private Scene task1Scene;
private boolean intialised;
private int state;
public Task1() {
}
public Task1(Stage _window) {
this.window = _window;
this.task1Scene = null; // Will be set later
this.intialised = false;
this.state = -1;
}
private Scene createScene() {
// Creates some GUI to interact
// Buttons in End, to control exit
HBox menu = new HBox();
Button pauseButton = new Button("Pause");
pauseButton.setOnAction(clickEvent -> this.state = 1);
menu.getChildren().add(pauseButton);
Button exitButton = new Button("Exit");
exitButton.setOnAction(clickEvent -> this.state = 0);
menu.getChildren().add(exitButton);
Scene scene = new Scene(menu, 400, 600);
return scene;
}
private void setupControls() {
// To assign event handlers to interact with GUI
}
public int runTask1() {
if (!this.intialised)
this.task1Scene = this.createScene();
this.window.setScene(this.task1Scene);
this.setupControls();
// while (this.state == -1)
// ;
return this.state;
}
}
As you can see, I simply commented out the while loop. The JavaFX application thread contains a loop that waits for user actions to occur such as moving the mouse or typing a key on the keyboard. You don't have to handle that in your code.
EDIT
As a result of the typo in the code in your question, that you mentioned in your comment to my answer and that you corrected in your question in a subsequent edit, I am editing my answer.
The way a JavaFX application works is that it reacts to user actions. You want class Tasks to be notified when the "state", in class Task1, is changed and the "state" is changed when the user clicks on either pauseButton or exitButton in class Task1. According to the code you posted, you could callback to class Tasks from the event handler of pauseButton.
Class Task1.
(Note comment CHANGE HERE and extra parameter in constructor.)
package tasks;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.layout.HBox;
import javafx.scene.control.Button;
public class Task1 {
private Tasks tasks;
private Stage window;
private Scene task1Scene;
private boolean intialised;
private int state;
public Task1(Stage _window, Tasks tasks) {
this.tasks = tasks;
this.window = _window;
this.task1Scene = null; // Will be set later
this.intialised = false;
this.state = -1;
}
private Scene createScene() {
HBox menu = new HBox();
Button pauseButton = new Button("Pause");
pauseButton.setOnAction(clickEvent -> tasks.setState(this.state = 1)); // CHANGE HERE
menu.getChildren().add(pauseButton);
Button exitButton = new Button("Exit");
exitButton.setOnAction(clickEvent -> this.state = 0);
menu.getChildren().add(exitButton);
Scene scene = new Scene(menu, 400, 600);
return scene;
}
private void setupControls() {
// To assign event handlers to interact with GUI
}
public int runTask1() {
if (!this.intialised) {
this.task1Scene = this.createScene();
}
this.window.setScene(this.task1Scene);
this.setupControls();
return this.state;
}
}
Class Tasks
(Added method setState(int).)
package tasks;
import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.layout.VBox;
import javafx.scene.control.Button;
public class Tasks extends Application {
private Stage window;
private Scene menuScene;
private Task1 task1;
public Tasks() {
this.window = null;
this.menuScene = null;
this.task1 = null;
}
private void setMenu() {
VBox menu = new VBox();
Button newTask1Button = new Button("New Task 1");
newTask1Button.setOnAction(clickEvent -> this.startNewTask1());
menu.getChildren().add(newTask1Button);
this.menuScene = new Scene(menu, 400, 600);
this.window.setScene(this.menuScene);
}
private void startNewTask1() {
this.task1 = new Task1(this.window, this);
this.launchTask1();
}
private void launchTask1() {
if (this.task1 != null) {
this.task1.runTask1();
}
}
#Override
public void start(Stage primaryStage) {
this.window = primaryStage;
this.window.setTitle("Tasks");
this.setMenu();
this.window.show();
}
public static void main(String[] args) {
Application.launch(args);
}
public void setState(int task1State) {
System.out.println("Task1 is in state " + task1State); // In no way part of program,
// just for debugging.
}
}
I use javafx, I have a TextField and a Button, when the button is pressed, it saves what is written in the TextField in a String. What I want to create is a method to mark a pause, while waiting for the Button to get pressed.
I have a class named pause.java, where I tried to put a obj.wait(); and a notifyAll(); in the event where the button is pressed, but the window isn't accessible during this time, I can't press the button or enter anything in the TextField.
So what I found was to put the obj.wait(); in a task, then I don't know why but it directly breaks out of the wait.
Here is my pause.java
package net.jpajavafx;
import java.util.logging.*;
import javafx.concurrent.Task;
public class pause {
Logger logger = Logger.getLogger(pause.class.getName());
MainController obj = new MainController();
public void waitinput() {
Task<Void> sleeper = new Task<Void>() {
#Override
protected Void call() throws Exception {
synchronized (obj) {
try {
String write = "Waiting for input...";
logger.log(Level.INFO, write);
obj.wait();
logger.log(Level.INFO, "Done");
} catch (Exception e) {
e.printStackTrace();
}
}
return null;
}
};
new Thread(sleeper).start();
}
}
How do I have to modify it to make it wait, while still having access to the GUI?
Here's my code simplified for the problem:
AlbumManager.java, where my main is.
package net.jpajavafx;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.*;
import javafx.application.*;
import javafx.fxml.FXMLLoader;
public class AlbumManager extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("Main.fxml"));
Scene scene = new Scene(root);
primaryStage.setTitle("Album Manager");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
MainController.java:
package net.jpajavafx;
import javafx.event.ActionEvent;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.fxml.FXML;
import java.util.logging.*;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
public class MainController {
#FXML
private TextArea textarea;
#FXML
private TextField textfield;
Variablesstoring stock = new Variablesstoring();
public void ok(ActionEvent event) {
String getValue = textfield.getText();
stock.setEntrystr(getValue); //here i have something to put in an Int, I put it aside to reduce the length
textfield.setText("");
notifyAll();
}
public void startprogram() {
int etat = 0;
int run = 1;
while (run == 1) {
textarea.setText("1: launch method");
pause.waitinput(); // here I want to wait for an input
etat = stock.getEntrystr();
switch (etat) {
case 1:
//runs a method
break;
default:
break;
}
}
}
}
It's really not clear what you're trying to achieve here that needs a separate thread: all the separate thread seems to try to do is wait until the button is pressed, and then execute some code. That functionality is already provided by the event management system in JavaFX (and the same is true for any UI toolkit): just execute the code in the event handler.
(As an aside, your use of wait() is incorrect, and if you fix that, the thread will never wake up because you are not calling notifyAll() on the same object on which you are calling wait().)
You can achieve what you seem to be trying to do simply with
package net.jpajavafx;
import javafx.event.ActionEvent;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.fxml.FXML;
import java.util.logging.*;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
public class MainController {
#FXML
private TextArea textarea;
#FXML
private TextField textfield;
Variablesstoring stock = new Variablesstoring();
public void ok(ActionEvent event) {
String getValue = textfield.getText();
stock.setEntrystr(getValue); //here i have something to put in an Int, I put it aside to reduce the length
textfield.setText("");
processInput();
}
public void processInput() {
int etat = stock.getEntrystr();
switch (etat) {
case 1:
//runs a method
break;
default:
break;
}
}
}
You have to start another thread using a Runnable, so your UI thread does not get blocked while the save-operation completes.
You can do this by placing a listener on the button that will start the save-operation on a new thread when the button is clicked.
The code for adding a listener to a button that starts a new thread would look something like this:
//Creating the mouse event handler
EventHandler<MouseEvent> eventHandler = new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent e) {
MainController controller = new MainController();
controller.start();
}
};
//Registering the event filter
button.addEventFilter(MouseEvent.MOUSE_CLICKED, eventHandler);
The code you posted doesn't really do anything. Your call to waitinput() only logs and calls wait(). wait() is not what you want, since this operation is intended for putting a thread on hold until it is notified, not for executing a task in a seperate thread. Remove the obj.wait(), and add a listener that calls your logging method when the button is clicked. Also, get rid of the while-loop. The EventHandler will take care of events in the background.
How can I make a custom Event that triggers on Stage.setScene()?
In my code, the button switches the Scenes and that works fine. However, I would like to extend the Stage to have an additional Event that is triggered when a button or possibly any other Element triggers a setScene.
Example:
package sample;
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.stage.Stage;
public class Main extends Application {
public static void main(String[] args) {
Application.launch(args);
}
#Override
public void start(Stage stage) {
Group g1 = new Group();
Button b1 = new Button("2");
g1.getChildren().setAll(b1);
Scene scene1 = new Scene(g1, 50, 50);
Group g2 = new Group();
Button b2 = new Button("1");
g2.getChildren().setAll(b2);
Scene scene2 = new Scene(g2, 50, 50);
stage.setScene(scene1);
stage.setTitle("JavaFX Application Life Cycle");
b1.setOnAction(actionEvent -> {
System.out.println("1");
stage.setScene(scene2);
});
b2.setOnAction(actionEvent -> {
System.out.println("2");
stage.setScene(scene1);
});
stage.show();
}
}
You can add a ChangeListener<Scene> to your Stage like this:
stage.sceneProperty().addListener((observable, oldScene, newScene) -> {
System.out.println("New scene: " + newScene);
System.out.println("Old scene: " + oldScene);
});
I believe using a listener, as shown in the answer by #M.S., is probably the best and simplest way to react to scene changes. However, you ask about how to make a "custom event" that you can fire when the scene changes; by "event" I assume you mean a subclass of javafx.event.Event. So while I recommend sticking with a simple listener, here's an example of a custom event.
First, you need a custom event class:
import javafx.event.Event;
import javafx.event.EventType;
import javafx.scene.Scene;
import javafx.stage.Window;
public class SceneChangedEvent extends Event {
public static final EventType<SceneChangedEvent> SCENE_CHANGED =
new EventType<>(Event.ANY, "SCENE_CHANGED");
public static final EventType<SceneChangedEvent> ANY = SCENE_CHANGED;
private transient Window window;
private transient Scene oldScene;
private transient Scene newScene;
public SceneChangedEvent(Window window, Scene oldScene, Scene newScene) {
super(window, window, SCENE_CHANGED);
this.window = window;
this.oldScene = oldScene;
this.newScene = newScene;
}
public Window getWindow() {
return window;
}
public Scene getOldScene() {
return oldScene;
}
public Scene getNewScene() {
return newScene;
}
}
I'm not sure what information you want to carry with the event so I just added the source Window as well as the old and new Scenes. If you're wondering about the ANY = SCENE_CHANGED, I'm just following the pattern used by javafx.event.ActionEvent (which also only has a single event-type).
Then you simply need to fire the event when the scene changes. To implement this you're still going to need a change listener. As you mention wanting to extend Stage here's an example of that:
import javafx.beans.NamedArg;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.event.EventHandler;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
public class CustomStage extends Stage {
private final ObjectProperty<EventHandler<? super SceneChangedEvent>> onSceneChanged =
new SimpleObjectProperty<>(this, "onSceneChanged") {
#Override
protected void invalidated() {
setEventHandler(SceneChangedEvent.SCENE_CHANGED, get());
}
};
public final void setOnSceneChanged(EventHandler<? super SceneChangedEvent> handler) {
onSceneChanged.set(handler);
}
public final EventHandler<? super SceneChangedEvent> getOnSceneChanged() {
return onSceneChanged.get();
}
public final ObjectProperty<EventHandler<? super SceneChangedEvent>> onSceneChangedProperty() {
return onSceneChanged;
}
public CustomStage() {
this(StageStyle.DECORATED);
}
public CustomStage(#NamedArg(value = "style", defaultValue = "DECORATED") StageStyle style) {
super(style);
sceneProperty().addListener((obs, ov, nv) -> fireEvent(new SceneChangedEvent(this, ov, nv)));
}
}
This would let you react to the scene changing using any of the following:
CustomStage stage = new CustomStage();
// addEventFilter/addEventHandler
stage.addEventFilter(SceneChangedEvent.SCENE_CHANGED, e -> { ... });
stage.addEventHandler(SceneChangedEvent.SCENE_CHANGED, e -> { ... });
// setOnSceneChanged
stage.setOnSceneChanged(e -> { ... });
Keep in mind that the event will only target the CustomStage instance. In other words, only event handlers added to the CustomStage instance will be notified of the event. And as you can see, this is much more complicated than simply adding a change listener to the scene property of the Stage.
I have desktop app with side menu bar. Main window is BorderPane with InsertLeft containing VBox. I set Hbox buttons and their behaviour then I add them one by one to the VBox. InsertCenter has just Pane with alot of elements.
I've created 3 fxml files for each GUI layout.
sample.fxml - BorderPane: InsertLeft->Menu(VBox), InsertCenter->Empty Pane
tab1_content.fxml - Pane filled with ProgressBar, Labels and Buttons
tab2_content.fxml - Not yet implemented (Empty Pane)
Each of these fxml files has their controllers.
I would like to switch content of borderPane.center() inside sample.fxml on menu button click.
I've managed to fix some issues, but main problem is with loading data into .fxml views.
As I run my App it works perfectly, each fxml file has his FXMLLoader which will load content into borderPane right inside main Controller.
Problem occurs while I click on Buttons. It will switch panes, but actual content will reset to default state and Main.class initialization is completely ignored. Button listeners and label values are not initialized. It's just empty fxml layout. Every variable inside Main.class, what I want to access from Tab1Controller is returning NullPointerException - even methods.
Each controller extends AbstractController, which contains Main.class instance which is initialized inside start() method. So i should be able to have access to every Main.class method/variable at any time.
Issue Gif:
Some code samples:
My Main.class start() method:
public Controller myController;
#Override
public void start(Stage primaryStage) throws Exception {
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource("/sample.fxml"));
myController = new Controller();
myController.setMainApp(this);
loader.setController(myController);
Parent root = loader.load();
primaryStage.setTitle("Simple App");
primaryStage.setScene(new Scene(root));
primaryStage.show();
<other stuff>
}
public void setDefaultViewProperties(){
currentScanningFileProperty = new SimpleStringProperty();
myController.tab1Controller.actualPath.textProperty().bind(currentScanningFileProperty); //NullPointerException while called from Controller
fileCounterProperty = new SimpleLongProperty();
myController.tab1Controller.numOfScanned.textProperty().bind(fileCounterProperty.asString());
maliciousFilesCounterProperty = new SimpleIntegerProperty();
myController.tab1Controller.numOfMaliciousFiles.textProperty().bind(maliciousFilesCounterProperty.asString());
myController.tab1Controller.fileChoiceBtn.setOnMouseClicked(event -> chooseFile());
myController.tab1Controller.runScanBtn.setOnMouseClicked(event -> new Thread(() -> {
try {
resetValues();
startFileWalking(chosenFile);
} catch (IOException e) {
e.printStackTrace();
}
}).start());
}
MainController:
package sample;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.geometry.Insets;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import java.io.IOException;
import java.net.URL;
import java.util.ResourceBundle;
public class Controller extends AbstractController implements Initializable{
public HBox sideMenu;
public VBox mainMenu;
public BorderPane borderPane;
public Boolean isButton1Pressed = false;
public Boolean isButton2Pressed = false;
public static final String TAB_1 = "TAB-1";
public static final String TAB_2 = "TAB-2";
public Button malwareButton;
public Button webShieldButton;
public Tab1Controller tab1Controller;
public Tab2Controller tab2Controller;
#Override
public void initialize(URL location, ResourceBundle resources) {
createMenuButtons();
setSideMenu();
setMenuButtonsListeners();
}
private void setSideMenu(){
mainMenu.getChildren().add(item(malwareButton));
mainMenu.getChildren().add(item(webShieldButton));
mainMenu.setStyle("-fx-background-color:#004D40");
}
private HBox item(Button menuButton){
menuButton.setPrefSize(200, 50);
menuButton.setStyle("-fx-background-color: transparent;");
menuButton.setTextFill(Color.web("#E0F2F1"));
menuButton.setPadding(Insets.EMPTY);
sideMenu = new HBox(menuButton);
return sideMenu;
}
public void setMenuButtonsListeners(){
malwareButton.setOnMousePressed(event -> {
setButtonStylePressed(malwareButton);
setButtonStyleUnpressed(webShieldButton);
isButton1Pressed = true;
isButton2Pressed = false;
loadTab1Content();
main.setDefaultViewProperties();
});
webShieldButton.setOnMousePressed(event -> {
setButtonStylePressed(webShieldButton);
setButtonStyleUnpressed(malwareButton);
isButton1Pressed = false;
isButton2Pressed = true;
loadTab2Content();
});
malwareButton.setOnMouseExited(event -> {
if(!isButton1Pressed){
setButtonStyleUnpressed(malwareButton);
}
});
webShieldButton.setOnMouseExited(event -> {
if(!isButton2Pressed){
setButtonStyleUnpressed(webShieldButton);
}
});
malwareButton.setOnMouseEntered(event -> setButtonStylePressed(malwareButton));
webShieldButton.setOnMouseEntered(event -> setButtonStylePressed(webShieldButton));
}
public void setButtonStylePressed(Button btn){
btn.setStyle("-fx-background-color: #E0F2F1");
btn.setTextFill(Color.web("#004D40"));
}
public void setButtonStyleUnpressed(Button btn){
btn.setStyle("-fx-background-color: transparent");
btn.setTextFill(Color.web("#E0F2F1"));
}
private void loadTab1Content(){
FXMLLoader tab1loader = new FXMLLoader();
tab1loader.setLocation(getClass().getResource("/tab_1_content.fxml"));
try {
if (tab1Controller == null){
tab1Controller = new Tab1Controller();
}
tab1loader.setController(tab1Controller);
borderPane.setCenter(tab1loader.load());
} catch (IOException e) {
e.printStackTrace();
}
}
private void loadTab2Content(){
FXMLLoader tab2loader = new FXMLLoader();
tab2loader.setLocation(getClass().getResource("/tab_2_content.fxml"));
try {
if (tab2Controller == null){
tab2Controller = new Tab2Controller();
}
tab2loader.setController(tab2Controller);
borderPane.setCenter(tab2loader.load());
} catch (IOException e) {
e.printStackTrace();
}
}
private void createMenuButtons(){
malwareButton = new Button();
malwareButton.setText(TAB_1);
webShieldButton = new Button();
webShieldButton.setText(TAB_2);
}
}
Tab1Controller:
package sample;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressBar;
import javafx.stage.Modality;
import javafx.stage.Stage;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.ResourceBundle;
/**
* Created by admin on 5. 5. 2018.
*/
public class Tab1Controller extends AbstractController implements Initializable {
public ProgressBar progressBar;
public Button runScanBtn;
public Button fileChoiceBtn;
public Label chosenPath;
public Label actualPath;
public Label numOfMaliciousFiles;
public Label hashValue;
public Label scanFinishedMsg;
public Label numOfScanned;
public Button showFoundMalwareButton;
#Override
public void initialize(URL location, ResourceBundle resources) {
runScanBtn.setDisable(true);
scanFinishedMsg.setVisible(false);
showFoundMalwareButton.setVisible(false);
showFoundMalwareButton.setOnAction(event -> showPopupWindow());
}
Update#1 - Updating fxml values through Main.class after button click
I've finally managed to run app without exception. I had to create next Controller for pane fxml layout itself called Tab1Controller. When I initialized Controller, it instantly initialized Tab1Controller inside. So when I want to change Center BorderPane label i had to call myController.tab1Controller.tabLabel.setText()
I don't know if it's good approach to this problem.
But now I'm back to my old problem. When I click on TAB-1 it will load content, but values are not initialized to default state.
For example I have couple of labels updated in real time. I binded some SimpleProperties into it with default values. It worked before, but as I have three controllers it will load data for a first time, but when I click TAB-1 button it will load just fxml content, but it will not set those labels.
So i made public method inside Main.class which I will call everytime I switch to TAB-1 from Controller.
public void setDefaultViewProperties(){
myController.tab1Controller.actualPath.textProperty().bind(currentScanningFileProperty);
myController.tab1Controller.numOfScanned.textProperty().bind(fileCounterProperty.asString());
myController.tab1Controller.numOfMaliciousFiles.textProperty().bind(maliciousFilesCounterProperty.asString());
}
But now everytime I click on TAB-1 I've got
java.lang.NullPointerException: Cannot bind to null
You can make two pane and switch between them using setVisible() method
example:
void btn1Clicked() {
pane1.setVisible(true);
pane2.setVisible(false);
}
void btn2Clicked() {
pane1.setVisible(false);
pane2.setVisible(true);
}
You could use a TabPane to achieve this behaviour:
https://docs.oracle.com/javase/8/javafx/api/javafx/scene/control/TabPane.html
Solved. I'm not sure how, but setDefaultViewProperties() are not throwing NullPointerException at the moment. I did not change anything inside the code:
malwareButton.setOnMousePressed(event -> {
setButtonStylePressed(malwareButton);
setButtonStyleUnpressed(webShieldButton);
isButton1Pressed = true;
isButton2Pressed = false;
loadTab1Content();
main.setDefaultViewProperties();
});