Featured Post

Applying Email Validation to a JavaFX TextField Using Binding

This example uses the same controller as in a previous post but adds a use case to support email validation.  A Commons Validator object is ...

Saturday, October 24, 2015

A Master / Detail Pattern with JavaFX Node Properties and UserData

This blog post presents a technique for building a Master / Detail screen using JavaFX Node Properties and UserData.  UserData is a free-form object that can be associated with any Node.  UserData shares data-- business data rather than FX UI or app functionality constructs -- between the Master view and the Detail view.  Node Properties extend the Node definition without using Java programming techniques like inheritance.

User Data Main Master Calling Message Details

All source code in this post is available on GitHub.

This video demonstrates the application.  A ListView is filled with rows of Buttons.  Each row starts with a Label and contains the same 3 commands: Edit, Run, Send.  Pressing any of the command will bring up the details of the selection, an identifier I stored with UserData.  Although, the identifier is a simple piece of data, this could grow to a complex, nested record coming from the Main view.


All of the code for the application is available on GitHub.

The Master view is an FXML file containing a ListView wrapped up in a VBox.  See the following screenshot from SceneBuilder.

User Data App Contains a ListView
In the FX Controller class, the ListView is defined as follows.

    @FXML
    ListView<Node> lvButtons;

lvButtons is a ListView of Nodes, specifically HBoxes.

To minimize the row-creation code, I'm driving the creation off of two data structures.  The first data structure, buttonDefs, contains a Label and command argument that is used to specify the type of operation that will be executed.  The second data structure, data, is a list of business records.  For demonstration purposes, this is just a pair of items.  However, you can swap out the simple ID String for a complex Java object.

Here is the code for buttonDefs

        String[][] buttonDefs = {
                {"Edit", "Editting"},
                {"Run", "Running"},
                {"Send", "Sending"}
        };

Here is the code for data
        String[][] data = {
                {"A", "ID: A001"},
                {"B", "ID: B001"},
                {"C", "ID: C001"},
                {"D", "ID: D001"},
                {"E", "ID: E001"}
        };

Next, I use a loop to iterate over the data, creating a row in the ListView for each record (A, B, C, etc).  The loop adds an HBox and a Button of each type to the ListView.  PROP_COMMAND_MESSAGE is a constant used to avoid a key mismatch between the put() and the get() call.  commandHandler will follow.

        for( String[] rec : data ) {

            HBox hbox = new HBox();
            hbox.setSpacing(20.0d);

            Label label= new Label( rec[0] );
            hbox.getChildren().add(label);

            for( String[] buttonDef : buttonDefs ) {
                Button btn = new Button(buttonDef[0]);
                btn.getProperties().put(PROP_COMMAND_MESSAGE, buttonDef[1]);
                btn.setUserData(rec[1]);
                btn.setOnAction(commandHandler);
                hbox.getChildren().add(btn);
            }

            lvButtons.getItems().add( hbox );
        }

Finally, commandHandler is an onAction shared among each Button in each row.

    private EventHandler<ActionEvent> commandHandler = (evt) -> {

        Node n = (Node)evt.getSource();
        String id = (String) n.getUserData();
        String commandMessage = (String)n.getProperties().get(PROP_COMMAND_MESSAGE);

        Alert alert = new Alert(Alert.AlertType.INFORMATION, commandMessage + " " + id);
        alert.showAndWait();
    };

commandHandler receives its operands via the ActionEvent.  The ActionEvent references a Button source which is loaded up with enough information to determine the type processing (commandMessage) and the source data (id).  This can scale to an EventHandler that delegates the processing to another object, possibly supplied via buttonDefs.

Sometimes, I'll create a separate class for the EventHandler which uses fields to hold operands.  That approach -- a separate class -- is better if you need to share the code outside of this Java file.  In that case, you may still want to use Node Properties if you need to process the Scene graph outside of the handler.  For example, all of the Buttons are tagged with a "commandMessage" Property which can be used for access control.

All source code in this post is available on GitHub.

No comments:

Post a Comment