0

/から「見たとおりに」データをコピーする普遍的な方法 (つまり、任意のTableVieworに適用できる) を探していました。(hereおよびhere )からコンテンツをコピーする方法に関するいくつかの投稿を見つけましたが、「ご覧のとおり」重要な部分はそれらすべての問題です。TreeTableViewTableViewTreeTableViewTableView

私が見たすべてのソリューションは、各セルに関連付けられたデータを取得し (実行するのは非常に簡単です)、それを呼び出すことに依存してい.toString()ます。問題は、1 つの型 ( a としましょうLong) を実際のデータとして列に格納し、それを a として表示するカスタム セル ファクトリを定義するString場合です (それを行う理由の範囲を超えています。メソッドが必要なだけです)。このようなテーブル ビューで動作します):

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をコピーするためです(ユーザーがテーブルに表示するもの)。

可能な (想定される) 解決策:

  1. TableCell表の各セルに関連付けられたオブジェクトを手に入れることができれば、それでTableCell.getText()完了です。残念ながら、TableViewこれは許可されていません (それを行う方法を見逃していませんか?)

  2. CellFactory列に関連付けられた を簡単に取得できるため、新しいTableCell(テーブル ビューに存在するものと同じ) を作成できます。

    TableCell<T, ?> cell = column.getCellFactory().call(column);

    問題は、a にメソッドを強制的TableCellに呼び出すupdateItem方法がないことです (繰り返しますが、見逃しましたか?)。を使用しようとしましたcommitEdit(T newValue)が、かなり面倒です。内部にチェックがあるため、すべてのもの (列、行、テーブル)を作成し、最初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);

ありがとう!

4

0 に答える 0