在TreeTableView中显示销售信息 [英] Display Sales information in TreeTableView

查看:50
本文介绍了在TreeTableView中显示销售信息的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想要实现的是或多或少具有Sale对象,将其扩展为在该Sale对象下显示SaleTransaction对象.此图像从代码显示项目

What I want to achieve is more or less having Sale object which when expanded to display SaleTransaction objects under that Sale object. Similar to want is displayed in this image from Code Project

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

where i can also do other CRUD functionality apart from just read(i.e Create, Update and delete).

我尝试像这样使用TreeTableView实现它:

I tried implementing it using TreeTableView like so:

 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);

但不显示任何内容.

推荐答案

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

It's possible to achieve a similar output with a TreeTableView provided you don't mind losing these inner headers:

Jewelsea在这里提供了一个很好的示例:

Jewelsea has a good example of how to approach this here:


建议的自定义实施方法

如果标题很重要,则可以创建一个自定义控件以获取所需的布局.我的建议是在VBox之类的容器中包含多个TitledPane,其中外部项目作为标题,内部项目作为TableView中的内容. TitledPane希望以String作为标题,但结合使用

If the headers are important you can create a custom control to get the desired layout. My suggestion would be multiple TitledPane's within a container such as a VBox, with the outer item as the title and the inner items as the content within a TableView. TitledPane expects a String as the title, but with a combination of setGraphic and setContentDisplay , this can be changed to a Node allowing you to maintain a consistent layout

此方法将允许同时显示多个项目(内部和外部).

This approach would allow for multiple items (both inner and outer) to be visible at the same time.

如果您只希望一次看到内部的内容,则Accordian将是VBox的合适选择,因为它仅允许在任何给定时间扩展一个TitledPane

In the event you only wish to be able to see the inner one at a time, an Accordian would be a suitable alternative to a VBox as it only allows one TitledPane to be expanded at any given time

TitledPaneAccordian都包含在本Oracle教程中:

Both TitledPane and Accordian are covered within this Oracle tutorial:

(我提供了一个有关如何实现此方法的示例,并在此答案的末尾显示了一个显示输出的图像)

(I've included an example on how this approach could be implemented and an image displaying the output at the end of this answer)


监视更改

JavaFX提供了ObservableList,它可以将所有更改通知侦听器.有效地使用此功能将允许用户界面自动反映任何更改.

JavaFX offers an ObservableList, which notifies listeners of any changes. Effective use of this would allow for the user interface to automatically reflect any changes.

例如,一个 ListChangeListener 来检测何时添加/删除了元素,这在 注意:如果修改了ObservableList中的子元素(例如嵌套销售商品中的价格),则可能没有更新事件.在这种情况下,应将提取器添加到列表中,该列表指定应遵守的其他值,如果这些值中的任何一个发生更改,则会触发事件更新

Note: If a child element within the ObservableList is modified (such as a price within a nested sales item), there may not be an update event. In this situation an extractor should be added to the list which specifies the additional values should be observed, firing the event updates if any of these values change

还有一系列可用的属性/绑定,在以下文档中进行了介绍:

There are also a range of properties / bindings available, which are covered in the following pieces of documentation:

  • Bindings
  • Using JavaFX Properties and Binding

(该示例包含有关如何使用上述示例的示例)


TableView上实现CRUD功能的建议:

(The example contains examples of how to use the above)


Suggestions for implementing the CRUD functionality on TableView's:

  • 创建Button栏:您可以将其放置在每个TableViewnestedView下,使用选定的单元格确定何时启用按钮以及修改哪些对象
  • TableView 上放置EventHandler:请参见使用Javafx TableView中的行按钮删除行
  • >
  • 使用ContextMenu
  • Creating a Button bar: You could place this under each TableView or the nestedView, using selected cells to determine when to enable the buttons and which objects to modify
  • Place an EventHandler on the TableView: See setOnEditCommint()
  • Adding custom cells to the TableView: There are several examples available on how to create custom TableCell's that delete the row when clicked.

    For example: Delete Rows using row buttons in Javafx TableView
  • Using ContextMenu's

示例:

我已在可能的地方添加了代码注释,但是如果有任何疑问或建议的改进,请发表评论

I've added code comments where possible, but if there are any questions or suggested improvements please leave a comment

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:

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();
    }
}


输出:

这篇关于在TreeTableView中显示销售信息的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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