Search code examples
javajavafxfxml

JavaFX: 2 independent windows at once


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:

    enter image description here

    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);
        }
    }