Monday, January 26, 2015

Dynamically adding JavaFX controls

The JavaFX SceneBuilder works very well for constructing a static GUI; that is, a GUI whose controls are determined entirely at compile-time.  But sometimes it is useful to be able to add new controls dynamically at run-time.  The example below demonstrates one way to do this.

In this example, the user creates Button objects on-the-fly.  They are displayed using a ListViewer and stored in an ObservableList.  Each button inverts the capitalization of its label when clicked.  The Controller class is given below; constructing the rest of the GUI is an exercise for the reader.

package application;

import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.ListView;
import javafx.scene.control.TextField;
import javafx.scene.input.MouseEvent;

public class Controller {
    @FXML
    private Button add;
    @FXML 
    private TextField button;
    @FXML 
    private ListView<Button> visibleList;
 
    private ObservableList<Button> buttons = 
        FXCollections.observableArrayList();
 
    @FXML
    protected void initialize() {
        visibleList.setItems(buttons);
    }
 
    @FXML
    void addName() {
        Button b = new Button(button.getText());
        buttons.add(b);
        b.addEventHandler(MouseEvent.MOUSE_CLICKED, 
            (event -> b.setText(invertCapitals(b.getText()))));
        button.setText("");
    }
 
    public static String invertCapitals(String other) {
        return other.chars().mapToObj(Controller::flipCap)
                            .map(c -> Character.toString(c))
                            .reduce("", (s, c) -> s + c);
    }
 
    public static Character flipCap(int c) {
        if (c >= 'A' && c <= 'Z') {
           return (char)(c - 'A' + 'a');
        } else if (c >= 'a' && c <= 'z') {
           return (char)(c - 'a' + 'A');
        } else {
           return (char)c;
        }
    }
}

Highlighted in red, the key to making this all work is the addName() method, working in combination with the ObservableList buttons.   This method creates a new Button object in response to every click of the add button.  This object is added to our ObservableList, which makes it visible in the ListViewer.  Finally, we employ a Java 8 lambda to implement the event handler.

There is a pretty long list of event types to which one might respond.  Check out the documentation for the MouseEvent and KeyEvent classes to get started.

Here's a sample run of the program:

7 comments:

  1. in intialize it is showing an error, I don't know why?

    ReplyDelete
    Replies
    1. Make sure visibleList is bound properly to an object in your FXML file.

      Delete
  2. what about deleting. I have taken pieces of this code to do a simple form. I want to add textfeilds dynamically. You code successfully does this. The problem I have is deleting. I can only delete once. If I add again, I can delete again, but only once. How can I add and delete components

    ReplyDelete
  3. Great tutorial . Thank you so much

    One thing to note is that when you create the add button in Scene builder , one needs to set the On Action property of the add button to addName for the add button. This part took me a couple of minutes to figure out. In case someone encounters the problem.

    ReplyDelete
  4. Hi! I'm trying to implement your code, but for a reason I don't understand my list view doesn't update at all. I've already bound my FXML object to the .fxml file itself properly but it is not working. I don't know if it has anything to do with the initialize method, because honestly I don't know where that method is being called. Thank you.

    ReplyDelete
  5. How to manage added buttons actions? i.e how do I know the added button id and do assign onAction Listener

    ReplyDelete