2011-08-21 7 views
28

Dato un JTable con una colonna di tipo Boolean.class, lo default renderer è un JCheckBox. È abbastanza facile selezionare singole celle in base a un user selection, ma potrebbe essere conveniente selezionare tutte o nessuna le caselle di controllo. Questi recentexamples menzionati utilizzando JCheckBox nell'intestazione della tabella, ma l'implementazione è stata scomoda e poco attraente. Se non ho bisogno di ordinare la colonna, come posso mettere un controllo ben educato nel JTableHeader?Come posso inserire un controllo in JTableHeader di una tabella J?

Addendum: Per comodità, ho aggiunto il mio sscce come answer, ma sarei lieto di accettare una risposta che affronta il ben educati aspetto del problema.

+0

hmm ... Che cosa è esattamente la questione, in particolare, che cosa si intende per ben educati? Sappiamo tutti che non c'è supporto per i componenti "live" nell'intestazione, tutto da fare da soli :-) Per quanto riguarda l'utilizzo di un pulsante di attivazione, non so se gli utenti capiscono cosa fa e quando - accidentalmente facendo clic sul pulsante la cella perde tutti i dati correnti nella colonna – kleopatra

+0

@kleopatra: buon punto; Suppongo che uno dovrebbe mettere in discussione la necessità di un tale dispositivo in primo luogo. – trashgod

risposta

14

Ci sono due parti del problema (secondo me :-)

usabilità: inventare UI-interazione/elementi è incline a confondere gli utenti. In nessun ordine particolare:

  • il titolo intestazione di colonna ha lo scopo di descrivere il contenuto della colonna, che la descrizione dei contenuti si perde quando in sua sostituzione con una descrizione azione
  • non è immediatamente (per me, la più stupida utente sulla terra :-) chiaro che la cella di intestazione ha la funzione di un pulsante di attivazione/disattivazione. Accidentalmente clic su di esso perderà tutto lo stato contenuto in precedenza in quella colonna

Quindi, anche se l'analisi dell'interazione esce con un chiaro che-do-bisogno/voglia-it,

  • azione solo in aggiunta a il contenuto
  • utilizza un widget più chiaro (ad esempio una casella di controllo a tre stati tutto-de-/selezionato, contenuto misto). Inoltre, de-/selezione deve essere possibile entrambi dal contenuto misto. A pensarci bene, probabilmente una casella di controllo non è la scelta migliore, non scavare ulteriormente
  • minimizzare la possibilità di accidentalmente (solo per me :-) cambiare lo stato di massa, (ad esempio con una chiara separazione visiva di un'area attiva - l'icona della casella di controllo) dall'area "intestazione normale".

Aspetti tecnici

  • TableHeader non è progettato per i componenti "live". Qualunque cosa si desideri deve essere controllata da noi stessi
  • esempi sono in giro (ad es.griglia JIDE supporta aggiunta di componenti)
  • giocherellare con un colpo di testa tende a guardare poco attraente, perché non è banale per cambiare il renderer e allo stesso tempo mantenere il LAF fornito aspetto
+1

+1 e il controllo per mettere l'intero approccio nel contesto. – trashgod

23

L'articolo How to Use Tables: Using Custom Renderers offre TableSorter come un esempio di come rilevare gli eventi del mouse sull'intestazione di una colonna. Utilizzando un approccio simile, SelectAllHeader extends JToggleButton e implements TableCellRenderer nell'esempio seguente per ottenere un effetto simile. Un TableModelListener viene utilizzato per condizionare il pulsante di attivazione quando tutte le caselle di controllo si trovano in uno stato uniforme.

enter image description here

import java.awt.*; 
import java.awt.event.*; 
import javax.swing.*; 
import javax.swing.event.TableModelEvent; 
import javax.swing.event.TableModelListener; 
import javax.swing.table.*; 

/** 
* @see http://stackoverflow.com/questions/7137786 
* @see http://stackoverflow.com/questions/7092219 
* @see http://stackoverflow.com/questions/7093213 
*/ 
public class SelectAllHeaderTest { 

    private static final int BOOLEAN_COL = 2; 
    private static final Object colNames[] = {"Column 1", "Column 2", ""}; 
    private DefaultTableModel model = new DefaultTableModel(null, colNames) { 

     @Override 
     public Class<?> getColumnClass(int columnIndex) { 
      if (columnIndex == BOOLEAN_COL) { 
       return Boolean.class; 
      } else { 
       return String.class; 
      } 
     } 
    }; 
    private JTable table = new JTable(model); 

    public void create() { 
     for (int x = 1; x < 6; x++) { 
      model.addRow(new Object[]{ 
        "Row " + x + ", Col 1", "Row " + x + ", Col 2", false 
       }); 
     } 
     table.setAutoCreateRowSorter(true); 
     table.setPreferredScrollableViewportSize(new Dimension(320, 160)); 
     TableColumn tc = table.getColumnModel().getColumn(BOOLEAN_COL); 
     tc.setHeaderRenderer(new SelectAllHeader(table, BOOLEAN_COL)); 
     JFrame f = new JFrame(); 
     f.add(new JScrollPane(table)); 
     f.pack(); 
     f.setLocationRelativeTo(null); 
     f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
     f.setVisible(true); 
    } 

