¿Por qué el encabezado JTable no aparece en la imagen?

Estaba ofreciendo asesoramiento sobre la captura de una imagen de datos tabulares en la API o herramienta Java para convertir datos tabulares en archivos de imágenes PNG , cuando el OP solicitó una muestra de código. ¡Resulta ser más difícil de lo que pensaba! El encabezado JTable desaparece del PNG que escribe el código.

PNG

PNG

Captura de pantalla

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

Nota: revisé la clase de imagen de pantalla de Camickr e incluí una llamada al método doLayout(Component) . El método es útil si un Component nunca se ha realizado en la pantalla, pero no tiene ningún efecto sobre este código (que muestra el panel que contiene la tabla en un panel de opciones antes de intentar renderizarlo).

¿Qué se necesita para que se muestre el encabezado de la tabla?

Actualización 1

Cambiando la línea ..

  p.paint(g); 

..to (con una importación apropiada) ..

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

..produce ..

2do intento

Seguiré retocando.

Actualización 2

Kleopatra (estrategia 1) y Camickr (estrategia 2) han proporcionado una respuesta cada uno, ambos funcionan, y ninguno de los dos requiere agregar JTable a un componente ficticio (que es un gran truco de IMO).

Mientras que la estrategia 2 recortará (o expandirá) a ‘solo la mesa’, la primera estrategia capturará el panel que contiene la tabla. Esto se vuelve problemático si la tabla contiene muchas entradas, mostrando una imagen de una tabla truncada con una barra de desplazamiento.

Si bien la estrategia 1 podría ser modificada aún más para evitar eso, Realmente me gusta la simplicidad ordenada de la estrategia 2, por lo que se lleva el tic.

Como señaló kleopatra, no se necesitó ningún ‘tweak’. Así que lo intentaré de nuevo …

Actualización 3

Esta es la imagen producida por los métodos presentados por camickr y kleopatra. Lo habría puesto dos veces, pero a mi juicio, son idénticos (aunque no he hecho una comparación de píxel por píxel).

JTable en 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))); } } 

Ambos logran el objective. Usando el método de Camickr usted aprovecha la potencia adicional de la API ScreenImage. Usando el método de Kleopatra: alrededor de una docena de líneas (menos los comentarios y el espacio en blanco) de J2SE puro.

Mientras que ScreenImage es una clase que usaré y recomendaré en el futuro, el otro enfoque que usa J2SE central es lo que probablemente usaría para esta circunstancia exacta.

Entonces, mientras que el ‘tic’ se quedará con camickr, la recompensa irá a kleopatra.

No era un requisito de este hilo reproducir la tabla sin mostrarla primero, pero ese era el objective final

ScreenImage maneja esto.

Debe agregar manualmente el encabezado al panel de desplazamiento.

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

Nota: La sugerencia de Kleopatra para usar addNotify () en el panel no funcionará con ScreenImage. El método addNotify () hace que el componente se pueda displayable y el código ScreenImage solo expondrá los componentes para los componentes no visualizables. Podría tratar de hacer esto más general.

Esa fue una difícil

Me sorprendió que ese encabezado no apareciera en la imagen: no estaba recortado o algo así, no estaba pintado en absoluto. La razón de esto es … que en el momento de pintar el panel en la imagen, el encabezado ya no forma parte de la jerarquía. Al cerrar la opciónPane, table.removeNotify elimina el encabezado. Para volver a agregarlo, llame a addNotify, como en este fragmento de código:

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

[resuelto en la edición] Lo que aún no entiendo por qué el panel aparece vacío sin haber sido mostrado primero en la opciónPanel, típicamente una combinación de …

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

… lo hará

Editar

última confusión resuelta: para forzar un nuevo diseño recursivo del panel, todos los contenedores a lo largo de la línea deben ser convencidos de que tienen un par: la validación está optimizada para no hacer nada si no es así. Hacer el addNotify en el panel (en lugar de en la tabla) más validar hace el truco

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

No probado para detectar efectos secundarios.

Editar 2

Solo refunfuñando un poco sobre el …

la estrategia 1 podría ser modificada aún más para evitar esa [columna de la tabla truncada]

En realidad no es necesario ningún ajuste (o el mismo ajuste necesario para ScreenImage, depende de lo que consideres un ajuste 🙂 – ambos requieren para hacer que JScrollPane no haga su trabajo al configurar el prefViewportSize de la tabla, exactamente la misma línea en ambos:

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

JPanel manualmente tu JPanel a un JFrame de tu propia creación, no solo el que usa JOptionPane, parece resolverlo.

La introducción de JFrame permite omitir la pantalla de diálogo inicial, si lo desea.

 // ...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")); 

Imagen de la tabla

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

por favor, esa no es respuesta solo comentar y poco … 🙂

@ Andrew Thompson, si entendí correctamente, por ejemplo , solo hace referencia a J/Components está dentro / pasado Graphics2D g2 = (Graphics2D) g; y no es aceptado TableHeader .... referenciado / derivado TableHeader .... probablemente (I perezoso para comprobar eso) algo en API

código cortado muestra 🙂

 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 

enter image description here

 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() { } } 

Intente deshabilitar el almacenamiento en búfer doble en el componente (o globalmente) antes de imprimirlo ( c.setDoubleBuffered(false) ). Aparentemente tiene un efecto 🙂 (ver http://www.java2s.com/Code/Java/2D-Graphics-GUI/PrintSwingcomponents.htm y http://www.apl.jhu.edu/~hall/java/ Swing-Tutorial / Swing-Tutorial-Printing.html )