Reputation: 415
In my JavaFX app I use ListView with custom cells. I want to handle list item clicks differently than clicks on the empty space below the items. I've set an event listener on whole ListView, but I can't determine which of the items was clicked (getSelectedItem()
is null
, probably because of bug in my custom cell code). How to handle following situation properly?
My component looks like this:
<fx:root type="javafx.scene.layout.VBox">
<Label fx:id="dayname" text="${controller.day.name}" />
<ListView fx:id="appointmentsListView" items="${controller.day.events}"
onMouseClicked="#handleAppointmentsClick" />
</fx:root>
ListView has custom cell factory which is set in component constructor:
public class DayComponent extends VBox {
@FXML
private ListView<Appointment> appointmentsListView;
public DayComponent() throws IOException {
// ...
appointmentsListView.setCellFactory(l -> new AppointmentCell());
}
@FXML
public void handleAppointmentsClick(MouseEvent event) {
System.out.println(appointmentsListView.getSelectionModel()
.getSelectedItem()); // null in every case
}
}
Custom cell code:
public class AppointmentCell extends ListCell<Appointment> {
@Override
protected void updateItem(Appointment item, boolean empty) {
super.updateItem(item, empty);
if (!empty) {
setGraphic(new AppointmentLabel(item));
} else {
setGraphic(null);
}
}
}
Upvotes: 1
Views: 4656
Reputation: 209418
One reasonably clean approach is to register a mouse listener with the cells in the list view, to handle clicks on non-empty cells. Consume the mouse event if the cell is non-empty. Register a second mouse listener on the list view itself, to handle clicks on empty cells (or on the list view's placeholder if the list view is empty). This needs two handlers, but at least separates the "empty" and "non-empty" functionality in a sensible way.
Here is a quick example:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonBar;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class ListViewMouseHandlerExample extends Application {
private int count = 0 ;
@Override
public void start(Stage primaryStage) {
ListView<String> listView = new ListView<>();
Button addButton = new Button("Add");
addButton.setOnAction(e -> listView.getItems().add("Item " + (++count)));
Button deleteButton = new Button("Delete");
deleteButton.setOnAction(e -> listView.getItems().remove(listView.getSelectionModel().getSelectedIndex()));
deleteButton.disableProperty().bind(listView.getSelectionModel().selectedItemProperty().isNull());
listView.setCellFactory(lv -> {
ListCell<String> cell = new ListCell<String>() {
@Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
setText(item);
}
};
cell.setOnMouseClicked(e -> {
if (!cell.isEmpty()) {
System.out.println("You clicked on " + cell.getItem());
e.consume();
}
});
return cell;
});
listView.setOnMouseClicked(e -> {
System.out.println("You clicked on an empty cell");
});
BorderPane root = new BorderPane(listView);
ButtonBar buttons = new ButtonBar();
buttons.getButtons().addAll(addButton, deleteButton);
root.setBottom(buttons);
Scene scene = new Scene(root, 600, 600);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Upvotes: 6
Reputation: 2086
You could try something like that:
public class AppointmentCellMouseClickHandler implements EventHandler<MouseEvent> {
private final Appointment appointment;
public AppointmentCellMouseClickHandler(Appointment appointment) {
this.appointment = appointment;
}
@Override
public void handle(MouseEvent arg0) {
//Do stuff with appointment
}
}
public class EmptyAppointmentCellMouseClickHandler implements EventHandler<MouseEvent> {
@Override
public void handle(MouseEvent arg0) {
//Do stuff without appointment
}
}
public class AppointmentCell extends ListCell<Appointment> {
@Override
protected void updateItem(Appointment item, boolean empty) {
super.updateItem(item, empty);
if (!empty) {
setGraphic(new AppointmentLabel(item));
this.setOnMouseClicked(new AppointmentCellMouseClickHandler(item));
} else {
setGraphic(null);
this.setOnMouseClicked(new EmptyAppointmentCellMouseClickHandler());
}
}
}
Edit related to comment:
To handle when the ListView is empty you can do:
//To handle the initialisation where the listener won't be call
appointmentsListView.setOnMouseClicked(new EmptyAppointmentCellMouseClickHandler());
appointmentsListView.getItems().addListener(new ListChangeListener<Appointment>() {
@Override
public void onChanged(Change<? extends Appointment> change) {
while(change.next()){
//Toogle the listener depending on the content of the list
listView.setOnMouseClicked(change.getList().isEmpty() ? new EmptyAppointmentCellMouseClickHandler() : null);
}
}
});
Upvotes: 0