Search code examples
javaswingjtablejtableheader

JTable Multiple Header Rows


I am using a JTable in my application and wish to have 2 rows for headings, similar to this: Like this

Is this even possible or will I have to do something else? If so, what? Using Supertitle-titleA, SuperTitle-titleB will take up too much space and make information redundant.


Solution

  • We had the same requirement in our last project. What I have found is an Implementation for a GroupableTableHeader on java2s.com. However, I have pimped it a bit, although I cannot recall what exactly. Beneath is the implementation of the three classes as how we use them.

    ColumnGroup.java

    import java.awt.Component;
    import java.awt.Dimension;
    import java.util.ArrayList;
    import java.util.Collections;
    import java.util.List;
    
    import javax.swing.JTable;
    import javax.swing.table.TableCellRenderer;
    import javax.swing.table.TableColumn;
    
    /**
     * ColumnGroup
     * 
     * @version 1.0 20.10.1998
     * @author Nobuo Tamemasa
     */
    public class ColumnGroup {
    
        protected TableCellRenderer renderer;
    
        protected List<TableColumn> columns;
        protected List<ColumnGroup> groups;
    
        protected String text;
        protected int margin = 0;
    
        public ColumnGroup(String text) {
            this(text, null);
        }
    
        public ColumnGroup(String text, TableCellRenderer renderer) {
            this.text = text;
            this.renderer = renderer;
            this.columns = new ArrayList<TableColumn>();
            this.groups = new ArrayList<ColumnGroup>();
        }
    
        public void add(TableColumn column) {
            columns.add(column);
        }
    
        public void add(ColumnGroup group) {
            groups.add(group);
        }
    
        /**
         * @param column
         *            TableColumn
         */
        public List<ColumnGroup> getColumnGroups(TableColumn column) {
            if (!contains(column)) {
                return Collections.emptyList();
            }
            List<ColumnGroup> result = new ArrayList<ColumnGroup>();
            result.add(this);
            if (columns.contains(column)) {
                return result;
            }
            for (ColumnGroup columnGroup : groups) {
                result.addAll(columnGroup.getColumnGroups(column));
            }
            return result;
        }
    
        private boolean contains(TableColumn column) {
            if (columns.contains(column)) {
                return true;
            }
            for (ColumnGroup group : groups) {
                if (group.contains(column)) {
                    return true;
                }
            }
            return false;
        }
    
        public TableCellRenderer getHeaderRenderer() {
            return renderer;
        }
    
        public void setHeaderRenderer(TableCellRenderer renderer) {
            this.renderer = renderer;
        }
    
        public String getHeaderValue() {
            return text;
        }
    
        public Dimension getSize(JTable table) {
            TableCellRenderer renderer = this.renderer;
            if (renderer == null) {
                renderer = table.getTableHeader().getDefaultRenderer();
            }
            Component comp = renderer.getTableCellRendererComponent(table, getHeaderValue() == null || getHeaderValue().trim().isEmpty() ? " "
                    : getHeaderValue(), false, false, -1, -1);
            int height = comp.getPreferredSize().height;
            int width = 0;
            for (ColumnGroup columnGroup : groups) {
                width += columnGroup.getSize(table).width;
            }
            for (TableColumn tableColumn : columns) {
                width += tableColumn.getWidth();
                width += margin;
            }
            return new Dimension(width, height);
        }
    
        public void setColumnMargin(int margin) {
            this.margin = margin;
            for (ColumnGroup columnGroup : groups) {
                columnGroup.setColumnMargin(margin);
            }
        }
    
    }
    

    GroupableTableHeader.java

    import java.util.ArrayList;
    import java.util.Collections;
    import java.util.List;
    
    import javax.swing.table.JTableHeader;
    import javax.swing.table.TableColumn;
    import javax.swing.table.TableColumnModel;
    
    /**
     * GroupableTableHeader
     * 
     * @version 1.0 20.10.1998
     * @author Nobuo Tamemasa
     */
    @SuppressWarnings("serial")
    public class GroupableTableHeader extends JTableHeader {
    
        @SuppressWarnings("unused")
        private static final String uiClassID = "GroupableTableHeaderUI";
    
        protected List<ColumnGroup> columnGroups = new ArrayList<ColumnGroup>();
    
        public GroupableTableHeader(TableColumnModel model) {
            super(model);
            setUI(new GroupableTableHeaderUI());
            setReorderingAllowed(false);
            // setDefaultRenderer(new MultiLineHeaderRenderer());
        }
    
        @Override
        public void updateUI() {
            setUI(new GroupableTableHeaderUI());
        }
    
        @Override
        public void setReorderingAllowed(boolean b) {
            super.setReorderingAllowed(false);
        }
    
        public void addColumnGroup(ColumnGroup g) {
            columnGroups.add(g);
        }
    
        public List<ColumnGroup> getColumnGroups(TableColumn col) {
            for (ColumnGroup group : columnGroups) {
                List<ColumnGroup> groups = group.getColumnGroups(col);
                if (!groups.isEmpty()) {
                    return groups;
                }
            }
            return Collections.emptyList();
        }
    
        public void setColumnMargin() {
            int columnMargin = getColumnModel().getColumnMargin();
            for (ColumnGroup group : columnGroups) {
                group.setColumnMargin(columnMargin);
            }
        }
    
    }
    

    GroupableTableHeaderUI.java

    import java.awt.Component;
    import java.awt.Dimension;
    import java.awt.Graphics;
    import java.awt.Rectangle;
    import java.util.Enumeration;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    import javax.swing.JComponent;
    import javax.swing.UIManager;
    import javax.swing.plaf.basic.BasicTableHeaderUI;
    import javax.swing.table.TableCellRenderer;
    import javax.swing.table.TableColumn;
    import javax.swing.table.TableColumnModel;
    
    public class GroupableTableHeaderUI extends BasicTableHeaderUI {
    
        protected GroupableTableHeader getHeader() {
            return (GroupableTableHeader) header;
        }
    
        @Override
        public void paint(Graphics g, JComponent c) {
            Rectangle clipBounds = g.getClipBounds();
            if (header.getColumnModel().getColumnCount() == 0) {
                return;
            }
            int column = 0;
            Dimension size = header.getSize();
            Rectangle cellRect = new Rectangle(0, 0, size.width, size.height);
            Map<ColumnGroup, Rectangle> groupSizeMap = new HashMap<ColumnGroup, Rectangle>();
    
            for (Enumeration<TableColumn> enumeration = header.getColumnModel().getColumns(); enumeration.hasMoreElements();) {
                cellRect.height = size.height;
                cellRect.y = 0;
                TableColumn aColumn = enumeration.nextElement();
                List<ColumnGroup> groups = getHeader().getColumnGroups(aColumn);
                int groupHeight = 0;
                for (ColumnGroup group : groups) {
                    Rectangle groupRect = groupSizeMap.get(group);
                    if (groupRect == null) {
                        groupRect = new Rectangle(cellRect);
                        Dimension d = group.getSize(header.getTable());
                        groupRect.width = d.width;
                        groupRect.height = d.height;
                        groupSizeMap.put(group, groupRect);
                    }
                    paintCell(g, groupRect, group);
                    groupHeight += groupRect.height;
                    cellRect.height = size.height - groupHeight;
                    cellRect.y = groupHeight;
                }
                cellRect.width = aColumn.getWidth();
                if (cellRect.intersects(clipBounds)) {
                    paintCell(g, cellRect, column);
                }
                cellRect.x += cellRect.width;
                column++;
            }
        }
    
        private void paintCell(Graphics g, Rectangle cellRect, int columnIndex) {
            TableColumn aColumn = header.getColumnModel().getColumn(columnIndex);
            TableCellRenderer renderer = aColumn.getHeaderRenderer();
            if (renderer == null) {
                renderer = getHeader().getDefaultRenderer();
            }
            Component c = renderer.getTableCellRendererComponent(header.getTable(), aColumn.getHeaderValue(), false, false,
                    -1, columnIndex);
    
            c.setBackground(UIManager.getColor("control"));
    
            rendererPane.paintComponent(g, c, header, cellRect.x, cellRect.y, cellRect.width, cellRect.height, true);
        }
    
        private void paintCell(Graphics g, Rectangle cellRect, ColumnGroup cGroup) {
            TableCellRenderer renderer = cGroup.getHeaderRenderer();
            if (renderer == null) {
                renderer = getHeader().getDefaultRenderer();
            }
    
            Component component = renderer.getTableCellRendererComponent(header.getTable(), cGroup.getHeaderValue(), false,
                    false, -1, -1);
            rendererPane
                    .paintComponent(g, component, header, cellRect.x, cellRect.y, cellRect.width, cellRect.height, true);
        }
    
        private int getHeaderHeight() {
            int headerHeight = 0;
            TableColumnModel columnModel = header.getColumnModel();
            for (int column = 0; column < columnModel.getColumnCount(); column++) {
                TableColumn aColumn = columnModel.getColumn(column);
                TableCellRenderer renderer = aColumn.getHeaderRenderer();
                if (renderer == null) {
                    renderer = getHeader().getDefaultRenderer();
                }
    
                Component comp = renderer.getTableCellRendererComponent(header.getTable(), aColumn.getHeaderValue(), false,
                        false, -1, column);
                int cHeight = comp.getPreferredSize().height;
                List<ColumnGroup> groups = getHeader().getColumnGroups(aColumn);
                for (ColumnGroup group : groups) {
                    cHeight += group.getSize(header.getTable()).height;
                }
                headerHeight = Math.max(headerHeight, cHeight);
            }
            return headerHeight;
        }
    
        @Override
        public Dimension getPreferredSize(JComponent c) {
            int width = 0;
            for (Enumeration<TableColumn> enumeration = header.getColumnModel().getColumns(); enumeration.hasMoreElements();) {
                TableColumn aColumn = enumeration.nextElement();
                width += aColumn.getPreferredWidth();
            }
            return createHeaderSize(width);
        }
    
        private Dimension createHeaderSize(int width) {
            TableColumnModel columnModel = header.getColumnModel();
            width += columnModel.getColumnMargin() * columnModel.getColumnCount();
            if (width > Integer.MAX_VALUE) {
                width = Integer.MAX_VALUE;
            }
            return new Dimension(width, getHeaderHeight());
        }
    
    }