Procedurally creating displays can really help keep your code concise – but the downside is that you don’t always have direct the opportunity to create custom click handlers for everything. In cases like that, how do you differentiate between multiple, similar objects when they’re clicked?

Fortunately, there are some quick ways to store some information in your buttons and nodes so that you can identify them later.

When a button is clicked, the button object can be accessed by invoking the getTarget() method of the resulting ActionEvent. The button can be identified by using its label text or, if the button has no text, by its id if this was encoded when the button was created.

What you’ll get out of this tutorial

In this tutorial, I’ll go through two scenarios where you might need to work out which element in a scene your user’s just interacted with.

How to check which button just got clicked (with text)

There are a lot of times when you might want to create a series of buttons, or interface elements that are very similar to each other. Creating a calculator, or dashboard, are great examples of this.

JavaFX GridPane layouts can be used to create different effects, such as a calculator or Dashboard

In those cases, it’s often easier and more concise to create a series of elements using a procedural loop rather than using custom code. If you’re setting a calculator number button that has text, you might create the button in a loop. That way, you can set the number on the button as you go based on the index of the loop.

FlowPane buttonContainer = new FlowPane(10, 10);
for (int i = 1; i < 10; i++) {
    buttonContainer.getChildren().add(new Button(String.valueOf(i)));
}
calculatorLayout.getChildren().add(buttonContainer);

To activate the button, we will need to add an action event, which we can later use to access the button using event.getTarget().

1. Check the button’s identity using the label text

In the simplest case, we don’t even need to access the Button – we just need the loop counter. Because the button is a labelled node, we can access its text by invoking getText(). Many calculator applications work by pushing operations onto some sort of operation Stack. so we’ll push the number associated with the button onto that.

for (int i = 0; i < 9; i++) {
    Button numberButton = new Button(String.valueOf(i + 1));
    numberButton.setOnAction(event -> {
        calculatorOperations.push(Double.valueOf(numberButton.getText()));
    });
    buttonContainer.getChildren().add(numberButton);
}

To access the button – style-setting for instance – I’d suggest you call the button object directly. However, to access the button later – for example, from an event, you’ll need to access the event target. The target is a property of an event based on what caused the event to happen. I’ve created a complete guide to JavaFX events, if you want to learn more about how they work.

2. How to check which button is pressed when you don’t have access to the button object

Regardless of how it was created, you can identify a button that has just been clicked through the ActionEvent‘s getTarget() method.

Then, in the same block of code, you can retrieve the button label text once the target is cast to the Button type.

EventHandler<ActionEvent> buttonEventHandler(){
    return event -> {
        Button numberButton = (Button) event.getTarget();
        calculatorOperations.push(Double.valueOf(numberButton.getText()));
    };
}

I don’t advocate including a type check for the Button type here if you’re writing the code and know this event handler will only be applied to these buttons. However, if you’re collaborating on a larger project, you might want to check your event target is a button before you cast to the Button type.

Often, I’ll create buttons using SVG graphics to make them look a little more appealing. I’ve actually created a whole tutorial about how to use SVGs in JavaFX; they’re a great way to make your programs more visually appealing.

In that case, the button itself won’t have any label text. So, I usually encode the nature of the button a little differently.

Checking which node just got clicked (without text)

Sometimes, you can procedurally generate elements in a scene that aren’t labelled – that is, they don’t have a text property you can hook into to identify the identity of the node.

To identify buttons in this case, we’ll need to set the button’s id property during the generation process, and then query that information later.

1. Encode the node’s function in its ID

To encode the id of the button, you just need to invoke setId(), passing in the id as a String.

for (int i = 1; i < 10; i++) {
    Button numberButton = new Button(String.valueOf(i));
    numberButton.setId("number-button-" + (i));
    buttonContainer.getChildren().add(numberButton);
}

Here, by labelling setting the id property to “number-button-1“, we’ve created a queryable String, which we can use to determine the button identity later, and created a an id to use as a CSS hook to set the SVG button shape.

If you’d previously been using the id property of your buttons to encode some other CSS styling, consider adding that style using a style class instead.

numberButton.getStyleClass().add("calculator-button");

That way, you can encode general characteristics of color and size separately to the button-specific information about shape and identity.

Now, when a button is clicked, you can check that identity by querying the id you’ve just set.

2. Set a node’s identity using the id property

We can use the same code to set the id of a Node object. If you’re working with Region objects, you can set their shape using an SVG path, just as with a button. However, if you’re grouping your nodes, the Group node does not have a shape.

for (int i = 1; i < 10; i++) {
    Group displayGroup = new Group();
    displayGroup.setId("group-" + (i));
    addChartToGroup(displayGroup);
    displayGroup.getStyleClass().add("dashboard-widget");
    board.getChildren().add(displayGroup);
}

Obviously, you’ll have your own implementation, but the important thing here is getting the id back from the button.

3. How to check what was pressed

You can access the node in the same way through the ActionEvent your EventHandler controls through the getTarget() method. Then, we cast to Node and grab the id from the object we just created.

EventHandler<ActionEvent> buttonEventHandler(){
    return event -> {
        Node node = (Node) event.getTarget();
        int id = Integer.parseInt(displayGroup.getId().split("-")[2]);
    };
}

Stepping up again in complexity, sometimes your application needs something more complicated than nine calculator buttons. In fact, procedurally-generated grids are more common than you think.

Procedurally creating displays can really help keep your code concise – but the downside is that you don’t always have direct access to creating custom click handlers for everything. If you’re interesting in getting the locations of nodes in a grid, check out my article where I take you step-by-step through working with objects in grid layouts like the GridPane.

Conclusions

JavaFX provides some really useful handles when working with objects we may not have created. Storing information in the text property of labelled controls, and the id property of nodes enables us to quickly check which node the user has interacted with.

The easiest way to access a button a user has interacted with is through the getTarget() method of the ActionEvent (or other event type) that is fired. Where your user has interacted with a node that doesn’t have label text, this can be achieved by querying the id property rather than the label text.