Question

Following Situation: I have a J(X)Table with RowHeader (As guidline I used one of Rob Camicks great Examples). All worked as expected.

enter image description here

By requirement the data I receive from server already contains a tablerownumber, which I have to show in the rowheader and the data should be filterable. So I extended the example, and I added a filter. When I filtered the view I saw gaps in my row numbers (for example: 1, 3, 6,..), which is the desired effect.

To be able to filter and sort the table by my own tablerow, I added a TableRowSorter. And here I started to get confused. The Example uses the same TableModel and SelectionModel for mainTable and rowHeaderTable:

setModel( main.getModel() );
setSelectionModel( main.getSelectionModel() );

This is great, since I don’t have to synchronize them. But concerning TableRowSorter I suddenly wasn’t sure, if I also can or even have to use the same TableRowSorter-Instance or if I have to create a TableRowSorter for each table. First I added the same to both Tables, since this seemed practically, but then I got IndexOutOfBound-Exceptions in many cases. After some digging I found out that this is because the TableRowSorter gets updated twice at each TableModelEvent, because each table (RowHeader and MainTable) notifies the TableRowSorter about tablechanges on its own.

Now I am not sure which the right way to go is. Following solutions came into my mind: Should I add a second TableRowSorter (one for each table) and synchronize these, or should I wrap the TableModel within the RowHeaderTable and let it not fireing any Events? Or maybe I should create my own kind of RowHeaderTable which doesn’t notify Sorters about changes at all?

Was it helpful?

Solution

Here's a quick (beware: not formally tested! the usage example works fine, though) implementation of a wrapping RowSorter.

  • does nothing on receiving notification of model changes
  • delegates all status queries
  • listens to wrapped rowSorter and propagates its events

It's client's responsibility to keep it in synch with the rowSorter used in the main table

Usage example (in terms of SwingX test infrastructure and with SwingX sortController/table):

public void interactiveRowSorterWrapperSharedXTable() {
    final DefaultTableModel tableModel = new DefaultTableModel(list.getElementCount(), 2) {

        @Override
        public Class<?> getColumnClass(int columnIndex) {
            return Integer.class;
        }

    };
    for (int i = 0; i < tableModel.getRowCount(); i++) {
        tableModel.setValueAt(i, i, 0);
        tableModel.setValueAt(tableModel.getRowCount() - i, i, 1);
    }
    final JXTable master = new JXTable(tableModel);
    final TableSortController<TableModel> rowSorter = (TableSortController<TableModel>) master.getRowSorter();
    master.removeColumn(master.getColumn(0));
    final JXTable rowHeader = new JXTable(master.getModel());
    rowHeader.setAutoCreateRowSorter(false);
    rowHeader.removeColumn(rowHeader.getColumn(1));
    rowHeader.setRowSorter(new RowSorterWrapper<TableModel>(rowSorter));
    rowHeader.setSelectionModel(master.getSelectionModel());
    // need to disable selection update on one of the table's 
    // otherwise the selection is not kept in model coordinates
    rowHeader.setUpdateSelectionOnSort(false);
    JScrollPane scrollPane = new JScrollPane(master);
    scrollPane.setRowHeaderView(rowHeader);
    JXFrame frame = showInFrame(scrollPane, "xtables (wrapped sortController): shared model/selection");
    Action fireAllChanged = new AbstractAction("fireDataChanged") {

        @Override
        public void actionPerformed(ActionEvent e) {
            tableModel.fireTableDataChanged();
        }

    };
    addAction(frame, fireAllChanged);
    Action removeFirst = new AbstractAction("remove firstM") {

        @Override
        public void actionPerformed(ActionEvent e) {
            tableModel.removeRow(0);

        }
    };
    addAction(frame, removeFirst);
    Action removeLast = new AbstractAction("remove lastM") {

        @Override
        public void actionPerformed(ActionEvent e) {
            tableModel.removeRow(tableModel.getRowCount() - 1);

        }
    };
    addAction(frame, removeLast);
    Action filter = new AbstractAction("toggle filter") {

        @Override
        public void actionPerformed(ActionEvent e) {
            RowFilter filter = rowSorter.getRowFilter();
            if (filter == null) {
                rowSorter.setRowFilter(RowFilter.regexFilter("^1", 1));
            } else {
                rowSorter.setRowFilter(null);
            }

        }
    };
    addAction(frame, filter);
    addStatusMessage(frame, "row header example with RowSorterWrapper");
    show(frame);
}

The RowSorterWrapper:

/**
 * Wrapping RowSorter for usage (f.i.) in a rowHeader.
 * 
 * Delegates all state queries, 
 * does nothing on receiving notification of model changes,
 * propagates rowSorterEvents from delegates.
 * 
 * Beware: untested! 
 * 
 * @author Jeanette Winzenburg, Berlin
 */
public class RowSorterWrapper<M> extends RowSorter<M> {

    private RowSorter<M> delegate;
    private RowSorterListener rowSorterListener;

    public RowSorterWrapper(RowSorter<M> delegate) {
        this.delegate = delegate;
        delegate.addRowSorterListener(getRowSorterListener());
    }

    /**
     * Creates and returns a RowSorterListener which re-fires received
     * events.
     * 
     * @return
     */
    protected RowSorterListener getRowSorterListener() {
        if (rowSorterListener == null) {
            RowSorterListener listener = new RowSorterListener() {

                @Override
                public void sorterChanged(RowSorterEvent e) {
                    if (RowSorterEvent.Type.SORT_ORDER_CHANGED == e.getType()) {
                        fireSortOrderChanged();
                    } else if (RowSorterEvent.Type.SORTED == e.getType()) {
                        fireRowSorterChanged(null);                }
                }
            };
            rowSorterListener = listener;
        }
        return rowSorterListener;
    }


    @Override
    public M getModel() {
        return delegate.getModel();
    }

    @Override
    public void toggleSortOrder(int column) {
        delegate.toggleSortOrder(column);
    }

    @Override
    public int convertRowIndexToModel(int index) {
        return delegate.convertRowIndexToModel(index);
    }

    @Override
    public int convertRowIndexToView(int index) {
        return delegate.convertRowIndexToView(index);
    }

    @Override
    public void setSortKeys(List keys) {
        delegate.setSortKeys(keys);
    }

    @Override
    public List getSortKeys() {
        return delegate.getSortKeys();
    }

    @Override
    public int getViewRowCount() {
        return delegate.getViewRowCount();
    }

    @Override
    public int getModelRowCount() {
        return delegate.getModelRowCount();
    }

    @Override
    public void modelStructureChanged() {
        // do nothing, all work done by delegate
    }

    @Override
    public void allRowsChanged() {
        // do nothing, all work done by delegate
    }

    @Override
    public void rowsInserted(int firstRow, int endRow) {
        // do nothing, all work done by delegate
    }

    @Override
    public void rowsDeleted(int firstRow, int endRow) {
        // do nothing, all work done by delegate
    }

    @Override
    public void rowsUpdated(int firstRow, int endRow) {
        // do nothing, all work done by delegate
    }

    @Override
    public void rowsUpdated(int firstRow, int endRow, int column) {
        // do nothing, all work done by delegate
    }

}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top