TableColumn 在场景中的位置
position of TableColumn in Scene
我有一个包含多个 TableColumn 的 TableView,我想在某个 TableColumn 下方放置一个节点。如何获得 TableColumn 的确切位置(x,y 坐标)以便我可以绑定节点的转换属性?
下面是我如何在 TableView 的右上角放置一个按钮的片段:
button.translateXProperty().unbind();
button.translateXProperty().bind(tableView.widthProperty().divide(2.0).subtract(button.getWidth() / 2.0 + 2.0) + tableView.localToScene(0.0, 0.0).getX());
这很好用,但显然只适用于 TableView。 TableColumns 没有那些转换属性或 localToScene 方法,所以我无法直接获取我想要绑定节点的位置。
我目前的解决方案(效果不是很好)是执行以下操作:
我读出我的 TableView 在场景 (PointA) 中的位置,然后遍历所有列的列表 (tableView.getColumns()) 并检查它们是否可见,如果可见,将它们的宽度添加到PointA 的 X 值。我这样做,直到找到我想要将节点放置在下方的实际列。
现在的问题是,我不能真正将节点位置绑定到这一点,因为当我更改列的顺序或使其中一列不可见时,我的列会改变屏幕上的位置。我将不得不为列顺序和可见性添加一个侦听器...
有没有更有效的方法来做我想做的事? :D
我通常不喜欢使用查找,但您可以使用查找 .table-view .column-header .label
检索用于显示列 header 的标签,然后使用该标签的边界绑定按钮的布局属性.
示例:
import java.util.Optional;
import java.util.function.Function;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
public class TableColumnLocationExample extends Application {
@Override
public void start(Stage primaryStage) {
TableView<Person> table = new TableView<>();
table.getColumns().add(column("First Name", Person::firstNameProperty, 120));
table.getColumns().add(column("Last Name", Person::lastNameProperty, 120));
table.getColumns().add(column("Email", Person::emailProperty, 250));
table.getItems().addAll(
new Person("Jacob", "Smith", "jacob.smith@example.com"),
new Person("Isabella", "Johnson", "isabella.johnson@example.com"),
new Person("Ethan", "Williams", "ethan.williams@example.com"),
new Person("Emma", "Jones", "emma.jones@example.com"),
new Person("Michael", "Brown", "michael.brown@example.com")
);
Pane root = new Pane(table);
Scene scene = new Scene(root, 600, 600);
primaryStage.setScene(scene);
primaryStage.show();
for (TableColumn<Person, ?> col : table.getColumns()) {
Optional<Label> header = findLabelForTableColumnHeader(col.getText(), root);
header.ifPresent(label -> {
Button button = new Button(col.getText());
button.prefWidthProperty().bind(Bindings.createDoubleBinding(() ->
label.getBoundsInLocal().getWidth(), label.boundsInLocalProperty()));
button.minWidthProperty().bind(button.prefWidthProperty());
button.maxWidthProperty().bind(button.prefWidthProperty());
button.layoutXProperty().bind(Bindings.createDoubleBinding(() ->
label.getLocalToSceneTransform().transform(label.getBoundsInLocal()).getMinX(),
label.boundsInLocalProperty(), label.localToSceneTransformProperty()));
button.layoutYProperty().bind(Bindings.createDoubleBinding(() ->
table.getBoundsInParent().getMaxY() ,table.boundsInParentProperty()));
root.getChildren().add(button);
});
}
}
private Optional<Label> findLabelForTableColumnHeader(String text, Parent root) {
return root.lookupAll(".table-view .column-header .label")
.stream()
.map(Label.class::cast)
.filter(label -> label.getText().equals(text))
.findAny(); // assumes all columns have unique text...
}
private <S,T> TableColumn<S,T> column(String title, Function<S,ObservableValue<T>> property, double width) {
TableColumn<S,T> col = new TableColumn<>(title);
col.setCellValueFactory(cellData -> property.apply(cellData.getValue()));
col.setPrefWidth(width);
return col ;
}
public static class Person {
private StringProperty firstName = new SimpleStringProperty();
private StringProperty lastName = new SimpleStringProperty();
private StringProperty email = new SimpleStringProperty();
public Person(String firstName, String lastName, String email) {
setFirstName(firstName);
setLastName(lastName);
setEmail(email);
}
public final StringProperty firstNameProperty() {
return this.firstName;
}
public final String getFirstName() {
return this.firstNameProperty().get();
}
public final void setFirstName(final String firstName) {
this.firstNameProperty().set(firstName);
}
public final StringProperty lastNameProperty() {
return this.lastName;
}
public final String getLastName() {
return this.lastNameProperty().get();
}
public final void setLastName(final String lastName) {
this.lastNameProperty().set(lastName);
}
public final StringProperty emailProperty() {
return this.email;
}
public final String getEmail() {
return this.emailProperty().get();
}
public final void setEmail(final String email) {
this.emailProperty().set(email);
}
}
public static void main(String[] args) {
launch(args);
}
}
如果您被允许使用 non-public api,您可以考虑通过其外观访问 TableColumnHeader,前提是它是 TableViewSkinBase 类型:它具有 api 来访问 TableRowHeader这是所有 TableColumnHeaders 的容器,并且具有 api 以查找它包含的任何列的 header。
代码片段(width/location 绑定是从 James 的示例中复制的,只是到 header 而不是标签)
private void buttonsPerHeader(TableView<Person> table, Pane root) {
if (!(table.getSkin() instanceof TableViewSkinBase)) return;
TableViewSkinBase skin = (TableViewSkinBase) table.getSkin();
TableHeaderRow headerRow = skin.getTableHeaderRow();
for (TableColumn col : table.getColumns()) {
TableColumnHeader header = headerRow.getColumnHeaderFor(col);
Button button = new Button(col.getText());
button.prefWidthProperty().bind(Bindings.createDoubleBinding(() ->
header.getBoundsInLocal().getWidth(), header.boundsInLocalProperty()));
button.minWidthProperty().bind(button.prefWidthProperty());
button.maxWidthProperty().bind(button.prefWidthProperty());
button.layoutXProperty().bind(Bindings.createDoubleBinding(() ->
header.getLocalToSceneTransform().transform(header.getBoundsInLocal()).getMinX(),
header.boundsInLocalProperty(), header.localToSceneTransformProperty()));
button.layoutYProperty().bind(Bindings.createDoubleBinding(() ->
table.getBoundsInParent().getMaxY() ,table.boundsInParentProperty()));
root.getChildren().add(button);
}
}
我有一个包含多个 TableColumn 的 TableView,我想在某个 TableColumn 下方放置一个节点。如何获得 TableColumn 的确切位置(x,y 坐标)以便我可以绑定节点的转换属性?
下面是我如何在 TableView 的右上角放置一个按钮的片段:
button.translateXProperty().unbind();
button.translateXProperty().bind(tableView.widthProperty().divide(2.0).subtract(button.getWidth() / 2.0 + 2.0) + tableView.localToScene(0.0, 0.0).getX());
这很好用,但显然只适用于 TableView。 TableColumns 没有那些转换属性或 localToScene 方法,所以我无法直接获取我想要绑定节点的位置。
我目前的解决方案(效果不是很好)是执行以下操作: 我读出我的 TableView 在场景 (PointA) 中的位置,然后遍历所有列的列表 (tableView.getColumns()) 并检查它们是否可见,如果可见,将它们的宽度添加到PointA 的 X 值。我这样做,直到找到我想要将节点放置在下方的实际列。 现在的问题是,我不能真正将节点位置绑定到这一点,因为当我更改列的顺序或使其中一列不可见时,我的列会改变屏幕上的位置。我将不得不为列顺序和可见性添加一个侦听器...
有没有更有效的方法来做我想做的事? :D
我通常不喜欢使用查找,但您可以使用查找 .table-view .column-header .label
检索用于显示列 header 的标签,然后使用该标签的边界绑定按钮的布局属性.
示例:
import java.util.Optional;
import java.util.function.Function;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
public class TableColumnLocationExample extends Application {
@Override
public void start(Stage primaryStage) {
TableView<Person> table = new TableView<>();
table.getColumns().add(column("First Name", Person::firstNameProperty, 120));
table.getColumns().add(column("Last Name", Person::lastNameProperty, 120));
table.getColumns().add(column("Email", Person::emailProperty, 250));
table.getItems().addAll(
new Person("Jacob", "Smith", "jacob.smith@example.com"),
new Person("Isabella", "Johnson", "isabella.johnson@example.com"),
new Person("Ethan", "Williams", "ethan.williams@example.com"),
new Person("Emma", "Jones", "emma.jones@example.com"),
new Person("Michael", "Brown", "michael.brown@example.com")
);
Pane root = new Pane(table);
Scene scene = new Scene(root, 600, 600);
primaryStage.setScene(scene);
primaryStage.show();
for (TableColumn<Person, ?> col : table.getColumns()) {
Optional<Label> header = findLabelForTableColumnHeader(col.getText(), root);
header.ifPresent(label -> {
Button button = new Button(col.getText());
button.prefWidthProperty().bind(Bindings.createDoubleBinding(() ->
label.getBoundsInLocal().getWidth(), label.boundsInLocalProperty()));
button.minWidthProperty().bind(button.prefWidthProperty());
button.maxWidthProperty().bind(button.prefWidthProperty());
button.layoutXProperty().bind(Bindings.createDoubleBinding(() ->
label.getLocalToSceneTransform().transform(label.getBoundsInLocal()).getMinX(),
label.boundsInLocalProperty(), label.localToSceneTransformProperty()));
button.layoutYProperty().bind(Bindings.createDoubleBinding(() ->
table.getBoundsInParent().getMaxY() ,table.boundsInParentProperty()));
root.getChildren().add(button);
});
}
}
private Optional<Label> findLabelForTableColumnHeader(String text, Parent root) {
return root.lookupAll(".table-view .column-header .label")
.stream()
.map(Label.class::cast)
.filter(label -> label.getText().equals(text))
.findAny(); // assumes all columns have unique text...
}
private <S,T> TableColumn<S,T> column(String title, Function<S,ObservableValue<T>> property, double width) {
TableColumn<S,T> col = new TableColumn<>(title);
col.setCellValueFactory(cellData -> property.apply(cellData.getValue()));
col.setPrefWidth(width);
return col ;
}
public static class Person {
private StringProperty firstName = new SimpleStringProperty();
private StringProperty lastName = new SimpleStringProperty();
private StringProperty email = new SimpleStringProperty();
public Person(String firstName, String lastName, String email) {
setFirstName(firstName);
setLastName(lastName);
setEmail(email);
}
public final StringProperty firstNameProperty() {
return this.firstName;
}
public final String getFirstName() {
return this.firstNameProperty().get();
}
public final void setFirstName(final String firstName) {
this.firstNameProperty().set(firstName);
}
public final StringProperty lastNameProperty() {
return this.lastName;
}
public final String getLastName() {
return this.lastNameProperty().get();
}
public final void setLastName(final String lastName) {
this.lastNameProperty().set(lastName);
}
public final StringProperty emailProperty() {
return this.email;
}
public final String getEmail() {
return this.emailProperty().get();
}
public final void setEmail(final String email) {
this.emailProperty().set(email);
}
}
public static void main(String[] args) {
launch(args);
}
}
如果您被允许使用 non-public api,您可以考虑通过其外观访问 TableColumnHeader,前提是它是 TableViewSkinBase 类型:它具有 api 来访问 TableRowHeader这是所有 TableColumnHeaders 的容器,并且具有 api 以查找它包含的任何列的 header。
代码片段(width/location 绑定是从 James 的示例中复制的,只是到 header 而不是标签)
private void buttonsPerHeader(TableView<Person> table, Pane root) {
if (!(table.getSkin() instanceof TableViewSkinBase)) return;
TableViewSkinBase skin = (TableViewSkinBase) table.getSkin();
TableHeaderRow headerRow = skin.getTableHeaderRow();
for (TableColumn col : table.getColumns()) {
TableColumnHeader header = headerRow.getColumnHeaderFor(col);
Button button = new Button(col.getText());
button.prefWidthProperty().bind(Bindings.createDoubleBinding(() ->
header.getBoundsInLocal().getWidth(), header.boundsInLocalProperty()));
button.minWidthProperty().bind(button.prefWidthProperty());
button.maxWidthProperty().bind(button.prefWidthProperty());
button.layoutXProperty().bind(Bindings.createDoubleBinding(() ->
header.getLocalToSceneTransform().transform(header.getBoundsInLocal()).getMinX(),
header.boundsInLocalProperty(), header.localToSceneTransformProperty()));
button.layoutYProperty().bind(Bindings.createDoubleBinding(() ->
table.getBoundsInParent().getMaxY() ,table.boundsInParentProperty()));
root.getChildren().add(button);
}
}