JavaFX TableView类别/分隔行

JavaFX TableView类别/分隔行,javafx,tableview,Javafx,Tableview,我很难找到解决方法: 我希望在场景图中有一个表视图,它将表示某个类(产品)。该类有一个字符串类型字段,我想用它对产品进行分类。因此,我希望在循环中填充TableView,添加所有产品,同时在每个特定类型的产品之前添加“类别”行 因此,如果我有10种产品,假设其中6种为“酒精”类型,其余为“食品”,TableView的第一行将其“名称”列设置为“酒精”,其余列为空白,而整行的格式略有不同(主要是bg颜色和字体)。然后,在最后一个“酒精”类产品之后,会出现另一个类似的行,如“食品”等 你知道我该怎么

我很难找到解决方法:

我希望在场景图中有一个表视图,它将表示某个类(产品)。该类有一个字符串类型字段,我想用它对产品进行分类。因此,我希望在循环中填充TableView,添加所有产品,同时在每个特定类型的产品之前添加“类别”行

因此,如果我有10种产品,假设其中6种为“酒精”类型,其余为“食品”,TableView的第一行将其“名称”列设置为“酒精”,其余列为空白,而整行的格式略有不同(主要是bg颜色和字体)。然后,在最后一个“酒精”类产品之后,会出现另一个类似的行,如“食品”等

你知道我该怎么做吗?据我所知,TableView只能表示一个类,我不能创建多个表,因为滚动时需要固定列标题功能


非常感谢。

您可以使用CSS完成几乎所有这一切,然后使用表上的
行工厂和一个单元格工厂实现一个小魔术,该工厂只为要“隐藏”的单元格设置一个CSS类,用于相同类型的后续项

行工厂
中,创建一个
表行
,该行观察表中的项目列表,并观察其自己的索引,并根据是否为其类型的第一个项目在该行上设置CSS PsuedoClass:

    PseudoClass firstOfTypePseudoclass = PseudoClass.getPseudoClass("first-of-type");

    table.setRowFactory(t -> {
        TableRow<Product> row = new TableRow<>();
        InvalidationListener listener = obs -> 
            row.pseudoClassStateChanged(firstOfTypePseudoclass, 
                    isFirstOfType(table.getItems(), row.getIndex()));
        table.getItems().addListener(listener);
        row.indexProperty().addListener(listener);
        return row ;
    });
通过将该样式表另存为first-of-type-table.css,下面的完整示例可以满足您的需要:

import java.util.Comparator;
import java.util.List;
import java.util.function.Function;

import javafx.application.Application;
import javafx.beans.InvalidationListener;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.SortedList;
import javafx.css.PseudoClass;
import javafx.geometry.HPos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableRow;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.ColumnConstraints;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Priority;
import javafx.stage.Stage;

public class FirstOfTypeTableExample extends Application {

