How can i sort java JTable with an empty Row and force the Empty row always be last?

后端 未结 5 856
心在旅途
心在旅途 2020-12-18 16:49

I am using JTable with an empty row at the bottom of the table in order to give the ability of adding new line to the table.

After insert or writing data in the empt

相关标签:
5条回答
  • 2020-12-18 17:06

    I know it is an old thread, but I was stuck here trying to find a way. Please, let me know if there is a new way not mentioned before. Actually this is my first post on SO, but since I spent lots of time finding a solution for that I decided to share.

    I solved that ordering problem by, instead of using 'null' as my 'new entry', I created a specific object (in my case from Object class itself, but it could be something more meaninful like a 'NewEntryDoNotCompareWithMePleaseLetMeBeTheLastEntryIBegYouDoNotSortMe' empty class).

    When comparing, I first check if the 'object' that came to compare is a object. Not 'instanceof' of course, but (obj.getClass() == Object.class). -- If thats the case, check the sorting order and return 1 or -1.

    Feel free to comment the any problems you find with that, I can try to create a small-working-version if someone needs the code.

    Cheers, Daniel

    0 讨论(0)
  • 2020-12-18 17:07

    The DefaultRowSorter is definitely flawed.

    The only option is to create your own RowSorter based on the DefaultRowsorter and correct the problem.

    0 讨论(0)
  • 2020-12-18 17:07

    I found another solution by subclassing only TableRowSorter.

    From the DefaultRowSorter documentation we know :

    The Comparator is never passed null

    When subclassing DefaultRowSorter.ModelWrapper, we can return a special Null-Object and create a custom comparator handling that value.

    Here follows my code. Probably it is not as efficient as a custom RowSorter implementation and it may still contain some bugs, I did not test everything, but for my requirements it works.

    class EmptyTableRowSorter<M extends AbstracTableModel> extends TableRowSorter<M> {
    
        private static final EmptyValue emptyValue = new EmptyValue();
    
        public EmptyTableRowSorter(M model) {
            super(model);
        }
    
        @Override
        public void modelStructureChanged() {
            // deletes comparators, so we must set again
            super.modelStructureChanged();
    
            M model = getModelWrapper().getModel();
            for (int i = 0; i < model.getColumnCount(); i++) {
                Comparator<?> comparator = this.getComparator(i);
                if (comparator != null) {
                    Comparator wrapper = new EmptyValueComparator(comparator, this, i);
                    this.setComparator(i, wrapper);
                }
            }
        }
    
        @Override
        public void setModel(M model) {
            // also calls setModelWrapper method
            super.setModel(model);
    
            ModelWrapper<M, Integer> modelWrapper = getModelWrapper();
            EmptyTableModelWrapper emptyTableModelWrapper = new EmptyTableModelWrapper(modelWrapper);
    
            // calls modelStructureChanged method
            setModelWrapper(emptyTableModelWrapper);
        }
    
        /**
         * The DefaulRowSorter implementation does not pass null values from the table
         * to the comparator.
         * This implementation is a wrapper around the default ModelWrapper,
         * returning a non null object for our empty row that our comparator can handle.
         */
        private class EmptyTableModelWrapper extends DefaultRowSorter.ModelWrapper {
    
            private final DefaultRowSorter.ModelWrapper modelWrapperImplementation;
    
            public EmptyTableModelWrapper(ModelWrapper modelWrapperImplementation) {
                this.modelWrapperImplementation = modelWrapperImplementation;
            }
    
            @Override
            public Object getModel() {
                return modelWrapperImplementation.getModel();
            }
    
            @Override
            public int getColumnCount() {
                return modelWrapperImplementation.getColumnCount();
            }
    
            @Override
            public int getRowCount() {
                return modelWrapperImplementation.getRowCount();
            }
    
            @Override
            public Object getValueAt(int row, int column) {
                M model = EmptyTableRowSorter.this.getModel();
    
                // my model has the empty row always at the end,
                // change this depending on your needs
                int lastRow = model.getRowCount() - 1;
                if (row == lastRow) {
                    return emptyValue;
                }
                return modelWrapperImplementation.getValueAt(row, column);
            }
    
            //@Override
            //public String getStringValueAt(int row, int column) {
            //    //  also override this if there is no comparator definied for a column
            //}
    
            @Override
            public Object getIdentifier(int row) {
                return modelWrapperImplementation.getIdentifier(row);
            }
    
        }
    
         /**
          * This is a wrapper around another comparator.
          * We handle our empty value and if none, we invoke the base comparator.
          */
        private class EmptyValueComparator implements Comparator {
    
            private final Comparator defaultComparator;
    
            private final TableRowSorter tableRowSorter;
    
            private final int columnIndex;
    
            public EmptyValueComparator(Comparator defaultComparator, TableRowSorter tableRowSorter, int columnIndex) {
                this.defaultComparator = defaultComparator;
                this.tableRowSorter = tableRowSorter;
                this.columnIndex = columnIndex;
            }
    
            @Override
            public int compare(Object o1, Object o2) {
                if (o1 instanceof EmptyValue && o2 instanceof EmptyValue) {
                    return 0;
                }
                if (o1 instanceof EmptyValue) {
                    return adjustSortOrder(1);
                }
                if (o2 instanceof EmptyValue) {
                    return adjustSortOrder(-1);
                }
                return defaultComparator.compare(o1, o2);
            }
    
            /**
             * Changes the result so that the empty row is always at the end,
             * regardless of the sort order.
             */
            private int adjustSortOrder(int result) {
                List sortKeys = tableRowSorter.getSortKeys();
                for (Object sortKeyObject : sortKeys) {
                    SortKey sortKey = (SortKey) sortKeyObject;
                    if (sortKey.getColumn() == columnIndex) {
                        SortOrder sortOrder = sortKey.getSortOrder();
                        if (sortOrder == SortOrder.DESCENDING) {
                            result *= -1;
                        }
                        return result;
                    }
                }
                return result;
            }
    
        }
    
        private static class EmptyValue {}
    
    }
    

    Now you can enable sorting in your table.

    JTable table = ...;
    TableRowSorter tableRowSorter = new EmptyTableRowSorter(table.getModel());
    table.setRowSorter(tableRowSorter);
    
    0 讨论(0)
  • 2020-12-18 17:23

    Set a different comparator using DefaultRowSorter's setComparator method.

    0 讨论(0)
  • 2020-12-18 17:26

    I gave this a try, and I think I found a solution.

    Instead of creating that empty row in the TableModel, I fake it in the JTable, and only create it when the user actually enters some data.

    The RowSorter only sorts rows of the TableModel, so our row is not affected and remains as the last row.

    public class NewLineTable extends JTable {
    
        @Override
        public int getRowCount() {
            // fake an additional row
            return super.getRowCount() + 1;
        }
    
        @Override
        public Object getValueAt(int row, int column) {
            if(row < super.getRowCount()) {
                return super.getValueAt(row, column);
            }
            return ""; // value to display in new line
        }
    
        @Override
        public int convertRowIndexToModel(int viewRowIndex) {
            if(viewRowIndex < super.getRowCount()) {
                return super.convertRowIndexToModel(viewRowIndex);
            }
            return super.getRowCount(); // can't convert our faked row
        }
    
        @Override
        public void setValueAt(Object aValue, int row, int column) {
            if(row < super.getRowCount()) {
                super.setValueAt(aValue, row, column);
            }
            else {
                Object[] rowData = new Object[getColumnCount()];
                Arrays.fill(rowData, "");
                rowData[convertColumnIndexToModel(column)] = aValue;
                // That's where we insert the new row.
                // Change this to work with your model.
                ((DefaultTableModel)getModel()).addRow(rowData);
            }
        }
    }
    
    0 讨论(0)
提交回复
热议问题