Trying to create JTable with proper row header
Asked Answered
A

2

14

I am trying to create a JTable that has a row header that looks just like a column header and I have spent altogether too much time on it :/ My situation is similar to this question: JTable Row Header Implementation and maybe this one: customizing jtable cellrenderer with table's cell header color

They don't seem to have gotten me all the way there yet. I have searched tried many examples out there and all are lacking. There aren't even any examples of tables with row headers at all from Oracle/Sun. It seems like this kind of table shouldn't be that rare.

This one just formats the first column but it doesn't quite look or act like the column header: http://java-swing-tips.blogspot.com/2011/01/jtable-rowheader.html

This one seems to use a JList for the row header and it has alignment problems (off by +1 pixel each row) and doesn't look nicer when I set the Look and Feel. http://www.java2s.com/Code/Java/Swing-Components/TableRowHeaderExample.htm

This one seems like the idea I needed to use (the row header is a separate JTable but is then loaded to the JScrollPane as the row header), but I need to adapt it to my code and then make sure the row header gets the correct look and feel of the header.
http://www.chka.de/swing/table/row-headers/JTable.html

That is what I've done minus the last bit. I try to get the table header's renderer to be the renderer for the row header too. The row header/first column is now gray instead of white as it was when it was just another data column, but still doesn't look like the column header. Is this right? Or should I stick with keeping it as a regular column in the main table and do something else with it?

So here is my code for updating the table. This method is just taking a String array for the column header, a String array for the row header, and a 2D String array for the main data. I have a JTable dispTableRowHeader for the row header and a JTable dispTable for the main data table.

    private void updateDispTable(String[][] graphicalTable, String[] graphicalTableColumnHeader, String[] graphicalTableRowHeader) {

    //set model for the main data table, put in data. Also prevent editing cells
    dispTable.setModel(new javax.swing.table.DefaultTableModel(
        graphicalTable,
        graphicalTableColumnHeader
    ){
        @Override
        public boolean isCellEditable(int rowIndex, int mColIndex) {
            return false;
        }
    });

    //some mods for main data table
    dispTable.getTableHeader().setReorderingAllowed(false);//Was also able to do this within NetBeans GUI Builder by doing Table Contents from Jtable inspector item
    dispTable.getTableHeader().setResizingAllowed(false);

    //load main table to scrollpane
    jScrollPane2.setViewportView(dispTable);

    //get model for JTable that will be used as the row header, fill in values
    DefaultTableModel rowHeaderTableModel = new DefaultTableModel(0, 1);//one column
    for (int i = 0; i < graphicalTable.length; i++)
        rowHeaderTableModel.addRow(new Object[] { graphicalTableRowHeader[i] } );

    //set model for row header, put in data. Alter renderer to make it like col header
    dispTableRowHeader.setModel(rowHeaderTableModel);
    dispTableRowHeader.setDefaultRenderer(Object.class, dispTableRowHeader.getTableHeader().getDefaultRenderer());//makes it gray but not like the header :/
    //dispTableRowHeader.setDefaultRenderer(Object.class, jScrollPane2.getColumnHeader().getDefaultRenderer());

    //load row header to scrollpane's row header
    jScrollPane2.setRowHeaderView(dispTableRowHeader);

    //set the table corner and disallow reordering and resizing
    JTableHeader corner = dispTableRowHeader.getTableHeader();
    corner.setReorderingAllowed(false);
    corner.setResizingAllowed(false);
    jScrollPane2.setCorner(JScrollPane.UPPER_LEFT_CORNER, corner);//load to scrollpane
}

I greatly appreciate any help!

EDIT BELOW EDIT BELOW I created a whole new project to experiment and tried trashgod's method (though doing it for a row header rather than as a second row) and I found it gave me the same result of just cells styled gray rather than styled like the header. Then I tried removing my Look and Feel adjustment

        // Set System L&F
        UIManager.setLookAndFeel( UIManager.getSystemLookAndFeelClassName() );

and then the row header looked the same as the column header! .. but that's in Nimbus or whatever default Look and Feel. See the images below. The first is with my Look and Feel set to the system's as above (in Win7), and the second is default.

System Look and Feel (Win7)- I adjusted look and feel, set to system look and feel

Java's Nimbus Look and Feel- default look and feel

