I am currently experimenting with JavaFX and SceneBuilder in eclipse to create and design my own program. In my first class "StartController" I am using a method called makeFadeIn. Basically, when I click a button another page loads up with a fade effect.
This is the code from StartController.java (notice makeFadeIn):
public class StartController {
#FXML
private AnchorPane rootPane;
private void makeFadeIn() {
FadeTransition fadeTransition = new FadeTransition();
fadeTransition.setDuration(Duration.millis(1000));
fadeTransition.setNode(rootPane);
fadeTransition.setFromValue(0);
fadeTransition.setToValue(1);
fadeTransition.play();
}
#FXML
private void loadSecondPage(ActionEvent event) throws IOException {
AnchorPane startPage = FXMLLoader.load(getClass().getResource("SecondController.fxml"));
rootPane.getChildren().setAll(startPage);
makeFadeIn();
}
Next, my other class loads up called "SecondController.java". In this class, I'm using the exact same method makeFadeIn (but I had to write it twice since it didn't let me run the program).
This is the code from SecondController.java:
public class SecondController {
#FXML
private AnchorPane rootPane;
private void makeFadeIn() {
FadeTransition fadeTransition = new FadeTransition();
fadeTransition.setDuration(Duration.millis(1000));
fadeTransition.setNode(rootPane);
fadeTransition.setFromValue(0);
fadeTransition.setToValue(1);
fadeTransition.play();
}
#FXML
private void loadFirstPage(ActionEvent event) throws IOException {
AnchorPane startPage = FXMLLoader.load(getClass().getResource("StartController.fxml"));
rootPane.getChildren().setAll(startPage);
}
My question is: can I somehow call the makeFadeIn method from the first class so I don't have to write it in my second class? I guess I need to inherit it in some way but I'm not sure how. I tried declaring it public instead of private but that did not help much.
You could move this functionality to a base class:
public class BaseController {
#FXML
private AnchorPane rootPane;
protected AnchorPane getRootPage() {
return rootPane;
}
protected void makeFadeIn() {
FadeTransition fadeTransition = new FadeTransition();
fadeTransition.setDuration(Duration.millis(1000));
fadeTransition.setNode(rootPane);
fadeTransition.setFromValue(0);
fadeTransition.setToValue(1);
fadeTransition.play();
}
}
And then have the other controllers extend it:
public class StartController extends BaseController {
#FXML
private void loadSecondPage(ActionEvent event) throws IOException {
AnchorPane startPage =
FXMLLoader.load(getClass().getResource("SecondController.fxml"));
getRootPane().getChildren().setAll(startPage);
makeFadeIn();
}
}
public class SecondController extends BaseController {
#FXML
private void loadFirstPage(ActionEvent event) throws IOException {
AnchorPane startPage =
FXMLLoader.load(getClass().getResource("StartController.fxml"));
getRootPane().getChildren().setAll(startPage);
}
}
Related
I'm making an application with JavaFX and Scene Builder.
I have two controllers:Controller and FontController
I have Main class that launch my program and open Stage with first fontroller (Controller)
public class Main extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
try {
Parent root = FXMLLoader.load(getClass().getResource("/card/card.fxml"));
Scene scene = new Scene(root, 1600, 600);
primaryStage.setScene(scene);
scene.getStylesheets().add(getClass().getResource("style.css").toExternalForm());
primaryStage.initStyle(StageStyle.UNDECORATED);
primaryStage.setMaximized(true);
primaryStage.setResizable(true);
primaryStage.getIcons().add(new Image("card/resources/logo-icon.png"));
primaryStage.show();
//adding resize and drag primary stage
ResizeHelper.addResizeListener(primaryStage);
//assign ALT+ENTER to maximize window
final KeyCombination kb = new KeyCodeCombination(KeyCode.ENTER, KeyCombination.CONTROL_DOWN);
scene.addEventHandler(KeyEvent.KEY_PRESSED, new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event) {
if (kb.match(event)) {
primaryStage.setMaximized(!primaryStage.isMaximized());
primaryStage.setResizable(true);
}
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
}
There is a label and a button in Controller. When I click on the button a method is called and new window with second controller appears(FontController):
#FXML private Button btnFont;
#FXML private Label category1
#FXML
void changeFont(ActionEvent event) {
try {
FXMLLoader fxmlLoader = new
FXMLLoader(getClass().getResource("font.fxml"));
Parent rootFont = (Parent) fxmlLoader.load();
Stage stage = new Stage();
stage.setTitle("Select Font");
stage.setScene(new Scene(rootFont));
stage.show();
} catch (Exception e) {
System.out.println("can't load new window");
}
}
There is the button "OK" and label in FontCOntroller:
#FXML private Label fontLabel;
#FXML private Button btnFontOk;
Please tell me, what should I do to send and apply text from label in FontController when I click on the burtton "OK" to label in Controller?
SOLUTION FOUND:
I created class "Context" in my project directory to make all controllers communicate each other. You can add as many controllers as you want there.
Here it looks like:
package card;
public class Context {
private final static Context instance = new Context();
public static Context getInstance() {
return instance;
}
private Controller controller;
public void setController(Controller controller) {
this.controller=controller;
}
public Controller getController() {
return controller;
}
private FontController fontController;
public void setFontController(FontController fontController) {
this.fontController=fontController;
}
public FontController getFontController() {
return fontController;
}
}
Controller:
I created getters and setters (ALT + Insert in IDEA) for Label that I wanna change
public Label getCategory1() {
return category1;
}
public void setCategory1(Label category1) {
this.category1 = category1;
}
To get FontController variables and methods through Context class I placed line of code
//getting FontController through Context Class
FontController fontCont = Context.getInstance().getFontController();
I registered Controller in Context class through my initialize method (my class implements Initializable)
#FXML
public void initialize(URL location, ResourceBundle resources) {
//register Controller in Context Class
Context.getInstance().setController(this);
}
FontController:
to get Controller variables and methods I placed this code:
//getting Controller variables and methods through Context class
Controller cont = Context.getInstance().getController();
I also registered FontController in Context class through initialize method:
#Override
public void initialize(URL location, ResourceBundle resources) {
//register FontController in Context Class
Context.getInstance().setFontController(this);
}
Method that send text and text color from label in this FontController to label in Controller when I click on button:
#FXML
void applyFont(ActionEvent event) {
cont.getCategory1().setText(fontLabel.getText());
cont.getCategory1().setTextFill(fontLabel.getTextFill());
}
*By creating Context class you can make controllers communicate each other and create as many controllers as you want there. Controllers see variables and methods of each other
In my JavaFx application I want to update my BarChart whenever calculate button is clicked. The problem is that I am getting:
java.lang.NullPointerException at
application.StatController.setActivityData(StatController.java:47)
It is always pointing to:`xAxis.setCategories(optionsNames);
But it has elements on the list (See printscreen:https://image.ibb.co/b9YO8Q/Capture.png
In my StatController class I have setActivityData which is called from FormController class.
StatController class:
public class StatController {
#FXML
private BarChart<String, Double> barChart;
#FXML
private CategoryAxis xAxis;
Activities activities = new Activities();
private Map<String, List<Double>> uniqueActivityOptions = new HashMap<>();
private ObservableList<String> optionsNames = FXCollections.observableArrayList();
public StatController(){}
#FXML
private void initialize() {
}
public void setActivityData(Activities activitiesList) {
for(Activity activity : activities.getActivityList()) {
String optionName = activity.getOption();
if(uniqueActivityOptions.containsKey(optionName)) {
uniqueActivityOptions.get(optionName).add((double) activity.getNumber());
} else {
List<Double> activityOptionList = new ArrayList<>();
activityOptionList.add((double) activity.getNumber());
uniqueActivityOptions.put(optionName, activityOptionList);
}
}
for (Map.Entry<String, List<Double>> entry : uniqueActivityOptions.entrySet()) {
optionsNames.add(entry.getKey());
}
xAxis.setCategories(optionsNames);
XYChart.Series<String, Double> series = new XYChart.Series<>();
for (Map.Entry<String, List<Double>> entry : uniqueActivityOptions.entrySet()) {
Double average = calculateAverage(entry.getValue());
series.getData().add(new XYChart.Data<>(entry.getKey().toString(), average));
}
barChart.getData().add(series);
}
private double calculateAverage(List<Double> values) {
double result = 0;
for (Double value : values) {
result += value;
}
return result / values.size();
}
}
FormController class:
public class FormController {
Activities act = new Activities();
List<Activity> activities = act.getActivityList();
private ObservableList<String> opt = FXCollections.observableArrayList(
"Option 1",
"Option 2",
"Option 3"
);
#FXML
private Button calculateButton;
#FXML
private TextField numberField;
#FXML
private ComboBox<String> options;
private String selectedOption;
#FXML
private void initialize() {
options.getItems().addAll(opt);
options.getSelectionModel().selectedItemProperty()
.addListener(new ChangeListener<String>() {
#Override
public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
selectedOption = newValue;
}
});
}
#FXML
public void calculateButtonClicked(){
activities.add(new Activity(selectedOption, Integer.parseInt(numberField.getText())));
StatController sc = new StatController();
sc.setActivityData(act);
}
}
I tested setActivityData method in StatsController and it is working correctly when I am passing Activities.
Please advise what to change in the code to pass and update BarChart.
I know that this is something trivial but I really don't know how to do it.
`
Thank you very much for the help!
Your problem is in FormController#calculateButtonClicked(). You create a new instance of StatController manually and then call the setActivityData() method. This is not how JavaFX works and will result in the xAxis member being null, hence your NullPointerException.
Controller members annotated with the #FXML annotation will be injected by the FXMLLoader class when you call the FXMLLoader#load() method. You will need to use the FXMLLoader class to load the .fxml file that corresponds to the StatController class, which will then create an instance of your StatController object for you and will also inject your xAxis and barChart instances.
Here's a quick (not production ready) example that you will have to adapt for your specific scenario:
FXMLLoader loader = new FXMLLoader();
loader.setLocation(filePath); // your path to the .fxml file
loader.load();
StatController controller = (StatController) loader.getController();
controller.setActivityData(activities); // your activities data
How and when you do that depends on how your scene graph is setup. Looking at your source code, I would suggest the following changes:
MainController.java
public class MainController
implements Initializable {
#FXML
private TabPane tabPane;
#FXML
private Tab formTabPage;
#FXML
private FormController formTabController; // change name to this
#FXML
private Tab statsTabPage;
#FXML
private StatController statsTabController; // change name to this
private MainApp mainApp;
public void setMainApp(MainApp mainApp) {
this.mainApp = mainApp;
}
#Override // rename your init() method to this
public void initialize(URL location, ResourceBundle resources) {
// add this line
formTabController.setStatController(statTabController);
}
}
FormController.java
public class FormController {
Activities act = new Activities();
List<Activity> activities = act.getActivityList();
private ObservableList<String> opt = FXCollections.observableArrayList(
"Option 1",
"Option 2",
"Option 3"
);
#FXML
private Button calculateButton;
#FXML
private TextField numberField;
#FXML
private ComboBox<String> options;
private String selectedOption;
private StatController statController; // add this member
#FXML
private void initialize() {
options.getItems().addAll(opt);
options.getSelectionModel().selectedItemProperty()
.addListener(new ChangeListener<String>() {
#Override
public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
selectedOption = newValue;
}
});
}
#FXML
public void calculateButtonClicked() {
// change this method here
statController.setActivityData(act);
}
// add this method here
public void setStatController(StatController statController) {
this.statController = statController;
}
}
There are other problems that occur, but this fixes your null pointer exception. The reason this works and your previous code didn't, is because you were manually creating a new instance of StatController and not using the instance that the FXMLLoader had already loaded and mapped to the user interface node for you. All I have done above, is capture a reference to the controller you needed, and provided it to the other through a setter method.
Every time I ran JavaFX app I encounter the error below.
15:11:52.778 [JavaFX Application Thread] ERROR org.fhl.Manifesto - javafx.fxml.LoadException:
/C:/ManGenFX/target/classes/dataController.fxml
/C:/ManGenFX/target/classes/ManifestoMain.fxml:7
But it only happens if I add the initialize method on Main Controller.
public class MainController{
#FXML
GenerateController genCont;
#FXML
private Pane generatePane;
#FXML
AnchorPane mainFx;
private Node source;
//Other Button, TextFields and Labels declaration
#FXML
private void browseFile(ActionEvent x) {
//Browse file definition
}
#FXML
private void savePath(ActionEvent x) {
//Save file definition
}
//Problem with this method
#FXML
public void initialize() {
System.out.println("Initialize generate controller panel");
logger.info("Initialize generate controller panel");
genCont.init(this);
}
}
If I remove the initialize method on MainController class it will not throw any error but nothing happens either if I click on the generate button which is defined in GenerateController class below.
public class GenerateController {
#FXML
Button btnGenerate;
#FXML
Pane paneGen;
#FXML
public void generateMan(ActionEvent event) {
//generateMan body when Generate button is clicked
}
public void init(MainController mc) {
System.out.println("Init mainControl");
mainControl = mc;
}
public void validate() {
//Definition of method body here
}
}
This is the main Class
public class Man extends Application {
private Stage mainStage;
#Override
public void start(Stage primaryStage) {
this.mainStage = primaryStage;
this.mainStage.setTitle("Manifest");
try{
Parent root = FXMLLoader.load(getClass().getResource("/ManifestoMain.fxml"));
Scene sMain = new Scene(root);
mainStage.setScene(sMain);
mainStage.show();
}catch(IOException ioE) {
logger.error(ioE);
}
}
public static void main(String[] args) {
launch(args);
}
}
Also, I placed my dataController.fxml, ManifestoMain.fxml, and generateView.fxml under resources folder but it don't have any problem accessing the files. Appreciate help on this.
I have this function in the controller class of the relevant fxml. I need this function to be fired on focus out from a textfield, but scene builder doesn't have an event similar to onfocusout. How to achieve this using the control class?
#FXML
private void ValidateBikeNo(){
Tooltip error = new Tooltip("This bike no exists");
BikeNoIn.setTooltip(error);
}
You can attach a focusListener to the TextField and then execute the code inside it. The listener can be attached inside the initialize() method of the controller.
public class MyController implements Initializable {
...
#FXML
private Textfield textField;
public void initialize() {
...
textField.focusedProperty.addListener((ov, oldV, newV) -> {
if (!newV) { // focus lost
// Your code
}
});
.....
}
}
You have to use method textField.focusedProperty() instead of textField.focusProperty
public class MyController implements Initializable {
...
#FXML
private Textfield textField;
public void initialize() {
textField.focusedProperty().addListener((ov, oldV, newV) -> {
if (!newV) { // focus lost
// Your code
}
});
}
}
I have created two java classes which have a static method which returns an AnchorPane after setting all properties of required labels and buttons.
For example:
class HomePageScene {
static AnchorPane getHomePageScene() {
//some code
//a button which is to be clicked to go to Login Page
//some code
}
}
class LoginPageScene {
static AnchorPane getLoginPageScene() {
//some code
}
}
And there is another class which has the main().
public class JavaFXEventDemo extends Application {
public static void main(String[] args) {
launch(args);
}
public void start(Stage myStage) {
myStage.setTitle("Program Windiw");
AnchorPane HomePane = HomePageScene.getHomePageScene();
AnchorPane LoginPane = LoginPageScene.getLoginPageScene();
Scene HomePage = new Scene(HomePane, 400.0, 300.0);
Scene LoginPage = new Scene(LoginPane, 400.0, 300.0);
myStage.setScene(HomePage);
myStage.show();
}
}
First I set the HomePage as the scene on the stage. In the screen there is a button, which when I click, I want the scene to the LoginPage. How do I do this?
All the three classes are in different files.I tried setting onAction() method, but in that, handle() method's return type is void, whereas I need to return an AnchorPane.
Bind a function for your button (onAction). In this function, call a function in your main class which will load the scene you want (void javafx.scene.Scene.setRoot(Parent value)) ?
EDIT:
What I meant :
public class JavaFXEventDemo extends Application {
private static Scene HomePage;
private static Scene LoginPage;
private static Stage myStage;
public static void main(String[] args) {
launch(args);
}
public void start(Stage myStage) {
JavaFXEventDemo.myStage = myStage;
myStage.setTitle("Program Windiw");
AnchorPane HomePane = HomePageScene.getHomePageScene();
AnchorPane LoginPane = LoginPageScene.getLoginPageScene();
HomePage = new Scene(HomePane, 400.0, 300.0);
LoginPage = new Scene(LoginPane, 400.0, 300.0);
loadHomePage();
myStage.show();
}
public static void loadHomePage(){
JavaFXEventDemo.myStage.setScene(HomePage);
}
public static void loadLoginPage(){
JavaFXEventDemo.myStage.setScene(LoginPage);
}
}
And just call loadXXXXPage() on your button.