java JTable 的实时排序

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/996948/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-29 14:44:35  来源:igfitidea点击:

Live sorting of JTable

javamodel-view-controllerswingsortingjtable

提问by Paul Fisher

I've figured out how to get a JTableto be sorted properly, but I can't figure out how to get it to automatically update the sort order when a table cell is changed. Right now, I have this (admittedly long) code, mostly based on that in the Java Tutorial's How to Use Tables. I've highlighted the changes I've made with // ADDED. In this case, newly-added values sort properly, but when I go in to edit a value, it doesn't seem to resort, even though I call fireTableCellUpdated?

我已经弄清楚如何JTable正确排序a ,但我无法弄清楚如何让它在表格单元格更改时自动更新排序顺序。现在,我有这个(不可否认的长)代码,主要基于 Java 教程的如何使用表中的代码。我已经突出显示了我对// ADDED. 在这种情况下,新添加的值排序正确,但是当我进入编辑值时,它似乎不会求助,即使我调用fireTableCellUpdated?

In short, how can I get a table to re-sort when a data value changes in the model?

简而言之,当模型中的数据值发生变化时,如何让表重新排序?

/*
 * Copyright (c) 1995 - 2008 Sun Microsystems, Inc.  All rights reserved.
 * See the standard BSD license.
 */ 

package components;

/*
 * TableSortDemo.java requires no other files.
 */

import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;

import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.AbstractTableModel;

public class TableSortDemo extends JPanel {
    private boolean DEBUG = false;

    public TableSortDemo() {
        super();
        setLayout(new BoxLayout(TableSortDemo.this, BoxLayout.PAGE_AXIS));
        final MyTableModel m = new MyTableModel();
        JTable table = new JTable(m);
        table.setPreferredScrollableViewportSize(new Dimension(500, 70));
        table.setFillsViewportHeight(true);
        table.setAutoCreateRowSorter(true);

        //Create the scroll pane and add the table to it.
        JScrollPane scrollPane = new JScrollPane(table);

        //Add the scroll pane to this panel.
        add(scrollPane);

        // ADDED: button to add a value
        JButton addButton = new JButton("Add a new value");
        addButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                m.addValue(
                        JOptionPane.showInputDialog(
                                TableSortDemo.this, "Value?"));
            }
        });

        // ADDED button to change a value
        JButton setButton = new JButton("Change a value");
        setButton.addActionListener(new ActionListener() {
            /* (non-Javadoc)
             * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
             */
            public void actionPerformed(ActionEvent e) {
                m.setValueAt(
                        JOptionPane.showInputDialog(
                                TableSortDemo.this, "Value?"),
                        Integer.parseInt(
                                JOptionPane.showInputDialog(
                                        TableSortDemo.this, "Which?")), 0);
            }
        });
        add(addButton);
        add(setButton);
    }

    class MyTableModel extends AbstractTableModel {
        private static final long serialVersionUID = -7053335255134714625L;
        private String[] columnNames = {"Column"};
        // ADDED data as mutable ArrayList
        private ArrayList<String> data = new ArrayList<String>();

        public MyTableModel() {
            data.add("Anders");
            data.add("Lars");
            data.add("Betty");
            data.add("Anna");
            data.add("Jon");
            data.add("Zach");
        }

        // ADDED
        public void addValue(Object v) {
            data.add(v.toString());
            int row = data.size() - 1;
            fireTableRowsInserted(row, row);
        }

        public int getColumnCount() {
            return columnNames.length;
        }

        public int getRowCount() {
            return data.size();
        }

        public String getColumnName(int col) {
            return columnNames[col];
        }

        public Object getValueAt(int row, int col) {
            return data.get(row) + " " + row;
        }

        /*
         * JTable uses this method to determine the default renderer/
         * editor for each cell.  If we didn't implement this method,
         * then the last column would contain text ("true"/"false"),
         * rather than a check box.
         */
        public Class<String> getColumnClass(int c) {
            return String.class;
        }

        /*
         * Don't need to implement this method unless your table's
         * editable.
         */
        public boolean isCellEditable(int row, int col) {
            //Note that the data/cell address is constant,
            //no matter where the cell appears onscreen.
            if (col < 2) {
                return false;
            } else {
                return true;
            }
        }

        /*
         * Don't need to implement this method unless your table's
         * data can change.
         */
        public void setValueAt(Object value, int row, int col) {
            if (DEBUG) {
                System.out.println("Setting value at " + row + "," + col
                                   + " to " + value
                                   + " (an instance of "
                                   + value.getClass() + ")");
            }

            data.set(row, value.toString());

            // ADDED: uncommented this line, despite warnings to the contrary
            fireTableCellUpdated(row, col);

            if (DEBUG) {
                System.out.println("New value of data:");
                printDebugData();
            }
        }

        private void printDebugData() {
            int numRows = getRowCount();
            int numCols = getColumnCount();

            for (int i=0; i < numRows; i++) {
                System.out.print("    row " + i + ":");
                for (int j=0; j < numCols; j++) {
                    System.out.print("  " + data.get(i));
                }
                System.out.println();
            }
            System.out.println("--------------------------");
        }
    }

    /**
     * Create the GUI and show it.  For thread safety,
     * this method should be invoked from the
     * event-dispatching thread.
     */
    private static void createAndShowGUI() {
        //Create and set up the window.
        JFrame frame = new JFrame("TableSortDemo");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        //Create and set up the content pane.
        TableSortDemo newContentPane = new TableSortDemo();
        newContentPane.setOpaque(true); //content panes must be opaque
        frame.setContentPane(newContentPane);

        //Display the window.
        frame.pack();
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        //Schedule a job for the event-dispatching thread:
        //creating and showing this application's GUI.
        javax.swing.SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                createAndShowGUI();
            }
        });
    }
}

