在JavaFx中自动调整分配为图形的形状到单元格

时间:2015-01-31 21:38:46

标签: java javafx javafx-8

在工作中,我创建了一个TableView,它需要让特定的单元格同时从一种颜色闪烁到另一种颜色。使用Rectangles,FillTransitions和ParallelTransition相对容易,如下面的玩具示例所示。将矩形分配给FillTransition后,我将TableCell的图形设置为矩形。然后我只需要在ParallelTransition中添加/删除FillTransition,具体取决于单元格是否应该闪烁。

然而,我遇到很多困难的一个方面是找出一种方法来将矩形缩放到包含它作为图形的TableCell的大小。我遇到的问题是TableCell总是调整自身大小,使其边界与矩形边界之间有空白。

我必须以一种非常乏味和圆润的方式解决这个问题:我必须调用setFixedCellSize来将表格的单元格高度固定到我的矩形的高度,将矩形重新定位到通过调用setTranslateX / Y进行试验和错误,并将列的minwidth和minheight设置为略小于我的矩形的宽度和高度设置为的值。它解决了这个问题,但我希望有点不那么乏味和讨厌。

我原以为可以通过对单元格执行以下一项或多项操作来避免这种情况:

  • 调用setScaleShape(true)

  • 调用setContentDisplay(ContentDisplay.GRAPHIC_ONLY)

  • 将单元格的CSS样式设置为包含" -fx-scale-shape:true"

可悲的是,这些都没有任何明显的效果......

我的问题是三个人:

  1. 是否有更好的方法来确定指定为单元格的形状的大小,以填充单元格的边界?

  2. 为什么上述三种方法都不会影响我的情况,它们的实际目的是什么?他们只申请使用setShape()分配的形状而不是setGraphic()吗?

  3. 有没有合理的理由说明为什么JavaFx不支持设置除子类别之外的节点的首选宽度或高度?自动调整似乎应该对层次结构中的所有节点都是通用的,并且看起来很直观,任何父节点都应该能够在必要时指定其子节点的大小。

  4. 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<PItem> 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(
            () -> itemAdder.addItem(),
            0, // initial delay
            1, // period
            TimeUnit.SECONDS); // time unit
    
        // set up a table view bound to the observable list
        final TableColumn<PItem, Priority> priCol = new TableColumn<>("Priority");
        priCol.setCellValueFactory(new PropertyValueFactory<PItem, Priority>("priority"));
        priCol.setCellFactory((col) -> new PriorityCell()); // create a blinking cell
        priCol.setMinWidth(50);
        priCol.setMaxWidth(50);
    
        final TableColumn<PItem, Integer> indexCol = new TableColumn<>("Index");
        indexCol.setCellValueFactory(new PropertyValueFactory<PItem, Integer>("index"));
        indexCol.setCellFactory((col) -> makeBorderedTextCell());
    
        final TableColumn<PItem, String> descriptionCol = new TableColumn<>("Description");
        descriptionCol.setCellValueFactory(new PropertyValueFactory<PItem, String>("description"));
        descriptionCol.setCellFactory((col) -> makeBorderedTextCell());
        descriptionCol.setMinWidth(300);
    
        final TableView<PItem> table = new TableView<>(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 <T> TableCell<PItem, T> makeBorderedTextCell() {
        return new TableCell<PItem, T>() {
          @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<PItem, Priority> {
        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<Priority> priority = new SimpleObjectProperty<>();
        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; }
      }
    }
    

    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<PItem> 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( () -> itemAdder.addItem(), 0, // initial delay 1, // period TimeUnit.SECONDS); // time unit // set up a table view bound to the observable list final TableColumn<PItem, Priority> priCol = new TableColumn<>("Priority"); priCol.setCellValueFactory(new PropertyValueFactory<PItem, Priority>("priority")); priCol.setCellFactory((col) -> new PriorityCell()); // create a blinking cell priCol.setMinWidth(50); priCol.setMaxWidth(50); final TableColumn<PItem, Integer> indexCol = new TableColumn<>("Index"); indexCol.setCellValueFactory(new PropertyValueFactory<PItem, Integer>("index")); indexCol.setCellFactory((col) -> makeBorderedTextCell()); final TableColumn<PItem, String> descriptionCol = new TableColumn<>("Description"); descriptionCol.setCellValueFactory(new PropertyValueFactory<PItem, String>("description")); descriptionCol.setCellFactory((col) -> makeBorderedTextCell()); descriptionCol.setMinWidth(300); final TableView<PItem> table = new TableView<>(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 <T> TableCell<PItem, T> makeBorderedTextCell() { return new TableCell<PItem, T>() { @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<PItem, Priority> { 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<Priority> priority = new SimpleObjectProperty<>(); 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; } } }

1 个答案:

答案 0 :(得分:3)

关于:

  

TableCell总是调整自身大小,使其边界与矩形边界之间有空白。

这是因为根据modena.css:

,默认情况下,单元格有2 px的填充
.table-cell {
    -fx-padding: 0.166667em; /* 2px, plus border adds 1px */
    -fx-cell-size: 2.0em; /* 24 */
}

摆脱这个空白区域的一个简单方法就是覆盖它:

@Override
protected void updateItem(Priority priority, boolean empty) {
    super.updateItem(priority, empty);
    ...
    setGraphic(rect);
    setStyle("-fx-padding: 0;");
    ...
}

您还提到的下一个问题是自动调整大小。根据JavaDoc,Node.isResizable()

  

如果此方法返回true,则父级将通过在布局传递期间调用node.resize(width,height)来调整节点的大小(理想情况下在其大小范围内)。所有区域,控件和WebView都是可调整大小的类,一旦应用了所有大小调整和CSS样式信息,它们依赖于父级在布局期间调整大小。   如果此方法返回false,则父级无法在布局期间调整大小(resize()为无操作),并且应返回其layoutBounds以获取最小,首选和最大大小。 Group,Text和所有Shapes都不可调整大小,因此依赖于应用程序通过设置适当的属性(例如Rectangle的宽度/高度,Text上的文本等)来确定其大小。在布局期间,仍可以重新定位不可调整大小的节点。

显然,Rectangle不可调整大小,但这并不意味着您无法调整大小:如果布局不适合您,则需要处理它。< / p>

因此,一个简单的解决方案可能是将矩形的尺寸与单元的尺寸相关联(单元格边框减去2个像素):

private final Rectangle rect = new Rectangle();

@Override
protected void updateItem(Priority priority, boolean empty) {
    super.updateItem(priority, empty);
    if(priority == null || empty) {
      setGraphic(null);
      return;
    }
    setGraphic(rect);
    setStyle("-fx-padding: 0;");
    rect.widthProperty().bind(widthProperty().subtract(2));
    rect.heightProperty().bind(heightProperty().subtract(2));
    ...
}

请注意,您不需要翻译矩形,也不需要修改单元格的大小和列的宽度,除非您想要给它一个固定的大小。

另请注意,setShape()旨在更改单元格形状,默认情况下已为矩形。

这可以回答你的前两个问题。对于第三个,有时你希望节点总是可以调整大小...但如果是这种情况我们会遇到相反的问题,试图让它们受到限制......