Reputation: 39
I have searched online for the past few hours and have tried to implement this feature with no luck.
I have a TableView with various columns, one of which is a String field which holds a time. I want to highlight all the rows that have a timeCol
value that is later than the current time.
This will have to be checked every 5 minutes and highlight the table accordingly. I am assuming this will need to run on a background thread but I'm in-experienced with concurrency.
TableView Content
@FXML
private TableView<BookingImpl> bookingTableView;
@FXML
private TableColumn<BookingImpl, String> timeCol;
@FXML
private TableColumn<BookingImpl, String> nameCol;
@FXML
private TableColumn<BookingImpl, String> pickUpCol;
@FXML
private TableColumn<BookingImpl, String> dropOffCol;
@FXML
private TableColumn<BookingImpl, String> commentCol;
@FXML
private TableColumn<BookingImpl, String> priceCol;
I have seen this post JavaFX tableview colors which looks like what I want to achieve, The highlight colour needs to be palevioletred.
Any guidance will be greatly appreciated.
UPDATE - James_D Solution
I have adjusted this to fit my application, however it only highlights the late bookings on input, i.e. if I enter a booking 1 minute from now and wait it will not highlight it. I have disabled the FXML implementation of columns but would prefer to use it.
I am referencing this new class from my main controller class as follows;
TableRowController rowController = new TableRowController();
rowController.start(bookingTableView);
TableRowController
package Controllers;
import Model.BookingImpl;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ChangeListener;
import javafx.css.PseudoClass;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableRow;
import javafx.scene.control.TableView;
import java.time.LocalTime;
import java.util.function.Function;
public class TableRowController{
private final PseudoClass future = PseudoClass.getPseudoClass("future");
public void start(TableView table){
ObjectProperty<LocalTime> now = new SimpleObjectProperty<>(LocalTime.now());
table.setRowFactory(tv -> {
TableRow<BookingImpl> row = new TableRow<>();
ChangeListener<LocalTime> listener = (obs, oldTime, newTime) -> updateRow(row, now.get());
now.addListener(listener);
row.itemProperty().addListener((obs, oldItem, newItem) -> {
if (oldItem != null) {
oldItem.getTimeProperty().removeListener(listener);
}
if (newItem != null) {
newItem.getTimeProperty().addListener(listener);
}
updateRow(row, now.get());
});
return row ;
});
configureTable(table);
}
public void updateRow(TableRow<BookingImpl> row, LocalTime now) {
boolean isFuture = false ;
if (row.getItem() != null) {
isFuture = row.getItem().getTime().isBefore(now);
}
row.pseudoClassStateChanged(future, isFuture);
}
private void configureTable(TableView<BookingImpl> table) {
table.getColumns().add(column("Time", (Function<BookingImpl, Property<LocalTime>>) (t) -> t.getTimeProperty()));
table.getColumns().add(column("Name", (Function<BookingImpl, Property<String>>) (t) -> new SimpleStringProperty(t.getClientName())));
table.getColumns().add(column("Pickup", (Function<BookingImpl, Property<String>>) (t) -> new SimpleStringProperty(t.getPickUpAddress())));
table.getColumns().add(column("Dropoff", (Function<BookingImpl, Property<String>>) (t) -> new SimpleStringProperty(t.getDropOffAddress())));
table.getColumns().add(column("Comment", (Function<BookingImpl, Property<String>>) (t) -> new SimpleStringProperty(t.getComments())));
table.getColumns().add(column("Price", (Function<BookingImpl, Property<String>>) (t) -> new SimpleStringProperty(t.getFormattedPrice())));
table.setItems(ObservableLists.bookingsList);
}
private <S,T> TableColumn<S,T> column(String title, Function<S, Property<T>> property) {
TableColumn<S,T> column = new TableColumn<>(title);
column.setCellValueFactory(cellData -> property.apply(cellData.getValue()));
return column ;
}
}
BookingImpl Class
public class BookingImpl implements Booking {
public static int bookingNumberCounter = 1001;
private final int bookingNumber;
private Account account;
private String vehicleType;
private String noPassengers;
private LocalDate date;
private ObjectProperty<LocalTime> time = new SimpleObjectProperty<>();
private String pickUpAddress;
private String dropOffAddress;
private String clientName;
private String clientTel;
private String clientEmail;
private String comments;
private double price;
private boolean completed = false;
private Driver driver;
public BookingImpl(Account account){
this.bookingNumber = bookingNumberCounter;
bookingNumberCounter++;
Archive.allBookings.add(this);
Archive.incompleteBookings.add(this);
ObservableLists.bookingsList.clear();
ObservableLists.bookingsList.addAll(Archive.incompleteBookings);
account.newBooking(this);
}
@Override
public void deleteBooking() {
account.deleteBooking(this);
Archive.allBookings.remove(this);
}
public int getBookingNumber() {
return bookingNumber;
}
public Account getAccount() {
return account;
}
public void setAccount(Account account) {
this.account = account;
}
public String getVehicleType() {
return vehicleType;
}
public void setVehicleType(String vehicleType) {
this.vehicleType = vehicleType;
}
public String getNoPassengers() {
return noPassengers;
}
public void setNoPassengers(String noPassengers) {
this.noPassengers = noPassengers;
}
public String getDate() {
return new SimpleDateFormat("dd/MM/yyyy").format(date);
}
public void setDate(LocalDate date) {
this.date = date;
}
public ObjectProperty<LocalTime> getTimeProperty() {
return time;
}
public LocalTime getTime() {
return this.getTimeProperty().get();
}
public void setTime(LocalTime time) {this.time.set(time);}
public String getPickUpAddress() {
return pickUpAddress;
}
public void setPickUpAddress(String pickUpAddress) {
this.pickUpAddress = pickUpAddress;
}
public String getDropOffAddress() {
return dropOffAddress;
}
public void setDropOffAddress(String dropOffAddress) {
this.dropOffAddress = dropOffAddress;
}
public String getClientName() {
return clientName;
}
public void setClientName(String clientName) {
this.clientName = clientName;
}
public String getClientTel() {
return clientTel;
}
public void setClientTel(String clientTel) {
this.clientTel = clientTel;
}
public String getClientEmail() {
return clientEmail;
}
public void setClientEmail(String clientEmail) {
this.clientEmail = clientEmail;
}
public String getComments() {
return comments;
}
public void setComments(String comments) {
this.comments = comments;
}
public double getPrice() {
return price;
}
public String getFormattedPrice() {
DecimalFormat df = new DecimalFormat("#.00");
return "£"+df.format(this.price);
}
public void setPrice(double price) {
this.price = price;
}
public boolean isCompleted() {
return completed;
}
public void setCompleted(boolean completed) {
this.completed = completed;
}
public Driver getDriver() {
return driver;
}
public void setDriver(Driver driver) {
this.driver = driver;
}
}
Upvotes: 1
Views: 1580
Reputation: 209494
I would approach this as follows:
Use a rowFactory
on the table. The row you create will observe the current item's time property, and update a CSS PseudoClass if it changes. You also need the row to observe the current time: to do this create a ObjectProperty<LocalTime>
and update it from an AnimationTimer
.
The advantage of using a CSS PseudoClass is that you can define the style in an external CSS file.
Here is an example:
import java.time.LocalTime;
import java.util.Random;
import java.util.function.Function;
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ChangeListener;
import javafx.css.PseudoClass;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableRow;
import javafx.scene.control.TableView;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class HighlightFutureItems extends Application {
private final PseudoClass future = PseudoClass.getPseudoClass("future");
@Override
public void start(Stage primaryStage) {
TableView<Item> table = new TableView<>();
ObjectProperty<LocalTime> now = new SimpleObjectProperty<>(LocalTime.now());
startClock(now);
table.setRowFactory(tv -> {
TableRow<Item> row = new TableRow<>();
ChangeListener<LocalTime> listener = (obs, oldTime, newTime) -> updateRow(row, now.get());
now.addListener(listener);
row.itemProperty().addListener((obs, oldItem, newItem) -> {
if (oldItem != null) {
oldItem.timeProperty().removeListener(listener);
}
if (newItem != null) {
newItem.timeProperty().addListener(listener);
}
updateRow(row, now.get());
});
return row ;
});
configureTable(table);
BorderPane root = new BorderPane(table);
Label clock = new Label();
root.setTop(clock);
clock.textProperty().bind(now.asString());
Scene scene = new Scene(root, 600, 600);
scene.getStylesheets().add("future-highlighting-table.css");
primaryStage.setScene(scene);
primaryStage.show();
}
private void updateRow(TableRow<Item> row, LocalTime now) {
boolean isFuture = false ;
if (row.getItem() != null) {
isFuture = row.getItem().getTime().isAfter(now);
}
row.pseudoClassStateChanged(future, isFuture);
}
private void startClock(ObjectProperty<LocalTime> clock) {
new AnimationTimer() {
@Override
public void handle(long timestamp) {
clock.set(LocalTime.now());
}
}.start();
}
private void configureTable(TableView<Item> table) {
table.getColumns().add(column("Item", Item::nameProperty));
table.getColumns().add(column("Time", Item::timeProperty));
Random rng = new Random();
LocalTime now = LocalTime.now();
for (int i = 1 ; i <= 50 ; i++) {
Item item = new Item("Item "+i, now.plusSeconds(rng.nextInt(120) - 60));
table.getItems().add(item);
}
}
private <S,T> TableColumn<S,T> column(String title, Function<S, Property<T>> property) {
TableColumn<S,T> column = new TableColumn<>(title);
column.setCellValueFactory(cellData -> property.apply(cellData.getValue()));
return column ;
}
public static class Item {
private final StringProperty name = new SimpleStringProperty();
private final ObjectProperty<LocalTime> time = new SimpleObjectProperty<>();
public Item(String name, LocalTime time) {
setName(name);
setTime(time);
}
public final StringProperty nameProperty() {
return this.name;
}
public final String getName() {
return this.nameProperty().get();
}
public final void setName(final String name) {
this.nameProperty().set(name);
}
public final ObjectProperty<LocalTime> timeProperty() {
return this.time;
}
public final LocalTime getTime() {
return this.timeProperty().get();
}
public final void setTime(final LocalTime time) {
this.timeProperty().set(time);
}
}
public static void main(String[] args) {
launch(args);
}
}
The CSS file (future-highlighting-table.css"
) can be as simple as
.table-row-cell:future {
-fx-background: palevioletred;
}
Upvotes: 2