2013-07-14 14 views
3

我在寫一個類來跟蹤線程並在JTable中顯示狀態/進度。我想到的是一個JTable,其中包含所有必需的狀態/按鈕等。列在一列,每行一個線程。我使用了單元格編輯器來獲取表格中的可點擊按鈕,但是我無法解決的問題是,除非單擊另一個單元格,否則所選單元格中的項目不會更新。有沒有辦法讓選中的單元格更新?下面的代碼演示了這個問題。單擊行中的開始按鈕將啓動該線程,但在選中該行時,行中的進度不會更新。JTable中的選定單元格不刷新

import javax.swing.*; 
import javax.swing.table.*; 
import java.util.Random; 
import java.lang.Thread; 
import java.lang.Math; 
import java.beans.*; 
import java.util.concurrent.*; 
import java.util.*; 
import java.awt.*; 
import java.awt.event.*; 
import javax.swing.border.*; 

/* 
* Program tracks some threads' progress, updating status in 
* cells in a JTable. 
* 
* Inner classes are MyDefTM, ThreadOB and ThreadCell which are 
* the table model, the object representing the thread's data and 
* the renderer/editor "stamp", respectively. 
*/ 
public class ThreadManager { 
    public JFrame jFrame; 
    public JTable jTable; 
    public MyDefTM tm; 
    public JScrollPane jsp; 

    public ThreadManager() { 
     tm = new MyDefTM(); 
     tm.addColumn("Threads"); 
     jFrame = new JFrame("Thread List"); 
     jTable = new JTable(); 
     jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
    } 

    public void createAndShowGUI() { 
     /* 
     * JTable in JScrollPane in JFrame. 
     */ 
     jTable.setModel(tm); 
     jTable.setRowHeight(60); 
     jsp = new JScrollPane(jTable); 
     jFrame.getContentPane().add(jsp); 
     jFrame.pack(); 

     jTable.setDefaultRenderer(Object.class, new ThreadCell()); 
     jTable.setDefaultEditor(Object.class, new ThreadCell()); 
     jTable.setShowHorizontalLines(true); 

     /* 
     * Add some test threads. 
     */ 
     for (int ii = 0; ii < 5; ii++) { 
      ThreadOb to = new ThreadOb(ii, jTable); 
      Vector v = new Vector(); 
      v.add(to); 
      tm.addRow(v); 
     } 

     jFrame.setSize(640, 480); 
     jFrame.setVisible(true); 
     return; 
    } 

    public static void main(String[] args) { 
     ThreadManager threadManager = new ThreadManager(); 
     threadManager.createAndShowGUI(); 
    } 

    /* 
    * Use DefaultTableModel but make every cell editable. 
    */ 
    public class MyDefTM extends DefaultTableModel { 
     public boolean isCellEditable(int row, int column) { 
      return true; 
     } 
    } 

    /* 
    * Represents a thread as stored in the table. Stores 
    * an ID for the thread, its progress and the result. 
    */ 
    public class ThreadOb { 
     public int threadID; 
     public int threadProgress; 
     public JTable jTable; 
     public SwingWorker workerThread; 
     public String threadResult; 

     public ThreadOb(int id, JTable t) { 
      jTable = t; 
      threadID = id; 
      threadProgress = 0; 
     } 

     public void buttonAction() { 
      /* 
      * Perform a task that takes just a little while to finish. 
      */ 
      workerThread = new SwingWorker<String,Object>() { 
       @Override 
       public String doInBackground() throws InterruptedException { 
        int prog = 0; 
        Random rand = new Random(42); 
        while (prog < 100) { 
         prog += Math.abs(rand.nextInt() % 5); 
         setProgress(prog); 
         Thread.sleep(1000); 
         setProgress(Math.min(prog, 100)); 
        } 
        return "42"; 
       } 
      }; 

      workerThread.addPropertyChangeListener(new PropertyChangeListener() { 
       @Override 
       public void propertyChange(PropertyChangeEvent e) { 
        if (e.getPropertyName() == "state" && 
                e.getNewValue() == "DONE") { 
         try { 
          threadResult = (String) workerThread.get(); 
         } catch (Exception ignore) { } 
        } 

        if (e.getPropertyName() == "progress") { 
         threadProgress = ((Integer) e.getNewValue()).intValue(); 
         /* 
         * Couple the model and view together. The table cells will 
         * not update without this line. 
         */ 
         ((MyDefTM) jTable.getModel()).fireTableDataChanged(); 
        } 
       } 

      }); 
      workerThread.execute(); 
     } 
    } 

