2011-08-21 14 views
28

Biorąc pod uwagę JTable z kolumną typu Boolean.class, default renderer jest JCheckBox. Wybranie pojedynczych komórek na podstawie numeru user selection jest dość łatwe, ale może też być wygodne zaznaczenie wszystkich lub żadnych pól wyboru. Te recentexamples wspomniano przy użyciu JCheckBox w nagłówku tabeli, ale implementacja była niezręczna i nieprzyjemna. Jeśli nie potrzebuję sortować kolumny, jak mogę umieścić dobrze zachowaną kontrolę w JTableHeader?Jak mogę umieścić kontrolkę w JTableHeader na JTable?

Uzupełnienie: Dla wygody Dodałem mój sscce jako answer, ale byłbym zadowolony, aby zaakceptować odpowiedź, która rozwiązuje grzeczne aspekt problemu.

+0

hmm ... co dokładnie jest kwestia, w szczególności, co masz na myśli przez grzeczne? Wszyscy wiemy, że w nagłówku nie ma obsługi "żywych" komponentów, wszystko musi być zrobione przez nas samych :-) Jeśli chodzi o użycie przycisku przełączającego, nie wiemy, czy użytkownicy rozumieją, co robi i kiedy - przypadkowo klikną komórka utraci wszystkie aktualne dane z kolumny – kleopatra

+0

@kleopatra: Dobra uwaga; Wnioskuję, że należy w pierwszej kolejności kwestionować potrzebę takiego urządzenia. – trashgod

Odpowiedz

14

Istnieją dwie części tego problemu (jak ja to widzę :-)

użyteczność: wynalezienie UI-interakcji/elementów jest podatna na mylące użytkowników. W przypadkowej kolejności:

  • tytuł nagłówka kolumna ma opisywać zawartość kolumny, że opis zawartość jest tracona podczas zastępując je wraz z opisem działania
  • to nie jest natychmiast (dla mnie, najgłupszy użytkownik na ziemi :-) wyczyść, że komórka nagłówka ma funkcję przełącznika. Przypadkowo kliknięcie spowoduje utratę wszystkich wcześniejszego stanu zawartości w tej kolumnie

Więc nawet jeśli analiza interakcji wychodzi z wyraźną my-do-potrzeby/chce-go,

  • działania tylko w-uzupełnieniu do zawartość korzystająca z widżetu jest bardziej przejrzysta (np. pole wyboru z trzema stanami all-de-/selected, treść mieszana). Ponadto zdezodowanie/wybór musi być możliwe zarówno z treści mieszanych. Po drugie, checkbox prawdopodobnie też nie jest najlepszym wyborem, nie wykopałem dalej, minimalizując możliwość przypadkowego (tylko dla mnie :-) zmiany stanu masy, (np. Przez wyraźne wizualne oddzielenie aktywnego obszaru - ikona pola wyboru) z regionu "normalnego nagłówka".

aspekty techniczne

  • TableHeader nie jest przeznaczony dla "na żywo" komponentów. Niezależnie od tego, co chcemy, musimy samodzielnie kontrolować:
  • przykłady znajdują się w okolicy (np.Jide siatki obsługuje komponentów dodawanie)
  • błahy z nagłówka zazwyczaj wyglądają nieatrakcyjne, ponieważ nie jest trywialne, aby zmienić renderujący i jednocześnie zachować LAF warunkiem wygląd
+1

+1 i czek na wprowadzenie całego podejścia w kontekście. – trashgod

23

Artykuł How to Use Tables: Using Custom Renderers oferuje TableSorter jako przykład wykrywania zdarzeń myszy w nagłówku kolumny. Używając podobnego podejścia, w poniższym przykładzie, aby uzyskać podobny efekt. A TableModelListener służy do warunkowania przycisku przełączania, gdy wszystkie pola wyboru są w jednolitym stanie.

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) wieje w Metal b) nie powinien usuwać/wybierać po kliknięciu w obszarze zmiany rozmiaru – kleopatra

+0

@kleopatra: Tego rodzaju rzeczy chciałem wiedzieć; rozważ proszę, czy jest to odpowiedź. Podczas dalszych testów "Metal" zdaje się odrzucać strzałę pochodzącą z recyklingu; Powinienem zrobić własny. – trashgod

+0

jeśli wstawisz nowy wiersz, wywołaj zdarzenie fireTableRowsInserted, spowoduje to wyjątek dla elementu itemStateChanged z niepoprawnym błędem zasięgu, czy ktoś to napotkał? więc tutaj tabela.getRowCount() nie jest poprawna, ponieważ tabela nie jest wyświetlana ... –

10

enter image description here

użyć niestandardowego TableCellRenderer:

// 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); 
     } 
    } 
} 

Należy pamiętać, że komponenty z menu podręcznym (np. JComboBox lub JMenu) nie działają dobrze. Patrz: JComboBox fails to expand in JTable TableHeader). Ale można użyć MenuButton w 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

Gdzie mogę posłuchać powyższych czynności dla JButton i JToggleButton? Jako drugie, dlaczego usuwasz edytor po zwolnieniu myszy? To sprawia, że ​​edytor znika po zdarzeniu kliknięcia. –

+0

To pytanie "ResourceManager.ARROW_BOTTOM" podając błąd, w jaki sposób mogę znaleźć klasę ResourceManager? w jakim jar @luca – JAVA

+0

To jest niestandardowa klasa, której użyłem dla ikon. Zamiast tego możesz przekazać własną ikonę – luca

Powiązane problemy