簡體   English   中英

如何識別JTable中JCheckBox的直接點擊?

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

使用JCheckBox作為JTable列中的編輯器,我想忽略TableCell中CheckBox左右兩側的鼠標點擊。

我在2011年的Oracle論壇上找到了一個討論,但問題沒有在那里解決: https//community.oracle.com/thread/2183210

這是我到目前為止所實現的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();
      }
    } );
  }
}

我喜歡這個解決方案:

  • 所有TableCell行為都是正確的:選擇,MouseOver,EditModes,...

我不喜歡它:

  • JCheckBox的硬編碼大小( int halfWidthOfClickArea
    • 我在哪里可以獲得未上漆組件的尺寸?

或者有更好的方法來實現此表和CheckBox行為?

編輯:

我根據camickr的建議更改了源代碼,並為具有更高RowHeights的表添加了垂直hitzone。
但到目前為止,我忘了提到我的問題的主要原因...... ;-)
我在方法shouldSelectCell(..)調用stopCellEditing() shouldSelectCell(..)

是否可以決定更多的細胞選擇?

我在哪里可以獲得未上漆組件的尺寸?

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

我認為在shouldSelectCell()方法中停止選擇實際是一種迂回方法,並且轉換鼠標事件似乎很奇怪。

相反,更簡潔的方法是使復選框不填充整個單元格,這樣只有直接按下它的“復選框”部分才能選中它。

這種行為可以通過將JCheckbox放在JPanel並在不拉伸它的情況下居中來實現。 為此,我們可以使JPanel的布局管理器成為GridBagLayout 看看當使用GridBagLayout ,內部內容不會拉伸:

布局管理員 (來自StackOverflow的答案

所以現在,如果你點擊它周圍的空白區域,你將點擊一個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:
此外,如果您希望能夠使用空格鍵切換,我認為您只需在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());
            }
        });
        // ...
    }

    // ...
}

我不確定你是否可以使用布爾值拖放。 我嘗試將“true”拖到原始版本的復選框中,但沒有任何反應,所以我認為你不必擔心DnD。

您的代碼有錯誤。 嘗試按單元格編輯器的復選框,拖動單元格外部並釋放鼠標按鈕。 然后單擊復選框外的某處。 正在編輯單元格。 我想這意味着shouldSelectCell對你來說不是正確的解決方案。

我認為您應該考慮禁用整個列的單元格編輯器,並在JTable上實現自定義MouseAdapter來計算復選框的位置並更改模型本身。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM