31

Basically, I have a JTable containing columns with right-aligned cells but left-aligned headers which looks really bad. I would like to right-align the headers of these columns without altering the "Look and Feel" of the headers.

Thanks

Eng.Fouad
  • 115,165
  • 71
  • 313
  • 417
Drew Galbraith
  • 2,216
  • 4
  • 19
  • 22

13 Answers13

47

Here's an alternate approach to modifying the TableCellRenderer of a table's JTableHeader. It's not strictly necessary for this usage, but it minimizes the impact on the UI delegate's appearance.

Typical usage:

JTable table = new JTable(…);
JTableHeader header = table.getTableHeader();
header.setDefaultRenderer(new HeaderRenderer(table));

Custom header renderer:

private static class HeaderRenderer implements TableCellRenderer {

    DefaultTableCellRenderer renderer;

    public HeaderRenderer(JTable table) {
        renderer = (DefaultTableCellRenderer)
            table.getTableHeader().getDefaultRenderer();
        renderer.setHorizontalAlignment(JLabel.CENTER);
    }

    @Override
    public Component getTableCellRendererComponent(
        JTable table, Object value, boolean isSelected,
        boolean hasFocus, int row, int col) {
        return renderer.getTableCellRendererComponent(
            table, value, isSelected, hasFocus, row, col);
    }
}
trashgod
  • 203,806
  • 29
  • 246
  • 1,045
  • +1. This is the right solution. Yesterday I was a bit tired and didn't came up anything to my mind :) . Setting a new renderer was not a good idea. – Heisenbug Sep 21 '11 at 10:10
  • See also Darryl Burke's [Default Table Header Cell Renderer](http://tips4java.wordpress.com/2009/02/27/default-table-header-cell-renderer/). – trashgod Sep 22 '11 at 00:07
  • 1
    just a minor problem, i think this is small **H]eader** `header.setDefaultRenderer(new HeaderRenderer(table));` btw i was looking for this solution also, and it works fine. The only thing that i need to change this captial **H to h.** – SüniÚr Nov 10 '14 at 13:04
43

Try this:

((DefaultTableCellRenderer)table.getTableHeader().getDefaultRenderer())
    .setHorizontalAlignment(JLabel.RIGHT);
trashgod
  • 203,806
  • 29
  • 246
  • 1,045
Eng.Fouad
  • 115,165
  • 71
  • 313
  • 417
6
DefaultTableCellRenderer renderer = (DefaultTableCellRenderer) your_jtable.getTableHeader().getDefaultRenderer();
renderer.setHorizontalAlignment(0);

Where 0 is Centre.

Scott
  • 21,211
  • 8
  • 65
  • 72
chathun
  • 169
  • 1
  • 4
  • 12
4

The HeaderRenderer shown above (2011/sep/21 by trashgod) combined with code from Heisenbug (2011/sep/21) , will only work correctly if you have all headers aligned the same.

If you want to align different headers differently, then you will have to use the following code:

int[] alignments = new int[] { JLabel.LEFT, JLabel.RIGHT, JLabel.RIGHT };
for (int i = 0 ; i < jTable.getColumnCount(); i++){
  jTable.getTableHeader().getColumnModel().getColumn(i)
    .setHeaderRenderer(new HeaderRenderer(jTable, alignments[i]));
}

and

private static class HeaderRenderer implements TableCellRenderer {
  DefaultTableCellRenderer renderer;
  int horAlignment;
  public HeaderRenderer(JTable table, int horizontalAlignment) {
    horAlignment = horizontalAlignment;
    renderer = (DefaultTableCellRenderer)table.getTableHeader()
        .getDefaultRenderer();
  }
  public Component getTableCellRendererComponent(JTable table, Object value,
      boolean isSelected, boolean hasFocus, int row, int col) {
    Component c = renderer.getTableCellRendererComponent(table, value,
      isSelected, hasFocus, row, col);
    JLabel label = (JLabel)c;
    label.setHorizontalAlignment(horAlignment);
    return label;
  }
}

That is:
Set the alignment in getTableCellRendererComponent , and not in the HeaderRenderer constructor.

3

A thing to remember about wrapping default table headers: do not hold on to a reference of them.

If you (or your users) are using a Windows Classic theme on Windows 7 and your application sets default system LAF, the answer posted by @trashgod may cause problems for you.

It is affected by this bug, posted a decade ago (seriously). If your table is showing and you switch the theme in Windows preferences from an Aero Theme to Windows Classic, there will be a barrage of NPEs. You are NOT supposed to hold on to a reference of a renderer as it may become invalid at some point in time. The wrapping should be done in a dynamic way, as suggested in the comments of the bug report. I took the code from there and created the following runnable example:

