【问题标题】:Display Sales information in TreeTableView在 TreeTableView 中显示销售信息
【发布时间】:2016-09-26 08:29:51
【问题描述】:

我想要实现的是或多或少拥有 Sale 对象,当该对象扩展为在该 Sale 对象下显示 SaleTransaction 对象时。来自Code Project的这张图片中显示了类似的想要

除了读取之外,我还可以执行其他 CRUD 功能(即创建、更新和删除)。

我尝试使用 TreeTableView 来实现它,如下所示:

 List<CreditSale> lstData = new ArrayList<CreditSale>(creditsaleservice.findAllCreditSales());

    TreeItem root = new TreeItem<>();
    for (CreditSaleTransaction cst : lstData.get(0).getCreditSaleTransaction()) {
        root.getChildren().addAll(new TreeItem<>(cst));
    }


    TreeTableColumn<CreditSaleTransaction, Product> productColumn = new TreeTableColumn<>("Product Name");

    productColumn.setPrefWidth(150);
    productColumn.setEditable(true);

    productColumn.setCellValueFactory((TreeTableColumn.CellDataFeatures<CreditSaleTransaction, Product> p) 
            -> new ReadOnlyObjectWrapper<>(p.getValue().getValue().getProduct()));


    TreeTableColumn<CreditSaleTransaction, Float>  quantityColumn = new TreeTableColumn<>("Quantity");
    quantityColumn.setPrefWidth(150);
    quantityColumn.setEditable(true);
    quantityColumn.setCellValueFactory((TreeTableColumn.CellDataFeatures<CreditSaleTransaction,Float> p)
            -> new ReadOnlyObjectWrapper<>(p.getValue().getValue().getAmount()));

    TreeTableColumn<CreditSaleTransaction, Float>  unitPColumn = new TreeTableColumn<>("Unit Price");
    unitPColumn.setPrefWidth(150);
    unitPColumn.setEditable(true);
    unitPColumn.setCellValueFactory((TreeTableColumn.CellDataFeatures<CreditSaleTransaction,Float> p)
            -> new ReadOnlyObjectWrapper<>(p.getValue().getValue().getUnitPrice()));

    TreeTableView<CreditSaleTransaction> treeTableView = new TreeTableView<>();
    treeTableView.setRoot(root);

    treeTableView.getColumns().addAll(productColumn,quantityColumn,unitPColumn);
    treeviewanchorpane.getChildren().add(treeTableView);

但什么都没有显示。

【问题讨论】:

  • 当我点击你的“代码项目”链接时它被破坏了。另外,您的问题有点模糊和开放式,您到底想要什么帮助?附带说明一下,如果您是 JavaFX 新手,我建议您从开发更简单的 UI 而不是 TreeTableView 开始,TreeTableView 可能是标准 JavaFX API 中最复杂的控件。
  • 我刚刚修复了链接。我想要一种方法,可以显示有关销售的信息,例如何时进行,总金额还可以显示每个销售交易的详细信息,例如现在出售了哪些产品,其单价,下拉列表中的数量
  • 您可能想尝试将 TreeTableView 添加到不同的布局。 AnchorPane 几乎从来都不是一个好的选择。试试 BorderPane 或 StackPane。您正在将该窗格添加到场景并将该场景传递给主舞台,对吗?

标签: java javafx-8


【解决方案1】:

如果您不介意丢失这些内部标头,则可以使用 TreeTableView 实现类似的输出:

Jewelsea 有一个很好的例子来说明如何解决这个问题:


**自定义实现的建议方法**

如果标题很重要,您可以创建自定义控件以获得所需的布局。我的建议是在VBox 等容器中使用多个TitledPane,外部项目作为标题,内部项目作为TableView 中的内容。 TitledPane 期望 String 作为标题,但结合 setGraphicsetContentDisplay ,可以将其更改为 Node 允许您保持一致的布局

