JavaFx - tableView中的String和FlowPane(row?)? [英] JavaFx - String and FlowPane (row?) within tableView?

查看:112
本文介绍了JavaFx - tableView中的String和FlowPane(row?)?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我目前正在尝试实现以下内容:

I'm currently trying to implement the following:


  • 一个带有ObservableList作为数据集的TableView,有两列,每个都包含字符串(玩家的名字)。这部分很简单。

  • A TableView with an ObservableList as dataset, with two columns, each of which contains Strings (names of the players). This part is easy enough.

点击播放器(名称)后,应在所选播放器下方注入自定义FlowPane。如果单击其他玩家,则流程窗口应消失并注入当前单击的玩家下方。

Once a Player(name) is clicked, a custom FlowPane should be injected below the selected player. If another player is clicked, the flowpane should disappear and be injected below the currently clicked player.

下面的代码实现了TableView(减去鼠标监听器部分)。请帮我让FlowPane跨越整行。我猜我需要一个RowFactory,但不知道如何让它适用于我的目的:)

The below code implements the TableView (minus the mouse listener part). Please help me let the FlowPane span the entire row. I'm guessing I need a RowFactory but have no clue how to make it work for my purposes :)

此外,显然我的两列现在都显示相同的数据。令人困惑:)有没有办法告诉一列使用一半数据集而另一列使用另一半?我显然不希望我的数据显示两次。

Also, apparently both my columns now show the same data. Confusing :) Is there a way to tell one column to use half the data set and the other column the other half? I obviously don't want my data shown twice.

public class main extends Application
{
    public static void main(String[] args)
    {
        launch(args);
    }

    @Override
    public void start(Stage stage) throws Exception
    {
        try
        {

            FlowPane f = new FlowPane();
            Scene scene = new Scene(f, 300, 200);

            Player p1 = new Player("player 1 ");
            Player p2 = new Player("player 2 ");
            Player p3 = new Player("player 3 ");

            ArrayList<Object> players = new ArrayList<>();
            players.add(p1);
            players.add(p2);
            players.add(p3);

            ObservableList<Object> observableList = FXCollections.observableArrayList(players);
            TableView<Object> table = createTableView(observableList, 300, 200);

            f.getChildren().add(table);
            injectFlowPane(table);

            stage.setScene(scene);
            stage.show();
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }

    }

    public TableView<Object> createTableView(ObservableList<Object> items, double width, double height)
    {
        TableView<Object> table = new TableView<>();

        table.setItems(items);

        table.getColumns().add(createTableColumn(width / 2));
        table.getColumns().add(createTableColumn(width / 2));


        table.setMinSize(width, height);
        table.setPrefSize(width, height);
        table.setMaxSize(width, height);
        return table;
    }

    private TableColumn<Object, Object> createTableColumn(double width)
    {
        TableColumn<Object, Object> tableColumn = new TableColumn<>();
        tableColumn.setCellFactory(
                new Callback<TableColumn<Object, Object>, TableCell<Object, Object>>() {
                    @Override
                    public TableCell<Object, Object> call(TableColumn<Object, Object> arg0)
                    {
                        return new PlayerCell();
                    }
                });

        tableColumn.setCellValueFactory(cellDataFeatures -> {

            Object item = cellDataFeatures.getValue();
            return new SimpleObjectProperty<>(item);

        });

        tableColumn.setMinWidth(width);

        return tableColumn;
    }

    private void injectFlowPane(TableView<Object> table)
    {
        FlowPane f = new FlowPane();
        f.setMinSize(50, 50);
        f.setBackground(new Background(new BackgroundFill(Color.DARKGREEN, CornerRadii.EMPTY, Insets.EMPTY)));
        table.getItems().add(1, f);
    }

}

public class PlayerCell extends TableCell<Object, Object>
{
    @Override
    protected void updateItem(Object item, boolean empty)
    {
        super.updateItem(item, false);

        //      if (empty)

        if (item != null)
        {

            if (item instanceof Player)
            {
                setText(((Player) item).getName());
                setGraphic(null);
            }
            else if (item instanceof FlowPane)
            {
                setGraphic((FlowPane) item);
            }
            else
            {
                setText("N/A");
                setGraphic(null);
            }
        }
        else
        {
            setText(null);
            setGraphic(null);
        }

    }
}


public class Player
{
    private String name;

    public Player(String name)
    {
        this.name = name;
    }

