2013-03-07 2 views
0

У меня есть проблема, которую я не могу решить с помощью JTable с редактором и рендерером, содержащим JProgressBar внутри. У меня есть JButton, который используется для запуска потока, который увеличивает значение индикатора выполнения. Проблема в том, что когда я нажимаю на ячейку JTable, индикатор выполнения больше не обновляется. Я попытался добавить ChangeListener в индикатор выполнения, который завершает редактирование, но затем другие ячейки также не редактируются.
Вот SSCCE:
ProgressBar внутри JTable

public class TableTest { 

    final static MyObjectTableModel model = new MyObjectTableModel(); 
    final static JTable table = new JTable(model); 
    private static Map<Integer, Future> mapSubmittedReadProgress = new HashMap<Integer, Future>(); 
    final static StartProgressActionListener progressActionListener = new StartProgressActionListener(); 
    final static CloseActionListener closeActionListener = new CloseActionListener(); 
    final static ProgressChangeListener progressChangeListener = new ProgressChangeListener(); 

    /** 
    * @param args the command line arguments 
    */ 
    public static void main(String[] args) { 
     EventQueue.invokeLater(new Runnable() { 
      @Override 
      public void run() { 
       new TableTest().createGUI(); 
      } 
     }); 
    } 

    public static class MyObjectTableModel extends AbstractTableModel { 

     private LinkedList<MyObject> myList; 

     public MyObjectTableModel() { 
      super(); 
      myList = new LinkedList<MyObject>(); 
     } 

     public MyObjectTableModel(SortedSet<MyObject> myObjects) { 
      super(); 
      this.myList = new LinkedList<MyObject>(myObjects); 
     } 

     public void addRow(MyObject myObject) { 
      myList.add(myObject); 
      fireTableRowsInserted(myList.size() - 1, myList.size() - 1); 
     } 

     public void removeRow(int row) { 
      myList.remove(row); 
      fireTableRowsDeleted(row, row); 
     } 

     @Override 
     public void setValueAt(Object aValue, int rowIndex, int columnIndex) { 
      myList.set(rowIndex, (MyObject) aValue); 
      fireTableCellUpdated(rowIndex, 0); 
     } 

     @Override 
     public int getRowCount() { 
      return myList.size(); 
     } 

     @Override 
     public int getColumnCount() { 
      return 1; 
     } 

     @Override 
     public Class<?> getColumnClass(int columnIndex) { 
      switch (columnIndex) { 
       case 0: 
        return MyObject.class; 
       default: 
        throw new IllegalArgumentException("invalid column: " + columnIndex); 
      } 
     } 

     @Override 
     public Object getValueAt(int rowIndex, int columnIndex) { 
      switch (columnIndex) { 
       case 0: 
        return myList.get(rowIndex); 
       default: 
        throw new IllegalArgumentException("invalid column: " + columnIndex); 
      } 
     } 

     public MyObject getMyObjectAt(int row) { 
      return myList.get(row); 
     } 

     @Override 
     public boolean isCellEditable(int rowIndex, int columnIndex) { 
      return true; 
     } 

     public int getIndexOf(MyObject myObject) { 
      return myList.indexOf(myObject); 
     } 
    } 

    private static void createGUI() { 
     JFrame f = new JFrame("TableTest"); 
     f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 

     table.getModel().addTableModelListener(new TableModelListener() { 
      @Override 
      public void tableChanged(TableModelEvent e) { 
       SwingUtilities.invokeLater(new Runnable() { 
        @Override 
        public void run() { 
         setPreferredRowHeights(); 
        } 
       }); 
      } 
     }); 

     for (int i = 0; i < 16; i++) { 
      MyObject myObject = new MyObject(); 
      myObject.setText1("" + i); 
      model.addRow(myObject); 
     } 
     table.setOpaque(false); 
     table.setShowGrid(false); 
     table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); 
     table.setDefaultRenderer(MyObject.class, new MyTableCellRenderer()); 
     table.setDefaultEditor(MyObject.class, new MyTableCellEditor()); 
     table.setFillsViewportHeight(true); 
     f.add(new JScrollPane(table)); 