    /* 
    * Represents the graphical "stamp" for the renderer and editor. 
    */ 
    public class ThreadCell extends AbstractCellEditor 
          implements TableCellRenderer, TableCellEditor { 

     private JLabel threadIDLabel; 
     private JLabel threadProgressLabel; 
     private JPanel threadPane; 
     private JButton but; 
     private JPanel statuspane; 
     private JProgressBar jpb; 
     private JPanel pane; 
     private Border offBorder; 
     private Border onBorder; 
     private ThreadOb val; 

     /* 
     * Establish the layout of the cells in the JTable. 
     * The selected cell has a red border. 
     */ 
     public ThreadCell() { 
      val = null; 
      threadIDLabel = new JLabel(); 
      threadProgressLabel = new JLabel(); 
      threadPane = new JPanel(); 
      threadPane.setLayout(new BoxLayout(threadPane, BoxLayout.X_AXIS)); 
      threadPane.add(threadIDLabel); 
      threadPane.add(threadProgressLabel); 
      statuspane = new JPanel(); 
      statuspane.setLayout(new BoxLayout(statuspane, BoxLayout.X_AXIS)); 
      statuspane.add(threadPane); 
      statuspane.add(Box.createHorizontalGlue()); 
      but = new JButton("Start"); 
      statuspane.add(but); 
      jpb = new JProgressBar(0, 100); 
      jpb.setStringPainted(true); 
      pane = new JPanel(); 
      pane.setLayout(new BoxLayout(pane, BoxLayout.Y_AXIS)); 
      pane.add(statuspane); 
      pane.add(jpb); 
      offBorder = BorderFactory.createEmptyBorder(2,2,2,2); 
      onBorder = BorderFactory.createLineBorder(java.awt.Color.RED, 2); 
      pane.setBorder(offBorder); 

      but.addActionListener(new ActionListener() { 
       @Override 
       public void actionPerformed(ActionEvent e) { 
        val.buttonAction(); 
        /* 
        * Uncomment to deselect the cell after clicking. 
        */ 
        //fireEditingStopped(); 
       } 
      }); 
     } 

     /* 
     * Populate the cell with the correct values. 
     */ 
     public void update(JTable table, 
          Object value, 
          boolean isSelected, 
          boolean hasFocus, 
          int row, 
          int column) { 
      if (value == null) { 
       return; 
      } 

      val = (ThreadOb) value; 
      threadIDLabel.setText("ID: " + ((ThreadOb) value).threadID + " "); 
      threadProgressLabel.setText("Progress: " + 
            ((ThreadOb) value).threadProgress + "%"); 
      jpb.setValue(((ThreadOb) value).threadProgress); 

      if (hasFocus) { 
       pane.setBorder(onBorder); 
      } else { 
       pane.setBorder(offBorder); 
      } 
     } 

     public Component getTableCellRendererComponent(JTable table, 
                 Object value, 
                 boolean isSelected, 
                 boolean hasFocus, 
                 int row, 
                 int column) { 
      update(table, value, isSelected, hasFocus, row, column); 
      return pane; 
     } 

     public Component getTableCellEditorComponent(JTable table, 
                Object value, 
                boolean isSelected, 
                int row, 
                int column) { 
      update(table, value, isSelected, true, row, column); 
      return pane; 
     } 

     public Object getCellEditorValue() { 
      return val; 
     } 
    } 
} 
+1

請您務必閱讀如何 - 一些基本教程(在線標籤的相應章節,在swing標籤wiki中引用) - 至今爲止,您正在這樣做_completely_ wrong:正如@MadProgrammer已經提到的那樣,您需要一個正確實現的_TableModel_來處理數據端... – kleopatra

回答

8

有這麼多的事情錯誤的(對不起),它是一種可怕的......

  • e.getPropertyName() == "state"不是String比較是如何完成的。您的if聲明永遠不會是true。改爲使用類似"state".equals(e.getPropertyName())的東西...

  • UI未開始更新,因爲單元格仍處於編輯模式。

  • 在你的編輯器,actionPerformed方法,添加一個stopCellEditing聲明,這將關閉該單元格編輯器,並允許單元格渲染器來完成其工作

  • 這不,真的,正確的方式使用JTable

一個可能的例子

此更新僅僅是一個可能的搜索解決方案的一個例子ñ。

JTable假設顯示數據的行和列。你有它目前設置的方式,這將是簡單的使用有點像GridBagLayout只是另一個佈局面板上的顯示器面板 - 恕我直言

