【问题标题】:Why does the JTable header not appear in the image?为什么 JTable 表头没有出现在图像中?
【发布时间】:2011-11-14 06:00:59
【问题描述】:

当 OP 请求代码示例时,我正在提供有关在 Java API or Tool to convert tabular data into PNG image file 上捕获表格数据图像的建议。结果比我想象的要难! JTable 标头从代码写入的 PNG 中消失。

PNG

屏幕截图

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 的 Screen Image 类并包含对 doLayout(Component) 方法的调用。如果 Component 从未在屏幕上实现,但对这段代码没有影响(在尝试呈现之前会在选项窗格中弹出包含表格的面板),则该方法很有用。

为了让表头呈现需要什么?

更新 1

换行..

        p.paint(g);

..to(带有适当的导入)..

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

..产生..

我会继续调整它。

更新 2

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

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

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

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

更新 3

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

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 大约有十几行(减去 cmets 和空白)。

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

因此,虽然 'tick' 将留在 camickr,但赏金将留在 kleopatra。

【问题讨论】:

  • 这样的问题让我不敢使用 Swing。
  • @Kublai 这是一个奇怪的角落案例,你是一只害怕的猫。 ;) BTW - 尝试在 AWT 中进行。
  • “哥们,我的头在哪里?” - 哥们,我不知道!你最后把它放在哪里了? :-)
  • @Stephen 这是电影“老兄!我的车在哪里”的双关语:imdb.com/title/tt0242423
  • @Suraj 也许这是斯蒂芬的一个挖洞,大意是我不应该在做双关语。很高兴我没有使用被认为是“老兄,我的头在哪里?”的变体。 ;)

标签: java image swing jtable


【解决方案1】:

这是一个艰难的过程

对于该标题根本没有出现在图像上感到困惑:它没有被剪裁或其他什么,它根本没有被绘制。原因是..在将面板绘制到图像时,标题不再是层次结构的一部分。关闭 optionPane 时,table.removeNotify 会删除标题。要再次添加它,请调用 addNotify,如在此 sn-p 中:

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

[在编辑中解决] 我仍然不明白为什么面板在没有首先显示在 optionPane 的情况下显示为空 - 通常是 ..的某种组合。

   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,两者完全相同:

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

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

【讨论】:

  • +1 一直忘记 addNotify。另外,现在看来我需要查看我的 ScreenImage 类,看看是否可以用简单的 validate() 替换递归 doLayout() 代码:-)
【解决方案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() 方法使组件成为displayable,ScreenImage 代码只会为不可显示的组件布置组件。我可能会考虑让这个更通用。

【讨论】:

  • @MarkJeronimus,在 Windows 7 上使用 JDK7_60 仍然适用于我。也许这是操作系统问题?如果是这样,请尝试将所有代码包装在 SwingUtilties.invokeLater(...) 中,以确保代码在 EDT 上执行,这是创建 GUI 的正确方法。
  • Inside ScreenImage.java at line component.paint( g2d );: NullPointerException in getVolatileOffscreenBuffer() 因为在内部它获得了不存在的顶级祖先窗口/对话框。 (我为此查看了 RepaintManager.java 源代码)
【解决方案3】:

拜托,这不是回答,只是评论和一点点...... :-)

@Andrew Thompson,如果我对example 的理解正确,只需引用J/Components 在内部/过去Graphics2D g2 = (Graphics2D) g; 并且不接受引用/派生TableHeader .... 可能(我懒得检查)API 中的某些东西

代码截图显示:-)

g2.scale(scale,scale);
tableView.paint(g2);
g2.scale(1/scale,1/scale);
g2.translate(0f,pageIndex*pageHeightForTable);
g2.translate(0f, -headerHeightOnPage);
g2.setClip(0, 0,
   (int) Math.ceil(tableWidthOnPage), 
   (int)Math.ceil(headerHeightOnPage));
g2.scale(scale,scale);
tableView.getTableHeader().paint(g2);
//paint header at top

