代码之家  ›  专栏  ›  技术社区  ›  Paul Fisher

JTable的实时排序

  •  6
  • Paul Fisher  · 技术社区  · 17 年前

    我已经想出了如何得到一份工作 JTable How to Use Tables . 我强调了我所做的更改 // ADDED . 在这种情况下,新添加的值会正确排序,但当我编辑一个值时,它似乎不会使用,即使我调用 fireTableCellUpdated

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

    /*
     * 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();
                }
            });
        }
    }
    
    4 回复  |  直到 17 年前
        1
  •  18
  •   Paul Fisher    17 年前

    这采取了两步解决方案:

    首先,我对数据更改进行了TableSorter排序,通过使用它而不是 autoCreateRowSorter :

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

    fireTableCellUpdated fireTableRowsUpdated 将只重新绘制更新的特定行,而不是整个表(这意味着您将得到一个重复的条目,该条目在以后重新绘制时会立即更改。因此,我更改了

    fireTableCellUpdated(row, col);
    

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

    现在,即使在数据发生变化时,它也能正确地进行排序,并保留选择。

        2
  •  4
  •   kleopatra Aji kattacherry    14 年前

    它是一个 long-standing bug on JTable ,2007年报道(令人惊讶的是它没有被修复,甚至在jdk7中也没有)

    在所有行上触发更新是一种合理的快速修复方法,前提是它不会太大程度地降低性能(由于触发频繁的完全恢复)。对于无所畏惧的人,这里有一个关于JTable-partial的部分修复,因为还没有捕获到所有可能的场景。这就是它从未进入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;
      }
    
    }
    
        3
  •  1
  •   boxofrats    17 年前

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

        4
  •  -1
  •   Eugene Ryzhikov    17 年前

    1. 由于表格模型包装了您的集合,因此它必须是可排序的。这意味着您的对象(行)必须实现可比较的接口,这样集合才能正确排序。
    2. 在setValueAt方法中,必须更新相应的属性,并使用Collections.sort调用集合。然后,显然,您必须调用fireTableDataChanged,让表知道它需要重新绘制。
    3. 假设添加数据时也会发生同样的情况。
    4. 如果您的收集量太大,您可能会考虑在开始时将数据添加到适当的位置,而不是求助于。