title: Auto-sizing a Shape Assigned as a Graphic to a Cell in JavaFx
body: <p>At work I created a TableView that needs to have specific cells flash from one color to the other simultaneously. This is relatively easy using Rectangles, FillTransitions, and a ParallelTransition as shown in the toy example below. After assigning the rectangle to the FillTransition, I set the TableCell's graphic to the rectangle. Then I just had to add/remove the FillTransition from the ParallelTransition depending on whether or not the cell should be blinking.</p>
  
  <p>An area where I had a lot of difficulty, however, was in figuring out a way to scale the rectangle to the size of the TableCell containing it as a graphic. The problem I had was that the TableCell would always resize itself to have empty space between its boundaries and the boundaries of the rectangle.</p>
  
  <p>I had to solve this in a very tedious and round-about way: I had to call setFixedCellSize to fix the table's cell height to whatever my rectangle's height was, reposition the rectangle up and to the left by trial and error through calling its setTranslateX/Y, and set the minwidth and minheight of the column to slightly less than whatever my rectangle's width and height was set to. It solved the problem, but I would've hoped for something a little less tedious and annoying.</p>
  
  <p>I would have assumed this could be avoided by doing one or more of the following with the cell: </p>
  
  <ul>
  <li><p>Calling setScaleShape(true) </p></li>
  <li><p>Calling setContentDisplay(ContentDisplay.GRAPHIC_ONLY)</p></li>
  <li><p>Setting the cell's CSS style to include "-fx-scale-shape: true"</p></li>
  </ul>
  
  <p>Sadly, none of these had any noticeable effect... </p>
  
  <p>My question is a three-parter:</p>
  
  <ol>
  <li><p>Is there a better way to size a Shape assigned as a graphic for a Cell to fill the boundaries of the Cell?</p></li>
  <li><p>Why would none of the three methods above have any effect in my case and what is their actual intended purpose? Do they only apply for a shape assigned with setShape() as opposed to setGraphic()?</p></li>
  <li><p>Are there any legitimate reasons why JavaFx wouldn't support setting the preferred width or height of Nodes other than those that subclass Region? Autosizing seems like something that should be universal to all Nodes in the hierarchy, and it seems intuitive that any Parent node should be able to dictate the size of its children when necessary.</p></li>
  </ol>
  
  <p><code></p>
  
  <pre><code>import java.util.concurrent.Executors;
  import java.util.concurrent.TimeUnit;
  import javafx.animation.Animation;
  import javafx.animation.FillTransition;
  import javafx.animation.ParallelTransition;
  import javafx.application.Application;
  import javafx.beans.property.IntegerProperty;
  import javafx.beans.property.ObjectProperty;
  import javafx.beans.property.SimpleIntegerProperty;
  import javafx.beans.property.SimpleObjectProperty;
  import javafx.beans.property.SimpleStringProperty;
  import javafx.beans.property.StringProperty;
  import javafx.collections.FXCollections;
  import javafx.collections.ObservableList;
  import javafx.scene.Scene;
  import javafx.scene.control.ContentDisplay;
  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.Border;
  import javafx.scene.layout.BorderStroke;
  import javafx.scene.layout.BorderStrokeStyle;
  import javafx.scene.paint.Color;
  import javafx.scene.shape.Rectangle;
  import javafx.stage.Stage;
  import javafx.util.Duration;
  
  
  public class FlashingPriorityTable extends Application {
  
    public static void main(String args[]) {
      FlashingPriorityTable.launch();
    }
  
    @Override
    public void start(Stage primaryStage) throws Exception {
  
      // periodically add prioritized items to an observable list
      final ObservableList&lt;PItem&gt; itemList = FXCollections.observableArrayList();
      class ItemAdder {
        private int state, count = 0; private final int states = 3;
        public synchronized void addItem() {
          state = count++ % states;
          PItem item;
          if(state == 0)
            item = new PItem(Priority.LOW, count, "bob saget");
          else if(state == 1)
            item = new PItem(Priority.MEDIUM, count, "use the force");
          else
            item = new PItem(Priority.HIGH, count, "one of us is in deep trouble");
          itemList.add(item);
        }
      };
      final ItemAdder itemAdder = new ItemAdder();
      Executors.newScheduledThreadPool(1).scheduleAtFixedRate(
          () -&gt; itemAdder.addItem(),
          0, // initial delay
          1, // period
          TimeUnit.SECONDS); // time unit
  
      // set up a table view bound to the observable list
      final TableColumn&lt;PItem, Priority&gt; priCol = new TableColumn&lt;&gt;("Priority");
      priCol.setCellValueFactory(new PropertyValueFactory&lt;PItem, Priority&gt;("priority"));
      priCol.setCellFactory((col) -&gt; new PriorityCell()); // create a blinking cell
      priCol.setMinWidth(50);
      priCol.setMaxWidth(50);
  
      final TableColumn&lt;PItem, Integer&gt; indexCol = new TableColumn&lt;&gt;("Index");
      indexCol.setCellValueFactory(new PropertyValueFactory&lt;PItem, Integer&gt;("index"));
      indexCol.setCellFactory((col) -&gt; makeBorderedTextCell());
  
      final TableColumn&lt;PItem, String&gt; descriptionCol = new TableColumn&lt;&gt;("Description");
      descriptionCol.setCellValueFactory(new PropertyValueFactory&lt;PItem, String&gt;("description"));
      descriptionCol.setCellFactory((col) -&gt; makeBorderedTextCell());
      descriptionCol.setMinWidth(300);
  
      final TableView&lt;PItem&gt; table = new TableView&lt;&gt;(itemList);
      table.getColumns().setAll(priCol, indexCol, descriptionCol);
      table.setFixedCellSize(25);
  
      // display the table view
      final Scene scene = new Scene(table);
      primaryStage.setScene(scene);
      primaryStage.show();
    }
  
    // render a simple cell text and border
    private &lt;T&gt; TableCell&lt;PItem, T&gt; makeBorderedTextCell() {
      return new TableCell&lt;PItem, T&gt;() {
        @Override protected void updateItem(T item, boolean empty) {
          super.updateItem(item, empty);
          if(item == null || empty) {
            setText(null);
          } else {
            setBorder(new Border(new BorderStroke(Color.GREEN, BorderStrokeStyle.SOLID, null, null)));
            setText(item.toString());
          }
        }
      };
    }
  
    /* for cells labeled as high priority, render an animation that blinks (also include a border) */
    public static class PriorityCell extends TableCell&lt;PItem, Priority&gt; {
      private static final ParallelTransition pt = new ParallelTransition();
      private final Rectangle rect = new Rectangle(49.5, 24);
      private final FillTransition animation = new FillTransition(Duration.millis(100), rect);
      public PriorityCell() {
        rect.setTranslateX(-2.75);
        rect.setTranslateY(-2.7);
        animation.setCycleCount(Animation.INDEFINITE); animation.setAutoReverse(true); }
      @Override
      protected void updateItem(Priority priority, boolean empty) {
        super.updateItem(priority, empty);
        if(priority == null || empty) {
          setGraphic(null);
          return;
        }
        setGraphic(rect);
        setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
        setBorder(new Border(new BorderStroke(Color.GREEN, BorderStrokeStyle.SOLID, null, null)));
        if(priority == Priority.HIGH) {
          if(!pt.getChildren().contains(animation)) {
            animation.setFromValue(Color.BLACK);
            animation.setToValue(priority.getColor());
            animation.setShape(rect);
            pt.getChildren().add(animation);
            pt.stop(); pt.play();
          }
        } else {
          if(pt.getChildren().contains(animation)) {
            pt.getChildren().remove(animation);
            pt.stop(); pt.play();
          }
          rect.setFill(priority.getColor());
        }
      }
    }
  
    /* an item that has a priority assigned to it */
    public static class PItem {
      private ObjectProperty&lt;Priority&gt; priority = new SimpleObjectProperty&lt;&gt;();
      private IntegerProperty index = new SimpleIntegerProperty();
      private StringProperty description = new SimpleStringProperty();
  
      public PItem(Priority priority, Integer index, String description) {
        setPriority(priority); setIndex(index); setDescription(description);
      }
  
      public void setPriority(Priority priority_) { priority.set(priority_); }
      public Priority getPriority() { return priority.get(); }
  
      public void setIndex(int index_) { index.set(index_); }
      public Integer getIndex() { return index.get(); }
  
      public void setDescription(String description_) { description.set(description_); }
      public String getDescription() { return description.get(); }
    }
  
    /* a priority */
    public enum Priority {
      HIGH(Color.RED), MEDIUM(Color.ORANGE), LOW(Color.BLUE);
      private final Color color;
      private Priority(Color color) { this.color = color; }
      public Color getColor() { return color; }
    }
  }
  </code></pre>
  
  <p></code></p>