JavaFX TableView:复制在单元格中呈现的文本

时间:2016-02-08 13:22:03

标签: javafx

我想在TableView中实现复制功能。要复制的文本应该是在单元格中呈现的实际文本,而不是要呈现的数据模型的.toString版本,也就是说,它应该是单元格的.getText。

有几种方法可以从单元格中获取数据。但是,要获取渲染的单元格文本内容,过程似乎是这样的:

  • 获取单元格数据。
  • 进入细胞工厂。
  • 使用工厂创建单元格。
  • 使用单元格的updateItem方法呈现数据,然后使用getText获取呈现的文本。

由于updateItem受到保护,最后一步无法实现。

如何访问TableView中任何给定单元格的渲染文本?

1 个答案:

答案 0 :(得分:2)

您概述的过程涉及从视图(单元格)获取文本(即数据),这违反了MVC / MVP设计背后的原则。从实际角度来看,它涉及创建UI元素(创建起来很昂贵)以基本上操纵数据(创建和处理的成本通常要低得多)。此外,根据您正在做的事情,UI元素可能会对您的代码施加额外的线程限制(因为它们本质上是单线程的)。

如果您需要使用"格式化文本"在单元格之外的功能,您应该将其分解到其他地方,并在" copy"您需要和在单元格中的功能。至少,这可以通过制作"格式文本"细胞工厂的功能部分:

import java.util.function.Function;

import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.util.Callback;

public class FormattingTableCellFactory<S, T> implements Callback<TableColumn<S, T>, TableCell<S, T>> {

    private final Function<T, String> formatter ;

    public FormattingTableCellFactory(Function<T, String> formatter) {
        this.formatter = formatter ;
    }

    public FormattingTableCellFactory() {
        this(T::toString);
    }

    public final Function<T, String> getFormatter() {
        return formatter ;
    }

    @Override
    public TableCell<S,T> call(TableColumn<S,T> col) {
        return new TableCell<S,T>() {
            @Override
            protected void updateItem(T item, boolean empty) {
                super.updateItem(item, empty);
                setText(item == null ? null : formatter.apply(item));
            }
        };
    }
}

(显然你可以扩展它以生成带有图形内容的更复杂的单元格等。)

现在,您的复制功能可以简单地将格式化程序应用于数据,而无需参考任何实际单元格。这是一个SSCCE:

import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.function.Function;

import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.SelectionMode;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;

public class Main extends Application {

    private String copy(TableView<Product> table) {

        StringBuilder sb = new StringBuilder();
        for (Product p : table.getSelectionModel().getSelectedItems()) {
            List<String> data = new ArrayList<>();
            for (TableColumn<Product, ?> column : table.getColumns()) {
                Function<Object, String> formatter = ((FormattingTableCellFactory) column.getCellFactory()).getFormatter();
                data.add(formatter.apply(column.getCellObservableValue(p).getValue()));
            }
            sb.append(String.join("\t", data)).append("\n");
        }
        return sb.toString() ;
    }

    @Override
    public void start(Stage primaryStage) {
        TableView<Product> table = new TableView<>();
        table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);

        table.getColumns().add(column("Product", Product::nameProperty, String::toString));
        NumberFormat currencyFormat = NumberFormat.getCurrencyInstance();
        table.getColumns().add(column("Price", Product::priceProperty, currencyFormat::format));

        Random rng = new Random();
        for (int i = 1; i <= 100; i++) {
            table.getItems().add(new Product("Product "+i, rng.nextDouble()*100));
        }

        Button copy = new Button("Copy");
        copy.setOnAction(e -> System.out.println(copy(table)));
        copy.disableProperty().bind(Bindings.isEmpty(table.getSelectionModel().getSelectedItems()));

        BorderPane root = new BorderPane(table);
        BorderPane.setAlignment(copy, Pos.CENTER);
        BorderPane.setMargin(copy, new Insets(10));
        root.setBottom(copy);
        Scene scene = new Scene(root, 600, 600);
        primaryStage.setScene(scene);
        primaryStage.show();
    }


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

    public static class Product {
        private final StringProperty name = new SimpleStringProperty();
        private final DoubleProperty price = new SimpleDoubleProperty() ;

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

        public final StringProperty nameProperty() {
            return this.name;
        }


        public final String getName() {
            return this.nameProperty().get();
        }


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

您可以摆脱较少的类型安全代码,但代价是灵活性较低:

private final Function<String, String> defaultFormatter = Function.identity() ;
private final Function<Number, String> priceFormatter = DecimalFormat.getCurrencyInstance()::format  ;

private String copy(TableView<Product> table) {
    return table.getSelectionModel().getSelectedItems().stream().map(product -> 
        String.format("%s\t%s", 
                defaultFormatter.apply(product.getName()),
                priceFormatter.apply(product.getPrice()))
    ).collect(Collectors.joining("\n"));
}

    table.getColumns().add(column("Product", Product::nameProperty, defaultFormatter));
    table.getColumns().add(column("Price", Product::priceProperty, priceFormatter));