    public String getName()
    {
        return name;
    }

}

编辑:

我现在已经实现了James_D的ExpandingTableRow,它可以很好地工作,只要显示所选TableRow下方的FlowPane。我还设法更改了我的数据结构,以便每列现在显示不同的播放器,而不是每列中的相同播放器。

I have now implemented James_D's ExpandingTableRow, which works neatly as far as showing the FlowPane below the selected TableRow is concerned. I have also managed to change my datastructures so that each column now shows different players instead of the same ones in each column.

但是,创建的FlowPane实际上应该取决于行中单击的实际播放器(单元格)。在James的例子中:如果选择了FirstName或LastName,则会创建不同的FlowPane(即使是同一行)。 FlowPane应以相同的方式显示 - 在所选行的下方 - 但它是不同的,新的FlowPane取决于是否单击了FirstName,或者是否单击了LastName。我怎么设法做到这一点?

However, the FlowPane that is created should actually depend on the actual player(cell) that is clicked within the row. In James' example: a different FlowPane would be created if the FirstName or LastName was selected (even for the same row). The FlowPane should be shown the same way - below the selected row - but it's a different, new FlowPane depending on if FirstName was clicked, or if LastName was clicked. How can I manage to do this?

我看过使用:

table.getSelectionModel().setCellSelectionEnabled(true);

但这实际上似乎禁用了James_d的解决方案。

But this actually seems to disable James_d's solution.

推荐答案

此解决方案仅适用于Java 9及更高版本。

This solution works only in Java 9 and later.

行的显示由 TableRow 管理,该行的实际布局由其外观执行( TableRowSkin )。因此,为了管理它,你需要一个 TableRow 的子类来安装自定义皮肤。

The display of a row is managed by a TableRow, and the actual layout of that row is performed by its skin (a TableRowSkin). So to manage this, you need a subclass of TableRow that installs a custom skin.

行实现很漂亮直截了当:在这个例子中,我为选择行时显示的附加内容添加了一个属性。它还会覆盖 createDefaultSkin()方法来指定自定义外观实现。

The row implementation is pretty straightforward: in this example I added a property for the "additional content" to be displayed when the row is selected. It also overrides the createDefaultSkin() method to specify a custom skin implementation.

import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.scene.Node;
import javafx.scene.control.Skin;
import javafx.scene.control.TableRow;

public class ExpandingTableRow<T> extends TableRow<T> {

    private final ObjectProperty<Node> selectedRowContent = new SimpleObjectProperty<>();

    public final ObjectProperty<Node> selectedRowContentProperty() {
        return this.selectedRowContent;
    }


    public final Node getSelectedRowContent() {
        return this.selectedRowContentProperty().get();
    }


    public final void setSelectedRowContent(final Node selectedRowContent) {
        this.selectedRowContentProperty().set(selectedRowContent);
    }

    public ExpandingTableRow(Node selectedRowContent) {
        super();
        setSelectedRowContent(selectedRowContent);
    }

    public ExpandingTableRow() {
        this(null);
    }

    @Override
    protected Skin<?> createDefaultSkin() {
        return new ExpandingTableRowSkin<T>(this);
    }

}

皮肤实现必须执行布局工作。它需要覆盖计算高度的方法,如果需要,计算额外内容的高度,并且需要覆盖 layoutChildren()方法,以定位附加内容内容,如果需要的话。最后,它必须管理附加内容,如果行的选定状态发生变化(或者附加内容本身已更改),则添加或删除其他内容。

The skin implementation has to do the layout work. It needs to override the methods that compute the height, accounting for the height of the extra content if needed, and it needs to override the layoutChildren() method, to position the additional content, if needed. Finally, it must manage the additional content, adding or removing the additional content if the selected state of the row changes (or if the additional content itself is changed).

import javafx.scene.control.skin.TableRowSkin;

public class ExpandingTableRowSkin<T> extends TableRowSkin<T> {

    private ExpandingTableRow<T> row;

    public ExpandingTableRowSkin(ExpandingTableRow<T> row) {
        super(row);
        this.row = row;

        row.selectedRowContentProperty().addListener((obs, oldContent, newContent) -> {
            if (oldContent != null) {
                getChildren().remove(oldContent);
            }
            if (newContent != null && row.isSelected()) {
                getChildren().add(newContent);
            }
            if (row.getTableView() != null) {
                row.getTableView().requestLayout();
            }
        });
        row.selectedProperty().addListener((obs, wasSelected, isNowSelected) -> {
            if (isNowSelected && row.getSelectedRowContent() != null
                    && !getChildren().contains(row.getSelectedRowContent())) {
                getChildren().add(row.getSelectedRowContent());
            } else {
                getChildren().remove(row.getSelectedRowContent());
            }
        });
    }