    @Override
    public void start(Stage primaryStage) {
        TableView<Product> table = new TableView<>() ;

        ObservableList<Product> products = FXCollections.observableArrayList();
        table.setItems(new SortedList<>(products, Comparator.comparing(Product::getType)));

        TableColumn<Product, Product.Type> typeColumn = column("Type", Product::typeProperty);
        typeColumn.setCellFactory(c -> {
            TableCell<Product, Product.Type> cell = new TableCell<Product, Product.Type>() {
                @Override
                public void updateItem(Product.Type type, boolean empty) {
                    super.updateItem(type, empty);
                    if (type == null) {
                        setText(null);
                    } else {
                        setText(type.toString());
                    }
                }
            };
            cell.getStyleClass().add("type-cell");
            return cell ;
        });

        table.getColumns().add(typeColumn);
        table.getColumns().add(column("Name", Product::nameProperty));
        table.getColumns().add(column("Price", Product::priceProperty));

        PseudoClass firstOfTypePseudoclass = PseudoClass.getPseudoClass("first-of-type");

        table.setRowFactory(t -> {
            TableRow<Product> row = new TableRow<>();
            InvalidationListener listener = obs -> 
                row.pseudoClassStateChanged(firstOfTypePseudoclass, 
                        isFirstOfType(table.getItems(), row.getIndex()));
            table.getItems().addListener(listener);
            row.indexProperty().addListener(listener);
            return row ;
        });

        products.addAll(
                new Product("Chips", 1.99, Product.Type.FOOD),
                new Product("Ice Cream", 3.99, Product.Type.FOOD),
                new Product("Beer", 8.99, Product.Type.DRINK),
                new Product("Laptop", 1099.99, Product.Type.OTHER));

        GridPane editor = createEditor(products);

        BorderPane root = new BorderPane(table, null, null, editor, null) ;
        Scene scene = new Scene(root, 600, 400);
        scene.getStylesheets().add("first-of-type-table.css");
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    private boolean isFirstOfType(List<Product> products, int index) {
        if (index < 0 || index >= products.size()) {
            return false ;
        }
        if (index == 0) {
            return true ;
        }
        if (products.get(index).getType().equals(products.get(index-1).getType())) {
            return false ;
        } else {
            return true ;
        }
    }

    private GridPane createEditor(ObservableList<Product> products) {
        ComboBox<Product.Type> typeSelector = new ComboBox<>(FXCollections.observableArrayList(Product.Type.values()));
        TextField nameField = new TextField();
        TextField priceField = new TextField();
        Button add = new Button("Add");
        add.setOnAction(e -> {
            Product product = new Product(nameField.getText(), 
                    Double.parseDouble(priceField.getText()), typeSelector.getValue());
            products.add(product);
            nameField.setText("");
            priceField.setText("");
        });

        GridPane editor = new GridPane();
        editor.addRow(0, new Label("Type:"), typeSelector);
        editor.addRow(1, new Label("Name:"), nameField);
        editor.addRow(2, new Label("Price:"), priceField);
        editor.add(add, 3, 0, 2, 1);

        GridPane.setHalignment(add, HPos.CENTER);
        ColumnConstraints leftCol = new ColumnConstraints();
        leftCol.setHalignment(HPos.RIGHT);
        leftCol.setHgrow(Priority.NEVER);

        editor.getColumnConstraints().addAll(leftCol, new ColumnConstraints());
        editor.setHgap(10);
        editor.setVgap(5);
        return editor;
    }


    private <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 Product {


        public enum Type {FOOD, DRINK, OTHER }

        private final ObjectProperty<Type> type = new SimpleObjectProperty<>();
        private final StringProperty name = new SimpleStringProperty();
        private final DoubleProperty price = new SimpleDoubleProperty();

        public Product(String name, double price, Type type) {
            setName(name);
            setPrice(price);
            setType(type);
        }

        public final StringProperty nameProperty() {
            return this.name;
        }
        public final java.lang.String getName() {
            return this.nameProperty().get();
        }
        public final void setName(final java.lang.String name) {
            this.nameProperty().set(name);
        }
        public final DoubleProperty priceProperty() {
            return this.price;
        }
        public final double getPrice() {
            return this.priceProperty().get();
        }
        public final void setPrice(final double price) {
            this.priceProperty().set(price);
        }
        public final ObjectProperty<Type> typeProperty() {
            return this.type;
        }
        public final FirstOfTypeTableExample.Product.Type getType() {
            return this.typeProperty().get();
        }
        public final void setType(final FirstOfTypeTableExample.Product.Type type) {
            this.typeProperty().set(type);
        }


    }


    public static void main(String[] args) {
        launch(args);
    }
}
import java.util.Comparator;
导入java.util.List;
导入java.util.function.function;
导入javafx.application.application;
导入javafx.beans.InvalizationListener;
导入javafx.beans.property.DoubleProperty;
导入javafx.beans.property.ObjectProperty;
导入javafx.beans.property.SimpleDoubleProperty;
导入javafx.beans.property.SimpleObject属性;
导入javafx.beans.property.SimpleStringProperty;
导入javafx.beans.property.StringProperty;
导入javafx.beans.value.observeValue;
导入javafx.collections.FXCollections;
导入javafx.collections.ObservableList;
导入javafx.collections.transformation.SortedList;
导入javafx.css.pseudo类;
导入javafx.geometry.HPos;
导入javafx.scene.scene;
导入javafx.scene.control.Button;
导入javafx.scene.control.ComboBox;
导入javafx.scene.control.Label;
导入javafx.scene.control.TableCell;
导入javafx.scene.control.TableColumn;
导入javafx.scene.control.TableRow;
导入javafx.scene.control.TableView;
导入javafx.scene.control.TextField;
导入javafx.scene.layout.BorderPane;
导入javafx.scene.layout.ColumnConstraints;
导入javafx.scene.layout.GridPane;
导入javafx.scene.layout.Priority;
导入javafx.stage.stage;
公共类FirstOfTypeTableExample扩展了应用程序{
@凌驾
公共无效开始(阶段primaryStage){
TableView table=新TableView();
ObservableList products=FXCollections.observableArrayList();
表.setItems(新的SortedList(products,Comparator.comparing(Product::getType)));
TableColumn typeColumn=列(“类型”,产品::typeProperty);
typeColumn.setCellFactory(c->{
TableCell单元格=新的TableCell(){
@凌驾
public void updateItem(Product.Type类型,布尔空){
super.updateItem(类型,空);
if(type==null){
setText(空);
}否则{
setText(type.toString());
}
}
};
cell.getStyleClass().add(“类型单元格”);
返回单元;
});
table.getColumns().add(typeColumn);
table.getColumns().add(column(“Name”,Product::nameProperty));
table.getColumns().add(列(“Price”,Product::priceProperty));
PseudoClass firstOfTypePseudoclass=PseudoClass.getPseudoClass(“类型的第一个”);
表3.setRowFactory(t->{
TableRow行=新TableRow();
InvalizationListener侦听器=obs->
row.pseudoClassStateChanged(firstOfTypePseudoclass,
isFirstOfType(table.getItems(),row.getIndex());
table.getItems().addListener(listener);
row.indexProperty().addListener(listener);
返回行;
});
products.addAll(
新产品(“芯片”,1.99,产品类型,食品),
新产品(“冰淇淋”,3.99,产品类型,食品),
新产品(“啤酒”,8.99,产品类型,饮料),
新产品(“笔记本电脑”,1099.99,产品类型。其他));
GridPane编辑器=createEditor(产品);
BorderPane根=新的BorderPane(表,null,null,编辑器,null);
场景=新场景(root,600400);
scene.getStylesheets().add(“table.css类型的第一个”);
初级阶段。场景(场景);
primaryStage.show();
}
私有布尔值isFirstOfType(列出产品、整数索引){
如果(索引<0 | |索引>=products.size()){
返回false;
}
如果(索引==0){
返回true;
}
if(products.get(index).getType()等于(products.get(index-1).getType()){
返回false;
}否则{
返回true;
}
}
私有GridPane createEditor(ObservableList产品){
ComboBox typeSelector=新的ComboBox(FXCollections.observableArrayList(Product.Type.values());
TextField nameField=新的TextField();
TextField priceField=新TextField();
按钮添加=新按钮(“添加”);
添加.设置操作(e->{
Product Product=新产品(nameField.getText(),
Double.parseDouble(priceField.getTex
.table-row-cell:first-of-type {
    -fx-background-color: antiquewhite ;
}
.table-row-cell:first-of-type:odd {
    -fx-background-color: derive(antiquewhite, 20%);
}
.table-row-cell .type-cell {
    visibility: hidden ;
}
.table-row-cell:first-of-type .type-cell {
    visibility: visible ;
}
import java.util.Comparator;
import java.util.List;
import java.util.function.Function;

import javafx.application.Application;
import javafx.beans.InvalidationListener;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.SortedList;
import javafx.css.PseudoClass;
import javafx.geometry.HPos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableRow;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.ColumnConstraints;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Priority;
import javafx.stage.Stage;

public class FirstOfTypeTableExample extends Application {

    @Override
    public void start(Stage primaryStage) {
        TableView<Product> table = new TableView<>() ;

        ObservableList<Product> products = FXCollections.observableArrayList();
        table.setItems(new SortedList<>(products, Comparator.comparing(Product::getType)));

        TableColumn<Product, Product.Type> typeColumn = column("Type", Product::typeProperty);
        typeColumn.setCellFactory(c -> {
            TableCell<Product, Product.Type> cell = new TableCell<Product, Product.Type>() {
                @Override
                public void updateItem(Product.Type type, boolean empty) {
                    super.updateItem(type, empty);
                    if (type == null) {
                        setText(null);
                    } else {
                        setText(type.toString());
                    }
                }
            };
            cell.getStyleClass().add("type-cell");
            return cell ;
        });

        table.getColumns().add(typeColumn);
        table.getColumns().add(column("Name", Product::nameProperty));
        table.getColumns().add(column("Price", Product::priceProperty));

        PseudoClass firstOfTypePseudoclass = PseudoClass.getPseudoClass("first-of-type");

        table.setRowFactory(t -> {
            TableRow<Product> row = new TableRow<>();
            InvalidationListener listener = obs -> 
                row.pseudoClassStateChanged(firstOfTypePseudoclass, 
                        isFirstOfType(table.getItems(), row.getIndex()));
            table.getItems().addListener(listener);
            row.indexProperty().addListener(listener);
            return row ;
        });

        products.addAll(
                new Product("Chips", 1.99, Product.Type.FOOD),
                new Product("Ice Cream", 3.99, Product.Type.FOOD),
                new Product("Beer", 8.99, Product.Type.DRINK),
                new Product("Laptop", 1099.99, Product.Type.OTHER));

        GridPane editor = createEditor(products);

        BorderPane root = new BorderPane(table, null, null, editor, null) ;
        Scene scene = new Scene(root, 600, 400);
        scene.getStylesheets().add("first-of-type-table.css");
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    private boolean isFirstOfType(List<Product> products, int index) {
        if (index < 0 || index >= products.size()) {
            return false ;
        }
        if (index == 0) {
            return true ;
        }
        if (products.get(index).getType().equals(products.get(index-1).getType())) {
            return false ;
        } else {
            return true ;
        }
    }

    private GridPane createEditor(ObservableList<Product> products) {
        ComboBox<Product.Type> typeSelector = new ComboBox<>(FXCollections.observableArrayList(Product.Type.values()));
        TextField nameField = new TextField();
        TextField priceField = new TextField();
        Button add = new Button("Add");
        add.setOnAction(e -> {
            Product product = new Product(nameField.getText(), 
                    Double.parseDouble(priceField.getText()), typeSelector.getValue());
            products.add(product);
            nameField.setText("");
            priceField.setText("");
        });

        GridPane editor = new GridPane();
        editor.addRow(0, new Label("Type:"), typeSelector);
        editor.addRow(1, new Label("Name:"), nameField);
        editor.addRow(2, new Label("Price:"), priceField);
        editor.add(add, 3, 0, 2, 1);

        GridPane.setHalignment(add, HPos.CENTER);
        ColumnConstraints leftCol = new ColumnConstraints();
        leftCol.setHalignment(HPos.RIGHT);
        leftCol.setHgrow(Priority.NEVER);

        editor.getColumnConstraints().addAll(leftCol, new ColumnConstraints());
        editor.setHgap(10);
        editor.setVgap(5);
        return editor;
    }


    private <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 Product {


        public enum Type {FOOD, DRINK, OTHER }

        private final ObjectProperty<Type> type = new SimpleObjectProperty<>();
        private final StringProperty name = new SimpleStringProperty();
        private final DoubleProperty price = new SimpleDoubleProperty();

        public Product(String name, double price, Type type) {
            setName(name);
            setPrice(price);
            setType(type);
        }

        public final StringProperty nameProperty() {
            return this.name;
        }
        public final java.lang.String getName() {
            return this.nameProperty().get();
        }
        public final void setName(final java.lang.String name) {
            this.nameProperty().set(name);
        }
        public final DoubleProperty priceProperty() {
            return this.price;
        }
        public final double getPrice() {
            return this.priceProperty().get();
        }
        public final void setPrice(final double price) {
            this.priceProperty().set(price);
        }
        public final ObjectProperty<Type> typeProperty() {
            return this.type;
        }
        public final FirstOfTypeTableExample.Product.Type getType() {
            return this.typeProperty().get();
        }
        public final void setType(final FirstOfTypeTableExample.Product.Type type) {
            this.typeProperty().set(type);
        }


    }


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