import java.awt.*;
import java.lang.ref.WeakReference;
import javax.swing.*;
import javax.swing.table.*;

public class TestFrame extends JFrame {

    private static final boolean I_WANT_THE_BUG_TO_HAPPEN = true;

    public static void main(String[] args) throws IllegalAccessException, ClassNotFoundException, InstantiationException, UnsupportedLookAndFeelException {
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                int res = JOptionPane.showConfirmDialog(null, "Do you want to use the XP L&F?", "laffo", JOptionPane.YES_NO_OPTION);
                if (res == JOptionPane.YES_OPTION) {
                    try {
                        UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel");
                    } catch (Exception ex) {
                    }
                }
                new TestFrame().setVisible(true);
            }

        });

    }

    public class MyModel extends AbstractTableModel {

        public int getRowCount() {
            return 10;
        }

        public int getColumnCount() {
            return 10;
        }

        public Object getValueAt(int rowIndex, int columnIndex) {
            return "" + rowIndex + " X " + columnIndex;
        }

    }

    public class MyJTable extends JTable {

        /**
         *
         */
        private static final long serialVersionUID = -233098459210523146L;

        public MyJTable(TableModel model) {
            super(model);
        }

        public void doSomething() {
            System.out.println("HEHE");
        }
    }

    public class MyAlternativeJTable extends JTable {

        private WeakReference<TableCellRenderer> wrappedHeaderRendererRef = null;
        private TableCellRenderer wrapperHeaderRenderer = null;

        public MyAlternativeJTable(TableModel model) {
            super(model);
        }

        private class MyAlternativeTableColumn extends TableColumn {

            MyAlternativeTableColumn(int modelIndex) {
                super(modelIndex);
            }

            @Override
            public TableCellRenderer getHeaderRenderer() {
                TableCellRenderer defaultHeaderRenderer
                        = MyAlternativeJTable.this.getTableHeader().getDefaultRenderer();
                if (wrappedHeaderRendererRef == null
                        || wrappedHeaderRendererRef.get() != defaultHeaderRenderer) {
                    wrappedHeaderRendererRef
                            = new WeakReference<TableCellRenderer>(defaultHeaderRenderer);
                    wrapperHeaderRenderer
                            = new DecoratedHeaderRenderer(defaultHeaderRenderer);
                }
                return wrapperHeaderRenderer;
            }
        }

        @Override
        public void createDefaultColumnsFromModel() {
            TableModel m = getModel();
            if (m != null) {
                // Remove any current columns
                TableColumnModel cm = getColumnModel();
                while (cm.getColumnCount() > 0) {
                    cm.removeColumn(cm.getColumn(0));
                }

                // Create new columns from the data model info
                for (int i = 0; i < m.getColumnCount(); i++) {
                    TableColumn newColumn = new MyAlternativeTableColumn(i);
                    addColumn(newColumn);
                }
            }
        }
    }

    private JPanel jContentPane = null;
    private JScrollPane jScrollPane = null;
    private JTable table1 = null;
    private JScrollPane jScrollPane1 = null;
    private JTable table2 = null;

    /**
     * This is the default constructor
     */
    public TestFrame() {
        super();
        initialize();
        int res = JOptionPane.showConfirmDialog(null, "Do you want to call updateUI() on the tables ?", "laffo", JOptionPane.YES_NO_OPTION);
        if (res == JOptionPane.YES_OPTION) {
            table2.updateUI();
            table1.updateUI();
        }
    }

    /**
     * This method initializes this
     *
     * @return void
     */
    private void initialize() {
        this.setSize(753, 658);
        this.setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
        this.setContentPane(getJContentPane());
        this.setTitle("JFrame");
    }

    /**
     * This method initializes jContentPane
     *
     * @return javax.swing.JPanel
     */
    private JPanel getJContentPane() {
        if (jContentPane == null) {
            jContentPane = new JPanel();
            jContentPane.setLayout(null);
            jContentPane.add(getJScrollPane(), null);
            jContentPane.add(getJScrollPane1(), null);
        }
        return jContentPane;
    }

    /**
     * This method initializes jScrollPane
     *
     * @return javax.swing.JScrollPane
     */
    private JScrollPane getJScrollPane() {
        if (jScrollPane == null) {
            jScrollPane = new JScrollPane();
            jScrollPane.setBounds(new java.awt.Rectangle(358, 0, 387, 618));
            jScrollPane.setViewportView(getTable1());
        }
        return jScrollPane;
    }

    /**
     * This method initializes table1
     *
     * @return javax.swing.JTable
     */
    private JTable getTable1() {
        if (table1 == null) {
            table1 = new JTable(new MyModel());
        }
        return table1;
    }

    /**
     * This method initializes jScrollPane1
     *
     * @return javax.swing.JScrollPane
     */
    private JScrollPane getJScrollPane1() {
        if (jScrollPane1 == null) {
            jScrollPane1 = new JScrollPane();
            jScrollPane1.setBounds(new java.awt.Rectangle(0, 0, 350, 618));
            jScrollPane1.setViewportView(getTable2());
        }
        return jScrollPane1;
    }

    /**
     * This method initializes table2
     *
     * @return javax.swing.JTable
     */
    private JTable getTable2() {
        if (table2 == null) {
            if (I_WANT_THE_BUG_TO_HAPPEN) {
                table2 = new MyJTable(new MyModel());
                JTableHeader header = table2.getTableHeader();
                TableCellRenderer render = new DecoratedHeaderRenderer(header.getDefaultRenderer());
                header.setDefaultRenderer(render);
            } else {
                table2 = new MyAlternativeJTable(new MyModel());
            }
        }
        return table2;
    }

    private class DecoratedHeaderRenderer implements TableCellRenderer {

        public DecoratedHeaderRenderer(TableCellRenderer render) {
            this.render = render;
        }
        private TableCellRenderer render;

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

    }

}

