为什么 JTable 标头未出现在图像中?

2022-09-01 15:40:10

当OP请求代码示例时,我提供了有关在Java API或工具上捕获表格数据图像以将表格数据转换为PNG图像文件的建议。结果比我想象的要难!标头将从代码编写的 PNG 中消失。JTable

巴新

PNG

屏幕截图

enter image description here

import javax.swing.*;
import java.awt.Graphics;
import java.awt.BorderLayout;
import java.awt.image.BufferedImage;

import javax.imageio.ImageIO;
import java.io.File;

class TableImage {

    public static void main(String[] args) throws Exception {
        Object[][] data = {
            {"Hari", new Integer(23), new Double(78.23), new Boolean(true)},
            {"James", new Integer(23), new Double(47.64), new Boolean(false)},
            {"Sally", new Integer(22), new Double(84.81), new Boolean(true)}
        };

        String[] columns = {"Name", "Age", "GPA", "Pass"};

        JTable table = new JTable(data, columns);
        JScrollPane scroll = new JScrollPane(table);
        JPanel p = new JPanel(new BorderLayout());
        p.add(scroll,BorderLayout.CENTER);

        JOptionPane.showMessageDialog(null, p);

        BufferedImage bi = new BufferedImage(
            (int)p.getSize().getWidth(),
            (int)p.getSize().getHeight(),
            BufferedImage.TYPE_INT_RGB
            );

        Graphics g = bi.createGraphics();
        p.paint(g);

        JOptionPane.showMessageDialog(null, new JLabel(new ImageIcon(bi)));
        ImageIO.write(bi,"png",new File("table.png"));
    }
}

注意:我检查了camickr的屏幕图像类,并包括对该方法的调用。如果 从未在屏幕上实现过 ,但对此代码没有影响,则该方法很有用(在尝试呈现之前,该代码会在选项窗格中弹出包含该表的面板)。doLayout(Component)Component

需要什么才能呈现表头?

更新 1

更改行..

        p.paint(g);

..到(使用适当的导入)。

        p.paint(g);
        JTableHeader h = table.getTableHeader();
        h.paint(g);

..生产。。

2nd try

我会继续调整它。

更新 2

kleopatra(策略1)和camickr(策略2)各自提供了一个答案,两者都有效,并且都不需要将添加到虚拟组件中(这是一个巨大的黑客IMO)。JTable

虽然策略 2 将裁剪(或扩展)为“仅表”,但策略 1 将捕获包含表的面板。如果表包含许多条目,显示带有滚动条的截断表的图像,则这将成为问题。

虽然策略1可能会进一步调整以解决这个问题,我真的很喜欢策略2的简洁性,所以它得到了满足。

正如kleopatra所指出的那样,不需要“调整”。所以我再试一次。

更新 3

这是由camickr和kleopatra提出的方法产生的图像。我本来会把它放两次,但在我看来,它们是相同的(尽管我没有做过一个像素一个像素的比较)。

JTable in Nimbus PLAF

import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;

import javax.imageio.ImageIO;
import java.io.File;

class TableImage {

    String[] columns = {"Name", "Age", "GPA", "Pass"};
    /** Any resemblance to persons living or dead is purely incidental. */
    Object[][] data = {
        {"André", new Integer(23), new Double(47.64), new Boolean(false)},
        {"Jeanie", new Integer(23), new Double(84.81), new Boolean(true)},
        {"Roberto", new Integer(22), new Double(78.23), new Boolean(true)}
    };

    TableImage() {
    }

    public JTable getTable() {
        JTable table = new JTable(data, columns);
        table.setGridColor(new Color(115,52,158));
        table.setRowMargin(5);
        table.setShowGrid(true);

        return table;
    }

    /** Method courtesy of camickr.
    https://stackoverflow.com/questions/7369814/why-does-the-jtable-header-not-appear-in-the-image/7375655#7375655
    Requires ScreenImage class available from..
    http://tips4java.wordpress.com/2008/10/13/screen-image/ */
    public BufferedImage getImage1(JTable table) {
        JScrollPane scroll = new JScrollPane(table);

        scroll.setColumnHeaderView(table.getTableHeader());
        table.setPreferredScrollableViewportSize(table.getPreferredSize());

        JPanel p = new JPanel(new BorderLayout());
        p.add(scroll, BorderLayout.CENTER);

        BufferedImage bi = ScreenImage.createImage(p);
        return bi;
    }

    /** Method courtesy of kleopatra.
    https://stackoverflow.com/questions/7369814/why-does-the-jtable-header-not-appear-in-the-image/7372045#7372045 */
    public BufferedImage getImage2(JTable table) {
        JScrollPane scroll = new JScrollPane(table);

        table.setPreferredScrollableViewportSize(table.getPreferredSize());

        JPanel p = new JPanel(new BorderLayout());
        p.add(scroll, BorderLayout.CENTER);

        // without having been shown, fake a all-ready
        p.addNotify();

        // manually size to pref
        p.setSize(p.getPreferredSize());

        // validate to force recursive doLayout of children
        p.validate();

        BufferedImage bi = new BufferedImage(p.getWidth(), p.getHeight(), BufferedImage.TYPE_INT_RGB);

        Graphics g = bi.createGraphics();
        p.paint(g);
        g.dispose();

        return bi;
    }

