arcy
arcy

Reputation: 13103

Set JTable header horizontal alignment based on value renderer column alignment

I've looked through a number of questions/answers on setting JTable header alignment, but they don't seem to cover this case. I want my JTable headers to have the same alignment as the data below them; it looks odd to me to have them all centered, especially for columns that have space for their occasional long strings but often contain only short strings -- the header is out in the center of the space by itself.

Many of the solutions I've seen depend on DefaultCellTableRenderer -- is that all the table headers use? I don't want to set the default renderer, since that would alter all columns; I want to write code to figure out the horizontal alignment for the data in the column, and set the header to do the same thing in terms of horizontal alignment.

Do I have to get the component from the renderer (using a specific row, column, and value) and set the alignment on the component? Do I have to write a custom renderer for the header just for this? What's the cleanest overall approach?

-- edit

Mr. Camick's solution is elegant and does almost what I want; it intercepts the creation of the table cell renderer for the column header. Somehow what I've done eliminates the normal formatting for a header, however -- there is no shading and no border on my headers (though they're aligned as I wanted!).

I'm currently have the following: where do I get the renderer for the table header as it is, so I can just change the alignment on it?

import java.awt.Component;

import javax.swing.JTable;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;

public class TableHeaderRenderer implements TableCellRenderer
{
    TableCellRenderer olderRenderer = null;

    public TableHeaderRenderer(TableCellRenderer givenRenderer)
    {
        olderRenderer = givenRenderer;
    }

//  // get the default renderer for the table header.
//  JTableHeader header = table.getTableHeader();
//  TableCellRenderer defaultRenderer = header.getDefaultRenderer();

    @Override
    public Component getTableCellRendererComponent(JTable table, Object value,
        boolean isSelected, boolean hasFocus, int row, int column)
    {
        try // if the casts fail, we end up in the catch below
    {
        // get the renderer for the first row
        DefaultTableCellRenderer firstRowTableCellRenderer = (DefaultTableCellRenderer)table.getCellRenderer(0, column);

        // get the renderer for this column header
        JTableHeader tableHeader = table.getTableHeader();
        TableColumnModel columnModel = tableHeader.getColumnModel();
        TableColumn tableColumn = columnModel.getColumn(column);
        DefaultTableCellRenderer headerRenderer = (DefaultTableCellRenderer)tableColumn.getCellRenderer();

        // if the renderer for the column is null, create one
        if (headerRenderer == null) 
        { headerRenderer =  new DefaultTableCellRenderer(); 
        }

        // finally -- set the header's horizontal alignment to be the same as
        // that for the first row, then get the component 
        headerRenderer.setHorizontalAlignment(firstRowTableCellRenderer.getHorizontalAlignment());
        Component result = headerRenderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);

        return result;
    }
        catch (ClassCastException cce)
        {
            // either the first row or the header has a renderer that isn't a DefaultTableCellRenderer
            // just return the component we already have
            return olderRenderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
        }
    }

}

--

Ok, edit 2:

import java.awt.BorderLayout;

import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.JTableHeader;

@SuppressWarnings("serial")
public class TableHeaderRendererDemo extends JFrame
{
    Object[][]data = { {new Integer(1), "two", "three"},
                   { new Integer(4), "five", "six" }
                     };
    Object[] columnNames = { "first", "second", "third" };

    public static void main(String ... arguments) 
    { new TableHeaderRendererDemo().go(); 
    }

    public void go()
    {
        JTable table = new JTable(data, columnNames);
        JScrollPane scrollPane = new JScrollPane(table);

        // set our own default renderer for the headers
        JTableHeader header = table.getTableHeader();
        try 
        { DefaultTableCellRenderer defaultRenderer = (DefaultTableCellRenderer)header.getDefaultRenderer();
            header.setDefaultRenderer(new TableHeaderRenderer(defaultRenderer));
        }
        catch (ClassCastException e)
        {
            System.err.println("Could not cast default renderer; table headers not aligned");
        }

        add(scrollPane, BorderLayout.CENTER);
        pack();
        setVisible(true);
    }
}

You can see the header problem I speak of; I hoped the Integers would default to right-justified, but for some reason they don't. But you can see the problem I'm having just from this.

Upvotes: 1

Views: 1983

Answers (2)

arcy
arcy

Reputation: 13103