Simply run the example and choose Yes twice and watch it break apart. Then change the I_WANT_THE_BUG_TO_HAPPEN static member to false and repeat. The case with this member set to true is essentially the same as the most upvoted answer here. The most important part of this example is the extended JTable (MyAlternativeJTable) which does the wrapping dynamically.

The currently accepted answer to this question is widely used, but it is ill advised. You can reproduce it with lost of applications, including Netbeans 8.0.2 (which itself is Java based) while it is showing a sortable table, such as Window > IDE Tools > Notifications, where you'll also get the NPE reports, ironically. Just switch the Windows theme from Aero to Windows Classic (via right-click Desktop > Personalize > Change the visuals and sounds on your computer) on Windows 7.

If you are using Glazed Lists and call ca.odell.glazedlists.swing.TableComparatorChooser.install, you are also affected. It injects its own custom renderer for sorting arrows.

I stumbled upon this by coincidence while trying to find a solution for this question which I suspect is related.

Community
  • 1
  • 1
predi
  • 5,528
  • 32
  • 60
2
DefaultTableCellRenderer renderer = (DefaultTableCellRenderer) 
MSISDNTable.getTableHeader().getDefaultRenderer();
renderer.setHorizontalAlignment(JLabel.RIGHT);

where MSISDNTable is your table

Igor Tyulkanov
  • 5,487
  • 2
  • 32
  • 49
Mahfuz Ahmed
  • 721
  • 9
  • 23
2
for (int i = 0 ; i < table.getColumnCount(); i++){

    DefaultTableCellRenderer renderer = new DefaultTableCellRenderer();
    renderer.setHorizontalAlignment(SwingConstants.RIGHT);
    table.getColumn(i).setHeaderRenderer(renderer);

}
Heisenbug
  • 38,762
  • 28
  • 132
  • 190
  • 1
    This is what I had originally but, unfortunately it overrides the Look and Feel of the headers. Also you must use the class `DefaultTableCellRenderer` for the the renderer object because `TableCellRenderer` does not have a method `setHorizontalAlighment` – Drew Galbraith Sep 21 '11 at 01:03
  • 1
    It's a kludge, but perhaps you could get the existing renderer and do an instanceof and cast to `DefaultTableCellRenderer` before setting the alignment? Or perhaps there's a way to force the new() one into the proper L&F? – Kevin Reid Sep 21 '11 at 01:15
  • Actually I was wrong. I don't know any better method to do that. Maybe @Kevin Reid is right. Let's wait and see if any swing guru can tell us a bit more about this. – Heisenbug Sep 21 '11 at 01:24
  • 3
    It seems more reliable to set the alignment on the existing renderer than to try and decorate a new one. (Not a guru; just a fellow traveler.) – trashgod Sep 21 '11 at 02:04
  • +1 for critically examining the approach. @mKorbel: I sense that we are _all_ students of kleopatra. :-) – trashgod Sep 21 '11 at 11:44
  • agreed with Jeanette, her cross-api-knowledge and clear critique moved us with giant steps forward, she's knowledge is endless treasury, hmmm there are a few another Java & Swing API's/Frameworks/L&F builders around us and former Old.Sun employee too (not sure ..., but I think that no one has contract with current owner, something happens Persons & JavaFX), but only Jeanette is daily contributor here – mKorbel Sep 21 '11 at 12:17
