1

I have view and there is a ListView and a Pane. I want to add Controllers as items of the ListView, and when I click on one of the listView's element, set the Pane's content to its the controllers content,

For example I have FruitsController and VegetablesController and of course the corresponding .fxml for each controller, and when I click on the first element set the Panes element a tableView of fruits, otherwise a tableview with vegetables. I tried whith <fx:include source=Fruits.fxml> and so on but I don't know how to switch the content, and how to display a custom name as element name in the ListView. So do you have any idea?

Edit:

I found partial solutions here How can I Populate a ListView in JavaFX using Custom Objects? and here JavaFX - ListView Item with an Image Button But the problem is still there I don't know how and where put the <fx:include> tag, to load it when I click on an item.

Sunflame
  • 2,993
  • 4
  • 24
  • 48
  • It's probably best not to do this with an ``. Just register a listener with the list view's selection model and load the new FXML when the selection changes, in the controller. – James_D Jul 19 '17 at 12:00
  • Yes this was the solution that I had, but I have there 9-10 items, and the list is growing by the time, so I thought there is a simpler way to do not write a loader for every time when I add a new item to the list. Why do you say "best not to do this"? it is a bad way or just not possible? – Sunflame Jul 19 '17 at 12:05
  • Not sure I understand. You would just have one listener for the selection, and choose the FXML file depending on what was selected. Then *create* a loader for that FXML file and load it. (I guess I don't understand what you mean by "write" a loader.) If you use `` you would basically have to load all the possible FXML files and somehow hide the ones which you didn't want to display, which would be more complex and would keep all those UIs in memory all the time. Maybe if you can't use the listener in an easy way post some code showing how you're trying to do it. – James_D Jul 19 '17 at 12:08
  • Hmm...you are right, thats a bad idea to keep all UIs in memory. It is not a problem to add a listener to my listview. The problem was that I have to keep all `.fxml` paths in this controller, then _ask_ the lisview's selected item: _are you the xController?...are you the yController?_ if one of them matches then load it. But if its not so clear ill post a code in a few minutes. – Sunflame Jul 19 '17 at 12:13
  • Just define a custom type for your list view that handles that. – James_D Jul 19 '17 at 12:14
  • That is exactly I did, I have an enum, but then I still have to include all `.fxml` paths. I thought i can somehow attach the controller class or `.fxml` file then in the listener when I click on one element then just load it, and don't say it explicitly the `.fxml` path. – Sunflame Jul 19 '17 at 12:17
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/149596/discussion-between-sunflame-and-james-d). – Sunflame Jul 19 '17 at 12:18
  • Just make the FXML path part of the enum. What am I missing? – James_D Jul 19 '17 at 12:21

1 Answers1

2

You can just register a listener with the list view's selection model, and load the corresponding view when the selection changes.

E.g., supposing you have

public class View {

    private final String name ;
    private final Class<?> controllerType ;

    // You could assume that the resource is name+".fxml" if you want to rely on conventions like that
    // That would eliminate the need for another field here, but would prevent
    // e.g. names with whitespace etc.
    private final String resource ;

    public View(String name, Class<?> controllerType, String resource) {
        this.name = name ;
        this.controllerType = controllerType ;
        this.resource = resource ;
    }

    public String getName() {
        return name ;
    }

    public Class<?> getControllerType() {
        return controllerType ;
    }

    public String getResource() {
        return resource ;
    }

    @Override
    public String toString() {
        return name ;
    }
}

Then in FXML:

<!-- imports, namespaces omitted -->

<BorderPane fx:id="root" fx:controller="app.MainController" ... >
    <left>
        <ListView fx:id="selector"/>
    </left>
</BorderPane>

and in the controller:

package app ;

public class MainController {

    @FXML
    private BorderPane root ;

    @FXML
    private ListView<View> selector ;

    public void initialize() {
        selector.getSelectionModel().selectedItemProperty().addListener((obs, oldView, newView) -> {
            if (newView == null) {
                root.setCenter(null);
            } else {
                // assumes each FXML is in the same package as its controller
                // (which I think is a nice way to organize things)
                FXMLLoader loader = new FXMLLoader(newView.getControllerType().getResource(newView.getResource()));
                try {
                    root.setCenter(loader.load());
                } catch (IOException exc) {
                    // unrecoverable...
                    throw new UncheckedIOException(exc);
                }
            }
        });

        selector.getItems().addAll(
            new View("Fruits", FruitsController.class, "Fruits.fxml"),
            new View("Vegetables", VegetablesController.class, "Vegetables.fxml")
        );
    }
}

If all the controllers have common functionality, you can make them implement a common interface (ProductsController, for example), and retrieve the current controller from the loader with ProductsController currentController = loader.getController(); after loading the new FXML. (You can also change the type of controllerType in View to Class<? extends ProductsController> in this case.)

James_D
  • 201,275
  • 16
  • 291
  • 322
  • Yes as you wrote I have an interface that is implemented by all controllers, so I think this is the solution that I will use, it is much simpler and understandable that I was using. – Sunflame Jul 19 '17 at 12:25
  • @Sunflame You can make `View` an enum if you prefer and still do exactly the same thing, of course. – James_D Jul 19 '17 at 12:27
  • I will better use the class, but I think I have to use the resource, because my _names_ are translated in several languages and there are also spaces between words, so if I understand correctly I cannot use that way you wrote in the comment above the _resource_, – Sunflame Jul 19 '17 at 12:32
  • ` root.setCenter(loader.load());` here for me it says i have to surround with try catch even if i put there throws `IOException` do you have any idea why? For you that code fragments compiles? – Sunflame Jul 19 '17 at 12:41