Hoy vamos a intentar explicar como expandir las columnas de un JTable, esto es muy común y fácil cuando hablamos de código HTML con las propiedades colspan y rowspan de las tablas. Pero en Swing esta característica no está contemplada, por lo que tendremos que darle una vuelta a la gestión de las celdas de un JTable.
En primer lugar necesitaremos un mapeo de las celdas que queremos expandir, para ello crearemos un mapa con las claves de fila y columna que indican la celda. Nos valdremos de la siguiente interfaz para gestionar nuestro mapa y la información que necesitamos.
package colspanTable; import java.util.HashMap; public interface ColSpanMap { /** * Indica si la celda [row,column] esta expandida * @param row celda logica de fila * @param column celda logica de columna * @return numero de columnas expandiodas por celda */ int span(int row, int column); /** * Devuelve el indice de las celda visibles. * @param row celda logica de fila * @param column celda logica de columna * @return indice de la celda visible que cubre la celda logica */ int visibleCell(int row, int column); /** * Establece el mapeo de filas/columnas con celdas expandidas y su posicion. * @param spanMap Mapa */ void setSpanMap(HashMap< Integer, Integer > spanMap); }
La implementación debería seguir un modelo similar a este que os presento a continuación.
package colspanTable; import java.util.HashMap; class TableColSpanMap implements ColSpanMap { /** Columnas a expandir por celda. */ private static final int CELLS_TO_SPAN = 5; /** Mapeo de las celdas a expandir. */ private HashMap< Integer, Integer > spanMap; @Override public int span(int row, int column) { if (spanMap != null && spanMap.containsKey(row)) { if (spanMap.get(row) == column) { return CELLS_TO_SPAN; } } return 1; } @Override public int visibleCell(int row, int column) { if (spanMap != null && spanMap.containsKey(row)) { if (column >= spanMap.get(row) && column < spanMap.get(row) + CELLS_TO_SPAN) { return spanMap.get(row); } } return column; } @Override public void setSpanMap(HashMap< Integer, Integer > spanMap) { this.spanMap = spanMap; } }
Ahora en nuestra tabla, a la hora de consultar la posición, índice y anchura de las celdas de la tabla deberemos de tener en cuenta nuestro mapeo especial, para ello sobrescribimos los métodos getCellRect para el tamaño de celda y getColumnAtPoint para buscar los índices correctos a la hora de editarla, además al contruir nuestra tabla le pasaremos en el constructor el mapeo de las celdas expandidas. Los cambios los muestro a continuación:
package colspanTable; import java.awt.Point; import java.awt.Rectangle; import javax.swing.JTable; import javax.swing.table.TableModel; public class SpanTable extends JTable { /** Map with expanded columns mapped. */ private ColSpanMap map; /** * Constructor. * @param csm Mapa de celdas * @param tbl Modelo de la tabla */ public SpanTable(ColSpanMap csm, TableModel tbl) { super(tbl); map = csm; setUI(new ColSpanTableUI()); } /** * Nos devuelve el mapa de las celdas a expandir. * @return map */ public ColSpanMap getSpanMap() { return map; } @Override public Rectangle getCellRect(int row, int column, boolean includeSpacing) { // required because getCellRect is used in JTable constructor if (map == null) return super.getCellRect(row, column, includeSpacing); // add widths of all spanned logical cells int sk = map.visibleCell(row, column); Rectangle r1 = super.getCellRect(row, sk, includeSpacing); if (map.span(row, sk) != 1) for (int i = 1; i < map.span(row, sk); i++) { r1.width += getColumnModel().getColumn(sk + i).getWidth(); } return r1; } @Override public int columnAtPoint(Point p) { int x = super.columnAtPoint(p); // -1 is returned by columnAtPoint if the point is not in the table if (x < 0) return x; int y = super.rowAtPoint(p); return map.visibleCell(y, x); } }Por último solo nos quedaría cuidarnos de la parte gráfica, tendremos que establecerle un UI peculiar a la tabla que nos pinte la rejilla de las celdas especial que necesitamos donde tendremos en cuenta las partes visibles de cada celda.
package colspanTable; import java.awt.Color; import java.awt.Component; import java.awt.Graphics; import java.awt.Point; import java.awt.Rectangle; import javax.swing.JComponent; import javax.swing.plaf.basic.BasicTableUI; import javax.swing.table.TableCellRenderer; public class ColSpanTableUI extends BasicTableUI { @Override public void paint(Graphics g, JComponent c) { Rectangle r = g.getClipBounds(); int firstRow = table.rowAtPoint(new Point(0, r.y)); int lastRow = table.rowAtPoint(new Point(0, r.y + r.height)); // -1 is a flag that the ending point is outside the table if (lastRow < 0) lastRow = table.getRowCount() - 1; for (int i = firstRow; i <= lastRow; i++) paintRow(i, g); } private void paintRow(int row, Graphics g) { Rectangle r = g.getClipBounds(); for (int i = 0; i < table.getColumnCount(); i++) { Rectangle r1 = table.getCellRect(row, i, true); if (r1.intersects(r)) // at least a part is visible { int sk = ((SpanTable) table).getSpanMap().visibleCell(row, i); paintCell(row, sk, g, r1); // increment the column counter i += ((SpanTable) table).getSpanMap().span(row, sk) - 1; } } } private void paintCell(int row, int column, Graphics g, Rectangle area) { int verticalMargin = table.getRowMargin(); int horizontalMargin = table.getColumnModel().getColumnMargin(); Color c = g.getColor(); g.setColor(table.getGridColor()); g.drawRect(area.x, area.y, area.width - 1, area.height - 1); g.setColor(c); area.setBounds(area.x + horizontalMargin / 2, area.y + verticalMargin / 2, area.width - horizontalMargin, area.height - verticalMargin); if (table.isEditing() && table.getEditingRow() == row && table.getEditingColumn() == column) { Component component = table.getEditorComponent(); component.setBounds(area); component.validate(); } else { TableCellRenderer renderer = table.getCellRenderer(row, column); Component component = table.prepareRenderer(renderer, row, column); if (component.getParent() == null) rendererPane.add(component); rendererPane.paintComponent(g, component, table, area.x, area.y, area.width, area.height, true); } } }Bien, espero que todo este lo más claro posible. Como siempre os dejo un ejemplo funcionando :)
package colspanTable; import java.util.HashMap; import javax.swing.JFrame; import javax.swing.JScrollPane; import javax.swing.table.DefaultTableModel; import javax.swing.table.TableModel; public class Test { private static void createAndShowGUI() { JFrame frame = new JFrame("Tabla con celdas expandidas"); ColSpanMap m = new TableColSpanMap(); HashMap< Integer, Integer > cellSpanMap = new HashMap(); // Aqui los indices [fila,columna] de las celdas que queremos expandir cellSpanMap.put(3, 3); cellSpanMap.put(4, 7); cellSpanMap.put(9, 5); m.setSpanMap(cellSpanMap); TableModel tm=new DefaultTableModel(15,20); SpanTable table = new SpanTable(m,tm); frame.getContentPane().add(new JScrollPane(table)); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setSize(500, 295); frame.setVisible(true); } public static void main(String[] args) { javax.swing.SwingUtilities.invokeLater(new Runnable() { public void run() { createAndShowGUI(); } }); } }
Espero que os siga siendo útil todo lo que aquí se dice, vuestros comentarios y dudas siempre son bienvenidos.
Un saludo y hasta la próxima
8 comentarios:
Gracias por su buen ejemplo.
De nada @Jon, gracias por leerme y valorar mis aportes.
@Angelon, a partir de su código de ejemplo, he creado un código que le permite crear una JTable con un número arbitrario de rowspans o colspans. Echa un vistazo a esto:
https://code.google.com/p/spantable
@Jon, me alegro de que le haya sido útil para crear su librería y espero que le ayude igualmente a otros desarrolladores.
BTW you have a really nice portfolio :)
Como podria hacer lo mismo pero para expandir solo ciertas celdas pero tendiendo en cuenta la fila?
Muy bueno tu aporte! y como sería un rowspan??
Graciass
Publicar un comentario