    @Override
    protected double computeMaxHeight(double width, double topInset, double rightInset, double bottomInset,
            double leftInset) {
        if (row.isSelected() && row.getSelectedRowContent() != null) {
            return super.computeMaxHeight(width, topInset, rightInset, bottomInset, leftInset)
                    + row.getSelectedRowContent().maxHeight(width);
        }
        return super.computeMaxHeight(width, topInset, rightInset, bottomInset, leftInset);
    }

    @Override
    protected double computeMinHeight(double width, double topInset, double rightInset, double bottomInset,
            double leftInset) {
        if (row.isSelected() && row.getSelectedRowContent() != null) {
            return super.computeMinHeight(width, topInset, rightInset, bottomInset, leftInset)
                    + row.getSelectedRowContent().minHeight(width);
        }
        return super.computeMinHeight(width, topInset, rightInset, bottomInset, leftInset);
    }

    @Override
    protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset,
            double leftInset) {
        if (row.isSelected() && row.getSelectedRowContent() != null) {
            return super.computePrefHeight(width, topInset, rightInset, bottomInset, leftInset)
                    + row.getSelectedRowContent().prefHeight(width);
        }
        return super.computePrefHeight(width, topInset, rightInset, bottomInset, leftInset);
    }

    @Override
    protected void layoutChildren(double x, double y, double w, double h) {
        if (row.isSelected()) {
            double rowHeight = super.computePrefHeight(w, snappedTopInset(), snappedRightInset(), snappedBottomInset(),
                    snappedLeftInset());
            super.layoutChildren(x, y, w, rowHeight);
            row.getSelectedRowContent().resizeRelocate(x, y + rowHeight, w, h - rowHeight);
        } else {
            super.layoutChildren(x, y, w, h);
        }
    }

}

最后,测试(使用Oracle的常用示例或其版本):

Finally, a test (using the usual example from Oracle, or a version of it):

import java.util.function.Function;

import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableRow;
import javafx.scene.control.TableView;
import javafx.scene.layout.FlowPane;
import javafx.stage.Stage;

public class ExpandingTableRowTest extends Application {

    @Override
    public void start(Stage primaryStage) {
        TableView<Person> table = new TableView<>();
        table.getColumns().add(column("First Name", Person::firstNameProperty));
        table.getColumns().add(column("Last Name", Person::lastNameProperty));

        table.setRowFactory(tv -> {
            Label label = new Label();
            FlowPane flowPane = new FlowPane(label);
            TableRow<Person> row = new ExpandingTableRow<>(flowPane) {
                @Override
                protected void updateItem(Person person, boolean empty) {
                    super.updateItem(person, empty);
                    if (empty) {
                        label.setText(null);
                    } else {
                        label.setText(String.format("Some additional information about %s %s here",
                                person.getFirstName(), person.getLastName()));
                    }
                }
            };
            return row;
        });

        table.getItems().addAll(
                new Person("Jacob", "Smith"), 
                new Person("Isabella", "Johnson"),
                new Person("Ethan", "Williams"), 
                new Person("Emma", "Jones"),
                new Person("Michael", "Brown")
        );

        Scene scene = new Scene(table);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    private static <S, T> TableColumn<S, T> column(String title, Function<S, ObservableValue<T>> property) {
        TableColumn<S, T> col = new TableColumn<>(title);
        col.setCellValueFactory(cellData -> property.apply(cellData.getValue()));
        return col;
    }

    public static class Person {
        private final StringProperty firstName = new SimpleStringProperty();
        private final StringProperty lastName = new SimpleStringProperty();

        public Person(String firstName, String lastName) {
            setFirstName(firstName);
            setLastName(lastName);
        }

        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 static void main(String[] args) {
        launch(args);
    }
}

正如你所看到的,风格稍微改进一点为了让这个生产准备就绪,可能需要调整大小,但这显示了可行的方法。

As you can see, a little refinement of the style and sizing may be needed to get this production-ready, but this shows the approach that will work.

这篇关于JavaFx - tableView中的String和FlowPane(row?)?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