I'm using a JTable that displays multiple sortable columns. I need any rows that contain a certain status to be frozen to the top of the list regardless of how the user has sorted the rest of the list. For example, using the following unsorted list: Unsorted Table
When the user clicks on the header for File Size, it should freeze any rows with the status "Downloading" to the top and then sort by the File Size column as seen here: Sorted Table
It is possible to solve this by simply using two JTables, but doing so looks ugly and presents its own challenges:
I decided this approach was not acceptable and thought up another approach: add a hidden Position column that has a value '1' for any rows that I want frozen to the top and a '2' for all other statuses and permanently keep this column sorted in ascending order. I was able to use a TableRowSorter with multiple SortKeys to sort the data in this manner on initial load; however, I am unable to keep the Position column sort happening prior to the user-selected column sort as there is no mechanism to prepend a SortKey. Trying to add another SortKey always adds it AFTER the existing SortKey(s).
I first tried adding a RowSorterListener to the TableRowSorter and re-sorting the table first on the Position column and then on the user-selected column on sorterChanged() event, but this did not work because it created an endless loop because the sorterChanged() event fired every time anything messed with the sort order.
Next, I tried adding a MouseListener to the TableHeader and manually sorting the table based on whichever column the user clicked. This half worked but ultimately failed because the JTable built-in sorter code still fires. So when a user clicks on a header, it sorts it once via my code and then sorts it the opposite direction via the JTable internal sort code. Thus resulting in no sort at all. I couldn't figure out any way to consume or prevent this internal event except to disable the TableHeader completely. This was unacceptable though because it also disables the ability for the user to resize columns and the directional arrows no longer display.
Doing some searching online, I came across a solution that functions perfectly (https://stackoverflow.com/a/37721594/6143124) but ultimately cannot be used because it will not be supported in future versions of Java and prints a warning detailing as much on Java 9:
WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by pkg.PredefinedRowSorter to field javax.swing.DefaultRowSorter.sortKeys WARNING: Please consider reporting this to the maintainers of pkg.PredefinedRowSorter
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release
I have been stumped on this one for the better park of a week and would greatly appreciate any help or guidance.
Thanks, Syd
I was able to achieve the desired outcome by creating my own TableRowSorter and overriding the toggleSortOrder() method.
import java.util.ArrayList;
import java.util.List;
import javax.swing.SortOrder;
import javax.swing.table.TableModel;
import javax.swing.table.TableRowSorter;
public class PredefinedTableRowSorter<M extends TableModel> extends TableRowSorter<TableModel>
{
public PredefinedTableRowSorter (TableModel model)
{
super(model);
}
@Override
public void toggleSortOrder (int column)
{
List<SortKey> newSortKeys = new ArrayList<>();
newSortKeys.add(new SortKey(0, SortOrder.ASCENDING));
SortKey currentKey = getSortKey(column);
SortOrder sortOrder = SortOrder.ASCENDING;
if (currentKey != null) {
sortOrder = currentKey.getSortOrder() == SortOrder.ASCENDING ? SortOrder.DESCENDING : SortOrder.ASCENDING;
}
currentKey = new SortKey(column, sortOrder);
newSortKeys.add(currentKey);
super.setSortKeys(newSortKeys);
super.sort();
}
private SortKey getSortKey (int column)
{
for (SortKey key : super.getSortKeys()) {
if (key.getColumn() == column) {
return key;
}
}
return null;
}
}
This solution presented a new issue: the sort arrow icon only appeared on the first column sorted. I overcame this by writing a custom DefaultTableCellRenderer and applying it to the table headers as necessary but it doesn't match the system look and feel (Windows 10 normally shows the sort arrows in the top middle of the header, whereas it gets placed after the text in my custom renderer).