Hello good day. I have an urgent need as it relates to creating a table. Is it possible to create sub-columns under a main column? Please see image file for clarification.
70637f1f94a4b270bf9cc203f22390bf
Ultimately, I want to create a coulmn heading, and then beneath that heading divide it's width into a number of other columns each with their own heading. I hope I am clear. Is there any way to do this please?

Recommended Answers

All 32 Replies

javax.swing.JTable is a class with many features and complexities, but I am sure that it can't do what you are asking for directly. I recommend that you use ordinary javax.swing.JLabels for your top column headings and then use two JTables, one for "Subjects" and one for "Group Letters". That won't give you all of the features of real JTable columns, but it should be much simpler than trying to make them full-featured. If you want your top columns to be resizable then you can use a javax.swing.JSplitPane.

Thank you very much for this response. I understand what you are saying.
A few questions:

  • Can I add two JTables to one ScrollPane?
  • How will I make the Tables interact? e.g. Copying entire rows.

It retrospect it is obvious that you would want to be able to select rows and you would want to keep the rows lined up when scrolling vertically, so I take back my suggestion of using two JTables. That option was too easy. Instead I suggest that you use one JTable which has all of the columns of your lower row of columns: Totals, Subject name, A, B, C, etc. Then you can use getTableHeader to get the component that draws the column headings. You can put the header into a custom JPanel of your own design that will draw your upper level of column headings. Then you can give that custom header to the JScrollPane using setColumnHeaderView.

I haven't actually tested this idea, but it seems likely to work.

Um...Hmm. I am new to GUI designing. So that flew right over my head. But I appreciate you taking the time. I'll have to research
those methods.

Can you be off any assitance with the JPanel please? I've gotten as far as here.

3f5027ee3c6959f7c347eab11fd6f6cd

You can see the JPanel peeking out at the top there. But I'm hoping to make it look like this below.

cd312d1a265189f3c5dd7a69164c9ef6

I've been struggling with the JPanel, but to no avail. Any help please?

I see you have the word "Subjects" there. I recommend that you center that word above the "Totals" and "Subject Name" columns, and also add a "Group Letters" heading on the right side. I expect you have tried to do this and encountered some difficulty, but since I don't know anything about that specific difficulty I can't be very specific in my advice.

I suggest that you use javax.swing.JLabel to draw your headings. You can use the javax.swing.table.TableColumnModel to give you the information that you need to put the labels in the right places, and you can write a javax.swing.event.TableColumnModelListener to allow you to update the positions of the labels when the positions of the columns change.

This is hlepful advice. I'll give it a try, and then I'll come back to you. I appreciate your patience thus far.

I'm not too sure about the positioning of the JLabels. This is my code so far.

JTableHeader groupsTblHead;
JPanel panel1=new JPanel(new GridBagLayout());//I chose GridBag due to its more flexible nature
public DefaultTableModel groupingTableModel = new DefaultTableModel(data_2,groupColumns);
JTable groupsTable= new JTable(groupingTableModel);
GridBagConstraints grp = new GridBagContraints();
JLabel subLabel = new JLabel("SUBJECTS");
JLabel lettLabel = new JLabel("GROUP LETTERS");
...

groupsTblHead = groupsTable.getTableHeader();

grp.gridx=0;
grp.gridy=0;    
panel1.add(subLabel,grp);

grp.gridx=1;
grp.gridy=0;               
panel1.add(lettLabel, grp);

grp.gridx=0;
grp.gridy=1;                           
panel1.add(groupsTblHead,grp);

tableScrollArea.setColumnHeaderView(panel1);

This results in the following interface.
d7d07dc1388f4582bbe5eb4c4d107b08

Not quite what I want. I am trying to at least see both of those labels. Any suggestions please?

You realize that I added two labels, but neither show up. Is there a reason for that please?

So I changed the code once more. I was messing around
Here are: the code and the visual result.
You'll notice that it still has a glitch in it, I can't figure out why it is behaving like this. Please help me, I'm truly trying.

    //Same declarations

    groupsTblHead = groupsTable.getTableHeader();

    grp.gridx=0;
    grp.gridy=0;     
    panel1.add(subLabel,grp);

    grp.gridx=1;
    grp.gridy=0;                  
    panel1.add(lettLabel, grp);

    grp.gridx=0;
    grp.gridy=1;
    panel1.setPreferredSize(new Dimension(150,50));
    grp.weightx=1;
    grp.weighty=1;
    grp.fill=GridBagConstraints.HORIZONTAL;
    panel1.add(groupsTblHead,grp);

    tableScrollArea.setColumnHeaderView(panel1); 

