Java JTable: Setting a primary column to always sort by first

你。 提交于 2019-12-24 04:19:10

问题


I'm using a JTable with say 3 columns, displaying information about files and folders:

Col1: Either "file" or "folder" (String)
Col2: Name of the file or folder (String)
Col3: Creation date (Timstamp)

I can easily sort the table by any column. But I'm not able to achieve the following desired behavior:

No matter what column header the user clicks, folders should always display before files. So basically sorting by Col1 in any order other than descending should not be possible. Also, if sorted by Col3, any folders should still display before files, regardless of their timestamp.

Any hint to how to do this is greatly appreciated.

Yours

Andreas


回答1:


Try to use next code it helps you:

public class Example extends JFrame {

public Example() {
    DefaultTableModel defaultTableModel = new DefaultTableModel(new Object[][]{{"1"},{"3"},{"2"}},new Object[]{"text"});
    JTable t = new JTable(defaultTableModel);
    TableRowSorter<TableModel> sorter = new TableRowSorter<TableModel>(defaultTableModel);
    sorter.setComparator(0, new Comparator<Object>() {
            @Override
            public int compare(Object arg0, Object arg1) {
                return arg0.toString().compareTo(arg1.toString());
            }
    });

    t.setRowSorter(sorter);
    add(new JScrollPane(t));
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    pack();
    setVisible(true);
}

public static void main(String...strings ){
    Example e = new Example();
}

}

Here I set custom Comparator to TableRowSorter for column 0. You can compare your rows in different ways with your own Comparator implementation. Try it!

Comparator

RowSorter tutorial




回答2:


Maybe this is a way to do it. There's probably a more elegant way to do it around.

The DefaultRowSorter Comparator only compares values for one column at a time, without additional information it alone can not make sure that one column in the set of columns (say column 1) must always be the first column to sort by.

If you mess with DefaultRowSorter.toggleSortOrder and add a new SortKey to keys at position 0 before setSortKeys gets called (this would be the primary column you always want to sort by first [column 1]), the ascending and descending arrows displayed in the column headers constantly mark column 1 (rightfully so), but this is not what the user expects if she clicks on column 2.

Another approach is to have Comparator know about column sort relationships. An easy way is to add information about sort precedences to the Objects for Comparator.compare. Simple precedences can be represented by a prefix String (for example: prefix "a" for column 1 and prefix "b" for all other columns).

PrefixedData can contain String, Boolean, Timestamp or null values and a prefix:

public class PrefixedData { 
private String sData=null, prefix="";
private Timestamp tData=null;
private Boolean bData=null;


public void setData(String data) {
    sData=data;     
}

public void setData(Timestamp data) {
    tData=data;     
}

public void setData(boolean data) {
    bData=data;     
}

public void setPrefix(String prefix) {
    this.prefix=prefix;
}

public String getPrefix() {
    return prefix;
}           

public Object getData() {       
    if(sData!=null) return sData;
    if(tData!=null) return tData;
    if(bData!=null) return bData;           
    return null;
}
}

JTable cells can be rendered like this for String, Boolean and Timestamp, if your table model returns PrefixedData for DefaultTableModel.getValueAt:

private static class PrefixedDataRenderer3 extends JLabel implements TableCellRenderer {

    private static final SimpleDateFormat formatter = new SimpleDateFormat("MM.yyyy  HH:mm:ss");
    private static final JCheckBox box = new JCheckBox();

    public PrefixedDataRenderer() {

        setOpaque(true);
    }

    public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {

        remove(box);
        PrefixedData p=(PrefixedData) value;

        if(p.getData()==null) return this;

        if(p.getData().getClass().equals(String.class)) 
            {
            setText((String)p.getData());
                 return this;
            }

        if(p.getData().getClass().equals(Timestamp.class))
        {
            setText(formatter.format((Timestamp)p.getData()));
            return this;
        }

        if(p.getData().getClass().equals(Boolean.class)) 
            {
            box.setSelected(((Boolean)p.getData()).booleanValue());
            add(box);
            }   

        return this;
    }
}

And finally the Comparator can decide the order of things:

