I\'d like to create 2 independent windows at once. One window would be able to hold an observable list, the other one would show the selected listobject\'s properties. I\'m tryi
You basically have to follow @Slaw instructions. Create a Model
. Share the Model
between the two Controllers
. Observe the model's current Customer
and react accordingly. MCVE Below:
Main Class: (load both stages with the correct Scene. Create model and pass it to both Controllers):
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
/**
*
* @author sedri
*/
public class JavaFXApplication36 extends Application {
@Override
public void start(Stage stage) {
try {
FXMLLoader listViewFXMLLoader = new FXMLLoader(getClass().getResource("ListViewFXML.fxml"));
Parent listViewRoot = listViewFXMLLoader.load();
ListViewController listViewController = listViewFXMLLoader.getController();
Scene scene1 = new Scene(listViewRoot);
stage.setScene(scene1);
FXMLLoader detailsFXMLLoader = new FXMLLoader(getClass().getResource("DetailsFXML.fxml"));
Parent detailsRoot = detailsFXMLLoader.load();
DetailsController detailsController = detailsFXMLLoader.getController();
Scene scene2 = new Scene(detailsRoot);
Stage stage2 = new Stage();
stage2.setScene(scene2);
DataModel model = new DataModel();
listViewController.initModel(model);
detailsController.initModel(model);
stage.show();
stage2.show();
} catch (IOException ex) {
Logger.getLogger(JavaFXApplication36.class.getName()).log(Level.SEVERE, null, ex);
}
}
/**
* @param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
Model Class: (Keep up with current Customer and ObservableList of Customer)
import javafx.beans.Observable;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
/**
*
* @author sedrick
*/
public class DataModel {
private final ObservableList<Customer> customerList = FXCollections.observableArrayList(customer -> new Observable[]{customer.nameProperty(), customer.ageProperty()});
private final ObjectProperty<Customer> currentCustomer = new SimpleObjectProperty();
public ObjectProperty<Customer> currentCustomerProperty() {
return currentCustomer;
}
public void setCurrentCustomer(Customer currentCustomer) {
this.currentCustomer.set(currentCustomer);
}
public Customer getCurrentCustomer() {
return this.currentCustomer.get();
}
public ObservableList<Customer> loadCustomers()
{
customerList.add(new Customer("John Doe", 21));
customerList.add(new Customer("Jane Joe", 20));
return customerList;
}
}
Customer Class:
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
/**
*
* @author sedrick
*/
public class Customer {
private final StringProperty name = new SimpleStringProperty();
private final IntegerProperty age = new SimpleIntegerProperty();
public Customer(String name, int age) {
this.name.set(name);
this.age.set(age);
}
public String getName()
{
return this.name.get();
}
public void setName(String name)
{
this.name.set(name);
}
public StringProperty nameProperty()
{
return this.name;
}
public int getAge()
{
return this.age.get();
}
public void setAge(int age)
{
this.age.set(age);
}
public IntegerProperty ageProperty()
{
return this.age;
}
}
ListView Controller: (Initialize model, setup ListView and observe current customer property)
import java.net.URL;
import java.util.ResourceBundle;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
/**
*
* @author sedri
*/
public class ListViewController implements Initializable {
@FXML private ListView<Customer> listView;
private DataModel model;
@Override
public void initialize(URL url, ResourceBundle rb) {
// TODO
}
public void initModel(DataModel model)
{
// ensure model is only set once:
if (this.model != null) {
throw new IllegalStateException("Model can only be initialized once");
}
listView.getSelectionModel().selectedItemProperty().addListener((obs, oldCustomer, newCustomer) ->
model.setCurrentCustomer(newCustomer));
model.currentCustomerProperty().addListener((obs, oldCustomer, newCustomer) -> {
if (newCustomer == null) {
listView.getSelectionModel().clearSelection();
} else {
listView.getSelectionModel().select(newCustomer);
}
});
listView.setCellFactory(lv -> new ListCell<Customer>() {
@Override
public void updateItem(Customer customer, boolean empty) {
super.updateItem(customer, empty);
if (empty) {
setText(null);
} else {
setText("Name: " + customer.getName() + " Age: " + customer.getAge());
}
}
});
listView.setItems(model.loadCustomers());
}
}
ListView FXML:
<StackPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1" fx:controller="javafxapplication36.ListViewController">
<children>
<ListView fx:id="listView" prefHeight="200.0" prefWidth="200.0" />
</children>
</StackPane>
DetailsController:(Initialize model and observe current customer property)
import java.net.URL;
import java.util.ResourceBundle;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.TextField;
/**
* FXML Controller class
*
* @author sedri
*/
public class DetailsController implements Initializable {
@FXML TextField tfName, tfAge;
private DataModel model;
/**
* Initializes the controller class.
*/
@Override
public void initialize(URL url, ResourceBundle rb) {
// TODO
}
public void initModel(DataModel model) {
// ensure model is only set once:
if (this.model != null) {
throw new IllegalStateException("Model can only be initialized once");
}
this.model = model ;
model.currentCustomerProperty().addListener((observable, oldCustomer, newCustomer) -> {
if(newCustomer == null){
tfName.setText("");
tfAge.setText("");
}
else{
tfName.setText(newCustomer.getName());
tfAge.setText(Integer.toString(newCustomer.getAge()));
}
});
}
}
Details FXML:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.VBox?>
<VBox alignment="CENTER" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1" fx:controller="javafxapplication36.DetailsController">
<children>
<Label text="Name" />
<TextField fx:id="tfName" />
<Label text="Age" />
<TextField fx:id="tfAge" />
</children>
<padding>
<Insets left="20.0" right="20.0" />
</padding>
</VBox>
More info:
@James D answer on Model-View-Controller(MVC).
GitHub code.
The following is a demonstration of two windows (stages) sharing the same model.
The demonstration is kept as simple as possible: one window displays a list. The second window dynamically displays the items that where selected on the first:
The shared model holds the information that the two windows need. Basically a list of items, and a list of selected items:
package two_windows;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
public class Model {
private final ObservableList<String> list;
private ObservableList<String> selected;
Model(){
list = FXCollections.observableArrayList();
}
void addMessage(String msg){
list.add(msg);
}
ObservableList<String> getMessages(){
return list;
}
ObservableList<String> getSelectedMessages(){
return selected;
}
void setSelected(ObservableList<String> selected) {
this.selected = selected;
}
}
The content of the first window is defined by List.fxml and its controller:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.Pane?>
<?import javafx.scene.control.ListView?>
<Pane xmlns="http://javafx.com/javafx/10.0.1" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="two_windows.ListController">
<children>
<ListView fx:id="list" prefHeight="300.0" prefWidth="150.0" />
</children>
</Pane>
The controller accepts a Model
, sets the selected items list in the model, listens and responds to model changes:
package two_windows;
import java.util.List;
import javafx.collections.ListChangeListener;
import javafx.fxml.FXML;
import javafx.scene.control.ListView;
import javafx.scene.control.SelectionMode;
public class ListController {
@FXML ListView<String> list;
void setModel(Model model) {
list.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);//allow multiple selection
//sets the selected items of the list to the model
model.setSelected(list.getSelectionModel().getSelectedItems());
//listen to changes in model, and respond
model.getMessages().addListener(
(ListChangeListener<String>) c -> {
c.next();
addElements(c.getAddedSubList());
}
);
}
private void addElements(List<? extends String> msgList){
for(String msg : msgList){
list.getItems().add(msg);
}
}
}
The content of the second window is very similar to the first, and is defined by Selected.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.Pane?>
<?import javafx.scene.control.ListView?>
<Pane xmlns="http://javafx.com/javafx/10.0.1" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="two_windows.SelectedController">
<children>
<ListView fx:id="selected" prefHeight="300.0" prefWidth="150.0" />
</children>
</Pane>
And its controller, which like the other controller accepts a Model
and responds to changes in it:
package two_windows;
import java.util.List;
import javafx.collections.ListChangeListener;
import javafx.fxml.FXML;
import javafx.scene.control.ListView;
public class SelectedController {
@FXML ListView<String> selected;
void setModel(Model model) {
//listen to changes in model, and respond
model.getSelectedMessages().addListener(
(ListChangeListener<String>) c -> {
c.next();
removeElements(c.getRemoved());
addElements(c.getAddedSubList());
}
);
}
private void removeElements(List<? extends String> msgList){
for(String msg : msgList){
selected.getItems().remove(msg);
}
}
private void addElements(List<? extends String> msgList){
for(String msg : msgList){
selected.getItems().add(msg);
}
}
}
Putting it all together and testing:
package two_windows;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class TwoWindows extends Application {
private Model model;
@Override
public void start(Stage primaryStage) throws Exception{
model = new Model();
FXMLLoader listLoader = new FXMLLoader(getClass().getResource("List.fxml"));
Parent list = listLoader.load();
ListController listController = listLoader.getController();
listController.setModel(model);
FXMLLoader selectedLoader = new FXMLLoader(getClass().getResource("Selected.fxml"));
Parent selected = selectedLoader.load();
SelectedController selectedController = selectedLoader.getController();
selectedController.setModel(model);
primaryStage.setScene(new Scene(list));
primaryStage.setX(350); primaryStage.setY(300);
Stage secondaryStage = new Stage();
secondaryStage.setScene(new Scene(selected));
secondaryStage.setX(550); secondaryStage.setY(300);
addMessages();
primaryStage.show();
secondaryStage.show();
}
private void addMessages() {
int counter = 0;
while(counter < 15) {
model.addMessage("message number "+ counter++);
}
}
public static void main(final String[] args) {
launch(args);
}
}