34 votes

Comment placer un contrôle dans le JTableHeader d'un JTable ?

Étant donné un JTable avec une colonne de type Boolean.class El Rendu par défaut es un JCheckBox . Il est assez facile de sélectionner des cellules individuelles sur la base d'un critère de sélection. sélection des utilisateurs mais il peut être utile de sélectionner toutes les cases à cocher ou aucune d'entre elles. Ces récent exemples a mentionné l'utilisation JCheckBox dans l'en-tête du tableau, mais l'implémentation était maladroite et peu attrayante. Si je n'ai pas besoin de trier la colonne, comment puis-je placer un contrôle bien conçu dans l'en-tête du tableau ? JTableHeader ?

Addendum : Pour plus de commodité, j'ai ajouté mon sscce comme un réponse mais je serais ravi d'accepter une réponse qui aborde la question de l'égalité des chances. Bien élevé aspect du problème.

23voto

trashgod Points 136305

L'article Comment utiliser les tableaux : Utiliser des moteurs de rendu personnalisés offre TableSorter comme exemple de détection des événements de souris sur un en-tête de colonne. En utilisant une approche similaire, SelectAllHeader extends JToggleButton y implements TableCellRenderer dans l'exemple ci-dessous pour obtenir un effet similaire. A TableModelListener est utilisé pour conditionner le bouton de basculement lorsque toutes les cases à cocher sont dans un état 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();
    }
}

15voto

kleopatra Points 31585

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

Facilité d'utilisation : l'invention d'interactions/éléments d'interface utilisateur est susceptible de dérouter les utilisateurs. Sans ordre particulier :

  • le titre de l'en-tête de colonne est censé décrire le contenu de la colonne, ce DeepL contenu est perdu lorsqu'il est remplacé par une description d'action
  • ce n'est pas immédiatement (pour moi, le plus stupide utilisateur sur terre :-) il est clair que la cellule d'en-tête a la fonction d'un bouton de basculement. Un clic accidentel sur ce bouton entraîne la perte de tout le contenu antérieur de cette colonne.

Donc, même si l'analyse de l'interaction aboutit à une conclusion claire : nous en avons besoin ou nous le voulons,

  • action uniquement en complément du contenu
  • utilisez un widget plus clair (par exemple, une case à cocher tri-state tout-dé-/sélectionné, contenu mixte). De plus, la dé-sélection et la sélection doivent toutes deux être possibles à partir d'un contenu mixte. À la réflexion, une case à cocher n'est probablement pas le meilleur choix non plus, mais je n'ai pas creusé davantage.
  • minimiser la possibilité de changer accidentellement (juste pour moi :-) l'état du bulk, (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 normale".

Aspects techniques

  • TableHeader n'est pas conçu pour les composants "live". Ce que l'on veut doit être contrôlé par nous-mêmes
  • des exemples existent (par exemple, la grille JIDE permet d'ajouter des composants)
  • La manipulation de l'en-tête a tendance à être peu attrayante parce qu'il n'est pas trivial de changer le moteur de rendu tout en conservant l'apparence fournie par LAF.

11voto

luca Points 61

enter image description here

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 Bouton de menu dans le 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;
    }
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X