На работе я создал TableView, в котором определенные ячейки должны одновременно мигать одним цветом и другим. Это относительно легко сделать, используя Rectangles, FillTransitions и ParallelTransition, как показано в игрушечном примере ниже. После назначения прямоугольника FillTransition я устанавливаю графику TableCell в прямоугольник. Затем мне просто нужно было добавить/удалить FillTransition из ParallelTransition в зависимости от того, должна ли ячейка мигать.
Однако область, в которой у меня были большие трудности, заключалась в том, чтобы найти способ масштабировать прямоугольник до размера TableCell, содержащего его как графику. Проблема заключалась в том, что TableCell всегда изменял свой размер, чтобы между его границами и границами прямоугольника оставалось пустое пространство.
Мне пришлось решить это очень утомительным и окольным путем: мне пришлось вызвать setFixedCellSize, чтобы зафиксировать высоту ячейки таблицы до любой высоты моего прямоугольника, переместить прямоугольник вверх и влево методом проб и ошибок, вызвав его setTranslateX/ Y и установите минимальную ширину и минимальную высоту столбца немного меньше, чем ширина и высота моего прямоугольника. Это решило проблему, но я бы надеялся на что-то менее утомительное и раздражающее.
Я бы предположил, что этого можно избежать, выполнив одно или несколько из следующих действий с ячейкой:
Вызов setScaleShape(true)
Вызов setContentDisplay(ContentDisplay.GRAPHIC_ONLY)
Настройка стиля CSS ячейки для включения «-fx-scale-shape: true»
К сожалению, ни один из них не имел никакого заметного эффекта...
Мой вопрос состоит из трех частей:
Есть ли лучший способ определить размер формы, назначенной в качестве изображения для ячейки, чтобы заполнить границы ячейки?
Почему ни один из трех вышеперечисленных методов не имеет никакого эффекта в моем случае и какова их фактическая цель? Применяются ли они только для формы, назначенной с помощью setShape(), а не setGraphic()?
Существуют ли какие-либо законные причины, по которым JavaFx не поддерживает установку предпочтительной ширины или высоты узлов, кроме тех, которые являются подклассами Region? Авторазмер кажется чем-то, что должно быть универсальным для всех узлов в иерархии, и кажется интуитивно понятным, что любой родительский узел должен иметь возможность при необходимости диктовать размер своих дочерних узлов.
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; }
}
}