在复制JTable的单元格时,如何修改HTML?

前端开发 2026-07-09

我有一个非常简单 JTable,类似于下面这个最小、可复现的示例

import module java.base;
import module java.desktop;

void main()
{

    SwingUtilities
        .invokeLater
        (
            () ->
            {

                JOptionPane
                    .showMessageDialog
                    (
                        null,
                        new JTable
                        (
                            new Object[][]
                            {
                                new Object[]{1, 2, 3},
                                new Object[]{4, 5, 6},
                                new Object[]{7777, 88888, 99999}
                            },
                            new Object[]{"abc", "xyz", "tuv"}

                        )
                    )
                    ;

            }
        )
        ;

}

当用户选中表格的全部内容并复制后,粘贴的位置不同,渲染的结果也会不同。

  • 如果粘贴到Notepad++,它呈现为以制表符分隔的字符串列表。
  • 如果在浏览器中的Gmail粘贴,它会渲染为一个HTML表格,包含字面量的 <table> 标签等。

那个HTML的效果很酷,但这是意料之中的——Swing的底层确实用了一点HTML,所以我也不感到惊讶。

但我想能够修改复制后的数据,比如给它加上边框。 在Swing中实现这一点的最简单的方法是什么?

显然,我可以在数据落入用户剪贴板后自行修改或生成HTML,但我会把那视为最后的手段。

最理想的情况是我可以使用类似 myTable.setBorder(BorderFactory.createLineBorder()) 的东西,让它为我把设置搞定。

显然,单元格是由诸如 ListCellRenderer 之类的东西处理的,所以我大概也能做到。

但我的目标是在单元格的HTML上找到最简单的修改方式。比如,为每个单元格创建边框。

解决方案

你看到的是JTable的默认TransferHandler的行为。如果你想改变这一行为,需要将你自定义的TransferHandler传递给JTable继承自的 setTransferHandler 方法。

改变JTable的外观(例如设置边框)将不起作用。剪贴板中的内容基于表格的数据,而不是它的外观。

幸运的是,编写一个自定义的TransferHandler相对来说比较直接:

import java.io.Serial;

import java.util.Formatter;
import java.util.Arrays;
import java.util.Objects;
import java.util.stream.IntStream;

import java.awt.EventQueue;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.UnsupportedFlavorException;

import javax.swing.TransferHandler;
import javax.swing.JComponent;
import javax.swing.JTable;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
import javax.swing.table.TableModel;

public class CustomHTMLTransferHandler
extends TransferHandler {
    @Serial
    private static final long serialVersionUID = 1;

    @Override
    public boolean canImport(TransferSupport support) {
        return false;
    }

    @Override
    public void exportToClipboard(JComponent c,
                                  Clipboard clipboard,
                                  int action) {

        if (action != COPY && action != COPY_OR_MOVE) {
            return;
        }

        clipboard.setContents(createTransferable(c), null);
    }

    @Override
    public Transferable createTransferable(JComponent c) {
        if (!(c instanceof JTable)) {
            throw new IllegalArgumentException(
                "This class can only handle export of data from a JTable.");
        }

        return new Transferable() {
            @Override
            public boolean isDataFlavorSupported(DataFlavor flavor) {
                return flavor.equals(DataFlavor.stringFlavor) ||
                       flavor.equals(DataFlavor.fragmentHtmlFlavor) ||
                       flavor.equals(DataFlavor.allHtmlFlavor);
            }

            @Override
            public DataFlavor[] getTransferDataFlavors() {
                return new DataFlavor[] {
                    DataFlavor.stringFlavor,
                    DataFlavor.fragmentHtmlFlavor,
                    DataFlavor.allHtmlFlavor
                };
            }

            @Override
            public Object getTransferData(DataFlavor flavor)
            throws UnsupportedFlavorException {
                JTable table = (JTable) c;
                TableModel model = table.getModel();
                int columns = model.getColumnCount();
                IntStream rows;
                int[] selectedRows = table.getSelectedRows();
                if (selectedRows.length > 0) {
                    Arrays.sort(selectedRows);
                    rows = Arrays.stream(selectedRows)
                        .map(table::convertRowIndexToModel);
                } else {
                    rows = IntStream.range(0, model.getRowCount());
                }

                if (flavor.equals(DataFlavor.stringFlavor)) {
                    StringBuilder tsv = new StringBuilder();
                    rows.forEach(r -> {
                        for (int c = 0; c < columns; c++) {
                            if (c > 0) {
                                tsv.append('\t');
                            }
                            Object value = model.getValueAt(r, c);
                            String text = Objects.toString(value, "");
                            tsv.append(text.replace('\t', ' '));
                        }
                        tsv.append(System.lineSeparator());
                    });
                    return tsv.toString();
                } else if (flavor.equals(DataFlavor.fragmentHtmlFlavor) ||
                           flavor.equals(DataFlavor.allHtmlFlavor)) {

                    Formatter html = new Formatter();
                    html.format("<table frame='border' rules='all'>\n");
                    rows.forEach(r -> {
                        html.format("<tr>");
                        for (int c = 0; c < columns; c++) {
                            html.format("<td>");
                            Object value = model.getValueAt(r, c);
                            String text = Objects.toString(value, "");
                            text.codePoints().forEach(p -> {
                                html.format(
                                    p >= 32 && p < 127 && p != '<' && p != '&'
                                    ? "%c" : "&#%d;", p);
                            });
                        }
                        html.format("\n");
                    });
                    html.format("</table>");
                    return html.toString();
                } else {
                    throw new UnsupportedFlavorException(flavor);
                }
            }
        };
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(() -> {
            JTable table = new JTable(
                new Object[][] {
                    {1, 2, 3},
                    {4, 5, 6},
                    {7777, 88888, 99999}
                },
                new Object[] {"abc", "xyz", "tuv"}
            );
            table.setTransferHandler(new CustomHTMLTransferHandler());
            JOptionPane.showMessageDialog(null, new JScrollPane(table));
        });
    }
}
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章