链接到多个 属性 的 JavaFx tablecell
JavaFx tablecell linked to more than one property
您好,我是 JavaFX 的新手,在使用 tables 单元格时,我 运行 遇到一些更新显示数据的问题。我希望能够设置我的 table 单元格,以便它们可以监听多个值,而无需在更新项方法中初始化监听器。
例如,我有一辆公交车 class,它包含三个属性,一个字符串公交车 ID,一个字符串街道名称和一个移动布尔值。我目前在第 1 列中设置了公共汽车 ID,在第 2 列中设置了当前街道,并且希望能够进行设置,如果公共汽车在行驶,街道名称为绿色,如果停止,街道名称为红色。目前我已将第 2 列的 setCellValueFactory 设置为街道名称 属性 并在这些单元格的 updateItem 方法中它初始化了一个侦听器以更新颜色。虽然当前有效,但如果我向单元格添加更多侦听器则很难使用,我是否可以在 setCellValueFactory 方法或 table 列上的另一种此类方法中将单元格传递多个 属性 以具有单元格为多个事件调用 updateItem 方法。
给定标准 JavaFX 模型 class:
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
public class Bus {
private final StringProperty id = new SimpleStringProperty();
private final StringProperty streetName = new SimpleStringProperty();
private final BooleanProperty moving = new SimpleBooleanProperty();
public Bus(String id, String streetName, boolean moving) {
setId(id);
setStreetName(streetName);
setMoving(moving);
}
public final StringProperty idProperty() {
return this.id;
}
public final String getId() {
return this.idProperty().get();
}
public final void setId(final String id) {
this.idProperty().set(id);
}
public final StringProperty streetNameProperty() {
return this.streetName;
}
public final String getStreetName() {
return this.streetNameProperty().get();
}
public final void setStreetName(final String streetName) {
this.streetNameProperty().set(streetName);
}
public final BooleanProperty movingProperty() {
return this.moving;
}
public final boolean isMoving() {
return this.movingProperty().get();
}
public final void setMoving(final boolean moving) {
this.movingProperty().set(moving);
}
}
通常的方法就是您描述的方法。您也许可以通过将列的类型设置为 TableColumn<Bus, Bus>
并使用绑定而不是侦听器来稍微清理代码:
import java.util.Random;
import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleObjectProperty;
import javafx.scene.Scene;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import javafx.util.Duration;
public class App extends Application {
private final String[] streets = {
"Main Street",
"Sunset Boulevard",
"Electric Avenue",
"Winding Road"
};
private final Random rng = new Random();
@Override
public void start(Stage stage) {
TableView<Bus> table = new TableView<>();
TableColumn<Bus, String> idCol = new TableColumn<>("Id");
idCol.setCellValueFactory(cellData -> cellData.getValue().idProperty());
TableColumn<Bus, Bus> streetColumn = new TableColumn<>("Street");
streetColumn.setCellValueFactory(cellData -> new SimpleObjectProperty<>(cellData.getValue()));
streetColumn.setCellFactory(tc -> new TableCell<>() {
@Override
protected void updateItem(Bus bus, boolean empty) {
super.updateItem(bus, empty);
textProperty().unbind();
styleProperty().unbind();
if (empty || bus == null) {
setText("");
setStyle("");
} else {
textProperty().bind(bus.streetNameProperty());
styleProperty().bind(
Bindings.when(bus.movingProperty())
.then("-fx-text-fill: green;")
.otherwise("-fx-text-fill: red;")
);
}
}
});
table.getColumns().add(idCol);
table.getColumns().add(streetColumn);
// to check it works:
TableColumn<Bus, Boolean> movingColumn = new TableColumn<>("Moving");
movingColumn.setCellValueFactory(cellData -> cellData.getValue().movingProperty());
table.getColumns().add(movingColumn);
for (int busNumber = 1 ; busNumber <= 20 ; busNumber++) {
table.getItems().add(createBus("Bus Number "+busNumber));
}
Scene scene = new Scene(new BorderPane(table));
stage.setScene(scene);
stage.show();
}
// Create a Bus and a timeline
// that makes it start and stop and change streets at random:
private Bus createBus(String id) {
String street = streets[rng.nextInt(streets.length)];
Bus bus = new Bus(id, street, true);
Timeline timeline = new Timeline(
new KeyFrame(Duration.seconds(1 + rng.nextDouble()),
event -> {
double choose = rng.nextDouble();
if (bus.isMoving() && choose < 0.25) {
bus.setStreetName(streets[rng.nextInt(streets.length)]);
} else {
if (choose < 0.5) {
bus.setMoving(! bus.isMoving());
}
}
}
)
);
timeline.setCycleCount(Animation.INDEFINITE);
timeline.play();
return bus ;
}
public static void main(String[] args) {
launch();
}
}
另一种方法是定义一个不可变的 class(或 record
,如果您使用 Java 15 或更高版本)封装街道和公共汽车是否在移动。然后使用一个单元格值工厂,它 returns 一个绑定,它包装了 class 的一个实例并绑定到 streetNameProperty
和 movingProperty
。如果有任何更改,单元实现将收到通知,因此那里不需要侦听器或绑定:
import java.util.Random;
import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.scene.Scene;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import javafx.util.Duration;
public class App extends Application {
private final String[] streets = {
"Main Street",
"Sunset Boulevard",
"Electric Avenue",
"Winding Road"
};
private final Random rng = new Random();
@Override
public void start(Stage stage) {
TableView<Bus> table = new TableView<>();
TableColumn<Bus, String> idCol = new TableColumn<>("Id");
idCol.setCellValueFactory(cellData -> cellData.getValue().idProperty());
TableColumn<Bus, StreetMoving> streetColumn = new TableColumn<>("Street");
streetColumn.setCellValueFactory(cellData -> {
Bus bus = cellData.getValue();
return Bindings.createObjectBinding(
() -> new StreetMoving(bus.getStreetName(), bus.isMoving()),
bus.streetNameProperty(),
bus.movingProperty());
});
streetColumn.setCellFactory(tc -> new TableCell<>() {
@Override
protected void updateItem(StreetMoving street, boolean empty) {
super.updateItem(street, empty);
if (empty || street == null) {
setText("");
setStyle("");
} else {
setText(street.street());
String color = street.moving() ? "green" : "red" ;
setStyle("-fx-text-fill: " + color + ";");
}
}
});
table.getColumns().add(idCol);
table.getColumns().add(streetColumn);
// to check it works:
TableColumn<Bus, Boolean> movingColumn = new TableColumn<>("Moving");
movingColumn.setCellValueFactory(cellData -> cellData.getValue().movingProperty());
table.getColumns().add(movingColumn);
for (int busNumber = 1 ; busNumber <= 20 ; busNumber++) {
table.getItems().add(createBus("Bus Number "+busNumber));
}
Scene scene = new Scene(new BorderPane(table));
stage.setScene(scene);
stage.show();
}
private Bus createBus(String id) {
String street = streets[rng.nextInt(streets.length)];
Bus bus = new Bus(id, street, true);
Timeline timeline = new Timeline(
new KeyFrame(Duration.seconds(1 + rng.nextDouble()),
event -> {
double choose = rng.nextDouble();
if (bus.isMoving() && choose < 0.25) {
bus.setStreetName(streets[rng.nextInt(streets.length)]);
} else {
if (choose < 0.5) {
bus.setMoving(! bus.isMoving());
}
}
}
)
);
timeline.setCycleCount(Animation.INDEFINITE);
timeline.play();
return bus ;
}
public static record StreetMoving(String street, boolean moving) {};
public static void main(String[] args) {
launch();
}
}
从设计的角度来看,我通常更喜欢第二种方法。由于 streetColumn
在街道或移动属性发生变化时会改变其外观,因此应将其视为这两个属性的视图。因此,定义一个 class 表示列是其视图的实体是有意义的;这就是 StreetMoving
记录的作用。这是在模型外部完成的 (Bus
),以免视图的细节“污染”模型。您可以将 StreetMoving
视为在模型和视图之间扮演数据传输对象 (DTO) 的角色。单元实现现在非常干净,因为 updateItem()
方法准确接收它应该呈现的数据(街道名称和公共汽车是否在移动)并且只需设置图形属性作为响应。
在现实生活中,我可能会使用自定义 CSS Pseudoclass
实现单元格,并根据 moving
值切换其值;然后将颜色的实际选择委托给外部 CSS 文件。
您好,我是 JavaFX 的新手,在使用 tables 单元格时,我 运行 遇到一些更新显示数据的问题。我希望能够设置我的 table 单元格,以便它们可以监听多个值,而无需在更新项方法中初始化监听器。
例如,我有一辆公交车 class,它包含三个属性,一个字符串公交车 ID,一个字符串街道名称和一个移动布尔值。我目前在第 1 列中设置了公共汽车 ID,在第 2 列中设置了当前街道,并且希望能够进行设置,如果公共汽车在行驶,街道名称为绿色,如果停止,街道名称为红色。目前我已将第 2 列的 setCellValueFactory 设置为街道名称 属性 并在这些单元格的 updateItem 方法中它初始化了一个侦听器以更新颜色。虽然当前有效,但如果我向单元格添加更多侦听器则很难使用,我是否可以在 setCellValueFactory 方法或 table 列上的另一种此类方法中将单元格传递多个 属性 以具有单元格为多个事件调用 updateItem 方法。
给定标准 JavaFX 模型 class:
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
public class Bus {
private final StringProperty id = new SimpleStringProperty();
private final StringProperty streetName = new SimpleStringProperty();
private final BooleanProperty moving = new SimpleBooleanProperty();
public Bus(String id, String streetName, boolean moving) {
setId(id);
setStreetName(streetName);
setMoving(moving);
}
public final StringProperty idProperty() {
return this.id;
}
public final String getId() {
return this.idProperty().get();
}
public final void setId(final String id) {
this.idProperty().set(id);
}
public final StringProperty streetNameProperty() {
return this.streetName;
}
public final String getStreetName() {
return this.streetNameProperty().get();
}
public final void setStreetName(final String streetName) {
this.streetNameProperty().set(streetName);
}
public final BooleanProperty movingProperty() {
return this.moving;
}
public final boolean isMoving() {
return this.movingProperty().get();
}
public final void setMoving(final boolean moving) {
this.movingProperty().set(moving);
}
}
通常的方法就是您描述的方法。您也许可以通过将列的类型设置为 TableColumn<Bus, Bus>
并使用绑定而不是侦听器来稍微清理代码:
import java.util.Random;
import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleObjectProperty;
import javafx.scene.Scene;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import javafx.util.Duration;
public class App extends Application {
private final String[] streets = {
"Main Street",
"Sunset Boulevard",
"Electric Avenue",
"Winding Road"
};
private final Random rng = new Random();
@Override
public void start(Stage stage) {
TableView<Bus> table = new TableView<>();
TableColumn<Bus, String> idCol = new TableColumn<>("Id");
idCol.setCellValueFactory(cellData -> cellData.getValue().idProperty());
TableColumn<Bus, Bus> streetColumn = new TableColumn<>("Street");
streetColumn.setCellValueFactory(cellData -> new SimpleObjectProperty<>(cellData.getValue()));
streetColumn.setCellFactory(tc -> new TableCell<>() {
@Override
protected void updateItem(Bus bus, boolean empty) {
super.updateItem(bus, empty);
textProperty().unbind();
styleProperty().unbind();
if (empty || bus == null) {
setText("");
setStyle("");
} else {
textProperty().bind(bus.streetNameProperty());
styleProperty().bind(
Bindings.when(bus.movingProperty())
.then("-fx-text-fill: green;")
.otherwise("-fx-text-fill: red;")
);
}
}
});
table.getColumns().add(idCol);
table.getColumns().add(streetColumn);
// to check it works:
TableColumn<Bus, Boolean> movingColumn = new TableColumn<>("Moving");
movingColumn.setCellValueFactory(cellData -> cellData.getValue().movingProperty());
table.getColumns().add(movingColumn);
for (int busNumber = 1 ; busNumber <= 20 ; busNumber++) {
table.getItems().add(createBus("Bus Number "+busNumber));
}
Scene scene = new Scene(new BorderPane(table));
stage.setScene(scene);
stage.show();
}
// Create a Bus and a timeline
// that makes it start and stop and change streets at random:
private Bus createBus(String id) {
String street = streets[rng.nextInt(streets.length)];
Bus bus = new Bus(id, street, true);
Timeline timeline = new Timeline(
new KeyFrame(Duration.seconds(1 + rng.nextDouble()),
event -> {
double choose = rng.nextDouble();
if (bus.isMoving() && choose < 0.25) {
bus.setStreetName(streets[rng.nextInt(streets.length)]);
} else {
if (choose < 0.5) {
bus.setMoving(! bus.isMoving());
}
}
}
)
);
timeline.setCycleCount(Animation.INDEFINITE);
timeline.play();
return bus ;
}
public static void main(String[] args) {
launch();
}
}
另一种方法是定义一个不可变的 class(或 record
,如果您使用 Java 15 或更高版本)封装街道和公共汽车是否在移动。然后使用一个单元格值工厂,它 returns 一个绑定,它包装了 class 的一个实例并绑定到 streetNameProperty
和 movingProperty
。如果有任何更改,单元实现将收到通知,因此那里不需要侦听器或绑定:
import java.util.Random;
import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.scene.Scene;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import javafx.util.Duration;
public class App extends Application {
private final String[] streets = {
"Main Street",
"Sunset Boulevard",
"Electric Avenue",
"Winding Road"
};
private final Random rng = new Random();
@Override
public void start(Stage stage) {
TableView<Bus> table = new TableView<>();
TableColumn<Bus, String> idCol = new TableColumn<>("Id");
idCol.setCellValueFactory(cellData -> cellData.getValue().idProperty());
TableColumn<Bus, StreetMoving> streetColumn = new TableColumn<>("Street");
streetColumn.setCellValueFactory(cellData -> {
Bus bus = cellData.getValue();
return Bindings.createObjectBinding(
() -> new StreetMoving(bus.getStreetName(), bus.isMoving()),
bus.streetNameProperty(),
bus.movingProperty());
});
streetColumn.setCellFactory(tc -> new TableCell<>() {
@Override
protected void updateItem(StreetMoving street, boolean empty) {
super.updateItem(street, empty);
if (empty || street == null) {
setText("");
setStyle("");
} else {
setText(street.street());
String color = street.moving() ? "green" : "red" ;
setStyle("-fx-text-fill: " + color + ";");
}
}
});
table.getColumns().add(idCol);
table.getColumns().add(streetColumn);
// to check it works:
TableColumn<Bus, Boolean> movingColumn = new TableColumn<>("Moving");
movingColumn.setCellValueFactory(cellData -> cellData.getValue().movingProperty());
table.getColumns().add(movingColumn);
for (int busNumber = 1 ; busNumber <= 20 ; busNumber++) {
table.getItems().add(createBus("Bus Number "+busNumber));
}
Scene scene = new Scene(new BorderPane(table));
stage.setScene(scene);
stage.show();
}
private Bus createBus(String id) {
String street = streets[rng.nextInt(streets.length)];
Bus bus = new Bus(id, street, true);
Timeline timeline = new Timeline(
new KeyFrame(Duration.seconds(1 + rng.nextDouble()),
event -> {
double choose = rng.nextDouble();
if (bus.isMoving() && choose < 0.25) {
bus.setStreetName(streets[rng.nextInt(streets.length)]);
} else {
if (choose < 0.5) {
bus.setMoving(! bus.isMoving());
}
}
}
)
);
timeline.setCycleCount(Animation.INDEFINITE);
timeline.play();
return bus ;
}
public static record StreetMoving(String street, boolean moving) {};
public static void main(String[] args) {
launch();
}
}
从设计的角度来看,我通常更喜欢第二种方法。由于 streetColumn
在街道或移动属性发生变化时会改变其外观,因此应将其视为这两个属性的视图。因此,定义一个 class 表示列是其视图的实体是有意义的;这就是 StreetMoving
记录的作用。这是在模型外部完成的 (Bus
),以免视图的细节“污染”模型。您可以将 StreetMoving
视为在模型和视图之间扮演数据传输对象 (DTO) 的角色。单元实现现在非常干净,因为 updateItem()
方法准确接收它应该呈现的数据(街道名称和公共汽车是否在移动)并且只需设置图形属性作为响应。
在现实生活中,我可能会使用自定义 CSS Pseudoclass
实现单元格,并根据 moving
值切换其值;然后将颜色的实际选择委托给外部 CSS 文件。