回答by Paul Fisher

This took a two-step solution:

这采取了两步解决方案:

First I had the TableSorter sort on data change, by using this rather than autoCreateRowSorter:

首先,我对数据更改进行了 TableSorter 排序,使用这个而不是autoCreateRowSorter

sorter = new TableRowSorter<MyTableModel>(m);
table.setRowSorter(sorter);
sorter.setSortsOnUpdates(true);

Then, I had to change the update method to update the entire table. The fireTableCellUpdatedand the fireTableRowsUpdatedwould only redraw the specific rows that were updated, not the entire table (meaning you'd get a duplicate-appearing entry that changed as soon as it was redrawn later. So, I changed

然后,我不得不更改更新方法以更新整个表。在fireTableCellUpdatedfireTableRowsUpdated只重绘进行了更新,而不是整个表中的特定行(这意味着你会得到一个复本出现的条目改变,只要它是重绘后。所以,我改变

fireTableCellUpdated(row, col);

to

fireTableRowsUpdated(0, data.size() - 1);

and now it sorts properly, even upon data changes, and selection is preserved.

现在它可以正确排序,即使在数据更改时也是如此,并且选择被保留。

回答by kleopatra

Its a long-standing bug on JTable, reported in 2007 (astonished that it isn't fixed, not even in jdk7)

它是JTable 上的一个长期存在的错误,于 2007 年报告(令人惊讶的是它没有修复,甚至在 jdk7 中也没有)

Firing a update on all rows is a reasonable quick fix if it doesn't degrade performance too much (due to triggering frequent complete resorts). For the fearless, here's a partial fix on JTable - partial, because not yet all possible scenarios are captured. Which is the reason it never made it into JXTable (or maybe I had other priorities then :-)

如果不会过多地降低性能(由于触发频繁的完整度假村),则对所有行进行更新是一个合理的快速修复。对于无所畏惧的人,这里是 JTable 的部分修复 - 部分,因为尚未捕获所有可能的场景。这就是它从未进入 JXTable 的原因(或者我当时有其他优先事项:-)

public static class JTableRepaintOnUpdate extends JTable {

  private UpdateHandler beforeSort;

  @Override
  public void sorterChanged(RowSorterEvent e) {
      super.sorterChanged(e);
      maybeRepaintOnSorterChanged(e);
  } 

  private void beforeUpdate(TableModelEvent e) {
      if (!isSorted()) return;
      beforeSort = new UpdateHandler(e);
  }

  private void afterUpdate() {
      beforeSort = null;
  }

  private void maybeRepaintOnSorterChanged(RowSorterEvent e) {
      if (beforeSort == null) return;
      if ((e == null) || (e.getType() != RowSorterEvent.Type.SORTED)) return;
      UpdateHandler afterSort = new UpdateHandler(beforeSort);
      if (afterSort.allHidden(beforeSort)) {
          return;
      } else if (afterSort.complex(beforeSort)) {
          repaint();
          return;
      }
      int firstRow = afterSort.getFirstCombined(beforeSort);
      int lastRow = afterSort.getLastCombined(beforeSort);
      Rectangle first = getCellRect(firstRow, 0, false);
      first.width = getWidth();
      Rectangle last = getCellRect(lastRow, 0, false);
      repaint(first.union(last));
  }

  private class UpdateHandler {
      private int firstModelRow;
      private int lastModelRow;
      private int viewRow;
      private boolean allHidden;

      public UpdateHandler(TableModelEvent e) {
          firstModelRow = e.getFirstRow();
          lastModelRow = e.getLastRow();
          convert();
      }

      public UpdateHandler(UpdateHandler e) {
          firstModelRow = e.firstModelRow;
          lastModelRow = e.lastModelRow;
          convert();
      }

      public boolean allHidden(UpdateHandler e) {
          return this.allHidden && e.allHidden;
      }

      public boolean complex(UpdateHandler e) {
          return (firstModelRow != lastModelRow);
      }

      public int getFirstCombined(UpdateHandler e) {
          if (allHidden) return e.viewRow;
          if (e.allHidden) return viewRow;
          return Math.min(viewRow, e.viewRow);
      }

      public int getLastCombined(UpdateHandler e) {
          if (allHidden || e.allHidden) return getRowCount() - 1;
          return Math.max(viewRow, e.viewRow);

      }

      private void convert() {
          // multiple updates
          if (firstModelRow != lastModelRow) {
              // don't bother too much - calculation not guaranteed to do anything good
              // just check if the all changed indices are hidden
              allHidden = true;
              for (int i = firstModelRow; i <= lastModelRow; i++) {
                  if (convertRowIndexToView(i) >= 0) {
                      allHidden = false;
                      break;
                  }
              }
              viewRow = -1;
              return;
          }
          // single update
          viewRow = convertRowIndexToView(firstModelRow);
          allHidden = viewRow < 0;
      }

  }

  private boolean isSorted() {
      // JW: not good enough - need a way to decide if there are any sortkeys which
      // constitute a sort or any effective filters  
      return getRowSorter() != null;
  }

  @Override
  public void tableChanged(TableModelEvent e) {
      if (isUpdate(e)) {
          beforeUpdate(e);
      }
      try {
          super.tableChanged(e);
      } finally {
          afterUpdate();
      }
  }

  /**
   * Convenience method to detect dataChanged table event type.
   * 
   * @param e the event to examine. 
   * @return true if the event is of type dataChanged, false else.
   */
  protected boolean isDataChanged(TableModelEvent e) {
      if (e == null) return false;
      return e.getType() == TableModelEvent.UPDATE && 
          e.getFirstRow() == 0 &&
          e.getLastRow() == Integer.MAX_VALUE;
  }

  /**
   * Convenience method to detect update table event type.
   * 
   * @param e the event to examine. 
   * @return true if the event is of type update and not dataChanged, false else.
   */
  protected boolean isUpdate(TableModelEvent e) {
      if (isStructureChanged(e)) return false;
      return e.getType() == TableModelEvent.UPDATE && 
          e.getLastRow() < Integer.MAX_VALUE;
  }

  /**
   * Convenience method to detect a structureChanged table event type.
   * @param e the event to examine.
   * @return true if the event is of type structureChanged or null, false else.
   */
  protected boolean isStructureChanged(TableModelEvent e) {
      return e == null || e.getFirstRow() == TableModelEvent.HEADER_ROW;
  }

}

回答by boxofrats

probably the easiest way to get it sorted would be to call fireTableDataChanged() instead of fireTableCellUpdated().

可能最简单的排序方法是调用 fireTableDataChanged() 而不是 fireTableCellUpdated()。

回答by Eugene Ryzhikov

There are several things you have to do here.

有几件事你必须在这里做。

  1. Since the table model wraps your collection it has to be sortable. That means that your object (row) has to implement Comparable interface so collection can be properly sorted.
  2. In your setValueAt method you have to to update appropriate attribute and resort the collection using Collections.sort. Then, obviously, you have to call fireTableDataChanged to let table know that it needs to redraw.
  3. Same thing suppose to happen on adding data.
  4. When data is removed you don't have to resort, but still have to fireTableDataChanged
  5. If your collection is to big, you may think about adding data to appropriate place initially instead of resorting.
  1. 由于表格模型包装了您的收藏,因此它必须是可排序的。这意味着您的对象(行)必须实现 Comparable 接口,以便可以正确排序集合。
  2. 在您的 setValueAt 方法中,您必须更新适当的属性并使用 Collections.sort 重新排序集合。然后,很明显,您必须调用 fireTableDataChanged 来让 table 知道它需要重绘。
  3. 假设添加数据时也会发生同样的事情。
  4. 当数据被删除时,你不必求助,但仍然需要 fireTableDataChanged
  5. 如果您的集合很大,您可能会考虑最初将数据添加到适当的位置,而不是求助于。

Hope this helps

希望这可以帮助