Sure enough the same happens with my program. So now it appears my problem is with the Look and Feel. I want it to look like the first image (system's look and feel), but with the left side styled too.

By the way, here is an example in Win7 of MySQL Workbench which has the properly styled row and column headers and they both even shade the cells bluish on mouseover. Too bad it isn't made in Java so I could try to check how they did it.

MySQL Workbench screenshot- MySQL Workbench screenshot

EDIT BELOW EDIT BELOW SSCCE code

package mytableexample2;
import java.awt.Component;
import javax.swing.JTable;
import javax.swing.UIManager;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableCellRenderer;

public class MyTableExample2 extends javax.swing.JFrame {

    public MyTableExample2() {
        initComponents();
    }

    @SuppressWarnings("unchecked")
    private void initComponents() {
        jScrollPane1 = new javax.swing.JScrollPane();
        jTable1 = new javax.swing.JTable(){
            @Override
            public Component prepareRenderer(
                    TableCellRenderer renderer, int row, int col) {
                if (col == 0) {
                    return this.getTableHeader().getDefaultRenderer()
                        .getTableCellRendererComponent(this,
                        this.getValueAt(row, col), false, false, row, col);
                } else {
                    return super.prepareRenderer(renderer, row, col);
                }
            }
        };
        jTable1.setAutoCreateRowSorter(false);
        final JTableHeader header = jTable1.getTableHeader();
        header.setDefaultRenderer(new HeaderRenderer(jTable1));

        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);

        jTable1.setModel(new javax.swing.table.DefaultTableModel(
            new Object [][] {
                {"Row 1", "Data 2", "Data 3", "Data 4", "Data 5"},
                {"Row 2", "Data 6", "Data 7", "Data 8", "Data 9"},
                {"Row 3", "Data 10", "Data 11", "Data 12", "Data 13"}
            },
            new String [] {
                "", "Col 1", "Col 2", "Col 3", "Col 4"
            }
        ));
        jScrollPane1.setViewportView(jTable1);


        //Netbeans generated layout
        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
        getContentPane().setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 380, Short.MAX_VALUE)
                .addContainerGap())
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 278, Short.MAX_VALUE)
                .addContainerGap())
        );
        pack();
    }


    public static void main(String args[]) {
        try {
            //THIS SETS TO SYSTEM'S LOOK AND FEEL
            UIManager.setLookAndFeel( UIManager.getSystemLookAndFeelClassName() );

            //THIS SETS TO OTHER JAVA LOOK AND FEEL
            //UIManager.setLookAndFeel("javax.swing.plaf.nimbus.NimbusLookAndFeel");
            //UIManager.setLookAndFeel("javax.swing.plaf.metal.MetalLookAndFeel");

        } catch (ClassNotFoundException ex) {
                java.util.logging.Logger.getLogger(MyTableExample2.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
        } catch (InstantiationException ex) {
        java.util.logging.Logger.getLogger(MyTableExample2.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
        } catch (IllegalAccessException ex) {
        java.util.logging.Logger.getLogger(MyTableExample2.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
        } catch (javax.swing.UnsupportedLookAndFeelException ex) {
        java.util.logging.Logger.getLogger(MyTableExample2.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
        }
        /* Create and display the form */
        java.awt.EventQueue.invokeLater(new Runnable() {
            public void run() {
                new MyTableExample2().setVisible(true);
            }
        });
    }
    // Variables declaration - do not modify
    private javax.swing.JScrollPane jScrollPane1;
    private javax.swing.JTable jTable1;
}

class HeaderRenderer implements TableCellRenderer {
TableCellRenderer renderer;
    public HeaderRenderer(JTable jTable1) {
        renderer = jTable1.getTableHeader().getDefaultRenderer();
    }
    @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);
    }
}
Angara answered 3/11, 2011 at 22:0 Comment(3)
I also tried the method here link with the same result.Angara
Please provide the sscce that produces the first image.Enos
I added MySQL Workbench screenshot which demonstrates a table with top and left like I'm trying to get. It's not written in Java though.Angara
D
4

maybe

enter image description here

import java.awt.*;
import java.awt.event.ActionEvent;
import javax.swing.*;
import javax.swing.UIManager.*;
import javax.swing.event.*;
import javax.swing.table.*;

public class JTableRowHeader {

    private JFrame frame = new JFrame("JTable RowHeader");
    private JScrollPane scrollPane;
    private JTable table;
    private DefaultTableModel model;
    private TableRowSorter<TableModel> sorter;
    private JTable headerTable;

    public JTableRowHeader() {
        table = new JTable(4, 4);
        for (int i = 0; i < table.getRowCount(); i++) {
            table.setValueAt(i, i, 0);
        }
        sorter = new TableRowSorter<TableModel>(table.getModel());
        table.setRowSorter(sorter);
        model = new DefaultTableModel() {

            private static final long serialVersionUID = 1L;

            @Override
            public int getColumnCount() {
                return 1;
            }

            @Override
            public boolean isCellEditable(int row, int col) {
                return false;
            }

            @Override
            public int getRowCount() {
                return table.getRowCount();
            }

            @Override
            public Class<?> getColumnClass(int colNum) {
                switch (colNum) {
                    case 0:
                        return String.class;
                    default:
                        return super.getColumnClass(colNum);
                }
            }
        };
        headerTable = new JTable(model);
        for (int i = 0; i < table.getRowCount(); i++) {
            headerTable.setValueAt("Row " + (i + 1), i, 0);
        }
        headerTable.setShowGrid(false);
        headerTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
        headerTable.setPreferredScrollableViewportSize(new Dimension(50, 0));
        headerTable.getColumnModel().getColumn(0).setPreferredWidth(50);
        headerTable.getColumnModel().getColumn(0).setCellRenderer(new TableCellRenderer() {

            @Override
            public Component getTableCellRendererComponent(JTable x, Object value, boolean isSelected, boolean hasFocus, int row, int column) {

                boolean selected = table.getSelectionModel().isSelectedIndex(row);
                Component component = table.getTableHeader().getDefaultRenderer().getTableCellRendererComponent(table, value, false, false, -1, -2);
                ((JLabel) component).setHorizontalAlignment(SwingConstants.CENTER);
                if (selected) {
                    component.setFont(component.getFont().deriveFont(Font.BOLD));
                    component.setForeground(Color.red);
                } else {
                    component.setFont(component.getFont().deriveFont(Font.PLAIN));
                }
                return component;
            }
        });
        table.getRowSorter().addRowSorterListener(new RowSorterListener() {

            @Override
            public void sorterChanged(RowSorterEvent e) {
                model.fireTableDataChanged();
            }
        });
        table.getSelectionModel().addListSelectionListener(new ListSelectionListener() {

            @Override
            public void valueChanged(ListSelectionEvent e) {
                model.fireTableRowsUpdated(0, model.getRowCount() - 1);
            }
        });
        scrollPane = new JScrollPane(table);
        scrollPane.setRowHeaderView(headerTable);
        table.setPreferredScrollableViewportSize(table.getPreferredSize());
        frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        frame.add(scrollPane);
        frame.add(new JButton(new AbstractAction("Toggle filter") {

            private static final long serialVersionUID = 1L;
            private RowFilter<TableModel, Object> filter = new RowFilter<TableModel, Object>() {

                @Override
                public boolean include(javax.swing.RowFilter.Entry<? extends TableModel, ? extends Object> entry) {
                    return ((Number) entry.getValue(0)).intValue() % 2 == 0;
                }
            };

            @Override
            public void actionPerformed(ActionEvent e) {
                if (sorter.getRowFilter() != null) {
                    sorter.setRowFilter(null);
                } else {
                    sorter.setRowFilter(filter);
                }
            }
        }), BorderLayout.SOUTH);
        frame.pack();
        frame.setLocation(150, 150);
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        try {// UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
            for (LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {
                if (info.getName().equals("Nimbus")) {
                    UIManager.setLookAndFeel(info.getClassName());
                    break;
                }
            }
        } catch (Exception e) {
            //e.printStackTrace();
        }
        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                JTableRowHeader TestTableRowHeader = new JTableRowHeader();
            }
        });
    }
}

Here it is with system Look and Feel- enter image description here

Domineer answered 4/11, 2011 at 16:46 Comment(6)
+1 Nice RowSorter. For some reason the JLabel.CENTER doesn't take effect until I hove the mouse over the column header.Enos
changed to the (SwingConstants.CENTER) :-)Domineer
No, it's not the constant; I think it's something odd with com.apple.laf.AquaTableHeaderUI.Enos
hmmmm I'm sorry, not idea how to solve that (my bad I'm AS xxx and Windows user) for Mac OS X, please are there same issues by using UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());Domineer
Thanks for the post! I loaded this up and I see this does exactly what mine does now (I added image to your post). The cells on the left are just plain gray with no shading when I use system look and feel.Angara
@mKorbel: Interesting; getSystemLookAndFeelClassName() returns com.apple.laf.AquaLookAndFeel. Sorry I didn't respond earlier.Enos
E
5

Using this HeaderRenderer as the first row column renderer may produce the effect you want:

Addendum: I've updated the example to reflect your sscce with a manual layout. My platform's getSystemLookAndFeelClassName() is com.apple.laf.AquaLookAndFeel, so I'm not seeing the same result. Two observations: You've already setAutoCreateRowSorter(false) to prevent the sorting widget from proliferating, and Nimbus retains it alternating row highlight.

