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.
1 Answers
Implementing a feature like this requires a few things:
- A proper data model for your
ListView
- A custom
CellFactory
for yourListView
that will modify the style of the items contained within it - A method of updating your Person model to mark an individual as "important"
There 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;
}
}

- 9,885
- 4
- 28
- 63
-
You are sure this is it? I mean, I want it to be bold after clicking the button and also I want it to be permanent (or at least until I decide to un-bold it). Like you know, I've selected let's say 'Dad', made it bold by clicking the button and when I select other contact I want 'Dad' to still be in bold. – MrJ_ May 09 '21 at 17:38
-
1I suggest you update your question to explain exactly what you are looking for. I may have misunderstood your meaning because you have two buttons on the screen (and a menu item), but you did not specify which button or that you want the change to be permanent. – Zephyr May 09 '21 at 17:40
-
That being said, you will want your data model to hold a reference to which persons are "important." From there, you will need to create your own CellFactory for the ListView and style its content accordingly based on that item's "important" property. Hopefully these buzz words can lead you in the right direction. I will work on a more complete answer in the meantime. – Zephyr May 09 '21 at 17:42
-
@MrJ_ I have updated my answer; please review and let me know if you have any questions on the details here. – Zephyr May 09 '21 at 18:18
-
I'm sorry, I always tend to forget that what's obvious to me (since I work on the code) might not be obvious to someone else... And I'm very grateful to you, your solution works perfectly and you even explained every single part of it to me. Thank you so much. – MrJ_ May 09 '21 at 21:38
-
refresh is wrong (very very very near to) always, certainly here - configure the items with an extractor instead – kleopatra May 11 '21 at 07:58
-
@kleopatra I was wondering what took you so long to come here and say that. But does that one small detail really warrant marking this entire answer as **bad**? – Zephyr May 11 '21 at 12:48
-
I have, however, updated the answer to include the extractor instead of `refresh()`, though that is entirely unrelated to the original question. :) – Zephyr May 11 '21 at 13:11
-
I have a life outside of SO
And yes, it's even worse: there is nothing as bad as proliferating errors in otherwise good answers - the less experienced reader will see it in a good context, think can't-be-wrong and use it forever thereafter ;). The original question is .. severely lacking (just gimme-the-codez) – kleopatra May 11 '21 at 14:03 -
code is okay now (arguably, the extractor doesn't belong into the data class - different views will have different needs, some only interested in a subrange of data properties or others - like tableView - none at all). Also you still mention refresh in the description. Nitpick: having two Person classes might confuse the casual reader :) – kleopatra May 11 '21 at 14:14