• Oct
    • 25
    • 2011

Views within Views, Controllers within Controllers

Posted by In Uncategorized
This entry is part 4 of 15 in the series Building JEE applications in JavaFX 2.0

GUIs are like onions (and Ogres) – they have layers. There are very few GUIs out there that show a single screen and that’s it. In traditional desktop applications we often end up with a screen full of complex sub-views, some tab panes perhaps, and often way too many popup dialogs. In a web app, we usually end up with a series of interconnected pages that hyperlink between each other, many of which contain sub-pages of information within them.

In either case we end up with the problem of how to embed and link our views and yet still keep them as nice, contained, reusable modules of code. At the core of it, all the presentation frameworks and architectures out there aim to address this issues in some way or another and FXML is no different. We just specify <fx:include> in our FXML file and the loader will magically pull in a second FXML file and drop it into our parent file. Great!

But there’s a problem. We’ve just come up with a great way to use dependency injection with our controllers and share resources between them, but this requires us to expose our controllers through the factory with the @Bean annotation. If the FXMLLoader magically creates our sub-views and sub-controllers for us, we’re back to square one.

So we need another way. In fact we need to make a mental gear change for this and come at it from another angle. The FXML approach assembles views by creating aggregates of other views, the controllers are somewhat of an afterthought. Sub-controllers have no real knowledge of their parent or child controllers, yet the views do. When we stop and think about this, shouldn’t it be the other way around? Our controllers should be the ‘brains’ of the operation, the views are just there to look pretty and smile.

Looking at if from this new angle, what we really need is a way to assemble controllers with sub-controllers and have these tell their corresponding views how to connect up with each other. As it turns out we’ve just put a mechanism in place for doing just that – dependency injection and a factory is exactly what we need to make this work.

In the rest of this post, I’m going to walk through a small example that has a three controllers. The MainController provides the outer container and a way to switch between the two sub-controllers (Page1Controller and Page2Controller) using a tab pane style approach. I’ll keep the controllers deliberately simple to get the idea across and, as usual, we’ll add more complexity in later posts.

Our end GUI is going to look something like this:

Let’s start with the simple stuff. Our two sub-pages will be simple, dumb controllers and views, just to give us something to work with. You can see the code for these here:

Now for the interesting stuff. Our MainController needs to provide a way to switch between the views – two buttons will do for this for now. It also needs a place for the sub-views to appear, and in our FXML this will just be a panel that acts as a container. Our FXML looks like this:

Main.fxml

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

<?import java.lang.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>

<BorderPane fx:id="view"
            fx:controller="com.zenjava.jfxspring.MainController"
            xmlns:fx="http://javafx.com/fxml">

  <styleClass> <String fx:value="welcome"/> </styleClass>

  <top>
    <HBox spacing="4">
      <children>
        <Button text="Show Page 1" onAction="#showPage1"/>
        <Button text="Show Page 2" onAction="#showPage2"/>
      </children>
    </HBox>
  </top>

  <center>
    <BorderPane fx:id="contentArea"/>
  </center>

</BorderPane>

So it’s left to our MainController to actually show the appropriate view when each button is selected. To do this it needs to get hold of the Page1 and Page2 controllers and then it can happily access the views for each. Using Spring’s @Autowired annotation we can easily achieve this, so long as all three controllers are exposed by our factory class. It all looks a little like this:

MainController.java

import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.Parent;
import javafx.scene.layout.BorderPane;
import org.springframework.beans.factory.annotation.Autowired;

public class MainController
{
    @FXML private Parent view;
    @FXML private BorderPane contentArea;

    @Autowired private Page1Controller page1Controller;
    @Autowired private Page2Controller page2Controller;

    public Parent getView()
    {
        return view;
    }

    public void showPage1(ActionEvent event)
    {
        contentArea.setCenter(page1Controller.getView());
    }

    public void showPage2(ActionEvent event)
    {
        contentArea.setCenter(page2Controller.getView());
    }
}

SampleAppFactory.java

import javafx.fxml.FXMLLoader;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.io.IOException;
import java.io.InputStream;

@Configuration
public class SampleAppFactory
{
    @Bean
    public MainController mainController() throws IOException
    {
        return (MainController) loadController("/Main.fxml");
    }

    @Bean
    public Page1Controller page1Controller() throws IOException
    {
        return (Page1Controller) loadController("/Page1.fxml");
    }

    @Bean
    public Page2Controller page2Controller() throws IOException
    {
        return (Page2Controller) loadController("/Page2.fxml");
    }

    protected Object loadController(String url) throws IOException
    {
        // as before (see full code for details)
    }
}

In the end, embedding views and controllers is actually quite simple with this approach. Architecturally it feels pretty clean, each of our child controllers is a stand-alone, reusable piece of code. Our parent controller has some fairly intimate relations with its children, and often this will be OK. In a lot of cases however, we will want a parent that is less tightly bound to its children. In the next post we’ll have a go at achieving that.

The full code for this post can be found at: http://code.google.com/p/jfxee/source/browse/trunk/jfxee4

Series Navigation<< Multiple Controllers with Shared ResourcesGeneric Controllers >>

7 Comments

  • November 5, 2011

    Nice blog! The FXML is quiet great. But can we set all the value of standard JavaFX Class value? from FXML.?

    Thanks.
    Narayan

  • zonski Author
    November 5, 2011

    Hi Narayan,

    Yep, FXML just uses reflection to call the methods on your classes so you can call anything (even methods you create yourself). You can read all about it in the FXML guides:

    http://download.oracle.com/javafx/2.0/fxml_get_started/jfxpub-fxml_get_started.htm
    http://download.oracle.com/javafx/2.0/api/javafx/fxml/doc-files/introduction_to_fxml.html

  • Nico
    September 24, 2012

    Nice stuff! I’m converting my application which was getting a bit too monolithic using your blog entries (albeit with Guice), thanks a lot for the information.

  • rod
    October 3, 2012

    Hi, I’m having difficulty making the sample works. I copied every files and upon running I get the result “java.lang.IllegalStateException: Cannot load configuration class: javafxapplication.SampleAppFactory”, please help … thanks.

  • September 6, 2013

    Greetings! I know this is somewhat off topic but I was wondering which blog platform are you using
    for this website? I’m getting sick and tired of WordPress because I’ve had problems
    with hackers and I’m looking at alternatives for another platform.
    I would be fantastic if you could point me in the direction of a good
    platform.

Leave a Comment