enter image description here

import java.awt.Component;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableCellRenderer;

public class MyTableExample3 extends JFrame {

    private JScrollPane scrollPane;
    private JTable table;

    public MyTableExample3() {
        initComponents();
    }

    @SuppressWarnings("unchecked")
    private void initComponents() {
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        table = new javax.swing.JTable() {

            @Override
            public Component prepareRenderer(
                TableCellRenderer renderer, int row, int col) {
                if (col == 0) {
                    return this.getTableHeader().getDefaultRenderer()
                        .getTableCellRendererComponent(this, this.getValueAt(
                            row, col), false, false, row, col);
                } else {
                    return super.prepareRenderer(renderer, row, col);
                }
            }
        };
        table.setAutoCreateRowSorter(false);
        final JTableHeader header = table.getTableHeader();
        header.setDefaultRenderer(new HeaderRenderer(table));

        table.setModel(new javax.swing.table.DefaultTableModel(
            new Object[][]{
                {"Row 1", "Data 2", "Data 3", "Data 4", "Data 5"},
                {"Row 2", "Data 6", "Data 7", "Data 8", "Data 9"},
                {"Row 3", "Data 10", "Data 11", "Data 12", "Data 13"}
            },
            new String[]{
                "", "Col 1", "Col 2", "Col 3", "Col 4"
            }));
        scrollPane = new JScrollPane(table);
        this.add(scrollPane);
        pack();
    }

    public static void main(String args[]) {
        java.awt.EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                new MyTableExample3().setVisible(true);
            }
        });
    }

    private static class HeaderRenderer implements TableCellRenderer {

        TableCellRenderer renderer;

        public HeaderRenderer(JTable table) {
            renderer = table.getTableHeader().getDefaultRenderer();
        }

        @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);
        }
    }
}
Enos answered 4/11, 2011 at 4:52 Comment(5)
You misunderstood me - I actually want the first column to be a header, not two rows - like MS Excel or anything like that, you know a table with a header across the top and a header down the left. But anyway I built an example your way and I got the same result (just gray cells instead of styled like the header). I then removed my Look and Feel adjustment and both the headers were actually the same! This is good. The only difference I noticed was that the row headers did not highlight in anyway when moused over like the column headers. I'm posting edit to my question with images.Angara
Updated. Sadly, I can't reproduce the effect on Mac OS X.Enos
Ok so it does show nice header cells on the left in OS X I see. I thought that might be the case after the Java Look and Feels worked but not the System (Win7) one.Angara
I notice that even with the Nimbus L&F looking consistent on both left and top, the left header cells do not highlight or shade differently on mouseover like the top header cells do. Does it work for you? By the way, nice shadow on your image :)Angara
Om Mac with Nimbus, using the header renderer on column 0 causes alternate rows to be highlighted, as with other rows. I can't take any credit for the shadow; it's a feature of the screenshot mechanism.Enos
D
4

maybe

enter image description here

import java.awt.*;
import java.awt.event.ActionEvent;
import javax.swing.*;
import javax.swing.UIManager.*;
import javax.swing.event.*;
import javax.swing.table.*;

public class JTableRowHeader {

    private JFrame frame = new JFrame("JTable RowHeader");
    private JScrollPane scrollPane;
    private JTable table;
    private DefaultTableModel model;
    private TableRowSorter<TableModel> sorter;
    private JTable headerTable;