Futzed around until I finally came on this; somehow the default renderer for Boolean is not a DefaultTableCellRenderer, so I can't get its alignment this way. I tried getting the component on the first row and getting ITS alignment, but that fails if there's no data in the table. This is good enough for what I want now. It seems to me that the alignment of the column has to live somewhere, and I still wish I could go get its value somehow, but so far all I've managed is a way to get it if the column data uses a DefaultTableCellRenderer.

Thanks to Messrs. camickr and MadProgrammer.

import java.awt.Component;

import javax.swing.JTable;
import javax.swing.SwingConstants;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableCellRenderer;

/**
 * Renderer that aligns the table's column header with the column's data;
 * if the renderer for the first row of the column data cannot be cast to
 * DefaultTableCellRenderer, this just sets the alignment to CENTER.
 * <P>Use:
 * <pre>
 *      // set our own default renderer for the headers
        JTableHeader header = table.getTableHeader();
        try 
        { DefaultTableCellRenderer defaultRenderer = (DefaultTableCellRenderer)header.getDefaultRenderer();
            header.setDefaultRenderer(new TableHeaderRenderer(defaultRenderer));
        }
        catch (ClassCastException e)
        {
            System.err.println("Could not cast default renderer; table headers not aligned");
        }
 *
 */
@SuppressWarnings("serial")
public class TableHeaderRenderer extends DefaultTableCellRenderer implements TableCellRenderer
{
    DefaultTableCellRenderer innerRenderer = null;

    public TableHeaderRenderer(TableCellRenderer givenRenderer)
    {
        // save the given renderer, normally the default for the header
        innerRenderer = (DefaultTableCellRenderer)givenRenderer;
    }

    @Override
    public Component getTableCellRendererComponent
    (JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column)
    {
        try
            {
                // get the renderer for the first row
                DefaultTableCellRenderer firstRowTableCellRenderer = (DefaultTableCellRenderer)table.getCellRenderer(0, column);
                int firstRowAlignment = firstRowTableCellRenderer.getHorizontalAlignment();
                // set the alignment of this header to that of the first row
                innerRenderer.setHorizontalAlignment(firstRowAlignment);
                return innerRenderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, firstRowAlignment, column);
            }
        catch (ClassCastException cce)
        {
          // the first row has a renderer that isn't a DefaultTableCellRenderer
          // just set the alignment to CENTER
            innerRenderer.setHorizontalAlignment(SwingConstants.CENTER);
          return innerRenderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
        }
    }

}

Upvotes: 0

camickr
camickr

Reputation: 324088

Here is an example of a custom header renderer that simply makes the text of the selected column Bold:

class MyTableHeaderRenderer implements TableCellRenderer
{
    private TableCellRenderer tableCellRenderer;

    public MyTableHeaderRenderer(TableCellRenderer tableCellRenderer)
    {
        this.tableCellRenderer = tableCellRenderer;
    }

    public Component getTableCellRendererComponent(JTable table, Object value,
        boolean isSelected, boolean hasFocus, int row, int column)
    {
        Component c = tableCellRenderer.getTableCellRendererComponent(
            table, value, isSelected, hasFocus, row, column);

        if (column == table.getSelectedColumn())
            c.setFont(getFont().deriveFont(Font.BOLD));

        return c;
    }
}

You would use the renderer with code like:

JTableHeader header = table.getTableHeader();
DefaultTableCellRenderer defaultRenderer = (DefaultTableCellRenderer)header.getDefaultRenderer();
header.setDefaultRenderer( new MyTableHeaderRenderer(defaultRenderer) );

In your case you would need to change the alignment, not the Font.

You might use code like the following:

TableCellRenderer tcr = table.getCellRenderer(0, column);
Component renderer = table.prepareRenderer(tcr, 0, column);
defaultRenderer.setAlignment( renderer.getAlignment() ); // whatever the alignment method is.

Edit:

Somehow what I've done eliminates the normal formatting for a header

You are using:

tableColumn.getCellRenderer(); 

should that not be:

tableColumn.getHeaderRenderer();

Rarely would people specify a special renderer for the table header by column. So if a header renderer is not specified for the TableColumn you should just use the default renderer of the JTableHeader, don't create a DefaultTableCellRenderer, this is NOT the renderer used by the table header.

Upvotes: 2

Related Questions