Search code examples
javajavafxborderpane

Setting center of borderpane form another controller class


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.


Solution

  • 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.