Comment puis-je mettre un contrôle dans le JTableHeader d'un JTable?

Donné un JTable avec une colonne de type Boolean.class, l'moteur de rendu par défaut est un JCheckBox. Il est assez facile de sélectionner des cellules individuelles en fonction d'un sélection de l'utilisateur, mais il peut être pratique de sélectionner Toutes ou aucune des cases à cocher, aussi. Ces récents les exemples mentionnés en utilisant JCheckBox dans l'en-tête de la table, mais l'implémentation était maladroite et peu attrayante. Si je n'ai pas besoin de trier la colonne, Comment puis-je mettre un contrôle bien élevé dans l'JTableHeader?

Addendum: pour plus de commodité, j'ai ajouté mon sscce comme réponse , mais je serais heureux d'accepter une réponse qui aborde l'aspect du problème.

29
demandé sur Community 2011-08-21 14:49:18

3 réponses

Il y a deux parties du problème (comme je le vois :-)

Facilité d'utilisation: l'invention de L'interface utilisateur-interaction / éléments est sujette à la confusion des utilisateurs. Sans ordre particulier:

  • le titre de l'en-tête de colonne est destiné à décrire le contenu de la colonne, cette description de contenu est perdue lorsqu'elle est remplacée par une description d'action
  • ce n'est pas immédiatement (pour moi, l'utilisateur le plus stupide sur terre :-) clair que la cellule d'en-tête a la fonction d'un bouton à bascule. Accidentellement en cliquant dessus, vous perdrez tout l'état de contenu antérieur dans cette colonne

Donc, même si l'analyse de l'interaction sort avec une claire nous-ne-besoin/envie-il,

  • action uniquement en plus du contenu
  • utilisez un widget plus clair (par exemple, une case à cocher tri-état tout-de-/sélectionné, contenu mixte). En outre, la désélectionnation / sélection doit être possible à partir d'un contenu mixte. À bien y penser, une case à cocher n'est probablement pas le meilleur choix non plus, n'a pas creusé plus loin
  • minimiser la possibilité pour accidentellement (juste pour moi: -) changer l'état en bloc, (par exemple par une séparation visuelle claire d'une zone active - l'icône de la case à cocher) de la région "en-tête normal".

Aspects Techniques

  • TableHeader n'est pas conçu pour les composants" live". Tout ce qui est voulu doit être contrôlé par nous-mêmes
  • les exemples sont autour (par exemple, Jide grid prend en charge l'ajout de composants)
  • jouer avec l'en-tête a tendance à sembler peu attrayant car il n'est pas trivial de changer le moteur de rendu et en même temps garder le LAF fourni apparence
14
répondu kleopatra 2016-03-07 22:15:57

L'article Comment utiliser les Tables: utilisation de rendus personnalisés offres TableSorter comme exemple de détection d'événements de souris sur un en-tête de colonne. En utilisant une approche similaire, SelectAllHeader extends JToggleButton et implements TableCellRenderer dans l'exemple ci-dessous pour obtenir un effet similaire. Un TableModelListener est utilisé pour conditionner le bouton bascule lorsque toutes les cases sont dans un état commun.

entrez la description de l'image ici

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();
    }
}
23
répondu trashgod 2011-08-23 10:36:53

entrez la description de l'image ici

Utilisez un TableCellRenderer personnalisé:

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

Veuillez noter que les composants avec popupmenu (par exemple JComboBox ou JMenu) ne fonctionnent pas bien. Voir: JComboBox ne parvient pas à se développer dans JTable TableHeader). Mais vous pouvez utiliser un MenuButton dans le TableHeader:

entrez la description de l'image ici

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;
    }
}
10
répondu luca 2017-05-23 11:54:09