Issue
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 trying to create the listview as a generic list, and combine it with an object specific window (eg. customer properties, beer properties, store properties).
In short: if the user clicks 'Customers', it shows the listview with all the customers, and the first customer's properties are shown in a seperate, customer-specific window.
If the user clicks 'Stores', it shows the same listview, but instead filled with stores. The store-specific window is also opened and contains the first store's properties.
I tried using 2 FXMLLoaders, but for some reason I can't figure out how to use them. I'm pretty mediocre at JavaFX so I can't even figure out where to start. This is what I've got, but it just seems wrong.
FXMLLoader loader = new FXMLLoader(getClass().getResource("List.fxml"));
loader.setRoot(this);
loader.setController(this);
FXMLLoader loader2 = new FXMLLoader(getClass().getResource("StoreWindow.fxml"));
loader2.setRoot(this);
loader2.setController(this);
try {
loader.load();
loader2.load();
} catch (IOException ex) {
throw new RuntimeException(ex);
}
Solution
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);
}
}
Answered By - c0der