Changing JavaFX table row styling using property binding

In my current project, we had the problem that JavaFX rows in TableView had to update their style based on a property. That property would not be visible for the user in any column. In our case, the client application would receive updates from the server and change row statuses to active/inactive in TableView.

There are many guides on how to create custom row factory that updates row styles, like rterp’s excellent example. When using visible properties this is pretty straightforward since updateItem() -method is always called when the property changes.

My first solution was to create columns that have their visibility set to false. Unfortunately, JavaFX does not call updateItem() -method if the column is not visible. Next, I created a column with zero width, but that left a narrow empty space. (Though you could probably get rid of it by messing around with CSS.)

One often suggested solution is to force the whole table to refresh by changing column visibility on and off. That solution did not play well with the FlashingTableCell which I created earlier, and it is also a bit sub-optimal since it calls updateItem() -method on every row/cell.

((TableColumn) getTableView().getColumns().get(0)).setVisible(false);
((TableColumn) getTableView().getColumns().get(0)).setVisible(true);

Eventually, I ended up making a binding between row- and item-level properties and updating the binding every time the item changed.

import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ObservableValue;
import javafx.scene.control.TableRow;

/**
 * Item change makes binding between table item and table row, to force
 * UpdateItem call when active property changes and We don't
 * have to force the update it's refresh with visibility true/false
 * trick. ex.
 *
 *  ((TableColumn) getTableView().getColumns().get(0)).setVisible(false);
 *  ((TableColumn) getTableView().getColumns().get(0)).setVisible(true);
 * 
 * Binding created is a weak reference so garbage collection should work properly.
 * 
 * @author jaakkju
 * @param 
 */
public class ToggleTableRow<T extends AbstractToggleTableItem> extends TableRow<T> {
    
    private final SimpleBooleanProperty active = new SimpleBooleanProperty();
    
    /* Current Item which is bound to this table row */
    private T currentItem = null;
    
    public ToggleTableRow() {
        super();

        active.addListener((ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) -> {
            
            /* If item is the same we know that the update came 
                from actual property change and not from the row reuse */
            
            if (currentItem != null && currentItem == getItem()) {
                updateItem(getItem(), isEmpty());
            }
        });

        /*
            JavaFX reuses rows in the same way as it reuses table cells, 
            item behind the row changes ex. if row if scrolled so that it is not visible.
         */
        itemProperty().addListener((ObservableValue<? extends T> observable, T oldValue, T newValue) -> {
            
            /* When the item changes, we unbind the
                old property and start listening to the new */
            
            active.unbind();
            
            if (newValue != null) {
                active.bind(newValue.activeProperty());
                
                /* We change current item only after
                    binding since since it trickers change in the properties */
                currentItem = newValue;
            }
        });
    }

    @Override
    final protected void updateItem(T item, boolean empty) {
        super.updateItem(item, empty);
        
        /* Setting disabled sets row's pseudoclass to disabled, we can use that
           value to assign inactive styling to the row. */
        
        setDisable(item != null && !item.isActive());
        setEditable(item != null && !item.isActive());
    }
}

Table items in the solution extend this simple abstract class with active boolean property.

import javafx.beans.property.SimpleBooleanProperty;
 
/**
 * Simple abstract class that has active boolean property
 * 
 * @author jaakkju
 */
abstract public class AbstractToggleTableItem {
 
    private final SimpleBooleanProperty active = new SimpleBooleanProperty(true);
 
    public AbstractToggleTableItem(boolean active) {
        this.active.set(active);
    }
 
    public SimpleBooleanProperty activeProperty() {
        return active;
    }
 
    public void setActive(boolean active) {
        this.active.set(active);
    }
 
    public boolean isActive() {
        return active.get();
    }
}

CSS styling using pseudoclasses.

.table-row-cell:disabled {
    -fx-background-color: #c1c1c1;
    -fx-control-inner-background: #c1c1c1;
}

 

Complete example with basic table implementation is available here.

Leave a Reply

Your email address will not be published. Required fields are marked *