9 Replies Latest reply: Nov 22, 2011 7:08 AM by 869629 RSS

    Table cell flashing

    869629
      Hi,

      Before I start to hack away a solution that might not be fx-like, I'd like to get your expert opinion about implementing cell flashing for a table view.
      I have done this several times in Swing (using a timer, a customer renderer, switching the bg/fg colors 3 times, and firing up an even table cell update), but how do implement this feature in JavaFX?

      * The table cell renderer (override def call (...)) could be used
      * The bg/fg color switch can be done using different css styles
      * what would be the best way to implement the timer and ask the view to "repaint" a specific cell?

      Thx v much
        • 1. Re: Table cell flashing
          jsmith
          what would be the best way to implement the timer and ask the view to "repaint" a specific cell?
          Use a http://download.oracle.com/javafx/2.0/api/javafx/animation/Timeline.html.
          The bg/fg color switch can be done using different css styles
          Yes, you can change the styles in your Timeline's http://download.oracle.com/javafx/2.0/api/javafx/animation/KeyFrame.html.
          You will have two KeyFrames, one for the normal colour and one for the highlighted color and then cycle the Timeline to alternate between them.
          If you want a gradual flash, you can also adjust the brightness of the cell using effects such as ColorAdjust or Glow applied to the cell and vary some of the effect parameters in the Timeline's KeyFrame or KeyValue with an appropriate Interpolator. (This would probably look quite nice).
          The table cell renderer (override def call (...)) could be used
          This is probably the right approach . . . for GroovyFX ;-)
          I haven't worked much with cell renderers so somebody else can advise on this with more definite info if needed.
          • 2. Re: Table cell flashing
            875756
            Below is a bit of code that does something like what you want (the port to groovy should be easy). It may need some testing/tweaking for a real scenario and I'm not sure just how many animated cells you could have going at a time before performance becomes an issue (maybe someone from the JFX team could comment on this?).

            Also, regarding the use of styles for animating the change, you should be aware of this: Removing CSS style classes?

            Basically you need to make sure your 'default' style has values set for everything your 'highlight' style messes with, otherwise the highlight won't get turned off.
            import javafx.animation.FadeTransition;
            import javafx.animation.Timeline;
            import javafx.application.Application;
            import javafx.beans.value.ChangeListener;
            import javafx.beans.value.ObservableValue;
            import javafx.scene.Scene;
            import javafx.scene.control.Label;
            import javafx.scene.control.TableCell;
            import javafx.scene.control.TableColumn;
            import javafx.scene.control.TableView;
            import javafx.scene.control.cell.PropertyValueFactory;
            import javafx.scene.layout.BorderPane;
            import javafx.stage.Stage;
            import javafx.util.Callback;
            import javafx.util.Duration;
            
            public class TestApp extends Application
            {
                public static void main(String[] args)
                {
                    Application.launch(args);
                }
            
                @Override
                public void start(Stage stage) throws Exception
                {
                    BorderPane root = new BorderPane();
            
                    TableView<Person> table = new TableView<Person>();
                    table.getItems().addAll(
                            new Person("Cathy", "Freeman"),
                            new Person("Albert", "Namatjira"),
                            new Person("Noel", "Pearson"),
                            new Person("Oodgeroo", "Nooncal")
                    );
            
                    TableColumn<Person, String> firstNameCol = new TableColumn<Person, String>("First Name");
                    firstNameCol.setCellValueFactory(
                            new PropertyValueFactory<Person, String>("firstName")
                    );
                    table.getColumns().add(firstNameCol);
            
                    TableColumn<Person, String> lastNameCol = new TableColumn<Person, String>("Last Name");
                    lastNameCol.setCellValueFactory(
                            new PropertyValueFactory<Person, String>("lastName")
                    );
            
                    lastNameCol.setCellFactory(new Callback<TableColumn<Person, String>, TableCell<Person, String>>()
                    {
                        public TableCell<Person, String> call(TableColumn<Person, String> column)
                        {
                            final FlashingLabel label = new FlashingLabel();
                            label.setStyle("-fx-background-color: #ffaaaa");
                            TableCell<Person, String> cell = new TableCell<Person, String>()
                            {
                                protected void updateItem(String value, boolean empty)
                                {
                                    super.updateItem(value, empty);
                                    label.setText(value);
                                    label.setVisible(!empty);
                                }
                            };
                            cell.setGraphic(label);
                            return cell;
                        }
                    });
            
                    table.getColumns().add(lastNameCol);
            
                    root.setCenter(table);
            
                    Scene scene = new Scene(root, 800, 600);
                    scene.getStylesheets().add("styles.css");
                    stage.setScene(scene);
                    stage.show();
                }
            
                //-------------------------------------------------------------------------
            
                public class FlashingLabel extends Label
                {
                    private FadeTransition animation;
            
                    public FlashingLabel()
                    {
                        animation = new FadeTransition(Duration.millis(1000), this);
                        animation.setFromValue(1.0);
                        animation.setToValue(0);
                        animation.setCycleCount(Timeline.INDEFINITE);
                        animation.setAutoReverse(true);
                        animation.play();
            
                        visibleProperty().addListener(new ChangeListener<Boolean>()
                        {
                            public void changed(ObservableValue<? extends Boolean> source, Boolean oldValue, Boolean newValue)
                            {
                                if (newValue)
                                {
                                    animation.playFromStart();
                                }
                                else
                                {
                                    animation.stop();
                                }
                            }
                        });
                    }
                }
            
                //-------------------------------------------------------------------------
            
                public class Person
                {
                    private String firstName;
                    private String lastName;
            
                    private Person(String firstName, String lastName)
                    {
                        this.firstName = firstName;
                        this.lastName = lastName;
                    }
            
                    public String getFirstName()
                    {
                        return firstName;
                    }
            
                    public String getLastName()
                    {
                        return lastName;
                    }
                }
            }
            • 3. Re: Table cell flashing
              869629
              Great! I'll give this a try. Thx
              • 4. Re: Table cell flashing
                869629
                Thx for this code, this is the Scala equivalent that works too well.. see below :-)
                sealed class FlashingLabel extends Label {
                  import javafx.animation.FadeTransition
                  import javafx.animation.Animation
                  import javafx.util.Duration
                 
                  implicit def changeWrapper(f:(ObservableValue[_ <: java.lang.Boolean], java.lang.Boolean, java.lang.Boolean) => Unit) = 
                    new ChangeListener[java.lang.Boolean] {
                      override def changed(obv : ObservableValue[_ <: java.lang.Boolean], ov: java.lang.Boolean, nv: java.lang.Boolean) = f(obv, ov, nv)
                    }
                  
                  val animation = {
                    val anim = new FadeTransition(Duration.millis(1000), this)
                    anim.setFromValue(1.0)
                    anim.setToValue(0)
                    anim.setCycleCount(Animation.INDEFINITE)
                    anim.setAutoReverse(true)
                    anim.play    
                    anim
                  }
                  visibleProperty.addListener((obv : ObservableValue[_ <: java.lang.Boolean], ov: java.lang.Boolean, nv: java.lang.Boolean) => {
                    if (nv) animation playFromStart else animation stop
                  })
                }
                and the cell:
                  val colRenderer = new Callback[TableColumn[BRow, String], TableCell[BRow, String]] {
                    override def call(col: TableColumn[BRow, String]) : TableCell[BRow, String] = {
                      val cell = new TableCell[BRow, String] {
                        val flashingLabel = new FlashingLabel
                        flashingLabel.setStyle("-fx-background-color: #ffaaaa; -fx-text-fill: #000000;");
                        setGraphic(flashingLabel)
                        override def updateItem(value: String, empty: Boolean) = {
                          super.updateItem(value, empty)
                          flashingLabel.setText(value)
                          flashingLabel.setVisible(!empty)
                        } // updateItem
                      } // TableCell
                      cell
                    }
                  }
                Question: how did you manage to keep the foreground color to black? Because the label fades everything, including the text?
                • 5. Re: Table cell flashing
                  869629
                  Hi,

                  I could not figure our to transform my Labal to only fade the bg and not the text (fg).
                  So a solution was to create a custom node...
                  import javafx.scene.Group
                  import javafx.scene.shape.Rectangle
                  import javafx.animation.FadeTransition
                  import javafx.util.Duration
                  import javafx.scene.text.Text
                  sealed class CustomFlashRenderer(cell: TableCell[BRow, String])  extends Group {
                    implicit def changeWrapper(f:(ObservableValue[_ <: java.lang.Boolean], java.lang.Boolean, java.lang.Boolean) => Unit) = 
                      new ChangeListener[java.lang.Boolean] {
                        override def changed(obv : ObservableValue[_ <: java.lang.Boolean], ov: java.lang.Boolean, nv: java.lang.Boolean) = f(obv, ov, nv)
                      }
                    
                    val rect = new Rectangle
                    rect.widthProperty().bind(cell.widthProperty())
                    rect.heightProperty().bind(cell.heightProperty())
                    rect.setFill(Color.WHITE)
                    val ft = new FadeTransition(Duration.millis(500), rect);
                    ft.setFromValue(1.0);
                    ft.setToValue(0.0);
                    ft.setAutoReverse(true)
                    ft.play()
                    val t = new Text
                    t.setText("... JavaFX ...")
                    t.setX(10)
                    t.setY(10)
                    getChildren().addAll(rect, t)
                    
                    def setText(value: String) = {
                      val same = t.getText.equals(value)
                      t.setText(value)
                      if (!same) {
                        rect.setFill(Color.RED)
                        ft playFromStart  
                      } else {
                        ft.stop
                        rect.setFill(Color.WHITE)
                      }
                    }
                  }
                  Not sure if this is the nicest way though....
                  • 6. Re: Table cell flashing
                    875756
                    A couple of options that might be easier for you.

                    *1) Use a StackPane to create a foreground and background layer, then fade only the background:*
                    public void start(Stage stage) throws Exception
                    {
                        FlowPane contentPane = new FlowPane();
                    
                        StackPane stack = new StackPane();
                        BorderPane background = new BorderPane();
                        background.setStyle("-fx-background-color: yellow");
                        stack.getChildren().add(background);
                        Label foreground = new Label("But thy eternal summer shall not fade");
                        stack.getChildren().add(foreground);
                        contentPane.getChildren().add(stack);
                    
                        FadeTransition animation = new FadeTransition(Duration.millis(2000), background);
                        animation.setFromValue(1.0);
                        animation.setToValue(0);
                        animation.setCycleCount(Timeline.INDEFINITE);
                        animation.setAutoReverse(true);
                        animation.play();
                    
                        Scene scene = new Scene(contentPane, 800, 600);
                        scene.getStylesheets().add("styles.css");
                        stage.setScene(scene);
                        stage.show();
                    }
                    *2) Use styles and a Timeline animation*

                    In a perfect world, this would have been clean and easy and the right way to go, but styles and style sheets are a mucky-business when working with them in code (my one true disgruntle towards JFX2 is that CSS is used as the in-code language for styling: http://javafx-jira.kenai.com/browse/RT-17293):
                    public void start(Stage stage) throws Exception
                    {
                        FlowPane contentPane = new FlowPane();
                    
                        final Label label = new Label("But thy eternal summer shall not fade");
                        contentPane.getChildren().add(label);
                    
                        DoubleProperty opacity = new SimpleDoubleProperty();
                        opacity.addListener(new ChangeListener<Number>()
                        {
                            public void changed(ObservableValue<? extends Number> source, Number oldValue, Number newValue)
                            {
                                label.setStyle(String.format("-fx-background-color: rgba(255, 255, 0, %f)", newValue.doubleValue()));
                            }
                        });
                        Timeline animation = new Timeline(
                                new KeyFrame(Duration.ZERO, new KeyValue(opacity, 1)),
                                new KeyFrame(Duration.millis(2000), new KeyValue(opacity, 0))
                        );
                        animation.setCycleCount(Timeline.INDEFINITE);
                        animation.setAutoReverse(true);
                        animation.play();
                    
                        Scene scene = new Scene(contentPane, 800, 600);
                        scene.getStylesheets().add("styles.css");
                        stage.setScene(scene);
                        stage.show();
                    }
                    Another alternative is if you are genuinely wanting to use a shape, you can use [url http://download.oracle.com/javafx/2.0/api/index.html]Fill Transition but I'm guessing that was just a work around for you.

                    I've gone for the fade-in, fade-out effect in both cases, but if you want true blinking (on/off), you can use a custom interpolator (or a number of other options).

                    Hope that helps.
                    zonski
                    • 7. Re: Table cell flashing
                      jsmith
                      Hi zonski and TSJ,

                      I was also thinking of something along the lines of (2) - the css style property manipulation is kind of the way that you do this kind of thing with jquery in the html+css world. I'm wondering what your thoughts are about the solution and if stuff like the -fx-background-* and -fx-border-* values should be exposed via Java APIs instead of or in addition to the css property values. Perhaps with a Java interface, this stuff would be less opaque to users, though the HTML guys seem to have got by with using css like this for a long time.

                      - John
                      • 8. Re: Table cell flashing
                        875756
                        Hi John,

                        I totally agree, something is needed in the code to make it more developer friendly.

                        Before I offend anyone from the JFX team though, let me first say that I think that styling is an absolutely critical/awesome aspect of JFX, and that the implementation provided is incredible in its depth and power (a whole lot of very clever coding has gone into this), and furthermore I don't disagree with the 'marketing' rationale behind using CSS. The fundamental problem is that with all the focus on the designers, the coders were overlooked and the CSS syntax/approach has ended up as the API we have to use from within our Java code. That's a bit like telling a builder he's going to have to use a paintbrush to lay his cement because that's what the interior decorators use. It's possible, but it's not much fun.

                        I would very much have preferred the core model to be in Java (as outlined loosely here CSS - why not Java? and the CSS to be just a syntax used for loading the styles (similar to FXML) but I suspect we would have a hard job convincing the JFX guys of such a big change from here, unless we can come up with a way to do it without breaking all the existing code being built out there right now (I'm thinking about it, but need to understand more of what is there first - bring on open source).

                        Putting a facade over it is perhaps our next best option, and it's been on the lower end of my todo list to look at for use in my own code. As such I only given it passing thoughts so far, but I have some suspicions about where it is going to be hard and messy. I'd be interested in kicking some ideas around on this if you want to.

                        One of the big problems to overcome is the fact that the CSS engine seems to do a lookup on the stylesheet at rendering time. So if the Node is not in a scene it doesn't even have a style, and I don't know if we can easily mess with the styles directly without the CSS engine overwritting us all the time. There's also some caching going on that we'd need to understand, particularly in relation to stuff like this: Removing CSS style classes?

                        This thread is starting to lose its original context however, maybe we should continue this conversation on this earlier thread: CSS and Java Objects (or the earlier one linked to above) or you can start a new one if you prefer. We really need to setup a mailing list or something for these more design-type, wouldn't-it-be-great-if topics.
                        • 9. Re: Table cell flashing
                          869629
                          John/Zonski,

                          Thank you both.
                          And I agree, design-wise, I'd rather have an API.
                          Thx for your help, on my way to try the two suggested solutions