使用重写的单元工厂从TableView / TreeTableView复制

时间:2016-01-19 13:10:48

标签: javafx copy tableview treetableview

我一直在寻找一种通用的方式(即,可以应用于任意TableViewTreeTableView)来复制数据" 如你所见 "来自TableView / TreeTableView。我发现了一些关于如何从TableViewherehere)复制内容的帖子,但重要部分" 正如您所见"是所有这些问题。

我看到的所有解决方案都依赖于获取与每个单元格关联的数据(非常容易),并在其上调用.toString()。问题是,当您将一种类型(让我们说Long)存储为列中的实际数据时,然后定义自定义单元格工厂以将其显示为String(它&# 39;超出了你为什么这样做的范围,我只想要一个适用于这种表视图的方法):

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的方法显然不起作用,因为它们会复制数字而不是 date (这是用户在表格中看到的内容)。

可能的(预想的)解决方案:

  1. 如果我可以抓住与每个表格单元相关联的TableCell对象,我可以TableCell.getText(),我们就完成了。不幸的是,TableView不允许这样做(我错过了一种方法吗?)

  2. 我可以轻松获取与该列相关联的CellFactory,从而创建一个新的TableCell(与表视图中存在的TableCell<T, ?> cell = column.getCellFactory().call(column);相同):

    TableCell

    那么问题是没有办法(再一次,我是否想念它?)强迫updateItem调用commitEdit(T newValue)方法!我尝试使用Editable,但它非常混乱:里面有检查,所以你需要制作整个内容(列,行,表)startEdit,然后调用{{1首先。

    2a上。因此,唯一适用于我的解决方案是使用Reflection来调用受保护的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 "";
    }
    
  3. 我很感激任何评论,即如果能用更少的血来实现这一点。或者可能表明我错过了我的解决方案的一些问题。

    这里有完整的代码,以防有人想要使用它(尽管它的丑陋:)

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

    谢谢!

0 个答案:

没有答案