    public static void main(String[] args) { 
     SwingUtilities.invokeLater(new Runnable() { 

      @Override 
      public void run() { 
       new SelectAllHeaderTest().create(); 
      } 
     }); 
    } 
} 

/** 
* A TableCellRenderer that selects all or none of a Boolean column. 
* 
* @param targetColumn the Boolean column to manage 
*/ 
class SelectAllHeader extends JToggleButton implements TableCellRenderer { 

    private static final String ALL = "✓ Select all"; 
    private static final String NONE = "✓ Select none"; 
    private JTable table; 
    private TableModel tableModel; 
    private JTableHeader header; 
    private TableColumnModel tcm; 
    private int targetColumn; 
    private int viewColumn; 

    public SelectAllHeader(JTable table, int targetColumn) { 
     super(ALL); 
     this.table = table; 
     this.tableModel = table.getModel(); 
     if (tableModel.getColumnClass(targetColumn) != Boolean.class) { 
      throw new IllegalArgumentException("Boolean column required."); 
     } 
     this.targetColumn = targetColumn; 
     this.header = table.getTableHeader(); 
     this.tcm = table.getColumnModel(); 
     this.applyUI(); 
     this.addItemListener(new ItemHandler()); 
     header.addMouseListener(new MouseHandler()); 
     tableModel.addTableModelListener(new ModelHandler()); 
    } 

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

    private class ItemHandler implements ItemListener { 

     @Override 
     public void itemStateChanged(ItemEvent e) { 
      boolean state = e.getStateChange() == ItemEvent.SELECTED; 
      setText((state) ? NONE : ALL); 
      for (int r = 0; r < table.getRowCount(); r++) { 
       table.setValueAt(state, r, viewColumn); 
      } 
     } 
    } 

    @Override 
    public void updateUI() { 
     super.updateUI(); 
     applyUI(); 
    } 

    private void applyUI() { 
     this.setFont(UIManager.getFont("TableHeader.font")); 
     this.setBorder(UIManager.getBorder("TableHeader.cellBorder")); 
     this.setBackground(UIManager.getColor("TableHeader.background")); 
     this.setForeground(UIManager.getColor("TableHeader.foreground")); 
    } 

    private class MouseHandler extends MouseAdapter { 

     @Override 
     public void mouseClicked(MouseEvent e) { 
      viewColumn = header.columnAtPoint(e.getPoint()); 
      int modelColumn = tcm.getColumn(viewColumn).getModelIndex(); 
      if (modelColumn == targetColumn) { 
       doClick(); 
      } 
     } 
    } 

    private class ModelHandler implements TableModelListener { 

     @Override 
     public void tableChanged(TableModelEvent e) { 
      if (needsToggle()) { 
       doClick(); 
       header.repaint(); 
      } 
     } 
    } 

    // Return true if this toggle needs to match the model. 
    private boolean needsToggle() { 
     boolean allTrue = true; 
     boolean allFalse = true; 
     for (int r = 0; r < tableModel.getRowCount(); r++) { 
      boolean b = (Boolean) tableModel.getValueAt(r, targetColumn); 
      allTrue &= b; 
      allFalse &= !b; 
     } 
     return allTrue && !isSelected() || allFalse && isSelected(); 
    } 
} 
+1

a) soffia in Metallo b) non dovrebbe de/selezionare quando cliccato nell'area di ridimensionamento – kleopatra

+0

@kleopatra: questo è il tipo di cosa che volevo sapere; per favore considera di renderlo una risposta. Su ulteriori test, 'Metal' sembra rifiutare la freccia riciclata; Dovrei creare il mio. – trashgod

+0

se si inserisce una nuova riga, si attiva l'evento fireTableRowsInserted, si genera l'eccezione per itemStateChanged con errore di intervallo non valido, qualcuno ha incontrato questo? quindi qui table.getRowCount() non è corretto, perché la tabella non viene visualizzata lì .... –

10

enter image description here

Utilizzare un TableCellRenderer personalizzato:

// column 1 
    col = table.getColumnModel().getColumn(1); 
    col.setHeaderRenderer(new EditableHeaderRenderer(new JButton("Button"))); 
    // column 2  
    col = table.getColumnModel().getColumn(2); 
    col.setHeaderRenderer(new EditableHeaderRenderer(new JToggleButton("Toggle"))); 
    // column 3 
    col = table.getColumnModel().getColumn(3); 
    col.setHeaderRenderer(new EditableHeaderRenderer(new JCheckBox("CheckBox"))); 



class EditableHeaderRenderer implements TableCellRenderer { 

    private JTable table = null; 
    private MouseEventReposter reporter = null; 
    private JComponent editor; 

    EditableHeaderRenderer(JComponent editor) { 
     this.editor = editor; 
     this.editor.setBorder(UIManager.getBorder("TableHeader.cellBorder")); 
    } 