这种方法允许多个项目(内部和外部)同时可见。

如果您一次只想看到内部的,Accordian 将是VBox 的合适替代品,因为它只允许在任何给定时间扩展一个TitledPane

TitledPaneAccordian 都包含在本 Oracle 教程中:

(我已经包含了一个关于如何实现此方法的示例,以及在此答案末尾显示输出的图像)


**监控变化**

JavaFX 提供了一个ObservableList,它会通知侦听器任何更改。有效利用这一点将允许用户界面自动反映任何更改。

例如,ListChangeListener 用于检测何时添加/删除了元素,覆盖了here

注意:如果ObservableList 中的子元素被修改(例如嵌套销售项目中的价格),则可能没有更新事件。在这种情况下,应将 extractor 添加到列表中,指定应观察的附加值,如果这些值中的任何一个发生更改,则触发事件更新

还有一系列可用的属性/绑定,这些都包含在以下文档中:

(示例包含如何使用上述示例)


在 `TableView` 上实现 CRUD 功能的建议:
  • 创建Button 栏:您可以将其放在每个TableViewnestedView 下,使用选定的单元格来确定何时启用按钮以及要修改哪些对象
  • TableView 上放置一个EventHandler:见setOnEditCommit()
  • 将自定义单元格添加到TableView 有几个示例可用于说明如何创建自定义TableCell 以在单击时删除行。

    例如:Delete Rows using row buttons in Javafx TableView
  • 使用ContextMenu

示例:

我已在可能的情况下添加了代码 cmets,但如果有任何问题或建议的改进,请发表评论

NestedTableView:

public class NestedTableView extends VBox {
    private ObservableList<Node> titledPanes = FXCollections.observableArrayList();
    private Region parent;

    public NestedTableView(Region parent, ObservableList<ProductBundle> bundlesToDisplay){
        this.parent = parent;
        VBox nestedView = new VBox();
        Bindings.bindContentBidirectional(nestedView.getChildren(), titledPanes);
        titledPanes.addAll(bundlesToDisplay.stream()
                .map(TablePane::new).collect(Collectors.toList()));
        getChildren().setAll(createHeader(), nestedView);
        getStylesheets().add("CSS/nestedTableViewStyles.css");
    }

    private HBox createHeader(){
        //Set up widths to align with the content headers beneath the header
        Label symbol = new Label("#");
        symbol.setPrefWidth(25); //Sum of the values used by the "arrow" region

        Label productId = new Label("Product Id");
        productId.prefWidthProperty().bind(parent.widthProperty().multiply(0.15));

        Label productName = new Label("Product Name");
        productName.prefWidthProperty().bind(parent.widthProperty().multiply(0.35)); //Give name extra space

        Label amount = new Label("Amount");
        amount.prefWidthProperty().bind(parent.widthProperty().multiply(0.15));

        Label date = new Label("Order Date");
        date.prefWidthProperty().bind(parent.widthProperty().multiply(0.15));

        Label quantityAvailable = new Label("#Available");
        quantityAvailable.prefWidthProperty().bind(parent.widthProperty().multiply(0.15));

        HBox header = new HBox(symbol, productId, productName, amount, date, quantityAvailable);
        header.getStyleClass().add("header");
        return header;
    }

    private class TablePane extends TitledPane {
        private ProductBundle productBundle;

        private HBox header;
        private TableView contentTableView;
        private MenuItem addToBundle, deleteBundle;

        public TablePane(ProductBundle productBundle){
            this.productBundle = productBundle;
            setupMenuItems();
            setupContentHeader();
            setGraphic(header);
            setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
            if(!productBundle.getBundleItems().isEmpty()){
                createTableView();
                setContent(contentTableView);
            }

            //Only display the expandable "arrow" if there is content to display
            collapsibleProperty().bind(contentProperty().isNotNull());
            //If the "arrow" isn't displayed, pad the area to mimic the arrow being present to align headers
            header.paddingProperty().bind(
                    Bindings.when(collapsibleProperty()).then(Insets.EMPTY).otherwise(new Insets(0,0,0,15)));
            /*  For testing purposes. With more rows this will clutter the UI
                ToDo: add logic to determine how many panes to expand before the viewport has been filled
             */
            setExpanded(true);
        }

        private void setupMenuItems(){
            addToBundle = new MenuItem("Add to bundle");
            addToBundle.setOnAction(event -> {
                //ToDo: Add CRUD create logic here
                System.out.println("Add to bundle: " + productBundle.idProperty());
            });
            deleteBundle = new MenuItem("Delete bundle");
            deleteBundle.setOnAction(event -> {
                //ToDo: Add CRUD delete logic here
                System.out.println("Delete bundle: " + productBundle.idProperty());
            });
        }

        private void setupContentHeader(){
            header = new HBox();
            //Bind the content header to the root so that it aligned with the initial header
            header.prefWidthProperty().bind(parent.widthProperty());
            header.maxWidthProperty().bind(parent.widthProperty());

            /* Set up each TextField with widths to align with the TableView
               Each TextField is editable with the exception of id as it would be the primary key
               and amount as it's value is calculated from the sub items */
            TextField id = new TextField();
            id.setEditable(false);
            modifyTextFieldContextMenu(id);
            id.textProperty().bind(productBundle.idProperty());
            id.prefWidthProperty().bind(header.widthProperty().multiply(0.15));

            TextField name = new TextField();
            modifyTextFieldForCRUDFunctionality(name);
            name.textProperty().bindBidirectional(productBundle.nameProperty());
            name.prefWidthProperty().bind(header.widthProperty().multiply(0.35)); //Give name extra space

            TextField amount = new TextField();
            amount.setEditable(false);
            Bindings.bindBidirectional(amount.textProperty(), productBundle.amountProperty(),
                    new NumberStringConverter(NumberFormat.getCurrencyInstance(Locale.US)));
            amount.prefWidthProperty().bind(header.widthProperty().multiply(0.15));

            TextField date = new TextField();
            modifyTextFieldForCRUDFunctionality(date);
            date.textProperty().bind(productBundle.orderDateProperty());
            date.prefWidthProperty().bind(header.widthProperty().multiply(0.15));

            TextField quantityRemaining = new TextField();
            modifyTextFieldForCRUDFunctionality(quantityRemaining);
            //Only display a quantity if it's a valid value (to match example screen shot)
            quantityRemaining.textProperty().bind(
                    Bindings.when(productBundle.quantityAvailableProperty().greaterThan(0))
                            .then(productBundle.quantityAvailableProperty().asString()).otherwise("N/A"));
            quantityRemaining.prefWidthProperty().bind(header.widthProperty().multiply(0.15));

            header.getChildren().setAll(id, name, amount, date, quantityRemaining);
            header.getStyleClass().add("content-header");
        }

        private void modifyTextFieldContextMenu(TextField textField){
            TextFieldSkin skin = new TextFieldSkin(textField){
                @Override
                public void populateContextMenu(ContextMenu contextMenu) {
                    super.populateContextMenu(contextMenu);
                    contextMenu.getItems().add(0, addToBundle);
                    contextMenu.getItems().add(1, deleteBundle);
                    contextMenu.getItems().add(2, new SeparatorMenuItem());
                }
            };
            textField.setSkin(skin);
        }

        private void modifyTextFieldForCRUDFunctionality(TextField textField){
            textField.setEditable(true);
            textField.focusedProperty().addListener(new ChangeListener<Boolean>() {
                private String previousText;
                @Override
                public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
                    String currentText = textField.getText();
                    if(newValue){
                        previousText = currentText;
                    }
                    //ToDo: Add CRUD update logic here
                    else if(!previousText.equals(currentText)){
                        System.out.println("Value has been changed from: " + previousText + " to: " + currentText);
                    }
                }
            });
        }

