I'm following the MVP pattern so the Presenter has access to the view and to the model and the view to the presenter.
View <-> Presenter -> Model.
My problem is, I can't populate the ComboBox. I am an amateur and this one here breaks me. I think I have a fundamental issue.
The Main class:
package test;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage;
public class Main extends Application
{
private Presenter presenter;
private Model model;
private ViewController view;
#Override
public void start(Stage primaryStage) throws Exception
{
presenter = initData();
testSampleData();
model.showList(); // prints out: [Susi 22, Peter 30, Tom 54]
AnchorPane paneMain = (AnchorPane) FXMLLoader.load(getClass().getResource("View.fxml"));
Scene scene = new Scene(paneMain);
primaryStage.setScene(scene);
primaryStage.show();
}
public void testSampleData()
{
model.getList().add(new Person("Susi", 22));
model.getList().add(new Person("Peter", 30));
model.getList().add(new Person("Tom", 54));
}
private Presenter initData()
{
presenter = new Presenter();
model = new Model();
view = new ViewController();
presenter.setModel(model);
presenter.setView(view);
view.setPresenter(presenter);
return presenter;
}
public static void main(String[] args)
{
launch(args);
}
}
Im trying to create an ComboBox with Objects.
The Person Class:
package test;
public class Person
{
private String name;
private int age;
public Person(String name, int alter)
{
this.name = name;
this.age = alter;
}
public String toString()
{
return name + " " + age;
}
}
The data is stored in the model class:
package test;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
public class Model
{
private ObservableList<Person> list = FXCollections.observableArrayList();
public void showList()
{
System.out.println(this.list.toString());
}
public ObservableList<Person> getList()
{
return list;
}
public void initTest()
{
list.add(new Person("Susi", 22));
list.add(new Person("Peter", 30));
list.add(new Person("Tom", 54));
}
}
The presenter is the "bridge" between view and model
package test;
import javafx.collections.ObservableList;
public class Presenter
{
private Model myModel;
private ViewController myViewController;
public void setModel(Model model)
{
this.myModel = model;
}
public void setView(ViewController view)
{
this.myViewController = view;
}
public ObservableList<Person> getList()
{
return myModel.getList();
}
}
And here is the view:
package test;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.TextArea;
public class ViewController
{
Presenter presenter;
#FXML
private Label myLabel;
#FXML
private ComboBox<Person> myComboBox;
#FXML
private TextArea myTextArea;
private ObservableList<Person> list;
#FXML
private void handleComboBoxAction() {
Person selectedPerson = myComboBox.getSelectionModel().getSelectedItem();
myTextArea.appendText("Selected Person: " + selectedPerson + "\n");
}
public ViewController()
{
// myComboBox.setItems(presenter.getList()); // -> nullpointer
}
public void initialize()
{
list = FXCollections.observableArrayList();
// list = presenter.getList(); // -> this is my goal
list.add(new Person("Tom", 54)); // this here works, but I want to get the data from the model class :(
// myComboBox.setItems(list);
}
public void setPresenter(Presenter presenter)
{
this.presenter = presenter;
}
}
Last but not least, the fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane minHeight="300.0" minWidth="300.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="test.ViewController">
<children>
<BorderPane prefHeight="300.0" prefWidth="300.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<center>
<ComboBox fx:id="myComboBox" onAction="#handleComboBoxAction" prefWidth="150.0" BorderPane.alignment="CENTER" />
</center>
<right>
<TextArea fx:id="myTextArea" prefHeight="200.0" prefWidth="200.0" BorderPane.alignment="CENTER" />
</right>
</BorderPane>
</children>
</AnchorPane>
Perhaps I spammed to much code in here but I thought it would be necessary.
EDIT:
I edited the FXML part in my main like this:
loader = new FXMLLoader(getClass().getResource("View.fxml"));
AnchorPane paneMain = (AnchorPane)loader.load();
view = (ViewController)loader.getController();
And now the advice from NAMS and James_D works!
Related
I made a simple FX program to replicate the problem I am seeing. I have a TestController that extends AbstractBar which extends AbstractFoo. After calling the initialize() method, a simple GUI displays with a button. I am expecting a "Hello World!" message to print to the console when the button is pressed, but I am getting a null value instead. Why does this happen and how do I fix this?
Expected result: Hello World!
Button result: null
Button result: null
Main.java
package com.test.main;
import com.test.gui.TestController;
import javafx.embed.swing.JFXPanel;
public class Main {
private static TestController controller;
public static void main(String[] args) {
String fxml = "/gui/Simple.fxml";
String content = "Hello World!";
new JFXPanel();
controller = new TestController();
controller.initialize(fxml, content);
System.out.println("Expected result: " + controller.getContent());
}
}
TestController.java
package com.test.gui;
import javafx.fxml.FXML;
public class TestController extends AbstractBar {
#FXML
private void testButton() {
System.out.println("Button result: " + super.getContent());
}
}
AbstractBar.java
package com.test.gui;
public abstract class AbstractBar extends AbstractFoo {
#Override
public void initialize(String fxml, String content) {
// Do stuff.
super.initialize(fxml, content);
}
}
AbstractFoo.java
package com.test.gui;
import java.io.IOException;
import javafx.application.Platform;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public abstract class AbstractFoo {
protected String fxml;
protected String content;
protected Stage stage;
protected Parent root;
public void initialize(String fxml, String content) {
this.fxml = fxml;
this.content = content;
Platform.runLater(() -> {
FXMLLoader loader = new FXMLLoader();
loader.setLocation(this.getClass().getResource(fxml));
try {
this.root = loader.load();
} catch (IOException e) {
e.printStackTrace();
}
this.stage = new Stage();
this.stage.setScene(new Scene(root));
this.stage.show();
});
}
public String getContent() {
return this.content;
}
}
Simple.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.control.Button?>
<BorderPane fx:id="root" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/15.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.test.gui.TestController">
<center>
<Button fx:id="button" text="Click me!" onAction="#testButton" />
</center>
</BorderPane>
As pointed out by the comments, it is correct that the FXMLLoader is not using the same controller instance I am expecting. The simple fix is to remove the fx:controller attribute from the fxml file. and add loader.setController(this) to AbstractFoo. Now everything works as expected, what a big oversight...
com.test.gui.TestController#568bf312
Expected result: Hello World!
com.test.gui.TestController#568bf312
Button result: Hello World!
Simple.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.control.Button?>
<BorderPane fx:id="root" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/15.0.1" xmlns:fx="http://javafx.com/fxml/1" >
<center>
<Button fx:id="button" text="Click me!" onAction="#testButton" />
</center>
</BorderPane>
AbstractFoo.java
package com.test.gui;
import java.io.IOException;
import javafx.application.Platform;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public abstract class AbstractFoo {
protected String fxml;
protected String content;
protected Stage stage;
protected Parent root;
public void initialize(String fxml, String content) {
this.fxml = fxml;
this.content = content;
Platform.runLater(() -> {
FXMLLoader loader = new FXMLLoader();
loader.setLocation(this.getClass().getResource(fxml));
loader.setController(this);
try {
this.root = loader.load();
} catch (IOException e) {
e.printStackTrace();
}
this.stage = new Stage();
this.stage.setScene(new Scene(root));
this.stage.show();
});
}
public String getContent() {
return this.content;
}
}
I'm working on a larger project (together with quite a number of fellow students), and I'm trying to display an arrayList (which type we're using over and over again in our project, so I wouldn't love to change all these to ObservableLists just for my rather small issue) in a TableView.
I've created a smaller project just in order to get to the heart of the problem and to show you:
In the class editMyGrades (together with an fxml file of the same name) I want to display my SUBJECTs (-> String subjectName), each one with its GRADE (-> int grade) in a TableView.
Initializing the parent root and the tableView the ArrayList subjects is transformed to an ObservableList, which is loaded into the TableView.
I really would like to understand theoretically, why changing GRADEs works with the tableView, but removing doesn't, and to know what to do about it practically ;-)
Thank you very much in advance!
Main class:
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import java.util.ArrayList;
public class Main extends Application {
private static ArrayList<Subject> subjects = new ArrayList<>();
public static ArrayList<Subject> getSubjects () {return subjects;}
// private static ObservableList<Subject> subjects = FXCollections.observableArrayList();
// public static ObservableList<Subject> getSubjects () {return subjects;}
public static void main (String[] args) {
subjects.add(new Subject("computer science", 2));
subjects.add(new Subject("literature", 4));
launch (args);
}
public void start (Stage primaryStage) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("/editMyGrades.fxml"));
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
}
Class Subject:
public class Subject {
private String subjectName;
private int grade;
public Subject (String subjectName, int grade) {
this.subjectName = subjectName;
this.grade = grade;
}
public String getSubjectName () {return subjectName;}
public int getGrade () {return grade;}
public void setGrade (int grade) {this.grade = grade;}
}
controlling class editMyGrades:
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.input.MouseEvent;
import java.util.ArrayList;
public class editMyGrades {
Subject subjectToEdit;
#FXML
private TableView<Subject> tableView;
#FXML
private TableColumn<Subject, String> subjectCol;
#FXML
private TableColumn<Subject, Number> gradeCol;
#FXML
private TextField textField;
#FXML
private Button changeButton;
#FXML
private Button deleteButton;
#FXML
public void initialize() {
subjectCol.setCellValueFactory(new PropertyValueFactory<>("subjectName"));
gradeCol.setCellValueFactory(new PropertyValueFactory<>("grade"));
//tableView.setItems (FXCollections.observableArrayList(Main.getSubjects()));
ObservableList<Subject> subjects = FXCollections.observableArrayList(Main.getSubjects());
tableView.setItems(subjects);
}
#FXML
private void tableViewClicked(MouseEvent event) {
if (event.getClickCount() == 2) {
subjectToEdit = tableView.getSelectionModel().getSelectedItem();
textField.setText ("" + tableView.getSelectionModel().getSelectedItem().getGrade());
}
}
#FXML
private void change(ActionEvent event) {
subjectToEdit.setGrade(Integer.parseInt(textField.getText()));
textField.clear();
tableView.refresh();
}
#FXML
private void delete (ActionEvent event) {
Subject selectedSubject = tableView.getSelectionModel().getSelectedItem();
ArrayList<Subject> subjects = Main.getSubjects();
subjects.remove(selectedSubject);
tableView.refresh();
}
}
editingMyGrades.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>
<VBox maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="editMyGrades">
<children>
<Label text="change your grades!" />
<HBox>
<children>
<TableView fx:id="tableView" onMouseClicked="#tableViewClicked" prefHeight="200.0" prefWidth="518.0">
<columns>
<TableColumn fx:id="subjectCol" prefWidth="262.0" text="subject" />
<TableColumn fx:id="gradeCol" minWidth="0.0" prefWidth="146.0" text="grade" />
</columns>
</TableView>
<VBox alignment="CENTER">
<children>
<Button mnemonicParsing="false" onAction="#delete" text="deleteButton" />
</children>
</VBox>
</children>
</HBox>
<TextField fx:id="textField" maxWidth="100.0" />
<Button fx:id="changeButton" mnemonicParsing="false" onAction="#change" text="Change!" />
</children>
</VBox>
When working with JavaFX it's good practice to always use FXCollections.
When you make a modification to the ArrayList, the changes are not generating list change nofications. You must get the ObservableList from the tableview and perform the operations on it since it is the wrapper (the smart list) that is backed by your ArrayList.
Thank you indeed for your explanations! In fact in my real problem I'm using MVC, but the Controller object is located in the main - static -, and as it appears, it can't be done in other way. For the illustration of my problem I thought id wouldn't make a difference, if I had a static controller object with the list or just the list itself (perhaps I was wrong ...).
Now, having tried to apply everything you told me, it's just the other way around: the tableView displays removements, but no changes.
just the fxml control class EditMyGrades:
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.input.MouseEvent;
public class EditMyGrades {
private ObservableList<Subject> subjects = FXCollections.observableArrayList();
private Subject subjectToEdit;
#FXML
private TableView<Subject> tableView;
#FXML
private TableColumn<Subject, String> subjectCol;
#FXML
private TableColumn<Subject, Number> gradeCol;
#FXML
private TextField textField;
#FXML
private Button changeButton;
#FXML
private Button deleteButton;
#FXML
public void initialize() {
subjectCol.setCellValueFactory(new PropertyValueFactory<>("subjectName"));
gradeCol.setCellValueFactory(new PropertyValueFactory<>("grade"));
subjects.add(new Subject("computer science", 2));
subjects.add(new Subject("literature", 4));
tableView.setItems(subjects);
}
#FXML
private void tableViewClicked(MouseEvent event) {
if (event.getClickCount() == 2) {
subjectToEdit = tableView.getSelectionModel().getSelectedItem();
textField.setText ("" + tableView.getSelectionModel().getSelectedItem().getGrade());
}
}
#FXML
private void change(ActionEvent event) {
subjectToEdit.setGrade(Integer.parseInt(textField.getText()));
textField.clear();
}
#FXML
private void delete (ActionEvent event) {
ObservableList<Subject> subjects = tableView.getItems();
Subject selectedSubject = tableView.getSelectionModel().getSelectedItem();
subjects.remove(selectedSubject);
}
}
Is there a way to bind an object's field to a TreeItem so that when the field is changed, the TreeItem is automatically updated in the TreeView?
I have objects with several fields that are set in TreeItems in a TreeView. The objects fields are updated sometimes at 5 to 10 times per second via their setters and I would like the TreeItems to update on the fly as the fields change. Any help on this would be greatly appreciated. I can't seem to find a way to do this other than reloading the entire TreeView.
Edit: Here is the existing code...
The list listener sets up the treeview for the player list. If the number of objects in the list changes, the tree is redrawn. That is all fine. What I want is, if you were to place code in the handle button click method that continuously changes player attributes(in a loop, maybe 2-10 times per second), that should be reflected in the treeview as those attributes change.(Just a simulation, I cant get into how those attributes are really being changed at that rate). An example of how to do this would be great.
package application;
import java.io.IOException;
import javafx.application.Application;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.stage.Stage;
import viewcontroller.PlayerViewController;
import javafx.scene.Scene;
import javafx.scene.layout.AnchorPane;
public class Main extends Application {
private Stage mainStage;
#FXML
private AnchorPane mainView;
#Override
public void start(Stage mainStage) {
this.mainStage = mainStage;
PlayerList.playerListListener();
this.showMainLayout();
}
/**
* shows the main screen
*/
public void showMainLayout() {
FXMLLoader loader = new FXMLLoader();
loader.setLocation(Main.class.getResource("/viewcontroller/playerView.fxml"));
try {
mainView = (AnchorPane) loader.load();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Scene scene = new Scene(mainView);
mainStage.sizeToScene();
mainStage.setScene(scene);
mainStage.show();
PlayerViewController controller = loader.getController();
controller.setMainApp(this);
}
public static void main(String[] args) {
launch(args);
}
}
package model;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
public class Player {
private StringProperty str1 = new SimpleStringProperty();
private StringProperty str2 = new SimpleStringProperty();
private StringProperty str3 = new SimpleStringProperty();
public Player() {
}
public Player(String str1, String str2, String str3) {
super();
this.str1 = new SimpleStringProperty(str1);
this.str2 = new SimpleStringProperty(str2);
this.str3 = new SimpleStringProperty(str3);
}
public StringProperty str1() {
return str1;
}
public String getStr1() {
return str1.get();
}
public void setStr1(String str1) {
this.str1.set(str1);
}
public StringProperty str2() {
return str2;
}
public String getStr2() {
return str2.get();
}
public void setStr2(String str2) {
this.str2.set(str2);
}
public StringProperty str3() {
return str3;
}
public String getStr3() {
return str3.get();
}
public void setStr3(String str3) {
this.str3.set(str3);
}
}
package application;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.scene.control.TreeItem;
import model.Player;
public class PlayerList {
private final ObservableList<Player> playerList = FXCollections.observableArrayList();
private static PlayerList instance = new PlayerList();
public static TreeItem<String> rootNode = new TreeItem<String>("Root");
private PlayerList() {}
public ObservableList<Player> getPlayerList() {
return playerList;
}
public static PlayerList getInstance() {
return instance;
}
public static void playerListListener() {
//adds a list listener which reloads tree if objects are added or removed
PlayerList.getInstance().getPlayerList().addListener((ListChangeListener<? super Player>) new ListChangeListener<Player>() {
#SuppressWarnings("unchecked")
public void onChanged(Change<? extends Player> change) {
//re-sort list
//System.out.println("List changed");
rootNode.getChildren().clear();//clear tree before reloading on list change
for(Player p: PlayerList.getInstance().getPlayerList()) {
//Parent Node
TreeItem<String> playerName = new TreeItem<String>(p.getStr1());
//Items
TreeItem<String> attr1 = new TreeItem<String>(p.getStr2());
TreeItem<String> attr2 = new TreeItem<String>(p.getStr3());
playerName.getChildren().addAll(attr1, attr2);
playerName.setExpanded(true);
rootNode.getChildren().add(playerName);
rootNode.setExpanded(true);
}
}
});
}
}
package viewcontroller;
import java.net.URL;
import java.util.ResourceBundle;
import application.Main;
import application.PlayerList;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.TreeView;
import javafx.scene.layout.VBox;
import model.Player;
public class PlayerViewController implements Initializable {
#FXML
private VBox vbox;
private TreeView<String> treeView;
public void setMainApp(Main main) {
// TODO Auto-generated method stub
}
#Override
public void initialize(URL location, ResourceBundle resources) {
// TODO Auto-generated method stub
Player p1 = new Player("Name1", "Attr1", "Attr2");
Player p2 = new Player("Name2", "Attr1", "Attr2");
Player p3 = new Player("Name3", "Attr1", "Attr2");
PlayerList.getInstance().getPlayerList().addAll(p1, p2, p3);//add players to list
treeView = new TreeView<String>(PlayerList.rootNode);
treeView.setShowRoot(false);
vbox.getChildren().add(treeView);//add tree view to GUI
}
#FXML
private void handleButtonClick() {
/*this method can hold code that would run in a loop, continuously setting new values
on p1, p2, and p3 attributes and would show the tree items updating automatically*/
}
}
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane fx:id="mainView" prefHeight="400.0" prefWidth="200.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="viewcontroller.PlayerViewController">
<children>
<VBox fx:id="vbox" layoutX="14.0" layoutY="14.0" prefHeight="200.0" prefWidth="100.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<children>
<Button mnemonicParsing="false" onAction="#handleButtonClick" text="Button" />
</children>
</VBox>
</children>
</AnchorPane>
I have 2 fxml file. Example A.fxml. B.fxml. I have 2 controller. AController (A.fxml) BController (B.fxml). A fxml and B fxml have change button changing Scene or fxml. This is button code;
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/infoLibrary/view/A.fxml"));
Parent root = loader.load();
Scene scene = new Scene(root);
Stage stage = (Stage)((Node)event.getSource()).getScene().getWindow();
stage.setScene(scene);
stage.show();
} catch (IOException e) {
e.printStackTrace();
}
Same code in BContrroller change button. When i click change button scane changing. But everytime init method and controller contructers working too. When user change fxml javafx everytime using new constructor. How can i change windows without new controller constructor?
Use a view model:
public class ViewModel {
public enum View {A, B}
private final ObjectProperty<View> currentView = new SimpleObjectProperty<>(View.A);
public ObjectProperty<View> currentViewProperty() {
return currentView ;
}
public final View getCurrentView() {
return currentViewProperty().get();
}
public final View setCurrentView(View view) {
currentViewProperty().set(view);
}
}
Now in your AController do:
public class AController {
private ViewModel viewModel ;
public void setViewModel(ViewModel viewModel) {
this.viewModel = viewModel ;
}
// button handler:
#FXML
private void goToB(ActionEvent event) {
viewModel.setCurrentView(ViewModel.View.B);
}
}
and BController is similar.
Finally, you set everything up with something like the following, which is executed only once (e.g. in your start() method, or somewhere similar):
Stage stage = ... ; // maybe it's the primary stage in start...
Scene scene = new Scene();
ViewModel viewModel = new ViewModel();
FXMLLoader aLoader = new FXMLLoader(getClass().getResource("/infoLibrary/view/A.fxml"));
Parent a = aLoader.load();
AController aController = aLoader.getController();
aController.setViewModel(viewModel);
FXMLLoader bLoader = new FXMLLoader(getClass().getResource("/infoLibrary/view/B.fxml"));
Parent b = bLoader.load();
BController bController = bLoader.getController();
bController.setViewModel(viewModel);
scene.rootProperty().bind(Bindings.createObjectBinding(() -> {
if (viewModel.getCurrentView() == ViewModel.View.A) {
return a ;
} else if (viewModel.getCurrentView() == ViewModel.View.B) {
return b ;
} else {
return null ;
}
}, viewModel.currentViewProperty());
stage.setScene(scene);
stage.show();
Now the two FXML files are only loaded once (so the controllers are only created once, and their initialize() methods called only once). The switching is managed by changing the state of the ViewModel and observing that state, so the scene's root is changed when the model state changes.
Here is a complete SSCCE:
ViewModel.java:
package sceneswitcher;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
public class ViewModel {
public enum View {A, B}
private final ObjectProperty<View> currentView = new SimpleObjectProperty<>(View.A);
public ObjectProperty<View> currentViewProperty() {
return currentView ;
}
public final View getCurrentView() {
return currentViewProperty().get();
}
public final void setCurrentView(View view) {
currentViewProperty().set(view);
}
}
AController.java:
package sceneswitcher;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.TextField;
public class AController {
private ViewModel viewModel ;
#FXML
private TextField textField ;
public void setViewModel(ViewModel viewModel) {
this.viewModel = viewModel ;
}
// button handler:
#FXML
private void goToB(ActionEvent event) {
viewModel.setCurrentView(ViewModel.View.B);
}
public String getText() {
return textField.getText();
}
}
A.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.control.Button?>
<VBox fx:controller="sceneswitcher.AController" spacing="5" alignment="CENTER"
xmlns:fx="http://javafx.com/fxml/1">
<Label text='This is view A'/>
<TextField fx:id="textField" />
<Button onAction="#goToB" text="Go to view B"/>
</VBox>
BController.java:
package sceneswitcher;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.TextArea;
public class BController {
private ViewModel viewModel ;
#FXML
private TextArea textArea ;
public void setViewModel(ViewModel viewModel) {
this.viewModel = viewModel ;
}
// button handler:
#FXML
private void goToA(ActionEvent event) {
viewModel.setCurrentView(ViewModel.View.A);
}
public String getText() {
return textArea.getText();
}
}
B.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextArea?>
<?import javafx.scene.control.Button?>
<VBox fx:controller="sceneswitcher.BController" spacing="5" alignment="CENTER"
xmlns:fx="http://javafx.com/fxml/1">
<Label text="This is view B"/>
<TextArea fx:id="textArea" />
<Button onAction="#goToA" text="Go to View A"/>
</VBox>
Main.java:
package sceneswitcher;
import java.io.IOException;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws IOException {
ViewModel viewModel = new ViewModel();
FXMLLoader aLoader = new FXMLLoader(getClass().getResource("A.fxml"));
Parent a = aLoader.load();
AController aController = aLoader.getController();
aController.setViewModel(viewModel);
FXMLLoader bLoader = new FXMLLoader(getClass().getResource("B.fxml"));
Parent b = bLoader.load();
BController bController = bLoader.getController();
bController.setViewModel(viewModel);
Scene scene = new Scene(a, 400, 400);
scene.rootProperty().bind(Bindings.createObjectBinding(() -> {
if (viewModel.getCurrentView() == ViewModel.View.A) {
return a ;
} else if (viewModel.getCurrentView() == ViewModel.View.B) {
return b ;
} else {
return null ;
}
}, viewModel.currentViewProperty()));
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
What is an expected method of connecting view and model in JavaFX?
Binding?
Suppose I want to make positioning in database with the following controls:
I have data (recordset) object in memory and it's properties are bindable. I.e. they are notifying when current record changes and when the number of records changes.
I want user to be able position inside recordset both with slider and text field.
How to accomplish that? There is no numeric spin in JavaFX, so how to bind text, slider and recordset object (three ends) together? Is it possible?
I cannot give an authoritative answer since I don't work for Oracle nor am I some JavaFX-pert but I usually construct the controller with an instance of the model and in the initialize method of the controller I create the bindings between the controls' properties and the model's properties.
To your concrete problem I have a lame but working solution. Lame because I can't figure out how to use the low-level binding API and I use two properties on the model. Here's how :
FXML :
<?import javafx.geometry.Insets?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.Slider?>
<?import javafx.scene.control.TextField?>
<GridPane fx:controller="sample.Controller"
xmlns:fx="http://javafx.com/fxml" alignment="center" hgap="10" vgap="10">
<Slider GridPane.columnIndex="0" fx:id="slider" min="0" max="99"/>
<Label GridPane.columnIndex="1" text="Record" />
<TextField fx:id="textfield" GridPane.columnIndex="2"/>
</GridPane>
Main.java :
package sample;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.layout.GridPane;
import javafx.stage.Stage;
import javafx.util.Callback;
import sample.Models.Model;
public class Main extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
final Model model = new Model();
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource("sample.fxml"));
loader.setControllerFactory(new Callback<Class<?>, Object>() {
#Override
public Object call(Class<?> aClass) {
return new Controller(model);
}
});
GridPane root = (GridPane) loader.load();
primaryStage.setTitle("Hello World");
primaryStage.setScene(new Scene(root, 300, 275));
primaryStage.show();
}
}
Controller.java :
package sample;
import javafx.beans.binding.DoubleBinding;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Slider;
import javafx.scene.control.TextField;
import sample.Models.Model;
import java.net.URL;
import java.util.ResourceBundle;
public class Controller implements Initializable {
private final Model model;
public Controller(Model model) {
this.model = model;
}
#FXML
public Slider slider;
#FXML
public TextField textfield;
#Override
public void initialize(URL url, ResourceBundle resourceBundle) {
slider.valueProperty().bindBidirectional(model.pageProperty());
textfield.textProperty().bindBidirectional(model.pageTextProperty());
}
}
Model.java :
package sample.Models;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
public class Model {
private IntegerProperty pageProperty;
private StringProperty pageTextProperty;
public Model() {
pageProperty = new SimpleIntegerProperty(2);
pageProperty.addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> observableValue, Number number, Number number2) {
int value = pageProperty.get();
//System.out.println("Page changed to " + value);
if (Integer.toString(value).equals(pageTextProperty.get())) return;
pageTextProperty.set(Integer.toString(value));
}
});
pageTextProperty = new SimpleStringProperty("2");
pageTextProperty.addListener(new ChangeListener<String>() {
#Override
public void changed(ObservableValue<? extends String> observableValue, String s, String s2) {
try {
int parsedValue = Integer.parseInt(observableValue.getValue());
if (parsedValue == pageProperty.get()) return;
pageProperty().set(parsedValue);
} catch (NumberFormatException e) {
// too bad
}
}
});
}
public int getPage() {
return pageProperty.get();
}
public void setPage(int page) {
pageProperty.set(page);
}
public IntegerProperty pageProperty() {
return pageProperty;
}
public String getPageText() {
return pageTextProperty.get();
}
public void setPageText(String pageText) {
pageTextProperty.set(pageText);
}
public StringProperty pageTextProperty() {
return pageTextProperty;
}
}