-1

I'm doing a project for a Restaurant Management system in JavaFX. I'm totally new to this world, so my first attempt looked dull. Here's an example:

First attempt

In this terrible UI, when you pressed a button it opened another window, where each had their own class. This is the ActionEvent:

public void apriSchermataNuovoPiatto(ActionEvent event) throws IOException {
        root = FXMLLoader.load(getClass().getResource("/personalizzaMenu/nuovo-piatto.fxml"));
        stage = (Stage)((Node)event.getSource()).getScene().getWindow();
        scene = new Scene(root);
        stage.setScene(scene);
        stage.show();
    }

After watching several tutorials, i made a much better looking UI, that looks like this:

Better looking attempt

Thing is, now i have to change things a bit. What i want now is that when you press a button it doesn't open a new window, but that window must "exist" on the right side of my dashboard.

I don't really know how should i deal with this. For a better management of my code, i'm using an MVC pattern, so i still want that every window has its own class that can be loaded with a method. But I don't want it to be a window. I want that by pressing a button, the "window" AnchorPane gets added to the right side of the dashboard.

Any help?

Ric97
  • 109
  • 1
  • 6
  • 3
    Use a [`BorderPane`](https://openjfx.io/javadoc/19/javafx.graphics/javafx/scene/layout/BorderPane.html) as the root of the scene. Put your side menu on the `left` and the real content in the `center`. Whenever the user presses a button, replace the `center` node as appropriate. Note I recommend avoiding `AnchorPane` as much as possible. It's rarely the layout you want. You should prefer layouts that handle relative sizing and relative positioning (anchor pane is absolute). You can nest layouts to get the desired overall layout. – Slaw Feb 20 '23 at 18:20
  • Thank you, i will look into borderpanes. Still, i'm a bit confused about the node changing. If i have multiple classes, each representing a node, how can i "inject" it into the central node? I only used the FXMLoader methods right now – Ric97 Feb 20 '23 at 18:46
  • This [`LayoutDemo`](https://github.com/openjdk/jfx/tree/master/apps/toys/LayoutDemo) is a convenient way to explore. – trashgod Feb 20 '23 at 18:56
  • See if [this](https://github.com/sedj601/RestaurantOrdersDuplicateFX/tree/master/src/restaurantordersduplicatefx) helps. – SedJ601 Feb 20 '23 at 19:28
  • @Ric97 The specifics depend on how you're structuring your code. But essentially a call to `FXMLLoader#load()` will return a `Node` (presumably). From there, you simply call `pane.setCenter(newlyLoadedNode)`. That obviously means that the mechanism you use somehow needs access to the `BorderPane` _instance_. I suppose you could do something like `(BorderPane) ((Node) event.getSource()).getScene().getRoot()`. But that's brittle and effectively relies on implementation details. A proper architecture like MVC or MVVM would probably be better. – Slaw Feb 20 '23 at 20:41

1 Answers1

2

Here is an example. I used MVC ideas from here and Passing Data ideas from here.

This example has a Data Model that simulates getting data from a database and passing it between subviews. DataModel is created in the Main and passed to the MainViewController.

DataModel dataModel = new DataModel();

FXMLLoader rootLoader = new FXMLLoader(getClass().getResource("MainView.fxml"));
rootLoader.load();
MainViewController mainViewController = rootLoader.getController();
mainViewController.initModel(dataModel);
mainViewController.setSubSceneInitalNode();

After DataModel is passed to MainViewController, MainViewController will pass it to the subview when it load subview.

Example loading the Orders view

public void handleBtnOnActionOrders(ActionEvent actionEvent) 
{
    try 
    {
        FXMLLoader loader = new FXMLLoader(getClass().getResource("OrdersSubsene.fxml"));
        loader.load();
        OrdersSubseneController ordersSubseneController = loader.getController();
        ordersSubseneController.initModel(dataModel);

        spSubScene.getChildren().clear();
        spSubScene.getChildren().add(ordersSubseneController.getVBoxRoot());
    } 
    catch (IOException ex) 
    {
        System.out.println(ex.toString());
    }
}

Full Code

Main

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.stage.Stage;

import java.io.IOException;

/**
 * JavaFX App Sedrick(Sedj601)
 */
public class App extends Application {
    @Override
    public void start(Stage stage) throws IOException {
    DataModel dataModel = new DataModel();

    FXMLLoader rootLoader = new FXMLLoader(getClass().getResource("MainView.fxml"));
    rootLoader.load();
    MainViewController mainViewController = rootLoader.getController();
    mainViewController.initModel(dataModel);
    mainViewController.setSubSceneInitalNode();

        Scene scene = new Scene(rootLoader.getRoot());

        stage.setScene(scene);
        stage.show();
    }
    
    public static void main(String[] args) {
        launch();
    }
}

DataModel

package sed.test.dashboardfx;

import java.util.ArrayList;
import java.util.List;

/**
 *
 * @author blj0011
 */
public class DataModel {
    public List<String> simulateGetDataFromDatabase()
    {
        List<String> name = new ArrayList();
        
        name.add("John Doe");
        name.add("Jane Doe");
        name.add("Kim Jackson");
        name.add("James Jones");
        
        return name;
    }
}

Module-Info

module sed.test.dashboardfx {
    requires javafx.controls;
    requires javafx.fxml;

    requires org.kordamp.ikonli.core;
    requires org.kordamp.ikonli.javafx;
    requires org.kordamp.ikonli.dashicons;
    requires java.base;
        
    opens sed.test.dashboardfx to javafx.fxml;
    exports sed.test.dashboardfx;
}

MainViewController

import java.io.IOException;
import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;

public class MainViewController {

    @FXML private StackPane spSubScene;
    @FXML private Button btnOverview;
            
    private DataModel dataModel;

    public void setSubSceneInitalNode() 
    {
        btnOverview.fire();
    }

    public void handleBtnOnActionOverview(ActionEvent actionEvent) 
    {
        try 
        {
            FXMLLoader loader = new FXMLLoader(getClass().getResource("OverviewSubscene.fxml"));
            loader.load();
            OverviewSubsceneController overviewSubsceneController = loader.getController();
            overviewSubsceneController.initModel(dataModel);
            
            spSubScene.getChildren().clear();
            spSubScene.getChildren().add(overviewSubsceneController.getVBoxRoot());
        } 
        catch (IOException ex) 
        {
            System.out.println(ex.toString());
        }
    }

    public void handleBtnOnActionOrders(ActionEvent actionEvent) 
    {
        try 
        {
            FXMLLoader loader = new FXMLLoader(getClass().getResource("OrdersSubsene.fxml"));
            loader.load();
            OrdersSubseneController ordersSubseneController = loader.getController();
            ordersSubseneController.initModel(dataModel);

            spSubScene.getChildren().clear();
            spSubScene.getChildren().add(ordersSubseneController.getVBoxRoot());
        } 
        catch (IOException ex) 
        {
            System.out.println(ex.toString());
        }
    }

    public void handleBtnOnActionSettings(ActionEvent actionEvent) 
    {
        try 
        {
            FXMLLoader loader = new FXMLLoader(getClass().getResource("SettingsSubscene.fxml"));
            loader.load();
            SettingsSubsceneController settingsSubsceneController = loader.getController();
            settingsSubsceneController.initModel(dataModel);
            
            spSubScene.getChildren().clear();
            spSubScene.getChildren().add(settingsSubsceneController.getVBoxRoot());
        } 
        catch (IOException ex) 
        {
            System.out.println(ex.toString());
        }
    }

    public void initModel(DataModel model) 
    {
        if (this.dataModel != null) {
            throw new IllegalStateException("Model can only be initialized once");
        }
        this.dataModel = model;
    }
    
    public void handleBtnExit(ActionEvent actionEvent)
    {
        Platform.exit();
    }
}

MainView.fxml

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.layout.VBox?>
<?import org.kordamp.ikonli.javafx.FontIcon?>

<VBox maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="720.0" prefWidth="1080.0" xmlns="http://javafx.com/javafx/18" xmlns:fx="http://javafx.com/fxml/1" fx:controller="sed.test.dashboardfx.MainViewController">
   <children>
      <HBox prefHeight="100.0" prefWidth="200.0" VBox.vgrow="ALWAYS">
         <children>
            <VBox alignment="TOP_RIGHT" prefHeight="400.0" prefWidth="256.0" style="-fx-background-color: #05071F;">
               <children>
                  <StackPane alignment="BOTTOM_CENTER" maxWidth="1.7976931348623157E308" prefHeight="175.0" prefWidth="200.0">
                     <padding>
                        <Insets bottom="5.0" />
                     </padding>
                     <children>
                        <FontIcon iconColor="WHITE" iconLiteral="dashicons-businessperson" iconSize="60" tabSize="0" />
                     </children>
                  </StackPane>
                  <Label fx:id="lblDisplayImageTitle" alignment="CENTER" maxWidth="1.7976931348623157E308" text="Current View" textFill="#e7e5e5">
                     <padding>
                        <Insets bottom="20.0" />
                     </padding>
                  </Label>
                  <Button fx:id="btnOverview" alignment="BASELINE_LEFT" graphicTextGap="22.0" maxWidth="1.7976931348623157E308" mnemonicParsing="false" onAction="#handleBtnOnActionOverview" prefHeight="55.0" style="-fx-background-color: #05071F;" text="Overview" textFill="#e7e5e5">
                     <padding>
                        <Insets left="60.0" />
                     </padding>
                     <graphic>
                        <FontIcon iconColor="#fcf9f9" iconLiteral="dashicons-desktop" iconSize="25" />
                     </graphic>
                  </Button>
                  <Button fx:id="btnOrders" alignment="BASELINE_LEFT" graphicTextGap="22.0" maxWidth="1.7976931348623157E308" mnemonicParsing="false" onAction="#handleBtnOnActionOrders" prefHeight="55.0" style="-fx-background-color: #05071F;" text="Orders" textFill="#e7e5e5">
                     <padding>
                        <Insets left="60.0" />
                     </padding>
                     <graphic>
                        <FontIcon iconColor="WHITE" iconLiteral="dashicons-list-view" iconSize="25" />
                     </graphic>
                  </Button>
                  <Button fx:id="btnSettings" alignment="BASELINE_LEFT" graphicTextGap="22.0" maxWidth="1.7976931348623157E308" mnemonicParsing="false" onAction="#handleBtnOnActionSettings" prefHeight="55.0" style="-fx-background-color: #05071F;" text="Settings" textFill="#e7e5e5">
                     <padding>
                        <Insets left="60.0" />
                     </padding>
                     <graphic>
                        <FontIcon iconColor="WHITE" iconLiteral="dashicons-admin-settings" iconSize="25" />
                     </graphic>
                  </Button>
                  <StackPane prefHeight="150.0" prefWidth="200.0" VBox.vgrow="ALWAYS" />
                  <StackPane>
                     <children>
                        <Button maxWidth="1.7976931348623157E308" mnemonicParsing="false" onAction="#handleBtnExit" text="Exit">
                           <StackPane.margin>
                              <Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
                           </StackPane.margin>
                        </Button>
                     </children>
                  </StackPane>
               </children>
            </VBox>
            <StackPane fx:id="spSubScene" style="-fx-background-color: #02030A;" HBox.hgrow="ALWAYS" />
         </children>
      </HBox>
   </children>
</VBox>

OrdersSubsceneController

import javafx.collections.FXCollections;
import javafx.fxml.FXML;
import javafx.scene.control.ListView;
import javafx.scene.layout.VBox;

/**
 * FXML Controller class
 *
 * @author blj0011
 */
public class OrdersSubseneController {

    private @FXML VBox vbRoot;
    private @FXML ListView<String> listView;
    
    private DataModel dataModel;
    
    VBox getVBoxRoot()
    {
        return vbRoot;
    }
    
    public void initModel(DataModel model) {
        if (this.dataModel != null) {
            throw new IllegalStateException("Model can only be initialized once");
        }
        this.dataModel = model ;
        
        listView.setItems(FXCollections.observableArrayList(model.simulateGetDataFromDatabase()));
    }       
}

OrdersSubscene.fxml

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.ListView?>
<?import javafx.scene.layout.VBox?>

<VBox fx:id="vbRoot" alignment="CENTER" spacing="20.0" xmlns="http://javafx.com/javafx/18" xmlns:fx="http://javafx.com/fxml/1" fx:controller="sed.test.dashboardfx.OrdersSubseneController">
    <children>
        <Label text="Orders Subscene" textFill="WHITE" />
      <Label text="Use Passed Data in ListView!" textFill="WHITE" />
      <ListView fx:id="listView" prefHeight="200.0" prefWidth="200.0" />
    </children>
    <padding>
        <Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
    </padding>
</VBox>

OverviewSubsceneController

import java.util.StringJoiner;
import javafx.fxml.FXML;
import javafx.scene.control.TextArea;
import javafx.scene.layout.VBox;

public class OverviewSubsceneController {
    private @FXML VBox vbRoot;
    private @FXML TextArea textArea;
    
    private DataModel dataModel;
          
    VBox getVBoxRoot()
    {
        return vbRoot;
    }
    
    public void initModel(DataModel model) {
        if (this.dataModel != null) {
            throw new IllegalStateException("Model can only be initialized once");
        }
        this.dataModel = model ;
        
        StringJoiner stringJoiner = new StringJoiner(", ");
        model.simulateGetDataFromDatabase().forEach(stringJoiner::add);
        textArea.setText(stringJoiner.toString());
    }    
}

OverviewSubscene.fxml

package sed.test.dashboardfx;

import java.util.StringJoiner;
import javafx.fxml.FXML;
import javafx.scene.control.TextArea;
import javafx.scene.layout.VBox;

public class OverviewSubsceneController {
    private @FXML VBox vbRoot;
    private @FXML TextArea textArea;
    
    private DataModel dataModel;
          
    VBox getVBoxRoot()
    {
        return vbRoot;
    }
    
    public void initModel(DataModel model) {
        if (this.dataModel != null) {
            throw new IllegalStateException("Model can only be initialized once");
        }
        this.dataModel = model ;
        
        StringJoiner stringJoiner = new StringJoiner(", ");
        model.simulateGetDataFromDatabase().forEach(stringJoiner::add);
        textArea.setText(stringJoiner.toString());
    }    
}

SettingsSubsceneController

import javafx.fxml.FXML;
import javafx.scene.control.ComboBox;
import javafx.scene.layout.VBox;

public class SettingsSubsceneController {
    private @FXML VBox vbRoot;
    private @FXML ComboBox<String> comboBox;
    
    private DataModel dataModel;
          
    VBox getVBoxRoot()
    {
        return vbRoot;
    }
    
    public void initModel(DataModel model) {
        if (this.dataModel != null) {
            throw new IllegalStateException("Model can only be initialized once");
        }
        this.dataModel = model ;
        
        comboBox.getItems().addAll(dataModel.simulateGetDataFromDatabase());
        comboBox.getSelectionModel().selectFirst();
    }    
}

SettingsSubscene.fxml

<?import javafx.geometry.Insets?>
<?import javafx.scene.control.ComboBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.VBox?>

<VBox fx:id="vbRoot" alignment="CENTER" spacing="20.0" xmlns="http://javafx.com/javafx/18" xmlns:fx="http://javafx.com/fxml/1" fx:controller="sed.test.dashboardfx.SettingsSubsceneController">
    <children>
        <Label text="Settings Subscene" textFill="WHITE" />
      <Label text="Use Passed Data in ComboBox" textFill="WHITE" />
      <ComboBox fx:id="comboBox" prefWidth="150.0" />
    </children>
    <padding>
        <Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
    </padding>
</VBox>

Output

enter image description here

SedJ601
  • 12,173
  • 3
  • 41
  • 59