    @Override 
    public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int col) { 
     if (table != null && this.table != table) { 
      this.table = table; 
      final JTableHeader header = table.getTableHeader(); 
      if (header != null) { 
       this.editor.setForeground(header.getForeground()); 
       this.editor.setBackground(header.getBackground()); 
       this.editor.setFont(header.getFont()); 
       reporter = new MouseEventReposter(header, col, this.editor); 
       header.addMouseListener(reporter); 
      } 
     } 

     if (reporter != null) reporter.setColumn(col); 

     return this.editor; 
    } 

    static public class MouseEventReposter extends MouseAdapter { 

     private Component dispatchComponent; 
     private JTableHeader header; 
     private int column = -1; 
     private Component editor; 

     public MouseEventReposter(JTableHeader header, int column, Component editor) { 
      this.header = header; 
      this.column = column; 
      this.editor = editor; 
     } 

     public void setColumn(int column) { 
      this.column = column; 
     } 

     private void setDispatchComponent(MouseEvent e) { 
      int col = header.getTable().columnAtPoint(e.getPoint()); 
      if (col != column || col == -1) return; 

      Point p = e.getPoint(); 
      Point p2 = SwingUtilities.convertPoint(header, p, editor); 
      dispatchComponent = SwingUtilities.getDeepestComponentAt(editor, p2.x, p2.y); 
     } 

     private boolean repostEvent(MouseEvent e) { 
      if (dispatchComponent == null) { 
       return false; 
      } 
      MouseEvent e2 = SwingUtilities.convertMouseEvent(header, e, dispatchComponent); 
      dispatchComponent.dispatchEvent(e2); 
      return true; 
     } 

     @Override 
     public void mousePressed(MouseEvent e) { 
      if (header.getResizingColumn() == null) { 
       Point p = e.getPoint(); 

       int col = header.getTable().columnAtPoint(p); 
       if (col != column || col == -1) return; 

       int index = header.getColumnModel().getColumnIndexAtX(p.x); 
       if (index == -1) return; 

       editor.setBounds(header.getHeaderRect(index)); 
       header.add(editor); 
       editor.validate(); 
       setDispatchComponent(e); 
       repostEvent(e); 
      } 
     } 

     @Override 
     public void mouseReleased(MouseEvent e) { 
      repostEvent(e); 
      dispatchComponent = null; 
      header.remove(editor); 
     } 
    } 
} 

si prega di notare che i componenti con PopupMenu (ad esempio JComboBox o JMenu) non funzionano bene. Vedi: JComboBox fails to expand in JTable TableHeader). Ma è possibile utilizzare un MenuButton nel TableHeader:

enter image description here

class MenuButtonTableHeaderRenderer extends JPanel implements TableCellRenderer { 

    private int  column = -1; 
    private JTable table = null; 
    private MenuButton b; 

    MenuButtonTableHeaderRenderer(String name, JPopupMenu menu) { 
     super(new BorderLayout()); 
     b = new MenuButton(ResourceManager.ARROW_BOTTOM, menu); 
     b.setBorder(BorderFactory.createEmptyBorder(1,1,1,1)); 
     JLabel l = new JLabel(name); 
     l.setFont(l.getFont().deriveFont(Font.PLAIN)); 
     l.setBorder(BorderFactory.createEmptyBorder(1,5,1,1)); 
     add(b, BorderLayout.WEST); 
     add(l, BorderLayout.CENTER); 
     setBorder(UIManager.getBorder("TableHeader.cellBorder")); 
    } 

    @Override 
    public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int col) { 

     if (table != null && this.table != table) { 
      this.table = table; 
      final JTableHeader header = table.getTableHeader(); 
      if (header != null) { 
       setForeground(header.getForeground()); 
       setBackground(header.getBackground()); 
       setFont(header.getFont()); 

       header.addMouseListener(new MouseAdapter() { 

        @Override 
        public void mouseClicked(MouseEvent e) { 
         int col = header.getTable().columnAtPoint(e.getPoint()); 
         if (col != column || col == -1) return; 

         int index = header.getColumnModel().getColumnIndexAtX(e.getPoint().x); 
         if (index == -1) return; 

         setBounds(header.getHeaderRect(index)); 
         header.add(MenuButtonTableHeaderRenderer.this); 
         validate(); 

         b.doClick(); 

         header.remove(MenuButtonTableHeaderRenderer.this); 

         header.repaint(); 
        } 
       }); 
      } 
     } 
     column = col; 
     return this; 
    } 
} 
+0

Dove posso ascoltare le azioni per JButton e JToggleButton sopra? Come secondo, perché rimuovi l'editor dopo il rilascio del mouse? Questo fa sparire l'editor dopo l'evento click. –

+0

Questa cosa "ResourceManager.ARROW_BOTTOM" dando errore, come ho potuto trovare la classe ResourceManager? in cosa jar @luca – JAVA

+0

Questa è una classe personalizzata che ho usato per le icone. Puoi invece passare la tua icona – luca

Problemi correlati