3869dfd39f631cb8eda3c14e148ffc3e

I recommend that you avoid java.awt.GridBagLayout. It is troublesome and overly complicated, so people rarely use it in practice which makes it hard to find good advice about its use.

In this case especially you should avoid layout managers because your layout needs are very specialized and specific. No existing layout manager is going to give you what you need, so you either need to write your own or else use null for your layout manager and position your labels directly. Since you know exactly where you want your labels, that should be much easier than struggling with a layout manager.

@bguild answers here going wrong directions, please to review that, from end to the start,

@CoilFyzx please whats your goal, there are simpler ways, by using JTable and/or with another, both JTables can has the same TableModel, depends of your final idea, used notifiers and Listeners

I recommend that you avoid java.awt.GridBagLayout

Just for balance - most of my Java work is GUI-centric, and GridBagLayout is the only layout manager I use for anything other than trivial cases. It's the only one that gives me enough control over what happens when the systen font is different or the window is resized. In my opinion it's well worth the effort to learn to use it.

Here's a simple way to do it provided you don't want to get too clever...
It just adds a JLabel across the top of the whole table, and paints the top-level headings into that JLabel at the right places - which it gets from the table's column model. By listening for column resizes it also adjusts to match the new column widths.
The following runnable proof of concept shows the idea... feel free to criticise!

import java.awt.Graphics;
import java.awt.GridBagConstraints;
import static java.awt.GridBagConstraints.*;
import java.awt.GridBagLayout;
import java.awt.Insets;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.table.*;

public class TableWithHeadings {

   public static void main(String... args) {
      new TableWithHeadings();
   }

   TableWithHeadings() {
      JFrame frame = new JFrame("Demo");
      JTable table = demoTable();

      frame.setLayout(new GridBagLayout());
      frame.add(new HeadingsLabel(table), new GridBagConstraints(0, RELATIVE, 1, 1, 0.0, 0.0,
              NORTHWEST, HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0));
      frame.add(table.getTableHeader(), new GridBagConstraints(0, RELATIVE, 1, 1, 0.0, 0.0,
              NORTHWEST, HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0));
      frame.add(table, new GridBagConstraints(0, RELATIVE, 1, 1, 0.0, 0.0,
              NORTHWEST, HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0));

      frame.pack();
      frame.setLocationRelativeTo(null);
      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      frame.setVisible(true);
   }

   JTable demoTable() {
      String[] headings = {"A", "B", "C", "D"};
      String[][] data = {{"1", "2", "3", "4"}, {"5", "6", "7", "8"}};
      JTable table = new JTable(data, headings);
      table.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
      return table;
   }

   class HeadingsLabel extends JLabel {

      JTable table;
      DefaultTableColumnModel colModel;

      String heading1 = "Heading 1";
      String heading2 = "Heading 2";
      int colsForHeading1 = 2; // how many cols to span

      HeadingsLabel(JTable table) {
         super(" ");
         this.table = table;
         colModel = (DefaultTableColumnModel) table.getColumnModel();

         colModel.addColumnModelListener(new TableColumnModelListener() {
            // just handle columnMarginChanged to re-paint headings
            @Override
            public void columnAdded(TableColumnModelEvent e) {
            }
            @Override
            public void columnRemoved(TableColumnModelEvent e) {
            }
            @Override
            public void columnMoved(TableColumnModelEvent e) {
            }
            @Override
            public void columnMarginChanged(ChangeEvent e) {
               repaint();
            }
            @Override
            public void columnSelectionChanged(ListSelectionEvent e) {
            }
         });
      }

      @Override
      public void paintComponent(Graphics g) {
         int head1width = -1;
         for (int cNumber = 0; cNumber < colsForHeading1; cNumber++) {
            TableColumn col = colModel.getColumn(cNumber);
            head1width += col.getWidth();
         }
         g.drawRect(0, 0, head1width, getHeight()-1);
         g.drawString(heading1, 20, 12);
         g.drawRect(head1width, 0, getWidth() - head1width-1, getHeight()-1);
         g.drawString(heading2, head1width + 20, 12);
      }
   }

}

