1

It was remarkably easy to introduce sorting for my JTable:

  //Existing code
  dftTableModel = new DefaultTableModel(0 , 4);
  tblOutput     = new JTable(dftTableModel);

  //Added code
  RowSorter<TableModel> sorter = new TableRowSorter<TableModel>(dftTableModel);
  tblOutput.setRowSorter(sorter);

But since I formatted the Size column as text with commas, it didn't sort:

enter image description here

I had never used a Comparator but found an example that I modified.

public class RowSorterWithComparator 
{
  static Comparator               compareNumericStringsWithCommas;
  static TableRowSorter           sorter;
  static JTable                   tblOutput;
  static JScrollPane              pane;
  static JFrame                   frame;
  static DefaultTableModel        dftTableModel; 
//  static TableColumnAdjuster      tca ;
  static DefaultTableCellRenderer rightRenderer;
  static JButton                  btnOpen;

  static String[] columnNames = { "Date", "Size" };
  static Object rows[][] = 
{
{"7/27/2015","96","mavenVersion.xml","C:\\Users\\Dov\\.AndroidStudio1.2\\config\\options\\"},
{"7/27/2015","120","keymap.xml","C:\\Users\\Dov\\.AndroidStudio1.2\\config\\options\\"},
{"7/27/2015","108","Default.xml","C:\\Users\\Dov\\.AndroidStudio1.2\\config\\inspection\\"},
{"4/27/2015","392","key pay.txt","C:\\Users\\Dov\\A\\"},
{"6/13/2015","161","BuildConfig.java","C:\\Users\\Dov\\androidfp2_examples\\eclipse_projects\\FlagQuiz\\gen\\com\\deitel\\flagquiz\\"}
};

  public static void main(String args[]) 
  {
    compareNumericStringsWithCommas = (Comparator) new Comparator() 
    {
      @Override public int compare(Object oo1, Object oo2) 
      {
        String o1 = oo1.toString().replace(",", "");
        String o2 = oo2.toString().replace(",", "");
        return Integer.valueOf(o1).compareTo(Integer.valueOf(o2));
      }
    };

    dftTableModel = new DefaultTableModel(0 , columnNames.length);
    tblOutput     = new JTable(dftTableModel);
    dftTableModel.setColumnIdentifiers(new Object[]{"Date", "Size", "File name", "Path to file"});

    rightRenderer = new DefaultTableCellRenderer();
    rightRenderer.setHorizontalAlignment(SwingConstants.RIGHT);
    tblOutput.getColumnModel().getColumn(1).setCellRenderer(rightRenderer);

    sorter      = new TableRowSorter<>(dftTableModel);
    sorter.setModel(tblOutput.getModel());
    sorter.setComparator(1,compareNumericStringsWithCommas);

    tblOutput.setRowSorter(sorter);
    tblOutput.setAutoResizeMode(AUTO_RESIZE_OFF);

//    tca = new tablecolumnadjuster.TableColumnAdjuster(tblOutput);
//    tca.setDynamicAdjustment(true);

    tblOutput.setFont(new Font("Courier New",Font.PLAIN,12));

    pane = new JScrollPane(tblOutput);

    for (int i = 0; i < 5; i++)
      dftTableModel.addRow(rows[i]);

    btnOpen = new JButton("Open selected file");

    btnOpen.addMouseListener(new MouseAdapter() {
        @Override
        public void mouseClicked(MouseEvent e)
        {       
          int row = tblOutput.getSelectedRow();
          String entry = (String)tblOutput.getModel().getValueAt(row, 3) 
                + "\\" + (String)tblOutput.getModel().getValueAt(row, 2);
          try 
          {
            Desktop.getDesktop().open(new File((entry.trim())));
          } catch (IOException ex) {System.out.println("Can't open file"); }
      }
    });

    frame = new JFrame("Sort Table Demo");
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.add(pane, BorderLayout.CENTER);
    frame.add(btnOpen, BorderLayout.AFTER_LAST_LINE);
    frame.setSize(800, 350);
    frame.setVisible(true);
  }
}

Works great.

enter image description here

It was easy to right-justify the size column:

rightRenderer = new DefaultTableCellRenderer();
rightRenderer.setHorizontalAlignment(SwingConstants.RIGHT);

I added a button to open the selected file. But after sorting, the wrong file opened.