        private void createTableView(){
            TableColumn<BundleItem, String> idColumn = new TableColumn<>("#ID");
            idColumn.setCellValueFactory(param -> param.getValue().getItem().itemIdProperty());

            TableColumn<BundleItem, String> nameColumn = new TableColumn<>("Item");
            nameColumn.setCellValueFactory(param -> param.getValue().getItem().itemNameProperty());

            TableColumn<BundleItem, String> amountColumn = new TableColumn<>("Amount");
            amountColumn.setCellValueFactory(param -> param.getValue().getItem().amountProperty().asString("$%.2f"));

            TableColumn<BundleItem, Number> quantityColumn = new TableColumn<>("Qty");
            quantityColumn.setCellValueFactory(param -> param.getValue().quantityProperty());

            TableView<BundleItem> tableView = new TableView<>(productBundle.getBundleItems());
            tableView.setPadding(new Insets(10));
            //Equal column widths
            tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
            tableView.getColumns().setAll(idColumn, nameColumn, amountColumn, quantityColumn);
            //Only show visible shows
            tableView.setFixedCellSize(30);
            tableView.prefHeightProperty().bind(Bindings.size(productBundle.getBundleItems())
                    .multiply(tableView.getFixedCellSize()).add(tableView.getFixedCellSize()*1.5));
            contentTableView = tableView;
        }
    }
}

销售相关对象:

public class ProductBundle {
    private ObservableList<BundleItem> bundleItems = FXCollections.observableArrayList();
    private SimpleStringProperty productId, productName, orderDate;
    private SimpleDoubleProperty amount = new SimpleDoubleProperty();
    private SimpleIntegerProperty quantityAvailable;

    private ProductBundle(String productId, String productName, String orderDate, int quantityAvailable){
        this.productId = new SimpleStringProperty(productId);
        this.productName = new SimpleStringProperty(productName);
        this.orderDate = new SimpleStringProperty(orderDate);
        this.quantityAvailable = new SimpleIntegerProperty(quantityAvailable);
    }

    public ProductBundle(String productId, String productName, String orderDate,
                         int quantityAvailable, ObservableList<BundleItem> bundleItems){
        this(productId, productName, orderDate, quantityAvailable);
        //Setup an extractor to "Observe" changes on the amount/quantity of any items in the bundle
        this.bundleItems = FXCollections.observableArrayList(new Callback<BundleItem, Observable[]>() {
            @Override
            public Observable[] call(BundleItem param) {
                return new Observable[]{param.amountProperty(), param.quantityProperty()};
            }
        });
        this.bundleItems.addAll(bundleItems);
        //Calculate the total worth of this bundle
        amount.bind(Bindings.createDoubleBinding(()->
                bundleItems.stream().collect(Collectors.summingDouble(BundleItem::getAmount)), this.bundleItems)
                .multiply(quantityAvailable));
    }

    public ProductBundle(String productId, String productName, String orderDate,
                         int quantityAvailable, double amount){
        this(productId, productName, orderDate, quantityAvailable);
        this.amount.set(amount);
    }

    public ObservableList<BundleItem> getBundleItems(){
        return bundleItems;
    }

    public SimpleStringProperty idProperty(){
        return productId;
    }

    public SimpleStringProperty nameProperty(){
        return productName;
    }

    public SimpleIntegerProperty quantityAvailableProperty(){
        return quantityAvailable;
    }

    public SimpleStringProperty orderDateProperty(){
        return orderDate;
    }

    public SimpleDoubleProperty amountProperty(){
        return amount;
    }

    public double getAmount(){
        return amount.get();
    }
}

public class BundleItem {
    private Item item;
    private SimpleIntegerProperty quantity;
    private SimpleDoubleProperty amount = new SimpleDoubleProperty();

    public BundleItem(Item item, int quantity){
        this.item = item;
        this.quantity = new SimpleIntegerProperty(quantity);
        amount.bind(item.amountProperty().multiply(quantity));
    }

