1

I have Borderpane as homePane (its fxml file is home.fxml). At first in center of homePane i have a HBox which contains a button called delivery. When the user clicks this button, the center of homePane switchs to display the list of customers. It works !

The code of homeController :

public class HomeController {

@FXML
private BorderPane homePane; 

@FXML
public void deliveryBtnClicked (){
    FXMLLoader loader = new FXMLLoader(getClass().getResource("/fragments/customers.fxml"));
    homePane.getChildren().remove(homePane.getCenter());
    try {
        Pane pane = loader.load();
        homePane.setCenter(pane);

    }catch (IOException e){
        System.out.println(e.getMessage());
    }
}

public BorderPane getHomePane() {
    return homePane;
}
}

The new Pane contains the table of customers and a button called order. It has own Controller :

public class CustomerController {
    
    @FXML
    public void loadOrderView() throws IOException {
        Customer customer = customerTable.getSelectionModel().getSelectedItem();
        FXMLLoader loader = new FXMLLoader();
        loader.setLocation(getClass().getResource("/home.fxml"));
        loader.load();

        HomeController homeController =  loader.getController();
        BorderPane homePane = homeController.getHomePane();


        homePane.getChildren().remove(homePane.getCenter());
        System.out.println(homePane.getCenter());
        FXMLLoader loader1 = new FXMLLoader(getClass().getResource("/fragments/order.fxml"));
        Pane orderView = loader1.load();
        homePane.setCenter(orderView);
        System.out.println(homePane.getCenter());
     }
} 

When user clicks the button order, the order-view have to be loaded in the center of homePane. the print command shows that the homePane center after removed is null and then after loaded gets new view.

null
AnchorPane@755b1503

But in running Programm its doen't show the order view. I have set onAction of button order to loadOrderView. This is definitive not the problem.

CT11
  • 73
  • 1
  • 6
  • 3
    You’re loading `home.fxml` a second time, creating a second border pane (and a second instance of `HomeController`). That second border pane is not part of the scene, so you won’t see anything when you change its content. – James_D Feb 10 '22 at 11:42
  • 2
    You either need to pass the `BorderPane` or the instance of the `BorderPane` or the *current* instance of `HomeController` to the `CustomerController` (both bad options imo) or use a model or view-model which encapsulates the current view state and share it with both controllers. – James_D Feb 10 '22 at 11:50
  • Thank you for your anwer. I don't know how can i use a model or view-model. Can you please show a example ? – CT11 Feb 10 '22 at 12:34
  • @jewelsea yes, it works – CT11 Feb 10 '22 at 13:01

2 Answers2

2

As James has pointed out, you should not load the home fxml again.

Instead:

  1. Define the orderButton in your controller and in your fxml (via a fx:id and @FXML injection).

  2. Use the scene graph to find the home pane so that you can replace its center.

public class CustomerController {
    
    @FXML Button orderButton
    
    @FXML
    public void loadOrderView() throws IOException {
        FXMLLoader loader = new FXMLLoader(getClass().getResource("/fragments/order.fxml"));
        Pane orderView = loader.load();
        BorderPane homePane = (BorderPane) orderButton.getScene().getRoot();
        homePane.setCenter(orderView);
     }
} 

An alternate method in a small framework is defined in the answer to:

jewelsea
  • 150,031
  • 14
  • 366
  • 406
2

You can navigate the scene graph as shown in another answer, and this is a perfectly good solution for a small application.

For larger applications, where you might need more flexibility in terms of the ability to change, e.g., how the layout is managed, consider using a MVC/MVVM type of approach. Start with a model class that maintains the current view state:

public class ViewModel {

    public enum View {
        CUSTOMER(ViewModel.class.getResource("/fragments/customer.fxml")),
        ORDER(ViewModel.class.getResource("/fragments/order.fxml"));

        private final URL resource ;

        private View(URL resource) {
            this.resource = resource ;
        }

        public URL getResource() {
            return resource ;
        }
    }

    private final ObjectProperty<View> currentView = new SimpleObjectProperty<>();

    public ObjectProperty<View> currentViewProperty() {
        return currentView ;
    }

    public final View getCurrentView() {
        return currentViewProperty().get();
    }

    public final void setCurrentView(View currentView) {
        currentViewProperty().set(currentView);
    }
}

Make your controllers implement an interface that allows them to have a reference to a ViewModel:

public interface ViewModelObserver {
    public void setViewModel(ViewModel model);
}

Your HomeController, assuming it is loaded once and the home page is always shown, can create a single instance of the ViewModel, observe it, update the border pane, and pass the instance to other controllers when they are loaded:

public class HomeController  {

    private final ViewModel viewModel = new ViewModel();

    @FXML
    private BorderPane homePane; 
    
    @FMXL
    public void initialize() {
        viewModel.currentViewProperty().addListener((obs, oldView, newView) -> {
            try {
                FXMLLoader loader = new FXMLLoader(newView.getResource());
                Parent root = loader.load();
                Object controller = loader.getController();
                if (controller instanceof ViewModelObserver) {
                    ((ViewModelObserver)controller).setViewModel(viewModel);
                }
                homePane.setCenter(root);
            } catch (Exception exc) {
                exc.printStackTrace();
            }
        });
    }
    
    @FXML
    public void deliveryBtnClicked (){
        viewModel.setCurrentView(ViewModel.View.CUSTOMER);
    }


}

Then make the controllers for the FXML "fragments" implement ViewModelObserver. They can simply update the current view, and the home controller will load and display it:

public class CustomerController implements ViewModelObserver {

    private ViewModel viewModel ;

    @Override
    public void setViewModel(ViewModel viewModel) {
        this.viewModel = viewModel ;
    }
    
    @FXML
    public void loadOrderView() throws IOException {
        viewModel.setCurrentView(ViewModel.View.ORDER);
     }
} 

You may need to include other properties in your view model, e.g. to give access to the Customer instance etc.

James_D
  • 201,275
  • 16
  • 291
  • 322