我正在寻找一种通用方式(即,可以应用于任意TableView
or )从/TreeTableView
复制数据“如您所见” 。我发现了几篇关于如何从(这里和这里)复制内容的帖子,但重要的部分“如你所见”是所有这些内容的问题。TableView
TreeTableView
TableView
我看到的所有解决方案都依赖于获取与每个单元格相关的数据(很容易做到),并调用.toString()
它。问题是,当您将一种类型(比如说 a Long
)存储为列中的实际数据,然后定义一个自定义单元工厂以将其显示为 aString
时(这超出了您这样做的范围,我只想要一个方法适用于此类表视图):
TableColumn<MyData, Long> timeColumn;
<...>
timeColumn.setCellFactory(param -> new TableCell<MyData, Long>() {
@Override
protected void updateItem(Long item, boolean empty) {
super.updateItem(item, empty);
if (item == null || empty) {
super.setText(null);
} else {
super.setText(LocalDate.from(Instant.ofEpochMilli(item)).format(DateTimeFormatter.ISO_DATE));
}
}
}
);
那些基于将基础数据(在Long
此处)转换为的方法String
显然不起作用,因为它们将复制数字而不是日期(这是用户在表中看到的)。
可能的(设想的)解决方案:
如果我可以将手放在
TableCell
与每个表格单元格关联的对象上,我可以做到TableCell.getText()
,我们就完成了。不幸的是,TableView
不允许这样做(我错过了一种方法吗?)我可以很容易地获得
CellFactory
与该列关联的内容,因此创建一个新的TableCell
(与表视图中现有的相同):TableCell<T, ?> cell = column.getCellFactory().call(column);
那么问题是没有办法(再次,我错过了吗?)强制 a
TableCell
调用该updateItem
方法!我尝试使用commitEdit(T newValue)
,但它非常混乱:里面有检查,所以你需要制作整个东西 (column, row, table)Editable
,然后startEdit
先调用。2a. 所以唯一对我有用的解决方案是使用 Reflection 来调用 protected
updateItem
,这感觉有点脏:// For TableView: T selectedItem = <...>; // OR for TreeTableView: TreeItem<T> selectedItem = <...>; TableCell<T, Object> cell = (TableCell<T, Object>) column.getCellFactory().call(column); try { Method update = cell.getClass().getDeclaredMethod("updateItem", Object.class, boolean.class); update.setAccessible(true); Object data = column.getCellData(selectedItem); update.invoke(cell, data, data == null); } catch (Exception ex) { logger.warn("Failed to update item: ", ex); } if (cell.getText() != null) { return cell.getText().replaceAll(fieldSeparator, ""); } else { return ""; }
我将不胜感激任何评论,即如果这可以用更少的血来实现。或者可能表明我的解决方案存在一些我错过的问题。
这是完整的代码,以防有人想使用它(尽管它很丑:)
package com.mycompany.util;
import com.google.common.collect.Lists;
import javafx.beans.property.ObjectProperty;
import javafx.scene.control.*;
import javafx.scene.input.Clipboard;
import javafx.scene.input.DataFormat;
import javafx.scene.input.KeyCode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
public class TableViewCopyable {
private static final Logger logger = LoggerFactory.getLogger(TableViewCopyable.class.getName());
protected final String defaultFieldSep;
protected final String defaultLineSep;
protected TableViewCopyable(String defaultFieldSep, String defaultLineSep) {
this.defaultFieldSep = defaultFieldSep;
this.defaultLineSep = defaultLineSep;
}
protected static final <T> void copyToClipboard(List<T> rows, Function<List<T>, String> extractor) {
logger.info("Copied " + rows.size() + " item(s) to clipboard");
Clipboard.getSystemClipboard().setContent(Collections.singletonMap(DataFormat.PLAIN_TEXT, extractor.apply(rows)));
}
public static TableViewCopyable with(String fieldSep, String lineSep) {
return new TableViewCopyable(fieldSep, lineSep);
}
public static TableViewCopyable toCsv() {
// When using System.lineSeparator() as line separator, there appears to be an extra line break :-/
return with(",", "\n");
}
public final <T> void makeCopyable(TableView<T> table, Function<List<T>, String> extractor) {
table.setOnKeyPressed(event -> {
if (event.getCode().equals(KeyCode.C) && event.isControlDown() || event.isControlDown() && event.getCode().equals(KeyCode.INSERT)) {
// "Smart" copying: if single selection, copy all by default. Otherwise copy selected by default
boolean selectedOnly = table.getSelectionModel().getSelectionMode().equals(SelectionMode.MULTIPLE);
copyToClipboard(getItemsToCopy(table, selectedOnly), extractor);
}
});
MenuItem copy = new MenuItem("Copy selected");
copy.setOnAction(event -> copyToClipboard(table.getSelectionModel().getSelectedItems(), extractor));
MenuItem copyAll = new MenuItem("Copy all");
copyAll.setOnAction(event -> copyToClipboard(table.getItems(), extractor));
addToContextMenu(table.contextMenuProperty(), copy, copyAll);
}
public final <T> void makeCopyable(TreeTableView<T> table, Function<List<TreeItem<T>>, String> extractor) {
table.setOnKeyPressed(event -> {
if (event.getCode().equals(KeyCode.C) && event.isControlDown() || event.isControlDown() && event.getCode().equals(KeyCode.INSERT)) {
// "Smart" copying: if single selection, copy all by default. Otherwise copy selected by default
boolean selectedOnly = table.getSelectionModel().getSelectionMode().equals(SelectionMode.MULTIPLE);
copyToClipboard(getItemsToCopy(table, selectedOnly), extractor);
}
});
MenuItem copy = new MenuItem("Copy selected");
copy.setOnAction(event -> copyToClipboard(getItemsToCopy(table, true), extractor));
MenuItem copyAll = new MenuItem("Copy all (expanded only)");
copyAll.setOnAction(event -> copyToClipboard(getItemsToCopy(table, false), extractor));
addToContextMenu(table.contextMenuProperty(), copy, copyAll);
}
protected <T> List<TreeItem<T>> getItemsToCopy(TreeTableView<T> table, boolean selectedOnly) {
if (selectedOnly) {
// If multiple selection is allowed, copy only selected by default:
return table.getSelectionModel().getSelectedItems();
} else {
// Otherwise, copy everything
List<TreeItem<T>> list = Lists.newArrayList();
for (int i = 0; i < table.getExpandedItemCount(); i++) {
list.add(table.getTreeItem(i));
}
return list;
}
}
protected <T> List<T> getItemsToCopy(TableView<T> table, boolean selectedOnly) {
if (selectedOnly) {
// If multiple selection is allowed, copy only selected by default:
return table.getSelectionModel().getSelectedItems();
} else {
return table.getItems();
}
}
protected void addToContextMenu(ObjectProperty<ContextMenu> menu, MenuItem... items) {
if (menu.get() == null) {
menu.set(new ContextMenu(items));
} else {
for (MenuItem item : items) {
menu.get().getItems().add(item);
}
}
}
public final <T> void makeCopyable(TableView<T> table, String fieldSeparator) {
makeCopyable(table, csvVisibleColumns(table, fieldSeparator));
}
public final <T> void makeCopyable(TreeTableView<T> table, String fieldSeparator) {
makeCopyable(table, csvVisibleColumns(table, fieldSeparator));
}
public final <T> void makeCopyable(TableView<T> table) {
makeCopyable(table, csvVisibleColumns(table, defaultFieldSep));
}
public final <T> void makeCopyable(TreeTableView<T> table) {
makeCopyable(table, defaultFieldSep);
}
protected <T> String extractDataFromCell(IndexedCell<T> cell, Object data, String fieldSeparator) {
try {
Method update = cell.getClass().getDeclaredMethod("updateItem", Object.class, boolean.class);
update.setAccessible(true);
update.invoke(cell, data, data == null);
} catch (Exception ex) {
logger.warn("Failed to updated item: ", ex);
}
if (cell.getText() != null) {
return cell.getText().replaceAll(fieldSeparator, "");
} else {
return "";
}
}
public final <T> Function<List<T>, String> csvVisibleColumns(TableView<T> table, String fieldSeparator) {
return (List<T> items) -> {
StringBuilder builder = new StringBuilder();
// Write table header
builder.append(table.getVisibleLeafColumns().stream().map(TableColumn::getText).collect(Collectors.joining(fieldSeparator))).append(defaultLineSep);
items.forEach(item -> builder.append(
table.getVisibleLeafColumns()
.stream()
.map(col -> extractDataFromCell(((TableColumn<T, Object>) col).getCellFactory().call((TableColumn<T, Object>) col), col.getCellData(item), fieldSeparator))
.collect(Collectors.joining(defaultFieldSep))
).append(defaultLineSep));
return builder.toString();
};
}
public final <T> Function<List<TreeItem<T>>, String> csvVisibleColumns(TreeTableView<T> table, String fieldSeparator) {
return (List<TreeItem<T>> items) -> {
StringBuilder builder = new StringBuilder();
// Write table header
builder.append(table.getVisibleLeafColumns().stream().map(TreeTableColumn::getText).collect(Collectors.joining(fieldSeparator))).append(defaultLineSep);
items.forEach(item -> builder.append(
table.getVisibleLeafColumns()
.stream()
.map(col -> extractDataFromCell(((TreeTableColumn<T, Object>) col).getCellFactory().call((TreeTableColumn<T, Object>) col), col.getCellData(item), fieldSeparator))
.collect(Collectors.joining(defaultFieldSep))
).append(defaultLineSep));
return builder.toString();
};
}
}
那么用法就很简单了:
TableViewCopyable.toCsv().makeCopyable(someTreeTableView);
TableViewCopyable.toCsv().makeCopyable(someTableView);
谢谢!