    public Item getItem(){
        return item;
    }

    public SimpleIntegerProperty quantityProperty(){
        return quantity;
    }

    public SimpleDoubleProperty amountProperty(){
        return amount;
    }

    public double getAmount(){
        return amount.get();
    }
}

public class Item {
    private SimpleStringProperty itemId, itemName;
    private SimpleDoubleProperty amount;

    public Item(String itemId, String itemName, double amount){
        this.itemId = new SimpleStringProperty(itemId);
        this.itemName = new SimpleStringProperty(itemName);
        this.amount = new SimpleDoubleProperty(amount);
    }

    public SimpleStringProperty itemIdProperty(){
        return itemId;
    }

    public SimpleStringProperty itemNameProperty(){
        return itemName;
    }

    public SimpleDoubleProperty amountProperty(){
        return amount;
    }

    public double getAmount(){
        return amount.get();
    }

    public void setAmount(double newValue){
        amount.set(newValue);
    }
}

nestedTableViewStyles.css:

.header {
    -fx-background-color: darkorange;
    -fx-pref-height: 30;
    -fx-padding: 5 0 0 0;
}

.header > .label {
    -fx-text-fill: white;
}

.header > .label, .content-header > .text-field {
    -fx-alignment: center;
    -fx-text-alignment: center;
}

.content-header > .text-field, .content-header > .text-field:focused {
    /* Make the TextField's display similar to a Label */
    -fx-background-color: transparent;
}

.content-header, .titled-pane > .title, .table-view {
    -fx-background-color: white;
}

.titled-pane > .title {
    -fx-border-color: lightgray;
    -fx-border-width: 0 0 1 0;
}

.table-view {
     -fx-table-cell-border-color: transparent;
}

.table-view .column-header-background {
    -fx-border-radius: 5 5 0 0;
    -fx-background-radius: 5 5 0 0;
}

.table-view .column-header-background, .table-row-cell {
    -fx-background-color: lightgray;
    -fx-border-color: gray;
    -fx-border-width: 0 0 1 0;
}

.table-view .column-header-background .label {
    -fx-background-color: lightgray;
    -fx-text-fill: black;
    -fx-font-weight: bold;
}

.table-view .column-header {
    -fx-background-color: transparent;
}

.table-column {
    -fx-alignment: center;
}

用法:

public class NestedTableViewExample extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception {
        ObservableList<ProductBundle> bundles =
                FXCollections.observableArrayList(
                        new ProductBundle("1001456", "Spring Season Gift", "02/14/2015", 1,
                                FXCollections.observableArrayList(
                                        new BundleItem(new Item("17890", "PS 3", 150.00), 1),
                                        new BundleItem(new Item("17891", "Heart shape ring", 100.00), 1)
                                )),
                        new ProductBundle("1001457", "Christmas Season Gift", "04/14/2015", 1,
                                FXCollections.observableArrayList(
                                        new BundleItem(new Item("17900", "Chocolate Giftbox", 150.00), 1),
                                        new BundleItem(new Item("17901", "Xbox 360", 199.00), 1)
                                )),
                        new ProductBundle("1001458", "Birthday Gift", "", 1, 200)
                );

        VBox root = new VBox();
        root.getChildren().setAll(new NestedTableView(root, bundles));
        Scene scene = new Scene(root, 500, 500);
        primaryStage.setScene(scene);
        primaryStage.setTitle("Nested TableView example");
        primaryStage.show();
    }
}

**输出:**

【讨论】:

  • 太棒了……令人惊叹……太棒了
猜你喜欢
  • 1970-01-01
  • 2017-04-17
  • 2018-04-19
  • 1970-01-01
  • 1970-01-01
  • 2019-04-01
  • 1970-01-01
  • 2014-01-25
  • 1970-01-01
相关资源
最近更新 更多