13.2. Creating a Custom Component

  1. Up to this point, you’ve been building apps using provided JavaFX components such as TextField and ImageView. In this next set of steps, we will walk you through the creation of your own custom, reusable component based on the set of existing components contained in the application.

    Consider the following containment hierarchy:

                                                              --|
                          Stage                                 |
                            |                                   |
                          Scene                                 |
           |--              |                                   |
           |               HBox                                 |
           |                |\                                  |
           |                | \------------------\              |
           |                |                    |              |
           |               VBox                 VBox            | Overall
           |               / \                  / \             | Containment
    Scene  |              /   \                /   \            | Hierarchy
    Graph  |            HBox  ImageView      HBox  ImageView    |
           |            / \                  / \                |
           |           /   \                /   \               |
           |    TextField  Button    TextField  Button          |
           |--                                                --|
    

    In this scenario, the root HBox of the scene graph contains two separate, but identical, VBox objects. Building this app would require the programmer to repeat the exact same code to create these two VBox objects. Now, imagine that there are hundreds of these in an app. Custom components allow us to avoid this type of redundancy!

    Consider the following scene graph where we replace the lower VBox sub-graphs with ImageLoader, a custom component we will create in the next step. The resulting scene graph is much cleaner (kinda looks like a giraffe, no?)!

                                                              --|
                          Stage                                 |
                            |                                   |
                          Scene                                 |
           |--              |                                   | Overall
           |               HBox                                 | Containment
    Scene  |                |\                                  | Hierarchy
    Graph  |                | \------------------\              |
           |                |                    |              |
           |           ImageLoader          ImageLoader         |
           |--                                                --|
    
  2. Now consider the sub-graph for the ImageLoader component we will create. We want to avoid repeating this work by replacing this redundant part of the original scene graph with a single ImageLoader component:

           |--
           |               VBox
           |               / \
    Sub-   |              /   \
    Graph  |            HBox  ImageView
           |            / \
           |           /   \
           |    TextField  Button
           |--
    

    Note that the root of this sub-graph is a VBox. With this in mind, create a class called ImageLoader in the cs1302.gui package that extends the VBox class (additional details are provided below; please read them carefully). As this class extends VBox, it “is-a” VBox and inherits all of the members of VBox (although only public and protected members will be directly visible).

    1. The class should contain the static constants from the ImageApp class. They can be cut and pasted directly from that class, perhaps changing them to protected visibility if you wish to do so. That way they can be accessed by the other classes in the package.

    2. Your ImageLoader class should contain instance variables for the nodes in the sub-graph above (HBox, TextField, Button, and ImageView). You do not need an instance variable for VBox because the ImageLoader itself is a VBox! For the most part, the required instance variables can be cut and paste from the ImageApp class. Any instance variables that you move into the ImageLoader class can be removed from ImageApp. You can also remove any imports that are no longer needed in ImageApp.

    3. In ImageLoader, add a default constructor that explicitly calls super(). After the call to super(), the constructor should instantiate the other nodes in the ImageLoader sub-graph (HBox, ImageView, TextField, and Button). Since ImageLoader extends VBox, it “is-a” VBox. Therefore, you can call any VBox methods using this as the calling object. Use this knowledge to add your newly created nodes to the sub-graph rooted at this similar to how they are added to the VBox node in the ImageApp class. Your code will likely look something like the code below with additional statements to instantiate the components and connect them:

      public ImageLoader() {
         super();
         // instantiate objects for the component's sub-graph, then
         // add them to the ImageLoader (i.e., this)...
         this.getChildren().addAll(urlLayer, imgView);
      } // ImageLoader
      
    4. Remove the code to create the subgraph (HBox, ImageView, TextField, and Button) from the constructor and init methods of ImageApp. All of that code will now be run when we create a new ImageLoader object. This includes the code to initialize the ImageView and set up the button handler.

    5. Take a moment to think about what you are doing. You have created your own custom class that extends the JavaFX VBox class. This class is essentially a VBox with some of the components built into it. Once we complete this class, we will be able to add objects of this class to a scene graph and all the messy details of creating that object will be hidden inside of ImageLoader!

    6. Move the loadImage method from ImageApp to ImageLoader. This is the method that is called when the button is clicked. Don’t forget to set the handler on your ImageLoader’s button.

    7. You’ve probably noticed that ImageApp has significantly decreased in size. We moved a lot of that code over into our custom component! Now, instantiate two objects of type ImageLoader within the constructor of ImageApp.

    8. Create an HBox instance variable in the ImageApp class and instantiate it within the constructor. This will serve as the container for our ImageLoader objects. Set the spacing property of the HBox to 10 by passing 10 into the HBox constructor. Now, in the init method, add the two ImageLoader objects to the HBox object of ImageApp.

    9. Make sure you pass the reference to your newly created HBox object into the Scene constructor within the start method of ImageApp. Previously, the root of our scene graph was a VBox. Now it’s an HBox that contains two ImageLoader objects.

    10. Compile and run your new app and load up a few 500x500 images. You should see something like the image below:

    11. Imagine all the ways you could use your new custom component! Also, think of other custom components you could build by extending existing JavaFX components. We will explore more uses of the ImageLoader component in class.