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.
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
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.
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();
}
}
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:
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;
}
}