Michael Pickett Michael Pickett - 2 months ago 12
CSS Question

Fade Transition via custom cell factory in javafx

I am using a custom cell factory in javafx to style cells based on an alert status, which is based upon the time now and a check time.

Everything works correctly, but I also want to add in a fade transition, or something else, that will make the rows essentially blink until it is acknowledged by changing of the checktime.

It is a little more complex than I think I could add a lot of code for, so I will post my cell factory class

public class FormattedTableCellFactory<S, T> implements Callback<TableColumn<S, T>, TableCell<S, T>> {
private TableCell<S, T> cell = null;
private FadeTransition ft;

public FormattedTableCellFactory() {
}

@Override
public TableCell<S, T> call(TableColumn<S, T> p) {
cell = new TableCell<S, T>() {
@Override
protected void updateItem(Object item, boolean empty) {
super.updateItem((T) item, empty);

// CSS Styles
String cssStyle;

Person person = null;

if(getTableRow() != null ) {
person = (Person) getTableRow().getItem();
}

ft = new FadeTransition(Duration.millis(500), cell);
ft.setFromValue(1.0);
ft.setToValue(0.1);
ft.setCycleCount(Timeline.INDEFINITE);
ft.setAutoReverse(true);
//Remove all previously assigned CSS styles from the cell.



//Determine how to format the cell based on the status of the container.

if(person != null){
switch(PersonAlert.getAlert(person)){
case WARNING:
ft.stop();
cssStyle = "warning";
break;
case PAST_DUE:
ft.stop();
cssStyle = "pastdue";
break;
case ERC_PAST_DUE:
ft.stop();
cssStyle = "ercpastdue";
break;
case OVERDUE:
ft.playFromStart();
cssStyle = "overdue";
break;
case OVERNIGHT:
ft.stop();
cssStyle = "overnight";
break;
default:
ft.stop();
cssStyle = "normal";
break;
}
}else{
ft.stop();
setText("");
return;
}

//Set the CSS style on the cell and set the cell's text.
getStyleClass().setAll(cssStyle);
if(item != null) {
setText(item.toString());
}else{
setText("");
}
}
};
return cell;
}
}


This code works correctly, whenever the person has a certain status, the colors of the cells will change appropriately. The only issue here is the fade transition, whenever it is first run, the fade transition runs fine, but when it changes to a different style, it does not stop and keeps blinking.

I can not get the fade transition to stop. I believe this is because each time this updateItem is called, it creates a new instance of my fade transition, thus when it calls stop, it is calling it on the current transition and not a previous one. I have tried to eliminate this by initializing the transition at the top, but then it won't even blink.

So my question is, how could I edit this to make fade transition stop when stop is called, OR is there a different way to make a cell/row "blink" using a cell factory?

Answer

You're scoping things at the wrong level.

Each cell should have one fade transition. Currently you create a new fade transition every time updateItem() is invoked on a cell. You keep a single reference for the entire factory (just one reference, no matter how many cells are created), which will only refer to the most recently created fade transition.

You similarly retain a reference in the factory to the most recently created cell, for some reason, though (probably thankfully) you never actually use it.

A couple of other things to note: it's not a good idea to call getStyleClass().setAll(...) as this will remove all the default style classes from the cell (such as .table-cell), which may have the effect of removing all the default style handling. Style classes are a bit of a pain to deal with, as they are implemented as lists, but you should remove all occurrences of the ones you don't want explicitly, and ensure that you don't add duplicates of ones you do want (unless you really intend to do that). Also, you're assuming (by casting) that the type for the table view is Person, so there's no point in making the row type generic.

So, while it's impossible for me to test as you didn't provide any code to test with, you probably want something along these lines:

import java.util.Arrays;
import java.util.List;

import javafx.animation.FadeTransition;
import javafx.animation.Timeline;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.util.Callback;
import javafx.util.Duration;

public class FormattedTableCellFactory<T> implements Callback<TableColumn<Person, T>, TableCell<Person, T>> {

    private static final List<String> ALL_STYLES = Arrays.asList(
        "warning", "pastdue", "ercpastdue", "overnight", "normal"
    );

    @Override
    public TableCell<Person, T> call(TableColumn<Person, T> p) {
        TableCell<Person, T> cell = new TableCell<Person, T>() {

            private FadeTransition ft ;

            {
                ft = new FadeTransition(Duration.millis(500), this);
                ft.setFromValue(1.0);   
                ft.setToValue(0.1);   
                ft.setCycleCount(Timeline.INDEFINITE);   
                ft.setAutoReverse(true);  
            }

            @Override
            protected void updateItem(T item, boolean empty) {
                super.updateItem(item, empty);

                // CSS Styles
                String cssStyle = "";

                Person person = null;

                if(! isEmpty() ) {
                    // avoid casting here, if you prefer:
                    person = getTableView().getItems().get(getIndex());
                }

                //Remove all previously assigned CSS styles from the cell.

                getStyleClass().removeAll(ALL_STYLES);


                //Determine how to format the cell based on the status of the container.

                if(person != null){
                    switch(PersonAlert.getAlert(person)){
                        case WARNING:
                            ft.stop();
                            setOpacity(1.0);
                            cssStyle = "warning";
                            break;
                        case PAST_DUE:
                            ft.stop();
                            setOpacity(1.0);
                            cssStyle = "pastdue";
                            break;
                        case ERC_PAST_DUE:
                            ft.stop();
                            setOpacity(1.0);
                            cssStyle = "ercpastdue";
                            break;
                        case OVERDUE:
                            ft.playFromStart();
                            cssStyle = "overdue";
                            break;
                        case OVERNIGHT:
                            ft.stop();
                            setOpacity(1.0);
                            cssStyle = "overnight";
                            break;
                        default:
                            ft.stop();
                            setOpacity(1.0);
                            cssStyle = "normal";
                            break;
                    }
                }else{
                    ft.stop();
                    setOpacity(1.0);
                    setText("");
                }

                //Set the CSS style on the cell and set the cell's text.
                if(item != null) {
                    getStyleClass().add(cssStyle);
                    setText(item.toString());
                }else{
                    setText("");
                }
            }
        };
        return cell;
    }


}