1
DefaultTableCellRenderer defaultHeaderRenderer = (DefaultTableCellRenderer) getTableHeader().getDefaultRenderer();
defaultHeaderRenderer.setHorizontalAlignment(JLabel.CENTER);         
getTableHeader().setDefaultRenderer(defaultHeaderRenderer);

I have tested in JAVA8. working fine.

4b0
  • 21,981
  • 30
  • 95
  • 142
0

Try this code,

JTableHeader jtableHeader = jtable.getTableHeader();
DefaultTableCellRenderer rend = (DefaultTableCellRenderer) jtable.getTableHeader().getDefaultRenderer();
rend.setHorizontalAlignment(JLabel.CENTER);
jtableHeader.setDefaultRenderer(rend);
Szymon
  • 42,577
  • 16
  • 96
  • 114
Kannan Arumugam
  • 1,119
  • 2
  • 18
  • 27
  • hmm ... nothing new compared to earlier answers, is there ;-) BTW: no need to set the default renderer again, it _already is_ the default renderer – kleopatra Sep 18 '13 at 07:50
0

I have created a class based on the solution of pvbemmelen62, that can be used very easily, for example:

AlignHeaderRenderer.install(myTable, new int[] { SwingConstants.RIGHT,
                        SwingConstants.RIGHT, SwingConstants.LEFT });

or

AlignHeaderRenderer.install(myTable, 0, SwingConstants.RIGHT);
AlignHeaderRenderer.install(myTable, 1, SwingConstants.RIGHT);

Here's the code:

public class AlignHeaderRenderer implements TableCellRenderer {

private final TableCellRenderer renderer;
private final int alignment;

public static void install(final JTable table, final int[] alignments) {
    for (int i = 0; i < alignments.length; ++i)
        install(table, i, alignments[i]);
}

public static void install(final JTable table, final int row,
        final int alignment) {
    table.getTableHeader().getColumnModel().getColumn(row)
            .setHeaderRenderer(new AlignHeaderRenderer(table, alignment));
}

private AlignHeaderRenderer(final JTable table, final int alignment) {
    renderer = table.getTableHeader().getDefaultRenderer();
    this.alignment = alignment;
}

@Override
public Component getTableCellRendererComponent(final JTable table,
        final Object value, final boolean isSelected,
        final boolean hasFocus, final int row, final int col) {
    final Component c = renderer.getTableCellRendererComponent(table,
            value, isSelected, hasFocus, row, col);
    ((JLabel) c).setHorizontalAlignment(alignment);
    return c;
}

}
Oliver Hoffmann
  • 146
  • 2
  • 11
0

The secret is to use the renderer from a dummy table to get correct L&F, and copy the alignment from the real table's row renderer. That way each column in aligned separately. Here is the code:

table.getTableHeader().setDefaultRenderer(new DefaultTableCellRenderer() {
    @Override
    public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int col) {
        Component c2 = dummy.getTableHeader().getDefaultRenderer().getTableCellRendererComponent(table, value, isSelected, hasFocus, row, col);
        if (table.getRowCount() > 0) {
            Component c3 = table.getCellRenderer(0, col).getTableCellRendererComponent(table, value, isSelected, hasFocus, 0, col);
            if (c2 instanceof JLabel && c3 instanceof JLabel)
                ((JLabel)c2).setHorizontalAlignment(((JLabel)c3).getHorizontalAlignment());
        }
        return c2;
    }
    private final JTable dummy = new JTable();
});

The above code does not keep any references to renderers, so it avoids the NPE bug mentioned above. It does not require any named class, so you can just drop the code in wherever you need it.

Adam Gawne-Cain
  • 1,347
  • 14
  • 14
-1
  ((DefaultTableCellRenderer)jTable2.getTableHeader().getDefaultRenderer())
    .setHorizontalAlignment(JLabel.CENTER);
Ahmad Aghazadeh
  • 16,571
  • 12
  • 101
  • 98
Shinwar ismail
  • 299
  • 2
  • 9
  • This is not an answer to a question. You should revise this to include an explanation as to how or why this solves the asked question, so others can learn and understand how to apply this to their own issues. – yanman1234 Aug 07 '17 at 17:32
-2
((JLabel)mTabBOM.getTableHeader().getDefaultRenderer()).setHorizontalAlignment( JLabel.CENTER );
Dmil
  • 11