使用JCheckBox作为JTable列中的编辑器,我想忽略TableCell中CheckBox左右两侧的鼠标点击。
我在2011年的Oracle论坛上找到了一个讨论,但问题没有在那里得到解决:https://community.oracle.com/thread/2183210
这是我到目前为止所实现的黑客攻击,有趣的部分始于class CheckBoxEditor
:
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.ComponentEvent;
import java.awt.event.MouseEvent;
import java.util.EventObject;
import javax.swing.DefaultCellEditor;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableColumn;
/**
* Trying to set the Checkbox only if clicked directly on the box of the CheckBox. And ignore clicks on the
* remaining space of the TableCell.
*
* @author bobndrew
*/
public class JustCheckOnCheckboxTable extends JPanel
{
private static final int CHECK_COL = 1;
private static final Object[][] DATA = { { "One", Boolean.TRUE }, { "Two", Boolean.FALSE },
{ "Three", Boolean.TRUE }, { "Four", Boolean.FALSE }, { "Five", Boolean.TRUE },
{ "Six", Boolean.FALSE }, { "Seven", Boolean.TRUE }, { "Eight", Boolean.FALSE },
{ "Nine", Boolean.TRUE }, { "Ten", Boolean.FALSE } };
private static final String[] COLUMNS = { "Number", "CheckBox" };
private final DataModel dataModel = new DataModel( DATA, COLUMNS );
private final JTable table = new JTable( dataModel );
public JustCheckOnCheckboxTable()
{
super( new BorderLayout() );
this.add( new JScrollPane( table ) );
table.setRowHeight( table.getRowHeight() * 2 );
table.setPreferredScrollableViewportSize( new Dimension( 250, 400 ) );
TableColumn checkboxColumn = table.getColumnModel().getColumn( 1 );
checkboxColumn.setCellEditor( new CheckBoxEditor() );
}
private class DataModel extends DefaultTableModel
{
public DataModel( Object[][] data, Object[] columnNames )
{
super( data, columnNames );
}
@Override
public Class<?> getColumnClass( int columnIndex )
{
if ( columnIndex == 1 )
{
return getValueAt( 0, CHECK_COL ).getClass();
}
return super.getColumnClass( columnIndex );
}
}
class CheckBoxEditor extends DefaultCellEditor
{
private final JCheckBox checkBox;
public CheckBoxEditor()
{
super( new JCheckBox() );
checkBox = (JCheckBox) getComponent();
checkBox.setHorizontalAlignment( JCheckBox.CENTER );
System.out.println( "the checkbox has no size: " + checkBox.getSize() );
}
@Override
public boolean shouldSelectCell( final EventObject anEvent )
{
System.out.println( "\nthe checkbox fills the TableCell: " + checkBox.getSize() );
//Throws NullPointerException: System.out.println( checkBox.getIcon().getIconWidth() );
System.out.println( "always JTable :-( " + anEvent.getSource() );
MouseEvent ev =
SwingUtilities.convertMouseEvent( ((ComponentEvent) anEvent).getComponent(), (MouseEvent) anEvent,
getComponent() );
System.out.println( "Position clicked in TableCell: " + ev.getPoint() );
System.out.println( "always JCheckBox :-( " + getComponent().getComponentAt( ev.getPoint() ) );
Point middleOfTableCell = new Point( checkBox.getWidth() / 2, checkBox.getHeight() / 2 );
System.out.println( "middleOfTableCell: " + middleOfTableCell );
Dimension preferredSizeOfCheckBox = checkBox.getPreferredSize();
int halfWidthOfClickArea = (int) (preferredSizeOfCheckBox.getWidth() / 2);
int halfHeightOfClickArea = (int) (preferredSizeOfCheckBox.getHeight() / 2);
if ( (middleOfTableCell.getX() - halfWidthOfClickArea > ev.getX() || middleOfTableCell.getX() + halfWidthOfClickArea < ev.getX())
|| (middleOfTableCell.getY() - halfHeightOfClickArea > ev.getY() || middleOfTableCell.getY() + halfHeightOfClickArea < ev.getY()) )
{
stopCellEditing();
}
return super.shouldSelectCell( anEvent );
}
}
private static void createAndShowUI()
{
JFrame frame = new JFrame( "Direct click on CheckBox" );
frame.add( new JustCheckOnCheckboxTable() );
frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
frame.pack();
frame.setLocationRelativeTo( null );
frame.setVisible( true );
}
public static void main( String[] args )
{
java.awt.EventQueue.invokeLater( new Runnable()
{
@Override
public void run()
{
createAndShowUI();
}
} );
}
}
我喜欢这个解决方案:
我不喜欢它:
int halfWidthOfClickArea
)
还是有更好的方法来实现这个Table和CheckBox行为吗?
编辑:
我按照camickr的建议更改了源代码,并为具有更高RowHeights的表添加了垂直hitzone。
但到目前为止,我忘了提到我的问题的主要原因...... ;-)
我在方法stopCellEditing()
中调用shouldSelectCell(..)
。
可以决定选择的不仅仅是细胞选择吗?
答案 0 :(得分:4)
我在哪里可以获得未上漆组件的尺寸?
System.out.println(checkBox.getPreferredSize() );
答案 1 :(得分:2)
我认为停止选择shouldSelectCell()
方法中的实际值是一种迂回的方法,转换鼠标事件似乎很奇怪。
相反,一个更清洁的方法是使复选框不填充整个单元格,这样只有直接按下它的“复选框”部分才能选中它。
这种行为可以通过将JCheckbox
置于JPanel
内并将其居中而不拉伸来实现。为此,我们可以将JPanel
的布局管理器设为GridBagLayout
。了解如何使用GridBagLayout
,内部内容不会延伸:
现在,如果您点击它周围的空白区域,您将点击JPanel
,这样您就不会更改JCheckBox
的值。
CheckBoxEditor
的代码最终结果如下:
class CheckBoxEditor extends AbstractCellEditor implements TableCellEditor {
private static final long serialVersionUID = 1L;
private final JPanel componentPanel;
private final JCheckBox checkBox;
public CheckBoxEditor() {
componentPanel = new JPanel(new GridBagLayout()); // Use GridBagLayout to center the checkbox
componentPanel.setOpaque(false);
checkBox = new JCheckBox();
checkBox.setOpaque(false);
componentPanel.add(checkBox);
}
@Override
public Object getCellEditorValue() {
return Boolean.valueOf(checkBox.isSelected());
}
@Override
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
if (value instanceof Boolean) {
checkBox.setSelected(((Boolean) value).booleanValue());
} else if (value instanceof String) {
checkBox.setSelected(value.equals("true"));
}
if (isSelected) {
setForeground(table.getSelectionForeground());
setBackground(table.getSelectionBackground());
} else {
setForeground(table.getForeground());
setBackground(table.getBackground());
}
return componentPanel;
}
}
(请注意,您不能再从DefaultCellEditor
延伸 - 在上面的代码中,您现在必须从AbstractCellEditor
扩展并实施TableCellEditor
)。
我认为这个版本的CheckBoxEditor
可以满足您的需求 - 如果您点击复选框周围的空白区域,则不会发生任何事情。如果您直接单击该复选框,则仅会选中该复选框。
此外,通过使用JPanel
,您不必进行任何MouseEvent
计算和(至少对我而言),代码看起来更清晰,更容易看到发生了什么
<强> EDIT1:强>
我读了你的评论,我找到了一个解决方案:为什么不按原样保留编辑器,然后只需创建一个源自DefaultTableCellRenderer
的渲染器?然后,在CheckBoxEditor
中使用与渲染器相同的边框和背景。
这应该达到你想要的效果(我将公共代码移动到外部类方法中,所以我不必重复它们):
private static void setCheckboxValue(JCheckBox checkBox, Object value) {
if (value instanceof Boolean) {
checkBox.setSelected(((Boolean) value).booleanValue());
} else if (value instanceof String) {
checkBox.setSelected(value.equals("true"));
}
}
private static void copyAppearanceFrom(JPanel to, Component from) {
if (from != null) {
to.setOpaque(true);
to.setBackground(from.getBackground());
if (from instanceof JComponent) {
to.setBorder(((JComponent) from).getBorder());
}
} else {
to.setOpaque(false);
}
}
class CheckBoxEditor extends AbstractCellEditor implements TableCellEditor {
private static final long serialVersionUID = 1L;
private final JPanel componentPanel;
private final JCheckBox checkBox;
public CheckBoxEditor() {
componentPanel = new JPanel(new GridBagLayout()); // Use GridBagLayout to center the checkbox
checkBox = new JCheckBox();
checkBox.setOpaque(false);
componentPanel.add(checkBox);
}
@Override
public Object getCellEditorValue() {
return Boolean.valueOf(checkBox.isSelected());
}
@Override
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
setCheckboxValue(checkBox, value);
TableCellRenderer renderer = table.getCellRenderer(row, column);
Component c = renderer.getTableCellRendererComponent(table, value, true, true, row, column);
copyAppearanceFrom(componentPanel, c);
return componentPanel;
}
}
class CheckBoxRenderer extends DefaultTableCellRenderer {
private static final long serialVersionUID = 1L;
private final JPanel componentPanel;
private final JCheckBox checkBox;
public CheckBoxRenderer() {
componentPanel = new JPanel(new GridBagLayout()); // Use GridBagLayout to center the checkbox
checkBox = new JCheckBox();
checkBox.setOpaque(false);
componentPanel.add(checkBox);
}
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
setCheckboxValue(checkBox, value);
copyAppearanceFrom(componentPanel, this);
return componentPanel;
}
}
然后,您必须在构造函数中将渲染器与编辑器一起设置:
checkboxColumn.setCellEditor(new CheckBoxEditor());
checkboxColumn.setCellRenderer(new CheckBoxRenderer());
以下是两个比较两种方法的截图:
您的原始方法: JPanel
和 JCheckBox
方法:
我几乎看不出差异:)
恕我直言,我仍然认为只使用普通的Java表格API比根据鼠标指针位置计算检查更清晰,但选择取决于你。
我希望这有帮助!
<强> EDIT2:强>
此外,如果您希望能够使用空格键进行切换,我认为您只需在componentPanel
构造函数中添加一个键绑定到CheckBoxEditor
:
class CheckBoxEditor extends AbstractCellEditor implements TableCellEditor {
// ...
public CheckBoxEditor() {
// ...
componentPanel.getInputMap().put(KeyStroke.getKeyStroke("SPACE"), "spacePressed");
componentPanel.getActionMap().put("spacePressed", new AbstractAction() {
private static final long serialVersionUID = 1L;
@Override
public void actionPerformed(ActionEvent e) {
checkBox.setSelected(!checkBox.isSelected());
}
});
// ...
}
// ...
}
我不确定你是否可以使用布尔值拖放。我尝试将“true”拖到原始版本的复选框中,但没有任何反应,所以我认为你不必担心DnD。
答案 2 :(得分:0)
您的代码有错误。尝试按下单元格编辑器的复选框,拖动单元格并释放鼠标按钮。然后单击复选框外的某处。正在编辑单元格。我想这意味着shouldSelectCell
不适合你。
我认为您应该考虑停用整个列的单元格编辑器,并在MouseAdapter
上实现自定义JTable
来计算复选框的位置并更改模型本身。