JTable Multiple Header Rows

16,565

Solution 1

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

}

Solution 2

Yes, you need to supply your own JTableHeader. The difficult comes in trying to layout it out.

You'll need to look at JTableHeader.getHeaderRect(column), this tells the renderer how to layout the column headers.

You're going to have to take into consideration the height of each column renderer as well as the height of the component you want to use a label renderer (I'd suggest using the Header's column renderer as a bases, but that's up to you)

Share:
16,565

Related videos on Youtube

Jonno_FTW
Author by

Jonno_FTW

I know Haskell PHP, CodeIgniter Python jQuery XHTML LaTeX Java C++ #SOreadytohelp

Updated on September 27, 2022

Comments

  • Jonno_FTW
    Jonno_FTW over 1 year

    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.

  • MadProgrammer
    MadProgrammer almost 12 years
    Ah yes, I think we used this as well. The main issues (I had) with it comes from it's lack of support for dragging the column positions as well as (at least the implementation we've ended up with), not rendering the sort indicators. I've not spent a lot of time looking at your code and maybe you've over come those issues.
  • Roland Schneider
    Roland Schneider almost 12 years
    Unfortunately my code also lacks these capabilities. But I guess I have to fix them in the next few weeks. When I am ready I will post the solution here.
  • MadProgrammer
    MadProgrammer almost 12 years
    sorry for putting you on the spot ;)
  • Jonno_FTW
    Jonno_FTW almost 12 years
    Thanks for the code, but on line 25 of GroupableTableHeader you do setDefaultRenderer(new MultiLineHeaderRenderer());. But this class cannot be resolved. Where did you get it from?
  • Roland Schneider
    Roland Schneider almost 12 years
    You can just remove this line. It's just a renderer to wrap the column heading into multiple rows.
  • pstanton
    pstanton almost 11 years
    thanks this is very helpful, and i know this is an old post, but is there any reason for the top header font ending up slightly heavier than the bottom row header? its not 'bold', just slightly visibly thicker...
  • pstanton
    pstanton almost 11 years
    ah .. its because the group header is repainted once for each member, ie 3 times if three sub-columns. you need a check to avoid re-painting the same group multiple times.
  • pstanton
    pstanton almost 11 years
    also this doesn't play nice with columns hidden via table.removeColumn
  • zygimantus
    zygimantus over 8 years
    But how to use those classes? I want to add header row to my jTable object but I don't see the way to do this.
  • SOFe
    SOFe over 7 years
    The header groups disappear when DefaultTableModel.setDataVector is called.