feel free to criticise!

I find the way HeadingsLabel extends JLabel to be a bit strange. HeadingsLabel doesn't conform to the rules described in the documentation for JLabel, and therefore isn't a proper JLabel. It looks like a JPanel could have served the same purpose with less potential confusion.

Not sure what rules you are referring to... "A display area for a short text string or an image, or both. A label does not react to input events."
But anyway, I chose JLabel because it comes with font/text attributes that make it easy to implement some control of formatting of the headings, and sets its height appropriately, but otherwize, yes, a JPanel would be a sensible choice.

Wow. This has been a great help. I'll update you guys as soon as I implement the suggestions. Thank you.

  • again I thik that answers going wrong directions
    _________________________________________________________________

  • never to use paintComponent for JTableHeader, is called more times than Renderer,

  • use Renderer for JTableHeader
    ________________________________________________________________
  • never to use JTableHeader out of JScrollPane

  • hide JScrollBars
    ________________________________________________________________

  • JTable, JScrollPane and JComboBox cant returns reasonable PreferredSize from methods implemented in theirs API

  • JTable/JScrollPane required to overide setPreferred(ScrollableViewport)Size for pack()

  • Standard LayoutManagers (most of todays customs too) accepting only PreferredSize with pack()

____________________________________________________________________________________________________________________________________________

  • three reasons for ColumnModelListener

    1. convertColumnIndexToModel for prepareRenderer/Editor, but by default code line convertXxxIndexToXxx replace, supply usage of this Listener

    2. auto column adjuster for table.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);, to hold rellative coordinates after container resized

    3. and (put all mentioned points together)

.

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

public class TableFilterRow extends JFrame implements TableColumnModelListener {

    private static final long serialVersionUID = 1L;
    private JTable table;
    private JPanel filterRow; 

    public TableFilterRow() {
        table = new JTable(3, 5);
        table.setPreferredScrollableViewportSize(table.getPreferredSize());
        JScrollPane scrollPane = new JScrollPane(table);
        getContentPane().add(scrollPane);
        table.getColumnModel().addColumnModelListener(this);
        filterRow = new JPanel(new FlowLayout(FlowLayout.CENTER, 0, 0));
        for (int i = 0; i < table.getColumnCount(); i++) {
            filterRow.add(new JTextField(" Sum at - " + i));
        }
        columnMarginChanged(new ChangeEvent(table.getColumnModel()));
        getContentPane().add(filterRow, BorderLayout.SOUTH);
    }

    @Override
    public void columnMarginChanged(ChangeEvent e) {
        TableColumnModel tcm = table.getColumnModel();
        int columns = tcm.getColumnCount();

        for (int i = 0; i < columns; i++) {
            JTextField textField = (JTextField) filterRow.getComponent(i);
            Dimension d = textField.getPreferredSize();
            d.width = tcm.getColumn(i).getWidth();
            textField.setPreferredSize(d);
        }

        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                filterRow.revalidate();
            }
        });
    }

    @Override
    public void columnMoved(TableColumnModelEvent e) {
        Component moved = filterRow.getComponent(e.getFromIndex());
        filterRow.remove(e.getFromIndex());
        filterRow.add(moved, e.getToIndex());
        filterRow.validate();
    }

    @Override
    public void columnAdded(TableColumnModelEvent e) {
    }

    @Override
    public void columnRemoved(TableColumnModelEvent e) {
    }

    @Override
    public void columnSelectionChanged(ListSelectionEvent e) {
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                JFrame frame = new TableFilterRow();
                frame.setDefaultCloseOperation(EXIT_ON_CLOSE);
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }
}
  • other events from ColumnModelListener are properly implemented in TableModel and its derivates (Default/Abstract/...)

Not sure what rules you are referring to... "A display area for a short text string or an image, or both. A label does not react to input events."

I'm refering to the fact that a HeadingsLabel is a JLabel and therefore everything in the documentation for JLabel should apply to HeadingsLabel. Even that rule you quoted is being bent, or at least not being followed in the spirit in which it was intended, since a HeadingsLabel is incapable of ever displaying an image. More importantly, HeadingsLabel has methods that are clearly broken such as getText, setText, getIcon, setIcon, and many other methods.

