so I am new to JavaFX and I am making my first application. I would like to have an option to make element in my ListView bold after selecting it and clicking the button. Unfortunatelly, I can't find any solution to my problem and I'm out of ideas. I would appreciate if you could tell me how to do it in .java or .fxml file, because I don't use CSS files as of now.
Implementing a feature like this requires a few things:
ListView
CellFactory
for your ListView
that will modify the style of the items contained within itThere is a complete sample application (with comments) at the bottom of this answer, but let's break it down and address the above points.
Data Model:
If you are using a simple String
as the type of your ListView
(ie: ListView<String>
), you will need to change that to provide a custom object instead. For this example, we will create a new Person
class.
class Person {
private final StringProperty name = new SimpleStringProperty();
private final BooleanProperty important = new SimpleBooleanProperty(false);
public Person(String name) {
this.name.set(name);
}
public String getName() {
return name.get();
}
public StringProperty nameProperty() {
return name;
}
public void setName(String name) {
this.name.set(name);
}
public boolean isImportant() {
return important.get();
}
public BooleanProperty importantProperty() {
return important;
}
public void setImportant(boolean important) {
this.important.set(important);
}
}
Now, you will need to define your ListView
accordingly:
ListView<Person> listView = new ListView<>();
Custom CellFactory:
The CellFactory
is the part of a ListView
that handles rendering the actual contents of each row in the ListView
. In order to change the appearance of a cell based on the properties of that particular Person
, we must provide our own implementation (the standard implementation of JavaFX will simply render each cell to contain the item's toString()
method).
listView.setCellFactory(cell -> new ListCell<Person>(){
@Override
protected void updateItem(Person person, boolean empty) {
super.updateItem(person, empty);
// First, we are only going to update the cell if there is actually an item to display there.
if (!empty && person != null) {
// Set the text of the cell to the Person's name
setText(person.getName());
// If the Person has the "important" flag, we can make the text bold here
if (person.isImportant()) {
setStyle("-fx-font-weight: bold");
} else {
// Remove any styles from the cell, because this Person isn't important
setStyle(null);
}
} else {
// If there is no item to display in this cell, set the text to null
setText(null);
}
}
});
Update "Important" property:
The action for your MenuItem
should update the selected Person's important
property. This is fairly straightforward:
mnuSetImportant.setOnAction(event -> {
Person selectedPerson = listView.getSelectionModel().getSelectedItem();
if (selectedPerson != null) {
selectedPerson.setImportant(!selectedPerson.isImportant());
}
});
Note that in this case, I have set the action to toggle the important status of the person. You could easily have this just set the property to true
, but you would have no way to set it to false
again without adding a separate menu option.
After this, however, you will notice that the cell does not update itself with the new style. This is because the ListView
does not "listen" for changes to the properties of each Person
. Therefore, in the example below, we add a static extractor()
method to our Person
class, which allows its properties to trigger updates to the ListView
.
That's it! Below is a complete application that you can try out for yourself to see it in action:
import javafx.application.Application;
import javafx.beans.Observable;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import javafx.util.Callback;
public class ListViewItemProperties extends Application {
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage primaryStage) {
// **********************************************************************************************
// Create a basic layout
// **********************************************************************************************
BorderPane root = new BorderPane();
// **********************************************************************************************
// Create the menu bar and Menu items
// **********************************************************************************************
MenuBar menuBar = new MenuBar();
Menu mnuMore = new Menu("More");
MenuItem mnuSetImportant = new MenuItem("Set important");
// **********************************************************************************************
// Create the ListView
// **********************************************************************************************
ListView<Person> listView = new ListView<>();
// **********************************************************************************************
// Set the action for the "Set important" menu item to update the property of the selected
// Person in the ListView, if applicable
// **********************************************************************************************
mnuSetImportant.setOnAction(event -> {
// **********************************************************************************************
// Get reference to the currently selected Person
// **********************************************************************************************
Person selectedPerson = listView.getSelectionModel().getSelectedItem();
if (selectedPerson != null) {
// **********************************************************************************************
// If a person has been selected, toggle their "important" property
// **********************************************************************************************
selectedPerson.setImportant(!selectedPerson.isImportant());
}
});
// **********************************************************************************************
// Assemble the menus
// **********************************************************************************************
mnuMore.getItems().add(mnuSetImportant);
menuBar.getMenus().add(mnuMore);
// **********************************************************************************************
// Create some sample Persons and add them to the ListView. This will also configure the list with
// the extractor we created in the Person class in order to trigger updates to the ListView when any
// of the Person's properties change.
// **********************************************************************************************
ObservableList<Person> people = FXCollections.observableArrayList(Person.extractor());
people.addAll(
new Person("Dad"),
new Person("Sis"),
new Person("Babe"));
listView.setItems(people);
// **********************************************************************************************
// Here we will create our own CellFactory so we can set the style of the text based on the
// important property of each Person
// **********************************************************************************************
listView.setCellFactory(cell -> new ListCell<Person>() {
@Override
protected void updateItem(Person person, boolean empty) {
super.updateItem(person, empty);
// First, we are only going to update the cell if there is actually an item to display there.
if (!empty && person != null) {
// Set the text of the cell to the Person's name
setText(person.getName());
// If the Person has the "important" flag, we can make the text bold here
if (person.isImportant()) {
setStyle("-fx-font-weight: bold");
} else {
// Remove any styles from the cell, because this Person isn't important
setStyle(null);
}
} else {
// If there is no item to display in this cell, set the text to null
setText(null);
}
}
});
// **********************************************************************************************
// Assemble the Scene
// **********************************************************************************************
root.setTop(menuBar);
root.setCenter(listView);
// **********************************************************************************************
// Set the Scene for the stage
// **********************************************************************************************
primaryStage.setScene(new Scene(root));
// **********************************************************************************************
// Configure the Stage
// **********************************************************************************************
primaryStage.setTitle("Test Application");
primaryStage.show();
}
}
class Person {
// Name of this person
private final StringProperty name = new SimpleStringProperty();
// Boolean property to track whether this person has been flagged as "important"
private final BooleanProperty important = new SimpleBooleanProperty(false);
public Person(String name) {
this.name.set(name);
}
/**
* Callback to trigger updates whenever a property of a Person is changed. This allows our ListView
* to refresh the cell when the "important" property changes.
*/
public static Callback<Person, Observable[]> extractor() {
// **********************************************************************************************
// Return the callback which fires change events whenever either name or important properties
// are updated. A ListView normally only listens for changes to its Items list but ignores
// changes to the item's internal properties.
// **********************************************************************************************
return (Person p) -> new Observable[]{
p.name, p.important
};
}
public String getName() {
return name.get();
}
public void setName(String name) {
this.name.set(name);
}
public StringProperty nameProperty() {
return name;
}
public boolean isImportant() {
return important.get();
}
public void setImportant(boolean important) {
this.important.set(important);
}
public BooleanProperty importantProperty() {
return important;
}
}