enter image description here

import java.awt.BorderLayout; 
import java.awt.Component; 
import java.awt.EventQueue; 
import java.beans.PropertyChangeEvent; 
import java.beans.PropertyChangeListener; 
import java.util.ArrayList; 
import java.util.EventObject; 
import java.util.List; 
import java.util.Random; 
import javax.swing.AbstractCellEditor; 
import javax.swing.JButton; 
import javax.swing.JFrame; 
import javax.swing.JLabel; 
import javax.swing.JPanel; 
import javax.swing.JProgressBar; 
import javax.swing.JScrollPane; 
import javax.swing.JTable; 
import javax.swing.SwingUtilities; 
import javax.swing.SwingWorker; 
import javax.swing.UIManager; 
import javax.swing.UnsupportedLookAndFeelException; 
import javax.swing.table.AbstractTableModel; 
import javax.swing.table.TableCellEditor; 
import javax.swing.table.TableCellRenderer; 

public class ThreadMonitorExample { 

    public static void main(String[] args) { 
     new ThreadMonitorExample(); 
    } 

    public ThreadMonitorExample() { 
     EventQueue.invokeLater(new Runnable() { 
      @Override 
      public void run() { 
       try { 
        UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); 
       } catch (ClassNotFoundException ex) { 
       } catch (InstantiationException ex) { 
       } catch (IllegalAccessException ex) { 
       } catch (UnsupportedLookAndFeelException ex) { 
       } 

       ThreadTableModel model = new ThreadTableModel(); 
       model.add(new Task(0, model)); 
       model.add(new Task(1, model)); 
       model.add(new Task(2, model)); 
       model.add(new Task(3, model)); 
       model.add(new Task(4, model)); 
       JTable table = new JTable(model); 

       TaskProgressRenderer progressRenderer = new TaskProgressRenderer(); 
       TaskStatusRenderer statusRenderer = new TaskStatusRenderer(); 
       table.getColumnModel().getColumn(1).setCellRenderer(progressRenderer); 
       table.getColumnModel().getColumn(2).setCellRenderer(statusRenderer); 
       table.getColumnModel().getColumn(2).setCellEditor(new TaskStatusEditor()); 

       table.setRowHeight(
           Math.max(getCellRendererHeight(table, 0, 1, progressRenderer), 
           getCellRendererHeight(table, 0, 2, statusRenderer))); 

       JFrame frame = new JFrame("Test"); 
       frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
       frame.setLayout(new BorderLayout()); 
       frame.add(new JScrollPane(table)); 
       frame.pack(); 
       frame.setLocationRelativeTo(null); 
       frame.setVisible(true); 
      } 
     }); 
    } 

    protected int getCellRendererHeight(JTable table, int row, int column, TableCellRenderer renderer) { 
     return renderer.getTableCellRendererComponent(table, table.getValueAt(row, column), true, true, row, column).getPreferredSize().height; 
    } 

    public class ThreadTableModel extends AbstractTableModel { 

     private String[] headers = {"ID", "Progress", "Action"}; 
     private List<Task> tasks; 

     public ThreadTableModel() { 
      tasks = new ArrayList<>(25); 
     } 

     public void add(Task task) { 
      int row = getRowCount(); 
      tasks.add(task); 
      fireTableRowsInserted(row, getRowCount() - 1); 
     } 

     @Override 
     public boolean isCellEditable(int rowIndex, int columnIndex) { 
      return columnIndex == 2 && !tasks.get(rowIndex).isRunning() && !tasks.get(rowIndex).isDone(); 
     } 

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

     @Override 
     public int getColumnCount() { 
      return headers.length; 
     } 

     @Override 
     public Class<?> getColumnClass(int columnIndex) { 
      Class clazz = Object.class; 
      switch (columnIndex) { 
       case 0: 
        clazz = String.class; 
        break; 
       case 1: 
        clazz = Integer.class; 
        break; 
      } 
      return clazz; 
     } 

     @Override 
     public String getColumnName(int column) { 
      return headers[column]; 
     } 

     @Override 
     public Object getValueAt(int rowIndex, int columnIndex) { 
      Task task = tasks.get(rowIndex); 
      Object value = null; 
      switch (columnIndex) { 
       case 0: 
        value = task.getID(); 
        break; 
       case 1: 
        value = task.getProgress(); 
        break; 
       case 2: 
        value = task; 
        break; 
      } 
      return value; 
     } 

     @Override 
     public void setValueAt(Object aValue, int rowIndex, int columnIndex) { 
      System.out.println("setValueAt " + rowIndex + "x" + columnIndex); 
      if (columnIndex == 2) { 
       Task task = tasks.get(rowIndex); 
       if (!task.isRunning() && !task.isDone()) { 
        task.execute(); 
        fireTableCellUpdated(rowIndex, columnIndex); 
       } 
      } 
     } 

     public void updated(Task task) { 
      int row = tasks.indexOf(task); 
      System.out.println("Row updated " + row); 
      fireTableRowsUpdated(row, row); 
     } 
    } 

    public class TaskProgressRenderer extends JProgressBar implements TableCellRenderer { 

     public TaskProgressRenderer() { 
      setOpaque(false); 
     } 

     @Override 
     public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { 
      if (value instanceof Integer) { 
       int progress = (int) value; 
       System.out.println("cellProgress = " + progress); 
       setValue(progress); 
      } 
      if (isSelected) { 
       setBackground(table.getSelectionBackground()); 
       setOpaque(true); 
      } else { 
       setBackground(table.getBackground()); 
       setOpaque(false); 
      } 
      return this; 
     } 

    } 

    public class TaskStatusEditor extends AbstractCellEditor implements TableCellEditor { 

     private JPanel editor; 

     public TaskStatusEditor() { 
      editor = new JPanel(); 
      editor.add(new JButton("Start")); 
     } 

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

     @Override 
     public Object getCellEditorValue() { 
      return null; 
     } 

     @Override 
     public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) { 
      SwingUtilities.invokeLater(new Runnable() { 
       @Override 
       public void run() { 
        stopCellEditing(); 
       } 
      }); 
      return editor; 
     } 

    } 

    public class TaskStatusRenderer extends JPanel implements TableCellRenderer { 

     private JButton start; 
     private JLabel label; 

     public TaskStatusRenderer() { 
      setOpaque(false); 
      start = new JButton("Start"); 
      label = new JLabel(); 
     } 

     @Override 
     public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { 
      removeAll(); 
      if (value instanceof Task) { 
       Task task = (Task) value; 
       if (task.isDone()) { 
        try { 
         label.setText(task.get()); 
        } catch (Exception ex) { 
         label.setText(ex.getMessage()); 
        } 
        add(label); 
       } else if (task.isRunning()) { 
        label.setText("Working"); 
        add(label); 
       } else { 
        add(start); 
       } 
      } 
      if (isSelected) { 
       setBackground(table.getSelectionBackground()); 
       setOpaque(true); 
      } else { 
       setBackground(table.getBackground()); 
       setOpaque(false); 
      } 
      return this; 
     } 

    } 

    public class Task extends SwingWorker<String, Object> { 

     private int id; 
     private String threadResult; 
     private ThreadTableModel tableModel; 
     private boolean running; 

     public Task(int id, ThreadTableModel tableModel) { 
      this.tableModel = tableModel; 
      this.id = id; 
      addPropertyChangeListener(new PropertyChangeListener() { 
       @Override 
       public void propertyChange(PropertyChangeEvent e) { 
        System.out.println(e.getPropertyName()); 
        if ("state".equalsIgnoreCase(e.getPropertyName()) 
            && "DONE".equalsIgnoreCase(e.getNewValue().toString())) { 
         try { 
          threadResult = (String)get(); 
         } catch (Exception ignore) { 
          ignore.printStackTrace(); 
         } 
        } 

        if ("progress".equalsIgnoreCase(e.getPropertyName())) { 
         System.out.println("task.getProgress = " + getProgress()); 
         Task.this.tableModel.updated(Task.this); 
        } 
       } 
      }); 
     } 

     public boolean isRunning() { 
      return running; 
     } 

     public int getID() { 
      return id; 
     } 

     @Override 
     protected String doInBackground() throws Exception { 
      running = true; 
      setProgress(0); 
      int prog = 0; 
      Random rand = new Random(42); 
      while (prog < 100) { 
       prog += Math.abs(rand.nextInt() % 5); 
       Thread.sleep(250); 
       setProgress(Math.min(prog, 100)); 
      } 
      return "42"; 
     } 
    } 
} 
+0

+1,爲更好的方法。 – camickr

+0

+1酷的例子 - 做OP的工作:-) – kleopatra

+0

@MadProgrammer:謝謝!我無法想象我所做的演員也很有趣。這個例子是從JRuby移植過來的;當我寫它時,我甚至沒有眨眼雙倍字符串比較。 – user2580127