    public void writeImage(BufferedImage image, String name) throws Exception {
        ImageIO.write(image,"png",new File(name + ".png"));
    }

    public static void main(String[] args) throws Exception {
        UIManager.setLookAndFeel("com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel");
        TableImage ti = new TableImage();
        JTable table;
        BufferedImage bi;

        table = ti.getTable();
        bi = ti.getImage1(table);
        ti.writeImage(bi, "1");
        JOptionPane.showMessageDialog(null, new JLabel(new ImageIcon(bi)));

        table = ti.getTable();
        bi = ti.getImage2(table);
        ti.writeImage(bi, "2");
        JOptionPane.showMessageDialog(null, new JLabel(new ImageIcon(bi)));
    }
}

两者都实现了目标。使用camickr的方法,您可以利用ScreenImage API的进一步功能。使用kleopatra的方法 - 大约十几行(减去注释和空白)纯J2SE。

虽然ScreenImage是我将来将使用和推荐的类,但使用核心J2SE的另一种方法是我可能会用于这种确切的情况。

因此,虽然“蜱虫”将与camickr一起停留,但赏金将停留在kleopatra。


答案 1

这是一个艰难的过程

感到困惑的是,那个标题根本没有出现在图像上:它没有被剪裁或什么东西,它根本没有被画出来。原因是..在将面板绘制到图像时,标题不再是层次结构的一部分。关闭选项窗格时,table.removeNotify 将删除标头。要再次添加它,请调用 addNotify,如以下代码段所示:

    JScrollPane scroll = new JScrollPane(table);
    JPanel p = new JPanel(new BorderLayout());
    p.add(scroll, BorderLayout.CENTER);
    JOptionPane.showMessageDialog(null, p);
    table.addNotify();
    p.doLayout();
    BufferedImage bi = new BufferedImage(p.getWidth() + 100,
            p.getHeight() + 100, BufferedImage.TYPE_INT_RGB);

    Graphics g = bi.createGraphics();
    p.paint(g);
    g.dispose();

[在编辑中解决]我仍然不明白为什么面板显示为空,而没有首先显示在选项窗格中 - 通常是..

   p.doLayout();
   p.setSize(p.getPreferredSize()) 

...会做的

编辑

最后解决的困惑是:要强制对窗格进行递归重新布局,必须引导沿线的所有容器相信它们有一个对等体 - 验证被优化为在没有对等节点时什么都不做。在面板上(而不是在桌子上)执行addNotify并验证可以解决问题

    //        JOptionPane.showMessageDialog(null, p);
    // without having been shown, fake a all-ready
    p.addNotify();
    // manually size to pref
    p.setSize(p.getPreferredSize());
    // validate to force recursive doLayout of children
    p.validate();
    BufferedImage bi = new BufferedImage(p.getWidth() + 100,
            p.getHeight() + 100, BufferedImage.TYPE_INT_RGB);

    Graphics g = bi.createGraphics();
    p.paint(g);
    g.dispose();

    JOptionPane.showMessageDialog(null, new JLabel(new ImageIcon(bi)));

未进行副作用测试。

编辑 2

只是抱怨一下..

策略 1 可能会进一步调整以绕过该 [截断的表列]

实际上不需要调整(或者与ScreenImage相同的调整,取决于你认为:)调整 - 两者都要求通过设置表的prefViewportSize来使JScrollPane完成其工作,两者中的行完全相同:

    // strategy 1
    table.setPreferredScrollableViewportSize(table.getPreferredSize());
    p.addNotify();

    // strategy 2
    scroll.setColumnHeaderView(table.getTableHeader());
    table.setPreferredScrollableViewportSize(table.getPreferredSize());

答案 2

此线程不需要在不首先显示表格的情况下呈现表格,但这是最终目标

ScreenImage处理这个问题。

您必须手动将标头添加到滚动窗格中。

import javax.swing.*;
import java.awt.Graphics;
import java.awt.BorderLayout;
import java.awt.image.BufferedImage;

import javax.imageio.ImageIO;
import java.io.File;

class TableImage {

    public static void main(String[] args) throws Exception {
        Object[][] data = {
            {"Hari", new Integer(23), new Double(78.23), new Boolean(true)},
            {"James", new Integer(23), new Double(47.64), new Boolean(false)},
            {"Sally", new Integer(22), new Double(84.81), new Boolean(true)}
        };

        String[] columns = {"Name", "Age", "GPA", "Pass"};

        JTable table = new JTable(data, columns);
        JScrollPane scroll = new JScrollPane(table);

        scroll.setColumnHeaderView(table.getTableHeader());
        table.setPreferredScrollableViewportSize(table.getPreferredSize());

        JPanel p = new JPanel(new BorderLayout());
        p.add(scroll, BorderLayout.CENTER);

        BufferedImage bi = ScreenImage.createImage(p);

        JOptionPane.showMessageDialog(null, new JLabel(new ImageIcon(bi)));
        ImageIO.write(bi,"png",new File("table.png"));
    }
}

注意:Kleopatra建议使用面板上的addNotify()将不适用于ScreenImage。addNotify() 方法生成组件,而 ScreenImage 代码将仅为不可显示的组件设置组件的布局。我可能会考虑使这一点更加普遍。displayable


推荐