4

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?

BenMorel
  • 34,448
  • 50
  • 182
  • 322
crusam
  • 6,140
  • 6
  • 40
  • 68
  • Have you tried using a separate row sorter for each table? I know, you have to keep it in sync, but it might get you over the initial hurdle – MadProgrammer Aug 02 '13 at 08:26
  • @ymene [please see my view of RowHeader(two JTables, two XxxModels, one listener for two notifiers)](http://stackoverflow.com/a/8187799/714968), there is only RowFilter, [there you can to try apply your own view of RowSorter](http://stackoverflow.com/a/16664124/714968), [if without success](http://stackoverflow.com/a/11280226/714968) then post, I think that must be based on your [SSCCE](http://sscce.org/), or wait to (@camickr), btw anyway (this one) could be very nice question +1 – mKorbel Aug 02 '13 at 08:41
  • can you show a SSCCE where it blows? I remember having had a similar discussion (but not its outcome and can't find it) - and a very simple example - with shared RowSorter - seems to behave ok. – kleopatra Aug 02 '13 at 09:29
  • not a solution, but found a decision (with failing tests, throwing IOOBExceptions just as you see): RowSorters can't be shared. My first try (never did, though) would be a wrapper RowSorter that doesn't do much on model changes but delegates to the "real" (aka: the one in the main table) – kleopatra Aug 02 '13 at 10:58
  • @kleopatra [please where is the issue](http://stackoverflow.com/a/11280226/714968) with RowSorters can't be shared or IndexOutOfBound-Exceptions on primary JTable(contains reasonable data) or secondary (RowHeader), (aka: the one in the main table) do you meaning put there synchronizations IDs valid for both JTables (culumn will be removed from both JTables views) – mKorbel Aug 02 '13 at 11:31
  • @mKorbel happens on changes to the model, f.i. when removing the last row: when the sorter is notified the second time, it already has updated its internals, so the old index is no longer valid – kleopatra Aug 02 '13 at 12:00
  • @MadProgrammer Haven't tried it yet, first wanted to hear about what people think about what might be the intended way to implement this kind of behaviour, but of course I will, especially since kleopatra gave some useful input on that topic. – crusam Aug 02 '13 at 13:05
  • @mKorbel thanks for your example, I'll have a closer look at it. Unfortunatly its not that easy to create a SSCCE with desired behaviour, so first I'll give kleopatras wrapper a chance, since that sounds like a good solution for my usecase. – crusam Aug 02 '13 at 13:09
  • @kleopatra I'll try your aproach with the wrapper class. If it should still fail, I'll try to break it down to a small SSCCE and add it to this question. If it should work, I of course will let you know as well! Thanks for sharing the wrapper with me! – crusam Aug 02 '13 at 13:12

1 Answers1

3

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
    }

}
Marcel
  • 1,509
  • 1
  • 17
  • 39
kleopatra
  • 51,061
  • 28
  • 99
  • 211