Swing - sort the JTable rows, but keep them grouped by one column and only show the value once on top of each group

I want to sort JTable

like this:

enter image description here

That is: ( EDIT )

  • If I clicked the Name column to sort, Tom, Polazzo and Anna should be sorted alphabetically, and rows with the same name should stay together ("grouped" by name) and each name should only be displayed once, the rest of the cells must be empty.

  • If I click on the Duration or Book # column, I want all rows to be sorted in ascending / descending order by duration / book number values, but the same as in 1), rows with the same name "should remain together, ie stay grouped, and only the first row in each group is displayed, and the rest of the "Name" remain blank.

The data in the table model vector is collected after parsing the XML file. Lines with the same "name" are found under the same node in the hierarchy tree.

I think there are two ways to do this:

a) While collecting data and building rows, under the same "name" node, give the cell in column 0 the value "Name" and leave the rest of the rows "" in the same column. But I don't know how to construct the Name column comparator so that the first row is always the top of the sort. (It can't be the largest and smallest when we override the method compare()

, can it?)

b) Every time we click the table header to sort, force the renderer to redraw the table the way we want: comparing the value in the first row of each group and if it matches the value of the last column of row 0, don't draw that cell until we reach a different meaning. This way we don't mess with comparators / sorters and this turns into a rendering issue. What I somehow achieved in SSCCE is below, but I'm halfway there and I need advice.

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.List;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.ArrayList;
import java.util.Comparator;

import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.RowSorter;
import javax.swing.RowSorter.SortKey;
import javax.swing.event.RowSorterEvent;
import javax.swing.event.RowSorterListener;
import javax.swing.SortOrder;
import javax.swing.SwingUtilities;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableModel;
import javax.swing.table.TableRowSorter;

import com.WindThunderStudio.JHeaderToolTip.JHeaderToolTip;

import net.miginfocom.swing.MigLayout;

public class RowGroupInTable extends JFrame {
    public RowGroupInTable() {
        begin();
    }

    private void begin() {
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        setLayout(new BorderLayout());

        DefaultTableModel model = new DefaultTableModel();
        Object[][] data = new Object[][] {{"Tom", "17", "Book1"},
            {"Tom", 23, "Book2"}, 
            {"Tom", 25, "Book3"},
            {"Polazzo", 41, "Book1"}, 
            {"Polazzo", 45, "Book2"},
            {"Polazzo", 12, "Book3"},
            {"Anna", 1, "Book3"}, 
            {"Anna", 33, "Book5"}};

        String[] titles = new String[] {"Name", "Last job duration", "Book #"};
        JTable table = new JTable(data, titles);
        table.setFillsViewportHeight(true);

        table.setAutoCreateRowSorter(false);

        TableRowSorter<TableModel> sorter = new TableRowSorter<TableModel>(table.getModel());
        ArrayList<SortKey> sortKeys = new ArrayList<RowSorter.SortKey>();
        sortKeys.add(new SortKey(2, SortOrder.ASCENDING));
        sortKeys.add(new SortKey(1, SortOrder.ASCENDING));

//        sorter.setSortKeys(sortKeys);
        sorter.setSortable(0, true);
        sorter.setSortable(1, false);
        sorter.setSortable(2, true);

        table.setRowSorter(sorter);

        table.setDefaultRenderer(Object.class, new MyRenderer(table.getDefaultRenderer(Object.class)));
        JTableHeader header = table.getTableHeader();
        header.addMouseListener(new MouseListener() {

            @Override
            public void mouseReleased(MouseEvent e) {
                // TODO Auto-generated method stub

            }

            @Override
            public void mousePressed(MouseEvent e) {
                // TODO Auto-generated method stub

            }

            @Override
            public void mouseExited(MouseEvent e) {
                // TODO Auto-generated method stub

            }

            @Override
            public void mouseEntered(MouseEvent e) {
                // TODO Auto-generated method stub

            }

            @Override
            public void mouseClicked(MouseEvent e) {
                int col = ((JTableHeader)(e.getComponent())).getColumnModel().getColumnIndexAtX(e.getX());

            }
        });

        JScrollPane sp = new JScrollPane(table);
        sp.setBounds(0, 0, 200, 200);

        add(sp, BorderLayout.CENTER);

        pack();
        setLocationRelativeTo(null);
        setVisible(true);
    }

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

            @Override
            public void run() {
                RowGroupInTable frame = new RowGroupInTable();

            }

        });
    }

    private class MyRenderer implements TableCellRenderer {

        TableCellRenderer def;
        public MyRenderer() {
            // TODO Auto-generated constructor stub
        }

        public MyRenderer(TableCellRenderer defaultRend) {
            this();
            this.def = defaultRend;
        }
        @Override
        public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus,
                int row, int column) {
            int rowCount = table.getModel().getRowCount();

            Component orig = (def).getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
            if (column == 0) {
                if (row == 0) {
                    return orig;
                } else if (row > 0 && row < rowCount) {
                    if (table.getModel().getValueAt(row-1, column).equals(value)) {
                        return new JLabel("");
                    } else {
                        return orig;
                    }
                }
            }

            return orig;
        }

    }
}

      

+3


source to share


1 answer


each name should only appear once, the rest of the cells should be blank.

If I understand your requirement, you can use table.getValueAt(...)

instead table.getModel().getValueAt(...)

