2011-08-21 35 views
28

Cho một số JTable với một cột thuộc loại Boolean.class, default rendererJCheckBox. Thật dễ dàng để chọn từng ô dựa trên user selection, nhưng cũng có thể thuận tiện khi chọn tất cả hoặc không chọn hộp kiểm nào. Các số recentexamples được đề cập sử dụng JCheckBox trong tiêu đề bảng, nhưng việc triển khai rất khó xử và không hấp dẫn. Nếu tôi không cần phải sắp xếp cột, làm thế nào tôi có thể đặt một kiểm soát tốt cư xử trong JTableHeader?Làm thế nào tôi có thể đặt một điều khiển trong JTableHeader của một JTable?

Phụ Lục: Để thuận tiện, tôi đã thêm sscce như một answer, nhưng tôi muốn được hài lòng để chấp nhận một câu trả lời mà đề cập đến well-behaved khía cạnh của vấn đề của tôi.

+0

hmm ... những gì chính xác được câu hỏi, đặc biệt là những gì bạn có nghĩa là bởi well-behaved? Chúng ta đều biết rằng không có hỗ trợ cho các thành phần "sống" trong tiêu đề, tất cả các nhu cầu phải được thực hiện bởi chính chúng ta :-) Như sử dụng một nút chuyển đổi, không biết nếu người dùng hiểu nó làm gì và khi nào - vô tình nhấp vào ô sẽ mất tất cả dữ liệu hiện tại trong cột – kleopatra

+0

@ kleopatra: Điểm tốt; Tôi suy ra rằng người ta nên đặt câu hỏi về sự cần thiết cho một thiết bị như vậy ngay từ đầu. – trashgod

Trả lời

14

Có hai phần của vấn đề (như tôi nhìn thấy nó :-)

Tính khả dụng: phát minh ra giao diện người dùng tương tác/các yếu tố dễ bị người dùng nhầm lẫn. Không theo thứ tự đặc biệt:

  • tiêu đề tiêu đề cột có nghĩa là để mô tả nội dung của cột, mà mô tả nội dung bị mất trong khi thay thế nó bằng một mô tả hành động
  • nó không phải ngay lập tức (đối với tôi, dumbest người dùng trên trái đất :-) rõ ràng rằng ô tiêu đề có chức năng của nút chuyển đổi. Vô tình nhấp vào đó sẽ mất tất cả trạng thái nội dung trước đó trong cột đó

Vì vậy, ngay cả khi phân tích tương tác đi ra với một rõ ràng chúng ta-do-cần/muốn-it,

  • hành động duy nhất trong Ngoài nội dung
  • sử dụng tiện ích con rõ ràng hơn (ví dụ: hộp kiểm ba trạng thái tất cả-de-/được chọn, nội dung hỗn hợp). Ngoài ra, việc chọn/chọn phải cả hai có thể từ nội dung hỗn hợp. Suy nghĩ thứ hai, một hộp kiểm có lẽ không phải là lựa chọn tốt nhất, hoặc không khai thác thêm
  • giảm thiểu khả năng vô tình (chỉ cho tôi :-) thay đổi trạng thái hàng loạt, (ví dụ: bằng cách tách hình ảnh rõ ràng của khu vực hoạt động - biểu tượng hộp kiểm) từ khu vực "tiêu đề bình thường".

khía cạnh kỹ thuật

  • TableHeader không được thiết kế cho các thành phần "sống". Bất cứ điều gì là mong muốn phải được kiểm soát bởi chính chúng ta
  • các ví dụ xung quanh (ví dụ:Jide lưới hỗ trợ các thành phần bổ sung)
  • loay hoay với tiêu đề có xu hướng trông không hấp dẫn vì nó không phải là tầm thường để thay đổi các renderer và đồng thời giữ cho LAF cung cấp xuất hiện
+1

+1 và kiểm tra để đưa toàn bộ cách tiếp cận trong ngữ cảnh. – trashgod

23

Bài viết How to Use Tables: Using Custom Renderers cung cấp TableSorter làm ví dụ về cách phát hiện sự kiện chuột trên tiêu đề cột. Sử dụng cách tiếp cận tương tự, SelectAllHeader extends JToggleButtonimplements TableCellRenderer trong ví dụ bên dưới để đạt được hiệu quả tương tự. A TableModelListener được sử dụng để điều chỉnh nút bật tắt khi tất cả các hộp kiểm ở trạng thái đồng nhất.

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) thổi trong Kim loại b) không nên bỏ/chọn khi được nhấp vào khu vực thay đổi kích thước – kleopatra

+0

@ kleopatra: Đây là loại điều tôi muốn biết; hãy xem xét làm cho nó một câu trả lời. Khi thử nghiệm thêm, 'Kim loại' dường như từ chối mũi tên tái chế; Tôi nên tự làm. – trashgod

+0

nếu bạn chèn một hàng mới, kích hoạt sự kiện fireTableRowsInserted, nó sẽ ném ngoại lệ cho itemStateChanged với lỗi phạm vi không hợp lệ, có ai gặp điều này không? vì vậy ở đây table.getRowCount() là không chính xác, bởi vì bảng không hiển thị ở đó .... –

10

enter image description here

Sử dụng một TableCellRenderer tùy chỉnh:

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

Xin lưu ý rằng các thành phần có popupmenu (ví dụ: JComboBox hoặc JMenu) không hoạt động tốt. Xem: JComboBox fails to expand in JTable TableHeader). Nhưng bạn có thể sử dụng một MenuButton trong 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

Tôi có thể nghe các hành động cho JButton và JToggleButton ở trên? Là thứ hai, tại sao bạn xóa trình chỉnh sửa sau khi chuột được phát hành? Điều đó làm cho trình chỉnh sửa biến mất sau sự kiện nhấp chuột. –

+0

Điều này "ResourceManager.ARROW_BOTTOM" điều cho lỗi, làm thế nào tôi có thể tìm thấy lớp ResourceManager? trong cái lọ @luca – JAVA

+0

Đó là một lớp tùy chỉnh mà tôi đã sử dụng cho các biểu tượng. Thay vào đó, bạn có thể chuyển biểu tượng của riêng mình – luca

Các vấn đề liên quan