     f.pack(); 
     f.setLocationRelativeTo(null); 
     f.setVisible(true); 
    } 

    private static void setPreferredRowHeights() { 
     for (int row = 0; row < table.getRowCount(); row++) { 
      setPreferredRowHeight(row); 
     } 
    } 

    private static void setPreferredRowHeight(int row) { 
     int prefHeight = getPreferredRowHeight(row); 
     table.setRowHeight(row, prefHeight); 
    } 

    public static int getPreferredRowHeight(int row) { 
     int pref = 0; 
     for (int column = 0; column < table.getColumnCount(); column++) { 
      TableCellRenderer renderer = table.getCellRenderer(row, column); 
      Component comp = table.prepareRenderer(renderer, row, column); 
      pref = Math.max(pref, comp.getPreferredSize().height); 
     } 
     return pref > 0 ? pref : table.getRowHeight(); 
    } 

    private static class MyTableCellEditor extends AbstractCellEditor implements TableCellEditor { 

     private MyObjectPanel myObjectPanel = new MyObjectPanel(); 
     private transient List<CellEditorListener> listeners; 

     public MyTableCellEditor() { 
      myObjectPanel.addStartProgressActionListener(progressActionListener); 
      myObjectPanel.addCloseActionListener(closeActionListener); 
//   myObjectPanel.addProgressChangeListener(progressChangeListener); 
      listeners = new ArrayList<>(); 
     } 

     @Override 
     public boolean isCellEditable(EventObject e) { 
      return true; 
     } 

     @Override 
     public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) { 
      MyObject myObject = (MyObject) value; 
      myObjectPanel.setMyObject(myObject); 
      return myObjectPanel; 
     } 

     @Override 
     public Object getCellEditorValue() { 
      MyObject myObject = myObjectPanel.getMyObject(); 
      return myObject; 
     } 

     @Override 
     public void addCellEditorListener(CellEditorListener l) { 
      listeners.add(l); 
     } 

     @Override 
     public void removeCellEditorListener(CellEditorListener l) { 
      listeners.remove(l); 
     } 

     @Override 
     protected void fireEditingStopped() { 
      ChangeEvent ce = new ChangeEvent(this); 
      for (int i = listeners.size() - 1; i >= 0; i--) { 
       ((CellEditorListener) listeners.get(i)).editingStopped(ce); 
      } 
     } 
    } 

    private static class MyTableCellRenderer implements TableCellRenderer { 

     private MyObjectPanel myObjectPanel = new MyObjectPanel(); 

     public MyTableCellRenderer() { 
      myObjectPanel.addStartProgressActionListener(progressActionListener); 
      myObjectPanel.addCloseActionListener(closeActionListener); 
//   myObjectPanel.addProgressChangeListener(progressChangeListener); 
     } 

     @Override 
     public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { 
      MyObject myObject = (MyObject) value; 
      myObjectPanel.setMyObject(myObject); 
      return myObjectPanel; 
     } 
    } 

    private static class ProgressChangeListener implements ChangeListener { 

     @Override 
     public void stateChanged(ChangeEvent e) { 
      if (table.isEditing()) { 
       table.getCellEditor().stopCellEditing(); 
      } 
     } 
    } 

    private static class CloseActionListener implements ActionListener { 

     @Override 
     public void actionPerformed(ActionEvent e) { 
      if (table.isEditing()) { 
       table.getCellEditor().stopCellEditing(); 
      } 
      model.removeRow(table.getSelectedRow()); 
     } 
    } 

    private static class StartProgressActionListener implements ActionListener { 

     @Override 
     public void actionPerformed(ActionEvent e) { 
      if (table.isEditing()) { 
       table.getCellEditor().stopCellEditing(); 
      } 
      final ExecutorService executor = Executors.newFixedThreadPool(1); 
      int row = table.getSelectedRow(); 
      MyObject myObject = (MyObject) table.getValueAt(row, 0); 
      myObject.setStartEnable(false); 
      myObject.setText1Enable(false); 
      myObject.setText2Enable(false); 
      Runnable progressRunnable = new ProgressRunnable(table.getSelectedRow(), myObject); 
      final Future<?> submit = executor.submit(progressRunnable); 
      mapSubmittedReadProgress.put(table.getSelectedRow(), submit); 
     } 
    } 

    private static class ProgressRunnable implements Runnable { 

     private ExecutorService executor; 
     private long beT; 
     private int dur = 30; // s 
     private int progress = 0; 
     private int row; 
     private MyObject myObject; 

     public ProgressRunnable(int row) { 
     } 

     private ProgressRunnable(int selectedRow, MyObject myObject) { 
      this.row = selectedRow; 
      this.myObject = myObject; 
      beT = System.currentTimeMillis(); 
     } 

     @Override 
     public void run() { 
      boolean abort = false; 
      int i = 0; 
      while (i <= dur && !abort) { 
       final long curT = System.currentTimeMillis(); 
       try { 
        Thread.sleep(1000); 
       } catch (InterruptedException e) { 
        abort = true; 
        executor.shutdown(); 
       } 
       if (Thread.currentThread().isInterrupted()) { 
        abort = true; 
        executor.shutdown(); 
       } 
       progress = (int) Math.round(100 * ((double) (curT - beT)/1000)/dur); 
       myObject.setProgress(progress); 
       table.setValueAt(myObject, row, 0); 
       i++; 
      } 
     } 
    } 

    // My object 
    static class MyObject { 

     private String text1; 
     private String text2; 
     private int progress; 
     private boolean startEnable = true; 
     private boolean abortEnable = true; 
     private boolean text1Enable = true; 
     private boolean text2Enable = true; 
     private boolean closeEnable = true; 

     public String getText1() { 
      return text1; 
     } 

     public void setText1(String text1) { 
      this.text1 = text1; 
     } 

     public String getText2() { 
      return text2; 
     } 

     public void setText2(String text2) { 
      this.text2 = text2; 
     } 

     public int getProgress() { 
      return progress; 
     } 

     public void setProgress(int progress) { 
      this.progress = progress; 
     } 

     public boolean isStartEnable() { 
      return startEnable; 
     } 

     public void setStartEnable(boolean startEnable) { 
      this.startEnable = startEnable; 
     } 

     public boolean isAbortEnable() { 
      return abortEnable; 
     } 

     public void setAbortEnable(boolean abortEnable) { 
      this.abortEnable = abortEnable; 
     } 

     public boolean isText1Enable() { 
      return text1Enable; 
     } 

     public void setText1Enable(boolean text1Enable) { 
      this.text1Enable = text1Enable; 
     } 

     public boolean isText2Enable() { 
      return text2Enable; 
     } 

     public void setText2Enable(boolean text2Enable) { 
      this.text2Enable = text2Enable; 
     } 

     public boolean isCloseEnable() { 
      return closeEnable; 
     } 

     public void setCloseEnable(boolean closeEnable) { 
      this.closeEnable = closeEnable; 
     } 
    } 

    // MyObjectPanel 
    static class MyObjectPanel extends javax.swing.JPanel { 

     /** 
     * Creates new form MyObjectPanel 
     */ 
     public MyObjectPanel() { 
      initComponents(); 
     } 

     /** 
     * This method is called from within the constructor to initialize the 
     * form. WARNING: Do NOT modify this code. The content of this method is 
     * always regenerated by the Form Editor. 
     */ 
     @SuppressWarnings("unchecked") 
     // <editor-fold defaultstate="collapsed" desc="Generated Code"> 
     private void initComponents() { 

      jTextField1 = new javax.swing.JTextField(); 
      jTextField2 = new javax.swing.JTextField(); 
      jProgressBar1 = new javax.swing.JProgressBar(); 
      btnStart = new javax.swing.JButton(); 
      btnStop = new javax.swing.JButton(); 
      btnClose = new javax.swing.JButton(); 

      btnStart.setText("Start"); 

      btnStop.setText("Stop"); 

      btnClose.setText("Close"); 

      javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); 
      this.setLayout(layout); 
      layout.setHorizontalGroup(
        layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) 
        .addGroup(layout.createSequentialGroup() 
        .addContainerGap() 
        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) 
        .addComponent(jTextField1) 
        .addComponent(jTextField2) 
        .addComponent(jProgressBar1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) 
        .addGroup(layout.createSequentialGroup() 
        .addComponent(btnStart) 
        .addGap(18, 18, 18) 
        .addComponent(btnStop) 
        .addGap(18, 18, 18) 
        .addComponent(btnClose) 
        .addGap(0, 199, Short.MAX_VALUE))) 
        .addContainerGap())); 
      layout.setVerticalGroup(
        layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) 
        .addGroup(layout.createSequentialGroup() 
        .addContainerGap() 
        .addComponent(jTextField1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) 
        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) 
        .addComponent(jTextField2, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) 
        .addGap(18, 18, 18) 
        .addComponent(jProgressBar1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) 
        .addGap(18, 18, 18) 
        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) 
        .addComponent(btnStart) 
        .addComponent(btnStop) 
        .addComponent(btnClose)) 
        .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))); 
     }// </editor-fold> 
     // Variables declaration - do not modify 
     private javax.swing.JButton btnClose; 
     private javax.swing.JButton btnStart; 
     private javax.swing.JButton btnStop; 
     private javax.swing.JProgressBar jProgressBar1; 
     private javax.swing.JTextField jTextField1; 
     private javax.swing.JTextField jTextField2; 
     // End of variables declaration 

     void setMyObject(MyObject myObject) { 
      jTextField1.setText(myObject.getText1()); 
      jTextField2.setText(myObject.getText2()); 
      jProgressBar1.setValue(myObject.getProgress()); 
      btnStart.setEnabled(myObject.isStartEnable()); 
      btnClose.setEnabled(myObject.isCloseEnable()); 
      btnStop.setEnabled(myObject.isAbortEnable()); 
      jTextField1.setEnabled(myObject.isText1Enable()); 
      jTextField2.setEnabled(myObject.isText2Enable()); 
     } 

     MyObject getMyObject() { 
      MyObject myObject = new MyObject(); 
      myObject.setText1(jTextField1.getText()); 
      myObject.setText2(jTextField2.getText()); 
      myObject.setProgress(jProgressBar1.getValue()); 
      myObject.setStartEnable(btnStart.isEnabled()); 
      myObject.setCloseEnable(btnClose.isEnabled()); 
      myObject.setAbortEnable(btnStop.isEnabled()); 
      myObject.setText1Enable(jTextField1.isEnabled()); 
      myObject.setText2Enable(jTextField2.isEnabled()); 
      return myObject; 
     } 

     void addStartProgressActionListener(ActionListener progressActionListener) { 
      btnStart.addActionListener(progressActionListener); 
     } 

     void addCloseActionListener(ActionListener closeActionListener) { 
      btnClose.addActionListener(closeActionListener); 
     } 

     void addProgressChangeListener(ChangeListener changeListener) { 
      jProgressBar1.addChangeListener(changeListener); 
     } 
    } 
} 