:

import java.awt.*;
import java.util.*;
import java.util.List;
import javax.swing.*;
import javax.swing.table.*;

public class RowGroupInTableTest {
  private JComponent makeUI() {
    String[] titles = new String[] {"Name", "Last job duration", "Book #"};
    DefaultTableModel model = new DefaultTableModel(null, titles) {
      @Override public Class<?> getColumnClass(int column) {
        return MyData.class;
      }
    };
    addMyData(model, new MyData("Tom",     17, "Book1"));
    addMyData(model, new MyData("Tom",     23, "Book2"));
    addMyData(model, new MyData("Tom",     25, "Book3"));
    addMyData(model, new MyData("Polazzo", 41, "Book1"));
    addMyData(model, new MyData("Polazzo", 45, "Book2"));
    addMyData(model, new MyData("Polazzo", 12, "Book3"));
    addMyData(model, new MyData("Anna",     1, "Book3"));
    addMyData(model, new MyData("Anna",    33, "Book5"));

    JTable table = new JTable(model);
    table.setFillsViewportHeight(true);
    table.setDefaultRenderer(MyData.class, new MyRenderer());

    TableRowSorter<TableModel> sorter = new TableRowSorter<TableModel>(table.getModel());
    Comparator<MyData> c = Comparator.comparing(MyData::getName);
    sorter.setComparator(0, c);
    sorter.setComparator(1, c.thenComparing(Comparator.comparingInt(MyData::getDuration)));
    sorter.setComparator(2, c.thenComparing(Comparator.comparing(MyData::getBook)));
    table.setRowSorter(sorter);

    return new JScrollPane(table);
  }
  private static void addMyData(DefaultTableModel model, MyData data) {
    //Omission work...
    model.addRow(Collections.nCopies(3, data).toArray());
  }
  public static void main(String[] args) {
    EventQueue.invokeLater(() -> {
      JFrame f = new JFrame();
      f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
      f.getContentPane().add(new RowGroupInTableTest().makeUI());
      f.setSize(320, 240);
      f.setLocationRelativeTo(null);
      f.setVisible(true);
    });
  }
}

class MyData {
  private final String name;
  private final int duration;
  private final String book;
  protected MyData(String name, int duration, String book) {
    this.name = name;
    this.duration = duration;
    this.book = book;
  }
  public String getName() {
    return name;
  }
  public int getDuration() {
    return duration;
  }
  public String getBook() {
    return book;
  }
}

class MyRenderer implements TableCellRenderer {
  TableCellRenderer def = new DefaultTableCellRenderer();
  @Override public Component getTableCellRendererComponent(
      JTable table, Object value, boolean isSelected, boolean hasFocus,
      int row, int column) {
    JLabel orig = (JLabel) def.getTableCellRendererComponent(
        table, value, isSelected, hasFocus, row, column);
    orig.setHorizontalAlignment(SwingConstants.LEFT);
    MyData data = (MyData) value;
    switch (table.convertColumnIndexToModel(column)) {
    case 0:
      String str = data.getName();
      if (row > 0) {
        //if (table.getModel().getValueAt(row-1, column).equals(value)) {
        //Since it compares with the value of the previous line on the display,
        //table.getModel() is not needed
        MyData prev = (MyData) table.getValueAt(row - 1, column);
        if (Objects.equals(prev.getName(), str)) {
          str = " ";
        }
      }
      orig.setText(str);
      break;
    case 1:
      orig.setHorizontalAlignment(SwingConstants.RIGHT);
      orig.setText("" + data.getDuration());
      break;
    case 2:
      orig.setText(data.getBook());
      break;
    default:
      break;
    }
    return orig;
  }
}

      

change

Now if I'm only using Java 7, is there some "old" way to do this? Just install comparators in Java 7?

You will need to implement Comparator

:



TableRowSorter<TableModel> sorter = new TableRowSorter<TableModel>(table.getModel());
//Comparator<MyData> c = Comparator.comparing(MyData::getName);
//sorter.setComparator(0, c);
//sorter.setComparator(1, c.thenComparing(Comparator.comparingInt(MyData::getDuration)));
//sorter.setComparator(2, c.thenComparing(Comparator.comparing(MyData::getBook)));
sorter.setComparator(0, new MyDataGroupComparator(0));
sorter.setComparator(1, new MyDataGroupComparator(1));
sorter.setComparator(2, new MyDataGroupComparator(2));
table.setRowSorter(sorter);

      

class MyDataGroupComparator implements Comparator<MyData> {
  private final int column;
  protected MyDataGroupComparator(int column) {
    this.column = column;
  }
  @Override public int compare(MyData a, MyData b) {
    if (a == null && b == null) {
      return 0;
    } else if (a != null && b == null) {
      return -1;
    } else if (a == null && b != null) {
      return 1;
    } else {
      int v = a.getName().compareTo(b.getName());
      if (v == 0) {
        switch (column) {
        case 2:
          return a.getBook().compareTo(b.getBook());
        case 1:
          return a.getDuration() - b.getDuration();
        case 0:
        default:
          return v;
        }
      }
      return v;
    }
  }
}

      

when i change table.getModel (). getValueAt () to table.getValueAt () I can't seem to get my original example to work. Why?

Works fine for me (only cell below is Anna

empty):

enter image description here

+1


source







All Articles