Comparator<Object> PrefixedDataComparator = new Comparator<Object>() {

    private List<? extends SortKey> sortKeys;

    @Override
    public int compare(Object o1, Object o2) {

        PrefixedData p1=(PrefixedData) o1;
        PrefixedData p2=(PrefixedData) o2;

        //First: compare prefixes (precedence data)         
        int prefixResult=p1.getPrefix().compareTo(p2.getPrefix());

        sortKeys = tableOU.getRowSorter().getSortKeys();

        //The prefixes are not the same, so return the result of the prefix comparision
        //The result has to be inverted if  we're sorting in descending order
        //for the column the user has clicked

        if(prefixResult!=0) return (sortKeys.get(0).getSortOrder().equals(SortOrder.ASCENDING) ? prefixResult : -prefixResult );

        //Only if the prefixes are the same do we have to compare the payload data          


        //Try to impose an order for null           
        if(p1.getData()==null && p2.getData()!=null) return -1;
        if(p1.getData()==null && p2.getData()==null) return 0;              
        if(p1.getData()!=null && p2.getData()==null) return 1;

        //Objects compared are identical            
        if(p1.getData().equals(p2.getData())) return 0;


        //Compare String
        if(p1.getData().getClass().equals(String.class)) {
            String s1=(String) p1.getData();
            String s2=(String) p2.getData();
            return s1.toLowerCase().compareTo(s2.toLowerCase());
        }

        //Compare Timestamp
        if(p1.getData().getClass().equals(Timestamp.class)) {
            Timestamp t1=(Timestamp) p1.getData();
            Timestamp t2=(Timestamp) p2.getData();
            if(t1.before(t2)) return -1;
            if(t1.equals(t2)) return 0;             
            return 1;
        }           

        //Compare Bool
        if(p1.getData().getClass().equals(Boolean.class)) {         
            boolean b1=((Boolean)p1.getData()).booleanValue();
            boolean b2=((Boolean)p2.getData()).booleanValue();
            if(b1==false && b2==true) return -1;
            if(b1==b2) return 0;                
            return 1;
        }


        return 0;
    }

};

Any suggestions to this or different approaches a very welcome.




回答3:


Well, the question is old but Google returned this page to me when I had a similar task. So, I'll post here my solution. Hope that it would help someone.

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.swing.DefaultRowSorter;
import javax.swing.JTable;
import javax.swing.RowSorter.SortKey;
import javax.swing.SortOrder;
import javax.swing.table.TableModel;
import javax.swing.table.TableRowSorter;
import org.apache.log4j.Logger;

public class PredefinedRowSorter extends TableRowSorter< TableModel > {
  private static final Logger LOGGER = Logger.getLogger( PredefinedRowSorter.class );
  private static final SortKey EMPTY_ARRAY[] = new SortKey[ 0 ];

  private final JTable table;
  private SortKey preColumns[] = EMPTY_ARRAY;
  private SortKey postColumns[] = EMPTY_ARRAY;

  public PredefinedRowSorter( JTable table ) {
    super( table.getModel() );
    this.table = table;
  }

  private void check( SortKey modelColumns[], SortKey crossCheckColumns[], boolean post ) {
    TableModel tm = table.getModel();
    int max = tm.getColumnCount();
    Set< Integer > used = new HashSet< Integer >();
    for ( SortKey key : modelColumns ) {
      if ( key == null )
        throw new IllegalArgumentException( "SortKey must be non-null" );
      if ( key.getColumn() < 0 )
        throw new IllegalArgumentException( "SortKey column must be non-negative" );
      if ( key.getColumn() >= max )
        throw new IllegalArgumentException( "SortKey column is too high (out of model scope)" );
      if ( key.getSortOrder() == SortOrder.UNSORTED )
        throw new IllegalArgumentException( "SortKey must be ordered (ascending or descending)" );
      if ( !used.add( key.getColumn() ) )
        throw new IllegalArgumentException( "SortKey column must be unique (column " + key.getColumn() + " is repeating)" );
    }
    for ( SortKey key : crossCheckColumns )
      if ( used.contains( key.getColumn() ) )
        throw new IllegalArgumentException( "SortKey column must be unique (column " + key.getColumn() + " is already contained in " + ( post ? "post" : "pre" ) + " columns list)" );
  }

  public PredefinedRowSorter withPreColumns( SortKey... modelColumns ) {
    if ( modelColumns == null )
      modelColumns = EMPTY_ARRAY;
    if ( !Arrays.equals( preColumns, modelColumns ) ) {
      check( modelColumns, postColumns, true );
      preColumns = modelColumns;
      setSortKeys( getSortKeys() );
    }
    return this;
  }

  public PredefinedRowSorter withPostColumns( SortKey... modelColumns ) {
    if ( modelColumns == null )
      modelColumns = EMPTY_ARRAY;
    if ( !Arrays.equals( postColumns, modelColumns ) ) {
      check( modelColumns, preColumns, false );
      postColumns = modelColumns;
      setSortKeys( getSortKeys() );
    }
    return this;
  }

  public JTable getTable() {
    return table;
  }

  public SortKey[] getPreColumns() {
    return preColumns.length == 0 ? preColumns : preColumns.clone();
  }

  public SortKey[] getPostColumns() {
    return postColumns.length == 0 ? postColumns : postColumns.clone();
  }

  private void setSortKeysInternal( List< ? extends SortKey > sortKeys ) {
    try {
      Field field = DefaultRowSorter.class.getDeclaredField( "sortKeys" );
      boolean accessible = field.isAccessible();
      if ( !accessible )
        field.setAccessible( true );
      field.set( this, sortKeys );
      if ( !accessible )
        field.setAccessible( false );
    } catch ( IllegalAccessException e ) {
      LOGGER.error( null, e );
    } catch ( IllegalArgumentException e ) {
      LOGGER.error( null, e );
    } catch ( NoSuchFieldException e ) {
      LOGGER.error( null, e );
    } catch ( SecurityException e ) {
      LOGGER.error( null, e );
    }
  }

