2013-03-07 1 views
0

J'ai un problème que je ne peux pas résoudre en utilisant JTable avec l'éditeur et le moteur de rendu contenant JProgressBar à l'intérieur. J'ai un JButton qui est utilisé pour démarrer un thread qui incrémente la valeur de la barre de progression. Le problème est que lorsque je clique sur la cellule de JTable, la barre de progression ne se rafraîchit plus. J'ai essayé d'ajouter un ChangeListener à la barre de progression qui termine les modifications, mais les autres cellules ne sont pas modifiables.
Voici un SSCCE:
ProgressBar à l'intérieur de 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); 
     } 
    } 
} 


Merci pour toute aide.

+2

* "Voici un SSCCE:" * Un SSCCE ne devrait avoir qu'une classe déclarée comme 'public' afin que d'autres classes puissent être incluses dans le même fichier source. –

+0

Pourquoi utiliser un JTable ici au lieu d'un BoxLayout? – aymeric

+0

Parce que je dois ajouter/supprimer/mettre à jour des objets du modèle de table à l'exécution, et il est plus facile d'utiliser un JTable pour cela que de jouer avec la mise en page. – jerome

Répondre

1

Il y a quelques petites choses à changer pour que ça marche.

Tout d'abord, votre classe MyObjectPanel doit avoir une variable MyObject et renvoyer cette variable au lieu d'une nouvelle chaque fois que getMyObject est appelée. Cette variable est définie lorsque setMyObject est appelée MyObjectPanel.

// End of variables declaration 

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

MyObject getMyObject() { 
    return object; 
} 

Deuxièmement, votre classe a besoin MyObject une variable int row pour être en mesure de savoir quelle ligne elle représente. Faire un getter aussi pour cette variable. Définissez cette variable dans la boucle for dans createGUI.

Enfin, dans le while de votre ProgressRunnable dans run, vous devez arrêter d'éditer la cellule si c'est votre progression. Pour ce faire, vous vérifiez avec JTable.getEditingRow, si la table isEditing que cette ligne est la même que myObject.getRow que la variable myObject de progression. Quelque chose comme ceci:

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

Notez aussi que j'ai changé le setValue là pour fireTableRowsUpdated à la place.

Cela devrait fonctionner comme vous le souhaitez.

Vous pouvez supprimer votre TableModelListener dans createGUI et seulement appeler setPreferredRowHeights une fois avant pack.