So I removed getModel from the statement in the mouse listener:

          String entry = (String)tblOutput.getValueAt(row, 3) 
                + "\\" + (String)tblOutput.getValueAt(row, 2);

I don't know why it was there in the first place since I've been stealing all kinds of code from various places.

Anyway, everything works now.

But I have questions:

(1) When would getModel be required in the context of getting a table row's values? I thought since dftTableModel was used for tblOutput that surely it was proper.

(2) Is the row returned by getModel the row that the data originally was at? If so, I guess that could be useful on occasion. Not sure when.... To "un-sort"?

(3) Is the fact that I used TableRowSorter the reason getModel didn't get the right data? (I.e., are the two incompatible?)

(4) Is dftTableModel equivalent to tblOutput.getModel()?

...............................

imports for program:

import java.awt.BorderLayout;
import java.awt.Desktop;
import java.awt.Font;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.File;
import java.io.IOException;
import java.util.Comparator;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import static javax.swing.JTable.AUTO_RESIZE_OFF;
import javax.swing.SwingConstants;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableRowSorter;
//import tablecolumnadjuster.TableColumnAdjuster;
DSlomer64
  • 4,234
  • 4
  • 53
  • 88
  • 1
    Don't use static variables. There is no need for that even in a SSCCE. – camickr Aug 24 '15 at 14:48
  • @camickr--Instead of asking "why?", I Googled and found a great discussion that every new Java programmer should read [(why-are-static-variables-considered-evil)](http://stackoverflow.com/questions/7026507/why-are-static-variables-considered-evil) . Given that discussion, overusing `static` variables is one of my many bad habits. – DSlomer64 Aug 24 '15 at 21:34

2 Answers2

5

So I removed getModel from the statement in the mouse listener:

The data in the TableModel is NOT sorted. The View (JTable) displays the data in a sorted order

You get the data from the view using:

table.getValueAt(row, column);

if you want to get the data from the TableModel you use:

int modelRow = table.convertRpwIndexToModel(row);
table.getModel().getValueAt(modelRow, column);

There are also method for converting the column indexes back and forth.

So you always need to know whether you are trying to access the data the way it is displayed on the JTable or the way it was loaded into the TableModel and then use the appropriate index which may (or may) not involve an index conversion.

String entry = (String)tblOutput.getValueAt(row, 3) ...

When you hardcode a value you need to know what the hard coded value represents. In this case you are accessing a dynamic row value from the table and a column from the model.

So in the above statement you need to convert the "3" from the model to the view since you are using the getValueAt(...) method of the table.

int viewColumn3 = table.convertColumnIndexToView(3);
String entry = (String)tblOutput.getValueAt(row, viewColumn3) 

Or, since you are referencing hardcoded values multiple times (ie. columns 2 and 3) it may be easier to keep the hardcoded column indexes and only convert the row index once and then access the data from the TableModel:

String entry = (String)tblOutput.getModel().getValueAt(modelRow, 3) ... 
camickr
  • 321,443
  • 19
  • 166
  • 288
  • @camickr--Wow. Thanks for all the input. I only just figured out (thanks to your "3" reference) that I can drag a column, thereby messing up royally trying to open a file whose name is no longer in column 2. I'll get right on that. (I assume I can prevent dragging columns.) – DSlomer64 Aug 24 '15 at 21:53
  • @DSlomer64, `I assume I can prevent dragging columns` - yes, but why would you want to do that? Let the user determine how they want to view the data. I showed how to access the data so you don't care about the column order. – camickr Aug 25 '15 at 02:25
  • @camickr--`I only just figured out (thanks to your "3" reference) that I can drag a column, thereby messing up royally trying to open a file whose name is no longer in column 2. I'll get right on that.` I am now on it, Thanks for encouragement. I'm reading the tutorial on tables and taking notes. In particular, I'm trying to clear up difference between `DefaultTableModel` and `TableModel`. – DSlomer64 Aug 25 '15 at 13:25
  • @camickr--Your Answer is a large part of those notes I mentioned. I'd say it's better than the tutorial, but you are addressing my particular usage, whereas the tutorial takes me through all that I should have gone through before starting to mess with tables. You have been an invaluable resource for me of late (and maybe longer ago; I should check) and I sincerely appreciate the effort and sharing of code. – DSlomer64 Aug 25 '15 at 13:38
  • `I'm trying to clear up difference between DefaultTableModel and TableModel` - "TableModel" is an "Interface". DetaultTableModel is an implementation of the interface. The same as "Vector" is an implementation of "List" or "ArrayList" is a different implementation of "List". This is a basic OO concept and has nothing specifically to do with a JTable. This concept should be covered in any text book. Or you can look at [The Java Tutorials](http://docs.oracle.com/javase/tutorial/index.html). The `Learning the Java Language` has a section on `Interfaces`. – camickr Aug 25 '15 at 14:02
  • Don't forget to "accept" answers when you have a solution so people know the problem has been solved. – camickr Aug 25 '15 at 14:03
  • I was waiting for more Answers until I realized today after studying what you wrote last night and reading a bit of the tutorial that, as I said not long ago, you are THE MAN. I'm going to post my own "Answer", which incorporates your suggestions. I'm at the moment having trouble opening the file after dragging columns. No drag: file opens fine; after drag: if filename and/or path column has changed, no open. If it's better to append the revised version to the original question, I will do that. – DSlomer64 Aug 25 '15 at 14:26
0

This IS NOW A SSCCE (see comments) but it is a revision of the original code based on suggestions in comments above and below by @camickr.

THIS PROBLEM IS FIXED At present, after dragging column, if filename or path column position has changed, clicking button results in error such as The file: xml\C:\Users\Dov\.AndroidStudio1.2\config\options doesn't exist. This occurred after moving Extension column (which was before Filename column) to after Path column.

import java.awt.BorderLayout;
import java.awt.Desktop;
import java.awt.Font;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.File;
import java.io.IOException;
import java.util.Comparator;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import static javax.swing.JTable.AUTO_RESIZE_OFF;
import javax.swing.SwingConstants;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableRowSorter;
//import tablecolumnadjuster.TableColumnAdjuster;

public class RowSorterWithComparator 
{
  static final int    DATE_COLUMN     = 0;
  static final int    SIZE_COLUMN     = 1;
  static final int    EXTENSION_COLUMN= 2;
  static final int    FILENAME_COLUMN = 3;
  static final int    PATH_COLUMN     = 4;

  public static void main(String args[]) 
  {
    Comparator               compareNumericStringsWithCommas;
    TableRowSorter           sorter;
    JTable                   tblOutput;
    JScrollPane              pane;
    JFrame                   frame;
    DefaultTableModel        dftTableModel; 
//    TableColumnAdjuster      tca ;
    DefaultTableCellRenderer rightRenderer;
    JButton                  btnOpen;

    Object[] columnNames = new Object[]{"Date", "Size", "Extension", "File name", "Path to file"};
    Object rows[][] = 
      {
      {"7/27/2015","9,600","xml","mavenVersion.xml","C:/Users/Dov/.AndroidStudio1.2/config/options/"},
      {"7/27/2015","120,000","xml","keymap.xml","C:/Users/Dov/.AndroidStudio1.2/config/options/"},
      {"7/27/2015","108","xml","Default.xml","C:/Users/Dov/.AndroidStudio1.2/config/inspection/"},
      {"4/27/2015","39,200","txt","key pay.txt","C:/Users/Dov/A/"},
      {"6/13/2015","91","java","BuildConfig.java","C:/Users/Dov/androidfp2_examples/eclipse_projects/FlagQuiz/gen/com/deitel/flagquiz/"}
      };

    compareNumericStringsWithCommas = (Comparator) new Comparator() 
    {
      @Override public int compare(Object oo1, Object oo2) 
      {
        String o1 = oo1.toString().replace(",", "");
        String o2 = oo2.toString().replace(",", "");
        return Integer.valueOf(o1).compareTo(Integer.valueOf(o2));
      }
    };

    dftTableModel = new DefaultTableModel(0 , columnNames.length);
    tblOutput     = new JTable(dftTableModel);
    dftTableModel.setColumnIdentifiers(columnNames);

    rightRenderer = new DefaultTableCellRenderer();
    rightRenderer.setHorizontalAlignment(SwingConstants.RIGHT);
    tblOutput.getColumnModel().getColumn(1).setCellRenderer(rightRenderer);

    sorter      = new TableRowSorter<>(dftTableModel);
    sorter.setModel(tblOutput.getModel());
    sorter.setComparator(1,compareNumericStringsWithCommas);

    tblOutput.setRowSorter(sorter);
    tblOutput.setAutoResizeMode(AUTO_RESIZE_OFF);

    //    tca = new tablecolumnadjuster.TableColumnAdjuster(tblOutput);
    //    tca.setDynamicAdjustment(true);

    tblOutput.setFont(new Font("Courier New",Font.PLAIN,12));

    pane = new JScrollPane(tblOutput);

    for (Object[] row : rows)
      dftTableModel.addRow(row);

    btnOpen = new JButton("Open selected file");

    btnOpen.addMouseListener(new MouseAdapter() 
    {
        @Override
        public void mouseClicked(MouseEvent e)
        {       
          int viewFilenameCol = tblOutput.convertColumnIndexToView(FILENAME_COLUMN);
          int viewPathCol     = tblOutput.convertColumnIndexToView(PATH_COLUMN);
          int row = tblOutput.getSelectedRow();
          String entry = (String)tblOutput.getValueAt(row, viewPathCol) 
                      +  (String)tblOutput.getValueAt(row, viewFilenameCol);
          try 
          {
            Desktop.getDesktop().open(new File((entry.trim())));
          } 
          catch 
          (
            Exception ex) {System.out.println("Can't open file <" + entry + ">"); 
          }
      }
    });

    frame = new JFrame("Sort Table Demo");
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.add(pane, BorderLayout.CENTER);
    frame.add(btnOpen, BorderLayout.SOUTH);
    frame.setSize(800, 350);
    frame.setVisible(true);
  }
}
DSlomer64
  • 4,234
  • 4
  • 53
  • 88
  • This is not a SSCCE. 1) the TableColumnAdjuster is irrelevant to the question and people can't test your code because they don't have access to the class. 2), invoking the Desktop class is irrelevant because we don't have access to your files. Instead you should just display the output of the "entry" variable to make sure the file name is correct, which it doesn't appear to be, which could potentially cause a problem (ie. why is there a "\\" between the path and file?). – camickr Aug 25 '15 at 15:33
  • 1
    For the second problem, you need to convert the indexes in the mouse clicked event. Again, where is your debug code to verify the value of the indexes? This is basic debugging. The problem with your code is that the index conversion is done outside the mouseClicked code, so the values never change as the columns are dragged. That is why I suggested the second solution to access the data from the model was easier, because you only need to convert the row index to the model. – camickr Aug 25 '15 at 15:33
  • Yes. I had commented out the references to TCA in OQ but neglected to do so here since I was rushing to a doctor's appt. I shouldn't have posted. The original wasn't a SSCCE either because of reference to `Desktop`. I should have also thought about this before posting. – DSlomer64 Aug 25 '15 at 17:15
  • Good point about the `\\`. Artifact of when code that this SSCCE is based on did NOT put slash at end of path. – DSlomer64 Aug 25 '15 at 17:50
  • I was torn about using `static` in defining the CONSTANT values for the column numbers. I HAD to use `static` on 3 and 4 inside the mouse listener, so I applied it to all for future possible similar use. NOT having them `static` is why the two lines referring to them were NOT inside the mouse listener. I overreacted to your advice "Don't use static variables. There is no need for that even in a SSCCE." If those values might need to be used in other listeners or methods in future versions of the larger program, isn't this declaration best? – DSlomer64 Aug 25 '15 at 17:57
  • "where is your debug code to verify the value of the indexes?" Any breakpoint or println would have told me nothing I didn't already know: the original column indices were being used. Had I not posted and rushed off to doctor, I probably would have realized that moving those two lines was the problem and wouldn't have generated this slew of comments. Poor judgement on my part. And I guess I missed your point of only needing to convert row index. – DSlomer64 Aug 25 '15 at 18:09
  • 1
    `I was torn about using static in defining the CONSTANT values for the column numbers` - this is fine, except that a constant should be "static final" since their values never change. `Any breakpoint or println would have told me nothing I didn't already know: the original column indices were being used.` - then you should ask a specific question stating what you learned from debugging, like "why doesn't the index value change" . Listing the error message doesn't help anybody doing a quick read of the question. They may not notice the filename is displayed before the pathname. – camickr Aug 25 '15 at 20:52
  • Again, I was only able to understand the question because of the background knowledge I gained from answering the original question. If you want other people to contribute, they need to know your analysis of the problem. – camickr Aug 25 '15 at 20:53