  private Object getViewToModelInternal() {
    try {
      Field field = DefaultRowSorter.class.getDeclaredField( "viewToModel" );
      boolean accessible = field.isAccessible();
      if ( !accessible )
        field.setAccessible( true );
      Object ret = field.get( this );
      if ( !accessible )
        field.setAccessible( false );
      return ret;
    } catch ( IllegalAccessException e ) {
      LOGGER.error( null, e );
    } catch ( IllegalArgumentException e ) {
      LOGGER.error( null, e );
    } catch ( NoSuchFieldException e ) {
      LOGGER.error( null, e );
    } catch ( SecurityException e ) {
      LOGGER.error( null, e );
    }
    return null;
  }

  private void sortExistingDataInternal() {
    try {
      Method method = DefaultRowSorter.class.getDeclaredMethod( "sortExistingData" );
      boolean accessible = method.isAccessible();
      if ( !accessible )
        method.setAccessible( true );
      method.invoke( this );
      if ( !accessible )
        method.setAccessible( false );
    } catch ( IllegalAccessException e ) {
      LOGGER.error( null, e );
    } catch ( IllegalArgumentException e ) {
      LOGGER.error( null, e );
    } catch ( NoSuchMethodException e ) {
      LOGGER.error( null, e );
    } catch ( InvocationTargetException e ) {
      LOGGER.error( null, e );
      LOGGER.error( null, ( ( InvocationTargetException )e ).getCause() );
    }
  }

  @Override
  public void setSortKeys( List< ? extends SortKey > sortKeys ) {
    List< ? extends SortKey > oldSortKeys = getSortKeys();
    List< ? extends SortKey > newSortKeys;
    if ( sortKeys != null && !sortKeys.isEmpty() ) {
      int max = getModelWrapper().getColumnCount();
      for ( SortKey key : sortKeys )
        if ( key == null || key.getColumn() < 0 || key.getColumn() >= max )
          throw new IllegalArgumentException( "Invalid SortKey" );
      newSortKeys = Collections.unmodifiableList( new ArrayList< SortKey >( sortKeys ) );
    } else
      newSortKeys = Collections.emptyList();
    setSortKeysInternal( newSortKeys );
    if ( !newSortKeys.equals( oldSortKeys ) ) {
      fireSortOrderChanged();
      boolean wasChanged = false;
      if ( preColumns.length > 0 || postColumns.length > 0 ) {
        List< SortKey > editableSortKeys = new ArrayList< SortKey >( newSortKeys );
        for ( int i = preColumns.length - 1; i >= 0; i-- ) {
          int modelColumn = preColumns[ i ].getColumn();
          int idx = indexOfColumn( editableSortKeys, preColumns.length - i - 1, editableSortKeys.size(), modelColumn );
          SortOrder sortOrder = idx < 0 ? preColumns[ i ].getSortOrder() : editableSortKeys.remove( idx ).getSortOrder();
          editableSortKeys.add( 0, new SortKey( modelColumn, sortOrder ) );
        }
        int to = editableSortKeys.size();
        for ( SortKey postColumn : postColumns ) {
          int modelColumn = postColumn.getColumn();
          int idx = indexOfColumn( editableSortKeys, preColumns.length, to, modelColumn );
          SortOrder sortOrder;
          if ( idx < 0 )
            sortOrder = postColumn.getSortOrder();
          else {
            sortOrder = editableSortKeys.remove( idx ).getSortOrder();
            to--;
          }
          editableSortKeys.add( new SortKey( modelColumn, sortOrder ) );
        }
        if ( wasChanged = !editableSortKeys.equals( newSortKeys ) )
          setSortKeysInternal( editableSortKeys );
      }
      if ( getViewToModelInternal() == null )
        sort();
      else
        sortExistingDataInternal();
      if ( wasChanged )
        setSortKeysInternal( newSortKeys );
    }
  }

  private int indexOfColumn( List< SortKey > sortKeys, int fromIncl, int toExcl, int column ) {
    for ( int i = toExcl - 1; i >= fromIncl; i-- )
      if ( sortKeys.get( i ).getColumn() == column )
        return i;
    return -1;
  }
};

Usage:

table.setRowSorter( new PredefinedRowSorter( table )
  .withPreColumns( new SortKey( 0, SortOrder.ASCENDING ),
                   new SortKey( 1, SortOrder.ASCENDING ) ) );

This would set a preceding sorting on first two columns.

Post-sorting is available.

An end-user may sort these two columns too, thus switching the order of sorting (ascending/descending).

Also, this class is available as a part of JBroTable library (source code).

The principle of action is so: predefined columns are added to a sorting columns list before sorting and removed from this list after sorting. Adding and removal is performed via reflection because DefaultRowSorter implementation doesn't provide an access to the list.



来源:https://stackoverflow.com/questions/19818973/java-jtable-setting-a-primary-column-to-always-sort-by-first

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!