import javax.swing.*;
import javax.swing.table.JTableHeader;
import java.awt.Graphics;
import java.awt.BorderLayout;
import java.awt.Graphics2D;
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), (true)},
            {"James", new Integer(23), new Double(47.64), (false)},
            {"Sally", new Integer(22), new Double(84.81), (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);
        JTableHeader h = table.getTableHeader();
        int x = h.getWidth();
        int y = h.getHeight() + table.getHeight();
        BufferedImage bi = new BufferedImage(
                x, y, BufferedImage.TYPE_INT_RGB);
        Graphics g = bi.createGraphics();
        //g.translate(0, table.getTableHeader().getHeight());
        Graphics2D g2 = (Graphics2D) g;
        table.paint(g2);
        //table.paint(g);
        table.getTableHeader().paint(g2);
        //h.paint(g);
        JOptionPane.showMessageDialog(null, new JLabel(new ImageIcon(bi)));
        ImageIO.write(bi, "png", new File("ctable.png"));
    }

    private TableImage() {
    }
}

【讨论】:

    【解决方案4】:

    手动将您的 JPanel 添加到您自己创建的 JFrame 中,而不仅仅是 JOptionPane 使用的,似乎可以解决此问题。

    引入JFrame 允许您根据需要跳过初始对话框显示。

    // ...snip 
    
    JOptionPane.showMessageDialog(null, p);
    
    JFrame frame = new JFrame();
    frame.setContentPane(p);
    frame.pack();
    
    BufferedImage bi = new BufferedImage(
        (int)p.getSize().getWidth(),
        (int)p.getSize().getHeight(),
        BufferedImage.TYPE_INT_RGB
    );
    
    Graphics g = bi.createGraphics();
    p.paint(g);
    g.dispose();
    frame.dispose();
    
    JOptionPane.showMessageDialog(null, new JLabel(new ImageIcon(bi)));
    ImageIO.write(bi,"png",new File("table.png"));
    

    【讨论】:

    • 好点。 这个线程不需要先显示表格就呈现表格,但这是最终目标。
    【解决方案5】:

    import javax.swing.*;
    import javax.swing.table.JTableHeader;
    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);
    
            JTableHeader h = table.getTableHeader();
            int x = h.getWidth();
            int y = h.getHeight() + table.getHeight();
    
            BufferedImage bi = new BufferedImage(
                (int)x,
                (int)y,
                BufferedImage.TYPE_INT_RGB
                );
    
            Graphics g = bi.createGraphics();
            h.paint(g);
            g.translate(0,h.getHeight());
            table.paint(g);
    
            JOptionPane.showMessageDialog(null, new JLabel(new ImageIcon(bi)));
            ImageIO.write(bi,"png",new File("table.png"));
        }
    }
    

    【讨论】:

    • 我删除了我的帖子,请修改你的自我回答
    • 我希望您取消删除您的答案。但是你确定它有效吗?我无法让它在我的示例中正常工作 - 也许我只是用错了。
    • heavens Dude,确保它有效,:-) 我确定你在某个地方迷路了 :-),而且我看不出自定义打印和自定义绘画之间的区别,从 JTable plus 获取大小TableHeader 分别传递给 Graphics2D,
    【解决方案6】:

    在打印之前尝试在组件(或全局)上禁用双缓冲 (c.setDoubleBuffered(false))。显然它有效果:)(见http://www.java2s.com/Code/Java/2D-Graphics-GUI/PrintSwingcomponents.htmhttp://www.apl.jhu.edu/~hall/java/Swing-Tutorial/Swing-Tutorial-Printing.html

    【讨论】:

    • 调用c.setDoubleBuffered(false) 没有明显效果。也许它纯粹与印刷有关。
    猜你喜欢
    • 2023-03-07
    • 2011-11-19
    • 2021-04-20
    • 1970-01-01
    • 2015-08-04
    • 2018-08-08
    • 1970-01-01
    • 2020-05-05
    • 1970-01-01
    相关资源
    最近更新 更多