How to detect a direct click on a JCheckBox in a JTable?

With the JCheckBox as the editor in the JTable column, I would like to ignore the mouseclicks in the space to the left and right of the CheckBox in the TableCell.

I found a discussion from 2011 on the Oracle forum, but the problem was not solved there: https://community.oracle.com/thread/2183210

This is the hack I understood so far, the interesting part starts with the 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(); } } ); } } 

What I like about this solution:

  • all TableCell behavior is correct: selection, MouseOver, EditModes, ...

What I don't like about this:

  • JCheckBox hard code ( int halfWidthOfClickArea )
    • Where can I get the dimensions of an unpainted component?

Or are there better ways to achieve this table and CheckBox behavior?

EDIT:

I changed the source code after recommending camickr and added a vertical zone for tables with higher RowHeights.
But so far I forgot to mention the main reason for my question ... ;-)
I call stopCellEditing() in the shouldSelectCell(..) method.

Can a decision be made on more than choosing Cell-Selection?

+5
source share
3 answers

Where can I get the dimensions of an unpainted component?

  System.out.println(checkBox.getPreferredSize() ); 
+4
source

I think stopping the selection of the actual in the shouldSelectCell() method is a kind of workaround for this, and converting mouse events seems weird.

Instead, a much cleaner approach would be to make the checkbox not fill the entire cell, so that it will only be selected if you click directly on the "checkbox" part.

You can do this by placing the JCheckbox inside the JPanel and centering it without stretching it. To do this, we can make the layout manager JPanel a GridBagLayout . See how when using GridBagLayout internal content is not stretched:

Layout managers (from fooobar.com/questions/398771 / ... )

So, if you click on the empty space around it, you will click on JPanel , so you will not change the value of JCheckbox .

The code for your CheckBoxEditor is ultimately obtained as follows:

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

(Note that you can no longer renew with DefaultCellEditor - in the code above you now need to switch from AbstractCellEditor and implement TableCellEditor ).

I think this version of your CheckBoxEditor does what you want - if you click into the empty space around this checkbox, nothing will happen. The checkbox will be checked only when you click on it.

Also, using JPanel , you don't need to do any MouseEvent calculations and (at least for me), the code looks a lot cleaner, and it's easier to see what happens.


EDIT1:
I read your comment and I found a solution: why not leave the editor as it is, but then just create a cell DefaultTableCellRenderer that comes from DefaultTableCellRenderer ? Then in CheckBoxEditor use the same borders and backgrounds as the rendering.

This should provide the desired effect (I translated the general code into methods of the outer class, so I do not need to repeat them):

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

Then you should set the render with the editor in your constructor:

 checkboxColumn.setCellEditor(new CheckBoxEditor()); checkboxColumn.setCellRenderer(new CheckBoxRenderer()); 

Here are some screenshots comparing the two methods:

Your original method:; JPanel and JCheckbox :
OldNew

I can barely see the difference :)

IMHO, I still think that just using a simple Java API table is cleaner than calculating mouse pointer checks, but the choice is up to you.

I hope this helps!


EDIT2:
Also, if you want to switch using a space, I think you can just add the key binding to componentPanel in the CheckBoxEditor constructor:

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

I'm not sure if you can use drag-and-drop with boolean values. I tried to drag "true" to the checkboxes in the original version, but nothing happened, so I don't think you need to worry about DnD.

+2
source

There is an error in your code. Try clicking on the cell editor check box, drag it outside the cell, and release the mouse button. Then click the β€œOUT OF VIEW” button. The cell is being edited. I guess that means shouldSelectCell not right for you.

I think you should consider disabling cell editors for the entire column and implementing a custom MouseAdapter on th JTable to calculate the location of the check box and change the model itself.

0
source

Source: https://habr.com/ru/post/1209256/


All Articles