简体   繁体   English

如何识别JTable中JCheckBox的直接点击?

[英]How to identify a direct click on a JCheckBox in a JTable?

With a JCheckBox as an Editor in a JTable column, I would like to ignore mouseclicks in the space left and right of a CheckBox in a TableCell. 使用JCheckBox作为JTable列中的编辑器,我想忽略TableCell中CheckBox左右两侧的鼠标点击。

I have found a discussion from 2011 on the Oracle forum, but the problem was not solved there: https://community.oracle.com/thread/2183210 我在2011年的Oracle论坛上找到了一个讨论,但问题没有在那里解决: https//community.oracle.com/thread/2183210

This is the hack I've realized so far, the interesting part begins at class CheckBoxEditor : 这是我到目前为止所实现的hack,有趣的部分从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 behaviour is correct: selecting, MouseOver, EditModes, ... 所有TableCell行为都是正确的:选择,MouseOver,EditModes,...

What I don't like about it: 我不喜欢它:

  • the hardcoded size of the JCheckBox ( int halfWidthOfClickArea ) JCheckBox的硬编码大小( int halfWidthOfClickArea
    • where can I get the dimensions of an unpainted component? 我在哪里可以获得未上漆组件的尺寸?

Or are there better ways to achieve this Table and CheckBox-behaviour? 或者有更好的方法来实现此表和CheckBox行为?

EDIT: 编辑:

I changed the sourcecode following the advice of camickr and added a vertical hitzone for tables with higher RowHeights. 我根据camickr的建议更改了源代码,并为具有更高RowHeights的表添加了垂直hitzone。
But so far I forgot to mention the main reason for my question... ;-) 但到目前为止,我忘了提到我的问题的主要原因...... ;-)
I'm calling stopCellEditing() in the method shouldSelectCell(..) . 我在方法shouldSelectCell(..)调用stopCellEditing() shouldSelectCell(..)

Is it ok to decide there about more than the Cell-Selection? 是否可以决定更多的细胞选择?

我在哪里可以获得未上漆组件的尺寸?

  System.out.println(checkBox.getPreferredSize() );

I think that stopping the selection of the actual in the shouldSelectCell() method is kind of a roundabout method of doing this, and converting mouse events seems weird. 我认为在shouldSelectCell()方法中停止选择实际是一种迂回方法,并且转换鼠标事件似乎很奇怪。

Instead, a much cleaner approach would be to make the checkbox not fill the entire cell, so that it only gets selected if you press directly on the "checkbox" part of it. 相反,更简洁的方法是使复选框不填充整个单元格,这样只有直接按下它的“复选框”部分才能选中它。

This behavior can be accomplished by putting your JCheckbox inside a JPanel and center it without stretching it. 这种行为可以通过将JCheckbox放在JPanel并在不拉伸它的情况下居中来实现。 To do this, we can make the JPanel 's layout manager a GridBagLayout . 为此,我们可以使JPanel的布局管理器成为GridBagLayout See how when using GridBagLayout , the inner content doesn't stretch: 看看当使用GridBagLayout ,内部内容不会拉伸:

布局管理员 (from this StackOverflow answer ) (来自StackOverflow的答案

So now, if you click in the empty space surrounding it, you will be clicking on a JPanel , so you won't be changing the JCheckBox 's value. 所以现在,如果你点击它周围的空白区域,你将点击一个JPanel ,这样你就不会改变JCheckBox的值。

The code for your CheckBoxEditor turns out like this in the end: 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;
    }
}

(Note that you can't be extending from a DefaultCellEditor anymore - in the code above, you're now having to extend from an AbstractCellEditor and implement a TableCellEditor ). (请注意,您不能再从DefaultCellEditor扩展 - 在上面的代码中,您现在必须从AbstractCellEditor扩展并实现TableCellEditor )。

I think that this version of your CheckBoxEditor does what you want - if you click in the empty space around the check box, nothing happens. 我认为这个版本的CheckBoxEditor您的需求 - 如果您点击复选框周围的空白区域,则没有任何反应。 The check box only becomes check if you click directly on it. 如果您直接单击该复选框,则仅会检查该复选框。

Also, by using a JPanel , you don't have to do any MouseEvent calculations and (to me, at least), the code looks much cleaner and it's easier to see what's going on. 此外,通过使用JPanel ,您不必执行任何MouseEvent计算(至少对我而言),代码看起来更清晰,并且更容易看到正在发生的事情。


EDIT1: EDIT1:
I read your comment and I found a solution: Why not leave the editor as it is, but then just make a cell renderer that derives from the DefaultTableCellRenderer ? 我读了你的评论,我找到了一个解决方案:为什么不保留编辑器 ,但是只需要创建一个派生自DefaultTableCellRenderer的单元格渲染器 Then, in your CheckBoxEditor use the same borders and backgrounds as the renderer. 然后,在CheckBoxEditor使用与渲染器相同的边框和背景。

This should achieve the effect you want (I've moved common code into outer class methods so I don't have 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 have to set the renderer along with the editor in your constructor: 然后,您必须在构造函数中将渲染器与编辑器一起设置:

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

Here's a couple of screenshots comparing the two methods: 这里有两个比较两种方法的截图:

Your original method: JPanel and JCheckBox method: 你原来的方法: JPanel JCheckBox方法:
旧新

I can barely see a difference :) 我几乎看不出差别:)

IMHO, I still think that just using the plain Java table API's is cleaner than calculating checks based on mouse pointer positions, but the choice is up to you. 恕我直言,我仍然认为只使用普通的Java表格API比计算基于鼠标指针位置的检查更简洁,但选择取决于你。

I hope this helped! 我希望这有帮助!


EDIT2: EDIT2:
Also, if you want to be able to toggle using the spacebar I think that you can just add a key binding to the componentPanel in the CheckBoxEditor constructor: 此外,如果您希望能够使用空格键切换,我认为您只需在CheckBoxEditor构造函数中添加一个键绑定到componentPanel

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 dragging "true" onto the checkboxes in the original version, but nothing happened, so I don't think you have to worry about DnD. 我尝试将“true”拖到原始版本的复选框中,但没有任何反应,所以我认为你不必担心DnD。

Your code has a bug. 您的代码有错误。 Try pressing a cell editor's checkbox, drag outside of the cell and release the mouse button. 尝试按单元格编辑器的复选框,拖动单元格外部并释放鼠标按钮。 Then click somewhere OUTSIDE the checkbox. 然后单击复选框外的某处。 The cell is being edited. 正在编辑单元格。 I guess it means shouldSelectCell is not the right solution for you. 我想这意味着shouldSelectCell对你来说不是正确的解决方案。

I think you should consider disabling the cell editors for the entire column, and implementing a custom MouseAdapter on th JTable to calculate the checkbox's location and change the model itself. 我认为您应该考虑禁用整个列的单元格编辑器,并在JTable上实现自定义MouseAdapter来计算复选框的位置并更改模型本身。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM