java 如何在 JTable 的 JTableHeader 中放置控件?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/7137786/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How can I put a control in the JTableHeader of a JTable?
提问by trashgod
Given a JTable
with a column of type Boolean.class
, the default rendereris a JCheckBox
. It's easy enough to select individual cells based on a user selection, but it may be convenient to select all or none of the check boxes, too. These recentexamplesmentioned using JCheckBox
in the table header, but the implementation was awkward and unappealing. If I don't need to sort the column, how can I put a well-behaved control in the JTableHeader
?
给定 aJTable
列的类型Boolean.class
,默认渲染器是 a JCheckBox
。根据用户选择选择单个单元格很容易,但也可以方便地选择所有复选框或不选择任何复选框。这些最近的例子JCheckBox
在表头中提到了 using ,但实现起来很笨拙且没有吸引力。如果不需要对列进行排序,如何在JTableHeader
.
Addendum: For convenience, I've added my sscceas an answer, but I'd be pleased to accept an answer that addresses the well-behavedaspect of the problem.
采纳答案by kleopatra
There are two parts of the problem (as I see it :-)
问题有两个部分(如我所见:-)
Usability: inventing UI-interaction/elements is prone to confusing users. In no particular order:
可用性:发明 UI 交互/元素很容易让用户感到困惑。没有特定的顺序:
- the column header title is meant to describe the content of the column, that content description is lost when in replacing it with an action description
- it's not immediately (for me, the dumbest useron earth :-) clear that the header cell has the function of a toggle button. Accidentally clicking it will loose all earlier content state in that column
- 列标题标题是为了描述列的内容,当用操作描述替换它时,内容描述会丢失
- 不是立即(对我来说,地球上最愚蠢的用户:-) 清楚标题单元格具有切换按钮的功能。不小心点击它会丢失该列中所有早期的内容状态
So even if interaction analysis comes out with a clear we-do-need/want-it,
因此,即使交互分析得出一个明确的我们需要/想要它,
- action only in-addition to the content
- use a widget that's clearer (e.g. a tri-state checkbox all-de-/selected, mixed content). Also, de-/selecting must both be possible from mixed content. On second thought, a checkbox probably isn't the best choice either, didn't dig further
- minimize the possibility to accidentally (just for me :-) change bulk state, (e.g. by a clear visual separation of an active area - the checkbox icon) from the "normal header" region.
- 仅在内容之外采取行动
- 使用更清晰的小部件(例如,三态复选框全部取消/选择、混合内容)。此外,必须可以从混合内容中取消/选择。再想一想,复选框可能也不是最好的选择,没有进一步挖掘
- 最大限度地减少意外(只为我 :-) 更改批量状态的可能性,(例如,通过活动区域的清晰视觉分离 - 复选框图标)与“正常标题”区域。
Technical aspects
技术方面
- TableHeader is not designed for "live" components. Whatever is wanted has to be controlled by ourselves
- examples are around (e.g. JIDE grid supports adding components)
- fiddling with header tends to look unattractive because it's not trivial to change the renderer and at the same time keep the LAF provided appearance
- TableHeader 不是为“实时”组件设计的。想要什么就由我们自己掌控
- 例子是周围(例如 JIDE 网格支持添加组件)
- 摆弄标题往往看起来没有吸引力,因为更改渲染器并同时保持 LAF 提供的外观并非易事
回答by trashgod
The article How to Use Tables: Using Custom Renderersoffers TableSorter
as an example of how to detect mouse events on a column header. Using a similar approach, SelectAllHeader extends JToggleButton
and implements TableCellRenderer
in the example below to achieve a similar effect. A TableModelListener
is used to condition the toggle button when all the check boxes are in a uniform state.
文章如何使用表格:使用自定义渲染器提供TableSorter
了如何检测列标题上的鼠标事件的示例。使用类似的方法,SelectAllHeader extends JToggleButton
并implements TableCellRenderer
在下面的例子中实现了类似的效果。TableModelListener
当所有复选框都处于统一状态时,使用A来调节切换按钮。
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();
}
}
回答by luca
Use a custom TableCellRenderer:
使用自定义 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);
}
}
}
Please note that components with popupmenu (e.g. JComboBox or JMenu) don't work well. See: JComboBox fails to expand in JTable TableHeader). But you can use a MenuButtonin the TableHeader:
请注意带有弹出菜单的组件(例如 JComboBox 或 JMenu)不能正常工作。请参阅:JComboBox 无法在 JTable TableHeader 中展开)。但是您可以在TableHeader中使用MenuButton:
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;
}
}