Спасибо за любую помощь.

+2

* «Здесь SSCCE:» * В SSCCE должен быть только один класс, объявленный как «открытый», поэтому другие классы могут быть включены в один и тот же исходный файл. –

+0

Зачем использовать JTable вместо BoxLayout? – aymeric

+0

Потому что я должен добавлять/удалять/обновлять объекты из модели таблицы во время выполнения, и для этого проще использовать JTable, чем играть с макетом. – jerome

ответ

1

Есть несколько вещей, которые нужно изменить, чтобы заставить его работать.

Прежде всего, ваш класс MyObjectPanel должен иметь переменную MyObject и возвращать это вместо нового каждый раз, когда вызывается getMyObject. Эта переменная устанавливается, когда setMyObject вызывается на MyObjectPanel.

// End of variables declaration 

private MyObject object; 
void setMyObject(MyObject myObject) { 
    object = myObject; 
... 
} 

MyObject getMyObject() { 
    return object; 
} 

Во-вторых, ваш MyObject класс нуждается в int row переменную, чтобы иметь возможность знать, какая строка представляет. Создайте геттер тоже для этой переменной. Установите эту переменную в цикле for в createGUI.

Последнее, что в while вашего ProgressRunnable в run, вам необходимо прекратить редактирование ячейки, если это тот прогресс, который достигнут. Чтобы сделать это, вы проверяете с помощью JTable.getEditingRow, если в таблице isEditing эта строка совпадает с myObject.getRow, что переменная myObject. Что-то вроде этого:

myObject.setProgress(progress); 
      if (table.isEditing()) { 
      if (table.getEditingRow() == myObject.getRow()) { 
       table.getCellEditor().stopCellEditing(); 
      } 
      } 
      model.fireTableRowsUpdated(row, row); 

Также обратите внимание, что я изменил setValue там fireTableRowsUpdated вместо этого.

Это должно заставить работать, как вы хотите.

Вы можете удалить ваш TableModelListener в createGUI и вызывать только setPreferredRowHeights однажды pack.