    public JTableRowHeader() {
        table = new JTable(4, 4);
        for (int i = 0; i < table.getRowCount(); i++) {
            table.setValueAt(i, i, 0);
        }
        sorter = new TableRowSorter<TableModel>(table.getModel());
        table.setRowSorter(sorter);
        model = new DefaultTableModel() {

            private static final long serialVersionUID = 1L;

            @Override
            public int getColumnCount() {
                return 1;
            }

            @Override
            public boolean isCellEditable(int row, int col) {
                return false;
            }

            @Override
            public int getRowCount() {
                return table.getRowCount();
            }

            @Override
            public Class<?> getColumnClass(int colNum) {
                switch (colNum) {
                    case 0:
                        return String.class;
                    default:
                        return super.getColumnClass(colNum);
                }
            }
        };
        headerTable = new JTable(model);
        for (int i = 0; i < table.getRowCount(); i++) {
            headerTable.setValueAt("Row " + (i + 1), i, 0);
        }
        headerTable.setShowGrid(false);
        headerTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
        headerTable.setPreferredScrollableViewportSize(new Dimension(50, 0));
        headerTable.getColumnModel().getColumn(0).setPreferredWidth(50);
        headerTable.getColumnModel().getColumn(0).setCellRenderer(new TableCellRenderer() {

            @Override
            public Component getTableCellRendererComponent(JTable x, Object value, boolean isSelected, boolean hasFocus, int row, int column) {

                boolean selected = table.getSelectionModel().isSelectedIndex(row);
                Component component = table.getTableHeader().getDefaultRenderer().getTableCellRendererComponent(table, value, false, false, -1, -2);
                ((JLabel) component).setHorizontalAlignment(SwingConstants.CENTER);
                if (selected) {
                    component.setFont(component.getFont().deriveFont(Font.BOLD));
                    component.setForeground(Color.red);
                } else {
                    component.setFont(component.getFont().deriveFont(Font.PLAIN));
                }
                return component;
            }
        });
        table.getRowSorter().addRowSorterListener(new RowSorterListener() {

            @Override
            public void sorterChanged(RowSorterEvent e) {
                model.fireTableDataChanged();
            }
        });
        table.getSelectionModel().addListSelectionListener(new ListSelectionListener() {

            @Override
            public void valueChanged(ListSelectionEvent e) {
                model.fireTableRowsUpdated(0, model.getRowCount() - 1);
            }
        });
        scrollPane = new JScrollPane(table);
        scrollPane.setRowHeaderView(headerTable);
        table.setPreferredScrollableViewportSize(table.getPreferredSize());
        frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        frame.add(scrollPane);
        frame.add(new JButton(new AbstractAction("Toggle filter") {

            private static final long serialVersionUID = 1L;
            private RowFilter<TableModel, Object> filter = new RowFilter<TableModel, Object>() {

                @Override
                public boolean include(javax.swing.RowFilter.Entry<? extends TableModel, ? extends Object> entry) {
                    return ((Number) entry.getValue(0)).intValue() % 2 == 0;
                }
            };

            @Override
            public void actionPerformed(ActionEvent e) {
                if (sorter.getRowFilter() != null) {
                    sorter.setRowFilter(null);
                } else {
                    sorter.setRowFilter(filter);
                }
            }
        }), BorderLayout.SOUTH);
        frame.pack();
        frame.setLocation(150, 150);
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        try {// UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
            for (LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {
                if (info.getName().equals("Nimbus")) {
                    UIManager.setLookAndFeel(info.getClassName());
                    break;
                }
            }
        } catch (Exception e) {
            //e.printStackTrace();
        }
        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                JTableRowHeader TestTableRowHeader = new JTableRowHeader();
            }
        });
    }
}

Here it is with system Look and Feel- enter image description here

Domineer answered 4/11, 2011 at 16:46 Comment(6)
+1 Nice RowSorter. For some reason the JLabel.CENTER doesn't take effect until I hove the mouse over the column header.Enos
changed to the (SwingConstants.CENTER) :-)Domineer
No, it's not the constant; I think it's something odd with com.apple.laf.AquaTableHeaderUI.Enos
hmmmm I'm sorry, not idea how to solve that (my bad I'm AS xxx and Windows user) for Mac OS X, please are there same issues by using UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());Domineer
Thanks for the post! I loaded this up and I see this does exactly what mine does now (I added image to your post). The cells on the left are just plain gray with no shading when I use system look and feel.Angara
@mKorbel: Interesting; getSystemLookAndFeelClassName() returns com.apple.laf.AquaLookAndFeel. Sorry I didn't respond earlier.Enos

© 2022 - 2024 — McMap. All rights reserved.