我想在TableView中实现复制功能。要复制的文本应该是在单元格中呈现的实际文本,而不是要呈现的数据模型的.toString版本,也就是说,它应该是单元格的.getText。
有几种方法可以从单元格中获取数据。但是,要获取渲染的单元格文本内容,过程似乎是这样的:
updateItem
方法呈现数据,然后使用getText
获取呈现的文本。由于updateItem
受到保护,最后一步无法实现。
如何访问TableView中任何给定单元格的渲染文本?
答案 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));