Of course, this is only a technicality. Your solution works very nicely.

I understand where you guys are going but none of your implementations have really imprved my understanding of the matter. You all seem to have varying views on what is acceptable, and what is not.
Sigh.

I'd love to understand. But anyway Mr. James Cherill, I'm currently using your code. The issue I have is that I don't want to fill the available Horizontal space. But whenever I change that field GridBagConstraints.HORIZONTAL, the whole thing is thrown off.

I'm trying to tweak the implementation to suit the images I have in the picture. I want the whole thing to be centered In the Middle of the frame. Would you like to see my entire code?

I want the whole thing to be centered In the Middle of the frame. Would you like to see my entire code?

  • I think that not, if is based on answers here, then code will be potentially big mess

  • reason why I wrote twice that answers going wrong direction, asked for goal, clarification
    ____________________________________________________________________________________

  • main problems are

    1. never to separe JTableHeader out of JTable,

    2. add JTable to JScrollPane, then all bothering with possitioning in used LayoutManagers are irrelevant

    3. use BorderLayout (GridLayout too for one JComponent in container) for JComponents implements Scrollable in API

    4. describtion of Q is poor, without main goal, just to back out of the impasse, based on pictures

    5. then answers (from 1st. to last) are wild shots to the dark

I have to say that mKorbel is not always easy to understand, but he is usually right, and he is making some very valid points here.
The code I posted was an example of how you could fudge or fake a quick solution for a simple case, but that's all. I certainly don't recommend it as a general approach. It's up to you to decide whether your current requirement can be met with a quick fudge or whether it needs a properly engineered solution.

I want the whole thing to be centered In the Middle of the frame

When I run my code the whole thing is centered in the frame (?)
If you want to use it in a frame with other components then you would need to put my three components in a JPanel then put that JPanel with your other components in the main JFrame.

for example

import java.awt.*;
import javax.swing.*;
import javax.swing.table.DefaultTableModel;

public class JTableAndGBC {

    private String[] columnNames = {"Source", "Hit", "Last", "Ur_Diff"};
    private JTable table;
    private Object[][] data = {{"Swing Timer", 2.99, 5, 1.01},
        {"Swing Worker", 7.10, 5, 1.010}, {"TableModelListener", 25.05, 5, 1.01}};
    private DefaultTableModel model = new DefaultTableModel(data, columnNames);

    public JTableAndGBC() {
        JPanel panel = new JPanel(new GridBagLayout());
        table = new JTable(model);
        GridBagConstraints gbc = new GridBagConstraints();
        gbc.weightx = 1.0;
        gbc.fill = GridBagConstraints.HORIZONTAL;
        panel.add(table, gbc);
        JFrame frame = new JFrame();
        frame.add(panel, BorderLayout.CENTER);
        frame.pack();
        frame.setVisible(true);
    }

    public static void main(String args[]) throws Exception {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                new JTableAndGBC();
            }
        });
    }
}

EDIT 1

  • JTable, JScollPane, JComboBox can't returns reasonable PreferredSize, see my code in the edit, then wokrs for all LayoutManagers,

  • notice carefully with table.setPreferredScrollableViewportSize(table.getPreferredSize());, I'd suggest to test if Dimension overload desired coordinates or screen resolution or not overload :-),

  • otherwise to shrink with new Dimension(int, int) instead of table.getPreferredSize()

.

import java.awt.*;
import javax.swing.*;
import javax.swing.table.DefaultTableModel;

public class JTableAndGBC {

    private String[] columnNames = {"Source", "Hit", "Last", "Ur_Diff"};
    private JTable table;
    private Object[][] data = {{"Swing Timer", 2.99, 5, 1.01},
        {"Swing Worker", 7.10, 5, 1.010}, {"TableModelListener", 25.05, 5, 1.01}};
    private DefaultTableModel model = new DefaultTableModel(data, columnNames);

