Sort JTable Except for Last Row

后端 未结 5 1237
盖世英雄少女心
盖世英雄少女心 2020-12-03 23:54

I have a JTable where the last row is the total row that aggregates all other rows. When the user clicks a column header on the table, rows are sorted by that c

相关标签:
5条回答
  • 2020-12-04 00:28

    Based on this example, the complete example example below illustrates the approach in the accepted answer. In particular,

    • The implementation of getRowCount() in the TableModel returns the size() of the List<Employee>, which is one less than the number of rows displayed in the table.

    • The implementation of getValueAt() in the TableModel illustrates the call to sum() in the COLUMN_SALARY and some possible display choices for the remaining columns in the extra row at the bottom.

    • The implementation of getViewRowCount() in the TableRowSorter enables the display of the extra row but precludes reasonable filtering.

    import java.awt.BorderLayout;
    import java.awt.Component;
    import java.awt.Dimension;
    import java.awt.EventQueue;
    import java.text.NumberFormat;
    import java.util.ArrayList;
    import java.util.List;
    import javax.swing.JFrame;
    import javax.swing.JLabel;
    import javax.swing.JScrollPane;
    import javax.swing.JTable;
    import javax.swing.table.AbstractTableModel;
    import javax.swing.table.DefaultTableCellRenderer;
    import javax.swing.table.TableModel;
    import javax.swing.table.TableRowSorter;
    
    /**
     * @see https://stackoverflow.com/a/37913520/230513
     * @see https://stackoverflow.com/a/37892395/230513
     */
    public class JTableColumnTotalExample {
    
        public static void main(String[] args) {
            EventQueue.invokeLater(JTableColumnTotalExample::display);
        }
    
        public static void display() {
            JFrame f = new JFrame("JTable Sorting Example");
            f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            List<Employee> listEmployees = createListEmployees();
            TableModel model = new EmployeeTableModel(listEmployees);
            JTable table = new JTable(model) {
                @Override
                public Dimension getPreferredScrollableViewportSize() {
                    return new Dimension(500, getRowCount() * getRowHeight());
                }
            };
            table.getColumnModel().getColumn(3).setCellRenderer(new CurrencyFormatter());
            TableRowSorter sorter = new TableRowSorter<TableModel>(model) {
                @Override
                public int convertRowIndexToModel(int index) {
                    int maxRow = super.getViewRowCount();
                    if (index >= maxRow) {
                        return index;
                    }
                    return super.convertRowIndexToModel(index);
                }
    
                @Override
                public int convertRowIndexToView(int index) {
                    int maxRow = super.getModelRowCount();
                    if (index > maxRow) {
                        return index;
                    }
                    return super.convertRowIndexToView(index);
                }
    
                @Override
                public int getViewRowCount() {
                    return super.getViewRowCount() + 1;
                }
            };
            table.setRowSorter(sorter);
            f.add(new JScrollPane(table), BorderLayout.CENTER);
            f.pack();
            f.setLocationRelativeTo(null);
            f.setVisible(true);
        }
    
        private static List<Employee> createListEmployees() {
            List<Employee> listEmployees = new ArrayList<>();
            listEmployees.add(new Employee("Peter", "Manager", 40000));
            listEmployees.add(new Employee("Paul", "Programmer", 25000));
            listEmployees.add(new Employee("Mary", "Designer", 25000));
            listEmployees.add(new Employee("Donald", "Leader", 30000));
            listEmployees.add(new Employee("Tom", "Designer", 28000));
            listEmployees.add(new Employee("Samantha", "Analyst", 50000));
            listEmployees.add(new Employee("Jerome", "Programmer", 32000));
            listEmployees.add(new Employee("Jonathon", "Developer", 29000));
            listEmployees.add(new Employee("Kevin", "Programmer", 23000));
            listEmployees.add(new Employee("Anthony", "Programmer", 23000));
            listEmployees.add(new Employee("John", "Designer", 33000));
            listEmployees.add(new Employee("David", "Developer", 28000));
            listEmployees.add(new Employee("Harry", "Designer", 31000));
            listEmployees.add(new Employee("Charles", "Programmer", 26000));
            listEmployees.add(new Employee("Joseph", "Manager", 40000));
            return listEmployees;
        }
    
        private static class EmployeeTableModel extends AbstractTableModel {
    
            private static final int COLUMN_NUM = 0;
            private static final int COLUMN_NAME = 1;
            private static final int COLUMN_JOB = 2;
            private static final int COLUMN_SALARY = 3;
    
            private final String[] columnNames = {"No", "Name", "Job", "Salary"};
            private final List<Employee> listEmployees;
    
            public EmployeeTableModel(List<Employee> listEmployees) {
                this.listEmployees = listEmployees;
                int indexCount = 1;
                for (Employee employee : listEmployees) {
                    employee.setIndex(indexCount++);
                }
            }
    
            @Override
            public int getColumnCount() {
                return columnNames.length;
            }
    
            @Override
            public int getRowCount() {
                return listEmployees.size();
            }
    
            @Override
            public String getColumnName(int columnIndex) {
                return columnNames[columnIndex];
            }
    
            @Override
            public Class<?> getColumnClass(int columnIndex) {
                return getValueAt(0, columnIndex).getClass();
            }
    
            @Override
            public Object getValueAt(int rowIndex, int columnIndex) {
                if (rowIndex == getRowCount()) {
                    switch (columnIndex) {
                        case COLUMN_NUM:
                            return 999_999_999;
                        case COLUMN_NAME:
                            return "Total";
                        case COLUMN_JOB:
                            return "Salary";
                        case COLUMN_SALARY:
                            return sum();
                    }
                }
                Employee employee = listEmployees.get(rowIndex);
                switch (columnIndex) {
                    case COLUMN_NUM:
                        return employee.getIndex();
                    case COLUMN_NAME:
                        return employee.getName();
                    case COLUMN_JOB:
                        return employee.getJob();
                    case COLUMN_SALARY:
                        return employee.getSalary();
                    default:
                        throw new IllegalArgumentException("Invalid column index");
                }
            }
    
            private int sum() {
                int sum = 0;
                for (int r = 0; r < getRowCount(); r++) {
                    sum += (int) getValueAt(r, COLUMN_SALARY);
                }
                return sum;
            }
        }
    
        private static class Employee {
    
            private int index;
            private String name;
            private String job;
            private int salary;
    
            public Employee(String name, String job, int salary) {
                this.name = name;
                this.job = job;
                this.salary = salary;
            }
    
            public int getIndex() {
                return index;
            }
    
            public void setIndex(int index) {
                this.index = index;
            }
    
            public String getName() {
                return name;
            }
    
            public void setName(String name) {
                this.name = name;
            }
    
            public String getJob() {
                return job;
            }
    
            public void setJob(String job) {
                this.job = job;
            }
    
            public int getSalary() {
                return salary;
            }
    
            public void setSalary(int age) {
                this.salary = age;
            }
        }
    
        private static class CurrencyFormatter extends DefaultTableCellRenderer {
    
            private NumberFormat numberFormat = NumberFormat.getCurrencyInstance();
    
            @Override
            public Component getTableCellRendererComponent(JTable jTable, Object value,
                boolean isSelected, boolean hasFocus, int row, int column) {
                Component c = super.getTableCellRendererComponent(jTable, value,
                    isSelected, hasFocus, row, column);
                if (c instanceof JLabel && value instanceof Number) {
                    JLabel label = (JLabel) c;
                    label.setHorizontalAlignment(JLabel.RIGHT);
                    Number num = (Number) value;
                    String text = numberFormat.format(num);
                    label.setText(text);
                }
                return c;
            }
        }
    }
    
    0 讨论(0)
  • 2020-12-04 00:31

    Personally, I would create a single-row 2nd table with the header removed, and place it directly underneath the main table, so as to create the illusion of a last row.

    Besides it solving your sorting problem, it will also persist as the user scrolls that main table, which is probably a good thing since it's a "totals" row.

    You could even add a ColumnModelListener to the main table's TableColumnModel to synch up column resizing.

    EDIT: Here's the general idea:

    import java.awt.*;
    import javax.swing.*;
    import javax.swing.event.*;
    import javax.swing.table.*;
    
    public class TestFrame implements Runnable
    {
      JTable mainTable;
      JTable fixedTable;
    
      public static void main(String[] args)
      {
        SwingUtilities.invokeLater(new TestFrame());
      }
    
      public void run()
      {
        mainTable = new JTable(8, 3);
        mainTable.getTableHeader().setReorderingAllowed(false);
        mainTable.setAutoCreateRowSorter(true);
    
        for (int r = 0; r < 8; r++)
        {
          for (int c = 0; c < 3; c++)
          {
            mainTable.setValueAt((int)(Math.random()*100), r, c);
          }
        }
    
        mainTable.getColumnModel().addColumnModelListener(
          new TableColumnModelListener()
          {
            public void columnAdded(TableColumnModelEvent e) {}
            public void columnRemoved(TableColumnModelEvent e) {}
            public void columnMoved(TableColumnModelEvent e) {}
            public void columnSelectionChanged(ListSelectionEvent e) {}
    
            public void columnMarginChanged(ChangeEvent e)
            {
              synchColumnSizes();
            }
          });
    
        setVisibleRowCount(mainTable, 5);
    
        JScrollPane scroll = new JScrollPane(mainTable);
        scroll.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
    
        fixedTable = new JTable(1, 3);
        fixedTable.setValueAt("will not sort or", 0, 0);
        fixedTable.setValueAt("scroll but will",  0, 1);
        fixedTable.setValueAt("resize with main", 0, 2);
    
        JPanel p = new JPanel(new GridBagLayout());
        p.setBorder(BorderFactory.createTitledBorder("Fixed Last Row"));
        GridBagConstraints gbc = new GridBagConstraints();
        gbc.anchor = GridBagConstraints.WEST;
        p.add(scroll, gbc);
        gbc.gridy = 1;
        p.add(fixedTable, gbc);
    
        JFrame f = new JFrame();
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.getContentPane().add(p, BorderLayout.CENTER);
        f.pack();
        f.setLocationRelativeTo(null);
        f.setVisible(true);
      }
    
      private void synchColumnSizes()
      {
        TableColumnModel tcmMain = mainTable.getColumnModel();
        TableColumnModel tcmFixed = fixedTable.getColumnModel();
    
        for (int i = 0; i < tcmMain.getColumnCount(); i++)
        {
          int width = tcmMain.getColumn(i).getWidth();
          tcmFixed.getColumn(i).setPreferredWidth(width);
        }
      }
    
      public static void setVisibleRowCount(JTable table, int rows)
      {
        table.setPreferredScrollableViewportSize(new Dimension( 
                table.getPreferredScrollableViewportSize().width, 
                rows * table.getRowHeight())); 
      }
    }
    
    0 讨论(0)
  • 2020-12-04 00:33

    The following solution worked for me....

    In your table model update the getRowCount() member to return 1 row less than required.

    Then modify the index and row counts reported by your sorter as follows...

    TableRowSorter<TableModel> sorter =
        new DefaultTableRowSorter<TableModel>(this.getModel()) 
    {
        public int convertRowIndexToModel(int index)
        {
            int maxRow = super.getViewRowCount();
            if (index >= maxRow)
                return index;
            return super.convertRowIndexToModel(index);
        }
    
        public int convertRowIndexToView(int index) 
        {
            int maxRow = super.getModelRowCount();
            if (index > maxRow)
                return index;
            return super.convertRowIndexToView(index);
        }
    
        public int getViewRowCount() 
        {
            return super.getViewRowCount() + 1;
        }
    };
    
    myTable.setRowSorter(sorter);
    
    0 讨论(0)
  • 2020-12-04 00:38

    @Das: maybe this version is better since you do not need to override the getRowCount(), which may be used and maybe causing problems by other functions

    public class TableRowSorterTotal<MyTableModel extends TableModel> extends TableRowSorter<TableModel>
    {   
    
        TableRowSorterTotal(MyTableModel model)
        {
            super(model);
        }
    
        public int convertRowIndexToModel(int index)
        {
            int maxRow = super.getViewRowCount();
            int currModel = super.convertRowIndexToModel(index);
            int maxModel = super.convertRowIndexToModel(maxRow-1);
    
            if(currModel == maxModel)
                return maxRow - 1;
    
            if(currModel > maxModel)
                return currModel- 1;
    
            return currModel;
        }
    
        public int convertRowIndexToView(int index) 
        {
            int maxRow = super.getModelRowCount();
            int currView= super.convertRowIndexToView(index);
            int maxView = super.convertRowIndexToView(maxRow-1);
    
            if(currView == maxView)
                return maxRow - 1;
    
            if(currView > maxView)
                return currView- 1;
    
            return currView;
        }
    }
    
    0 讨论(0)
  • 2020-12-04 00:42

    Is there a simple way to implement this with TableRowSorter?

    • not isn't simple

    • good idea, quite is possible to set flag for RowSorter, its SortingKeys

    • there is bug for all ClassTypes except String instance

    0 讨论(0)
提交回复
热议问题