    public JTableAndGBC() {
        JPanel panel = new JPanel(new GridBagLayout());
        table = new JTable(model);
        GridBagConstraints gbc = new GridBagConstraints();
        gbc.weightx = 1.0;
        gbc.fill = GridBagConstraints.HORIZONTAL;
        JScrollPane pane = new JScrollPane(table);
        table.setPreferredScrollableViewportSize(table.getPreferredSize());
        panel.add(pane, gbc);
        JFrame frame = new JFrame();
        frame.add(panel, BorderLayout.CENTER);
        frame.pack();
        frame.setVisible(true);
    }

    public static void main(String args[]) throws Exception {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                new JTableAndGBC();
            }
        });
    }
}

EDIT 2

I'm strongly to suggest to use BorderLayout, GridLayout, instead of GBC (JTable, JScollPane, JComboBox can't returns reasonable PreferredSize, required to override GBC, brrrr, not why bothering)

code for BorderLayout

import java.awt.*;
import javax.swing.*;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableColumn;

public class JTableAndGBC {

    private String[] columnNames = {"Source", "Hit", "Last", "Ur_Diff"};
    private JTable table;
    private Object[][] data = {{"Swing Timer", 2.99, 5, 1.01},
        {"Swing Worker", 7.10, 5, 1.010}, {"TableModelListener", 25.05, 5, 1.01}};
    private DefaultTableModel model = new DefaultTableModel(data, columnNames);

    public JTableAndGBC() {
        JPanel panel = new JPanel(new BorderLayout()/*(new GridBagLayout()*/);
        table = new JTable(model);
        //GridBagConstraints gbc = new GridBagConstraints();
        //gbc.weightx = 1.0;
        //gbc.fill = GridBagConstraints.HORIZONTAL;
        JScrollPane pane = new JScrollPane(table, 
                ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
                ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
        /*TableColumn firstColumn = table.getColumnModel().getColumn(0);
        firstColumn.setMinWidth(150);
        firstColumn.setMaxWidth(200);
        TableColumn secondColumn = table.getColumnModel().getColumn(1);
        secondColumn.setMinWidth(200);
        secondColumn.setMaxWidth(250);
        TableColumn thirdColumn = table.getColumnModel().getColumn(1);
        thirdColumn.setMinWidth(50);
        thirdColumn.setMaxWidth(100);   */     
        table.setRowHeight(30);
        table.setPreferredScrollableViewportSize(table.getPreferredSize());
        panel.add(pane/*, gbc*/);
        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add(panel, BorderLayout.CENTER);
        frame.pack();
        frame.setVisible(true);
    }

    public static void main(String args[]) throws Exception {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                new JTableAndGBC();
            }
        });
    }
}

Nice responses again. I don't want to "fudge" a solution. Haha. I really wanted to understand how to do it, as I had stated (best practises included). Um...I'm not quite sure where to proceed from here.

My main objective: Is to create a table that accepts user data into a table. The tables design(which is what I'm trying to accomplish now), is to create a structured view of the information(which really is the purpose of every table).

So the user will add data to the section labelled subject letters.

As it relates to functionality, that will be simple to implement. All I wish for is the visual design. I don't know how to make that any more clear(I sya this because I keep being asked for the objective or my goal; which is exactly to create what I designed in the image).

So what would be the best practice for me to get this done please?

(mKorbel is difficult to comprehend, even though I think that I understand him.)

Well what I've done so far is to use the, er...'fugde'-d example by Mr James Cherill while changing the insets values.

This Looks okay but I haven't benefited as it relates to a learning experience.

I haven't benefited as it relates to a learning experience.

If there is any part of it that you don't understand, I'm sure we will be glad to explain it to you.

commented: Firstly, what would one consider to be the a good way to solve this problem, keeping in mind the principles of best-practice? (You guys seem to refer to trying to code as properly as possible) +1

Firstly, what would one consider to be the a good way to solve this problem, keeping in mind the principles of best-practice? (You guys seem to refer to trying to code as properly as possible)

The "proper" way to do it is probably to subclass JTable and override the method that implements the UI for the table header. There are a few discussions about that on the web. It looks pretty difficult to me.

In my opinion while you should always strive to do the best, real life constrains you, and sometimes a quick fix is the most appropriate thing to do.
In this case for example, if the exercise is all about UI then you need to do it properly, but if it's an exercise in database mananagement then spending a too much time on prettyfying the UI is probably a mistake...

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.